diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 1a8500f..2db3a5a 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -32,7 +32,7 @@ jobs:
- name: Build Executable
uses: Nuitka/Nuitka-Action@main
with:
- script-name: DeGourou.py
+ script-name: DeGourou/DeGourou.py
onefile: true
standalone: true
diff --git a/DeGourou.py b/DeGourou/DeGourou.py
old mode 100755
new mode 100644
similarity index 86%
rename from DeGourou.py
rename to DeGourou/DeGourou.py
index 215fb74..1ec57a3
--- a/DeGourou.py
+++ b/DeGourou/DeGourou.py
@@ -1,18 +1,18 @@
#!/usr/bin/env python
-from setup.loginAccount import loginAndGetKey
-from setup.fulfill import downloadFile
+from .setup.loginAccount import loginAndGetKey
+from .setup.fulfill import downloadFile
-from decrypt.decodePDF import decryptPDF
-from decrypt.decodeEPUB import decryptEPUB
+from .decrypt.decodePDF import decryptPDF
+from .decrypt.decodeEPUB import decryptEPUB
import argparse
from os import mkdir, remove, rename
from os.path import exists
-from setup.params import FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML
-from decrypt.params import KEYPATH
-from setup.data import createDefaultFiles
-from setup.ia import SESSION_FILE, manage_login, get_book, return_book
+from .setup.params import FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML
+from .decrypt.params import KEYPATH
+from .setup.data import createDefaultFiles
+from .setup.ia import SESSION_FILE, manage_login, get_book, return_book
def loginADE(email, password):
@@ -32,7 +32,7 @@ def loginIA(email,password):
manage_login(email,password)
print()
-def main(acsmFile, outputFilename):
+def start(acsmFile, outputFilename):
if not exists('account'): mkdir('account')
# setting up the account and keys
@@ -81,13 +81,13 @@ def handle_IA(url,format):
if acsmFile is None:
print("Could not get Book, try using ACSm file as input")
return
- main(acsmFile,None)
+ start(acsmFile,None)
remove(acsmFile)
if(return_book(url) is None):
print("Please return it yourself")
-if __name__ == "__main__":
+def main():
parser = argparse.ArgumentParser(description="Download and Decrypt an encrypted PDF or EPUB file.")
parser.add_argument("-f", type=str, nargs='?', default=None, help="path to the ACSM file")
parser.add_argument("-u", type=str, nargs='?', default=None, help="book url from InternetArchive")
@@ -134,8 +134,11 @@ if __name__ == "__main__":
elif args.f == None:
if exists("URLLink.acsm"):
args.f = "URLLink.acsm"
- main(args.f, args.o)
+ start(args.f, args.o)
else: parser.print_help()
else:
- main(args.f, args.o)
+ start(args.f, args.o)
+
+
+if __name__ == "__main__": main()
\ No newline at end of file
diff --git a/decrypt/decodeEPUB.py b/DeGourou/decrypt/decodeEPUB.py
similarity index 96%
rename from decrypt/decodeEPUB.py
rename to DeGourou/decrypt/decodeEPUB.py
index 7c69ad0..7b746e3 100644
--- a/decrypt/decodeEPUB.py
+++ b/DeGourou/decrypt/decodeEPUB.py
@@ -1,313 +1,313 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# ineptepub.py
-# Copyright © 2009-2022 by i♥cabbages, Apprentice Harper et al.
-
-# Released under the terms of the GNU General Public Licence, version 3
-#
-
-"""
-Decrypt Adobe Digital Editions encrypted ePub books.
-"""
-
-from decrypt.params import KEYPATH
-__license__ = 'GPL v3'
-__version__ = "8.0"
-
-import sys
-import os
-import traceback
-import base64
-import zlib
-from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
-from decrypt.zeroedzipinfo import ZeroedZipInfo
-from contextlib import closing
-from lxml import etree
-from uuid import UUID
-import hashlib
-
-try:
- from Cryptodome.Cipher import AES, PKCS1_v1_5
- from Cryptodome.PublicKey import RSA
-except ImportError:
- from Crypto.Cipher import AES, PKCS1_v1_5
- from Crypto.PublicKey import RSA
-
-
-def unpad(data, padding=16):
- if sys.version_info[0] == 2:
- pad_len = ord(data[-1])
- else:
- pad_len = data[-1]
-
- return data[:-pad_len]
-
-
-class ADEPTError(Exception):
- pass
-
-class ADEPTNewVersionError(Exception):
- pass
-
-META_NAMES = ('mimetype', 'META-INF/rights.xml')
-NSMAP = {'adept': 'http://ns.adobe.com/adept',
- 'enc': 'http://www.w3.org/2001/04/xmlenc#'}
-
-class Decryptor(object):
- def __init__(self, bookkey, encryption):
- enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
- self._aes = AES.new(bookkey, AES.MODE_CBC, b'\x00'*16)
- self._encryption = etree.fromstring(encryption)
- self._encrypted = encrypted = set()
- self._encryptedForceNoDecomp = encryptedForceNoDecomp = set()
- self._otherData = otherData = set()
-
- self._json_elements_to_remove = json_elements_to_remove = set()
- self._has_remaining_xml = False
- expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
- enc('CipherReference'))
- for elem in self._encryption.findall(expr):
- path = elem.get('URI', None)
- encryption_type_url = (elem.getparent().getparent().find("./%s" % (enc('EncryptionMethod'))).get('Algorithm', None))
- if path is not None:
- if (encryption_type_url == "http://www.w3.org/2001/04/xmlenc#aes128-cbc"):
- # Adobe
- path = path.encode('utf-8')
- encrypted.add(path)
- json_elements_to_remove.add(elem.getparent().getparent())
- elif (encryption_type_url == "http://ns.adobe.com/adept/xmlenc#aes128-cbc-uncompressed"):
- # Adobe uncompressed, for stuff like video files
- path = path.encode('utf-8')
- encryptedForceNoDecomp.add(path)
- json_elements_to_remove.add(elem.getparent().getparent())
- else:
- path = path.encode('utf-8')
- otherData.add(path)
- self._has_remaining_xml = True
-
- for elem in json_elements_to_remove:
- elem.getparent().remove(elem)
-
- def check_if_remaining(self):
- return self._has_remaining_xml
-
- def get_xml(self):
- return "\n" + etree.tostring(self._encryption, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("utf-8")
-
-
- def decompress(self, bytes):
- dc = zlib.decompressobj(-15)
- 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 or path.encode('utf-8') in self._encryptedForceNoDecomp:
- data = self._aes.decrypt(data)[16:]
- if type(data[-1]) != int:
- place = ord(data[-1])
- else:
- place = data[-1]
- data = data[:-place]
- if not path.encode('utf-8') in self._encryptedForceNoDecomp:
- data = self.decompress(data)
- return data
-
-# check file to make check whether it's probably an Adobe Adept encrypted ePub
-def adeptBook(inpath):
- 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:
- return False
- try:
- rights = etree.fromstring(inf.read('META-INF/rights.xml'))
- adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
- expr = './/%s' % (adept('encryptedKey'),)
- bookkey = ''.join(rights.findtext(expr))
- if len(bookkey) in [192, 172, 64]:
- return True
- except:
- # if we couldn't check, assume it is
- return True
- return False
-
-def isPassHashBook(inpath):
- # If this is an Adobe book, check if it's a PassHash-encrypted book (B&N)
- 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:
- return False
- try:
- rights = etree.fromstring(inf.read('META-INF/rights.xml'))
- adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
- expr = './/%s' % (adept('encryptedKey'),)
- bookkey = ''.join(rights.findtext(expr))
- if len(bookkey) == 64:
- return True
- except:
- pass
-
- return False
-
-# Checks the license file and returns the UUID the book is licensed for.
-# This is used so that the Calibre plugin can pick the correct decryption key
-# first try without having to loop through all possible keys.
-def adeptGetUserUUID(inpath):
- with closing(ZipFile(open(inpath, 'rb'))) as inf:
- try:
- rights = etree.fromstring(inf.read('META-INF/rights.xml'))
- adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
- expr = './/%s' % (adept('user'),)
- user_uuid = ''.join(rights.findtext(expr))
- if user_uuid[:9] != "urn:uuid:":
- return None
- return user_uuid[9:]
- except:
- return None
-
-def removeHardening(rights, keytype, keydata):
- adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
- textGetter = lambda name: ''.join(rights.findtext('.//%s' % (adept(name),)))
-
- # Gather what we need, and generate the IV
- resourceuuid = UUID(textGetter("resource"))
- deviceuuid = UUID(textGetter("device"))
- fullfillmentuuid = UUID(textGetter("fulfillment")[:36])
- kekiv = UUID(int=resourceuuid.int ^ deviceuuid.int ^ fullfillmentuuid.int).bytes
-
- # Derive kek from just "keytype"
- rem = int(keytype, 10) % 16
- H = hashlib.sha256(keytype.encode("ascii")).digest()
- kek = H[2*rem : 16 + rem] + H[rem : 2*rem]
-
- return unpad(AES.new(kek, AES.MODE_CBC, kekiv).decrypt(keydata), 16) # PKCS#7
-
-def decryptBook(userkey, inpath, outpath):
- with closing(ZipFile(open(inpath, 'rb'))) as inf:
- namelist = inf.namelist()
- if 'META-INF/rights.xml' not in namelist or \
- 'META-INF/encryption.xml' not in namelist:
- print("{0:s} is DRM-free.".format(os.path.basename(inpath)))
- return 1
- for name in META_NAMES:
- namelist.remove(name)
- try:
- rights = etree.fromstring(inf.read('META-INF/rights.xml'))
- adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
- expr = './/%s' % (adept('encryptedKey'),)
- bookkeyelem = rights.find(expr)
- bookkey = bookkeyelem.text
- keytype = bookkeyelem.attrib.get('keyType', '0')
- if len(bookkey) >= 172 and int(keytype, 10) > 2:
- print("{0:s} is a secure Adobe Adept ePub with hardening.".format(os.path.basename(inpath)))
- elif len(bookkey) == 172:
- print("{0:s} is a secure Adobe Adept ePub.".format(os.path.basename(inpath)))
- elif len(bookkey) == 64:
- print("{0:s} is a secure Adobe PassHash (B&N) ePub.".format(os.path.basename(inpath)))
- else:
- print("{0:s} is not an Adobe-protected ePub!".format(os.path.basename(inpath)))
- return 1
-
- if len(bookkey) != 64:
- # Normal or "hardened" Adobe ADEPT
- rsakey = RSA.importKey(userkey) # parses the ASN1 structure
- bookkey = base64.b64decode(bookkey)
- if int(keytype, 10) > 2:
- bookkey = removeHardening(rights, keytype, bookkey)
- try:
- bookkey = PKCS1_v1_5.new(rsakey).decrypt(bookkey, None) # automatically unpads
- except ValueError:
- bookkey = None
-
- if bookkey is None:
- print("Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
- return 2
- else:
- # Adobe PassHash / B&N
- key = base64.b64decode(userkey)[:16]
- bookkey = base64.b64decode(bookkey)
- bookkey = unpad(AES.new(key, AES.MODE_CBC, b'\x00'*16).decrypt(bookkey), 16) # PKCS#7
-
- if len(bookkey) > 16:
- bookkey = bookkey[-16:]
-
- encryption = inf.read('META-INF/encryption.xml')
- decryptor = Decryptor(bookkey, encryption)
- kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
- with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
-
- for path in (["mimetype"] + namelist):
- data = inf.read(path)
- zi = ZipInfo(path)
- zi.compress_type=ZIP_DEFLATED
-
- if path == "mimetype":
- zi.compress_type = ZIP_STORED
-
- elif path == "META-INF/encryption.xml":
- # Check if there's still something in there
- if (decryptor.check_if_remaining()):
- data = decryptor.get_xml()
- print("Adding encryption.xml for the remaining embedded files.")
- # We removed DRM, but there's still stuff like obfuscated fonts.
- else:
- continue
-
-
- try:
- # get the file info, including time-stamp
- oldzi = inf.getinfo(path)
- # copy across useful fields
- zi.date_time = oldzi.date_time
- zi.comment = oldzi.comment
- zi.extra = oldzi.extra
- zi.internal_attr = oldzi.internal_attr
- # external attributes are dependent on the create system, so copy both.
- zi.external_attr = oldzi.external_attr
-
- zi.volume = oldzi.volume
- zi.create_system = oldzi.create_system
- zi.create_version = oldzi.create_version
-
- if any(ord(c) >= 128 for c in path) or any(ord(c) >= 128 for c in zi.comment):
- # If the file name or the comment contains any non-ASCII char, set the UTF8-flag
- zi.flag_bits |= 0x800
- except:
- pass
-
- # Python 3 has a bug where the external_attr is reset to `0o600 << 16`
- # if it's NULL, so we need a workaround:
- if zi.external_attr == 0:
- zi = ZeroedZipInfo(zi)
-
-
- if path == "META-INF/encryption.xml":
- outf.writestr(zi, data)
- else:
- outf.writestr(zi, decryptor.decrypt(path, data))
- except:
- 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
-
-
-def decryptEPUB(inpath):
- keypath = KEYPATH
- outpath = os.path.basename(inpath).removesuffix(".epub") + "_decrypted.epub"
- userkey = open(keypath,'rb').read()
- result = decryptBook(userkey, inpath, outpath)
- if result == 0:
- print("Successfully decrypted")
- return outpath
- else:
- print("Decryption failed")
- return None
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# ineptepub.py
+# Copyright © 2009-2022 by i♥cabbages, Apprentice Harper et al.
+
+# Released under the terms of the GNU General Public Licence, version 3
+#
+
+"""
+Decrypt Adobe Digital Editions encrypted ePub books.
+"""
+
+from .params import KEYPATH
+__license__ = 'GPL v3'
+__version__ = "8.0"
+
+import sys
+import os
+import traceback
+import base64
+import zlib
+from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
+from .zeroedzipinfo import ZeroedZipInfo
+from contextlib import closing
+from lxml import etree
+from uuid import UUID
+import hashlib
+
+try:
+ from Cryptodome.Cipher import AES, PKCS1_v1_5
+ from Cryptodome.PublicKey import RSA
+except ImportError:
+ from Crypto.Cipher import AES, PKCS1_v1_5
+ from Crypto.PublicKey import RSA
+
+
+def unpad(data, padding=16):
+ if sys.version_info[0] == 2:
+ pad_len = ord(data[-1])
+ else:
+ pad_len = data[-1]
+
+ return data[:-pad_len]
+
+
+class ADEPTError(Exception):
+ pass
+
+class ADEPTNewVersionError(Exception):
+ pass
+
+META_NAMES = ('mimetype', 'META-INF/rights.xml')
+NSMAP = {'adept': 'http://ns.adobe.com/adept',
+ 'enc': 'http://www.w3.org/2001/04/xmlenc#'}
+
+class Decryptor(object):
+ def __init__(self, bookkey, encryption):
+ enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
+ self._aes = AES.new(bookkey, AES.MODE_CBC, b'\x00'*16)
+ self._encryption = etree.fromstring(encryption)
+ self._encrypted = encrypted = set()
+ self._encryptedForceNoDecomp = encryptedForceNoDecomp = set()
+ self._otherData = otherData = set()
+
+ self._json_elements_to_remove = json_elements_to_remove = set()
+ self._has_remaining_xml = False
+ expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
+ enc('CipherReference'))
+ for elem in self._encryption.findall(expr):
+ path = elem.get('URI', None)
+ encryption_type_url = (elem.getparent().getparent().find("./%s" % (enc('EncryptionMethod'))).get('Algorithm', None))
+ if path is not None:
+ if (encryption_type_url == "http://www.w3.org/2001/04/xmlenc#aes128-cbc"):
+ # Adobe
+ path = path.encode('utf-8')
+ encrypted.add(path)
+ json_elements_to_remove.add(elem.getparent().getparent())
+ elif (encryption_type_url == "http://ns.adobe.com/adept/xmlenc#aes128-cbc-uncompressed"):
+ # Adobe uncompressed, for stuff like video files
+ path = path.encode('utf-8')
+ encryptedForceNoDecomp.add(path)
+ json_elements_to_remove.add(elem.getparent().getparent())
+ else:
+ path = path.encode('utf-8')
+ otherData.add(path)
+ self._has_remaining_xml = True
+
+ for elem in json_elements_to_remove:
+ elem.getparent().remove(elem)
+
+ def check_if_remaining(self):
+ return self._has_remaining_xml
+
+ def get_xml(self):
+ return "\n" + etree.tostring(self._encryption, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("utf-8")
+
+
+ def decompress(self, bytes):
+ dc = zlib.decompressobj(-15)
+ 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 or path.encode('utf-8') in self._encryptedForceNoDecomp:
+ data = self._aes.decrypt(data)[16:]
+ if type(data[-1]) != int:
+ place = ord(data[-1])
+ else:
+ place = data[-1]
+ data = data[:-place]
+ if not path.encode('utf-8') in self._encryptedForceNoDecomp:
+ data = self.decompress(data)
+ return data
+
+# check file to make check whether it's probably an Adobe Adept encrypted ePub
+def adeptBook(inpath):
+ 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:
+ return False
+ try:
+ rights = etree.fromstring(inf.read('META-INF/rights.xml'))
+ adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
+ expr = './/%s' % (adept('encryptedKey'),)
+ bookkey = ''.join(rights.findtext(expr))
+ if len(bookkey) in [192, 172, 64]:
+ return True
+ except:
+ # if we couldn't check, assume it is
+ return True
+ return False
+
+def isPassHashBook(inpath):
+ # If this is an Adobe book, check if it's a PassHash-encrypted book (B&N)
+ 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:
+ return False
+ try:
+ rights = etree.fromstring(inf.read('META-INF/rights.xml'))
+ adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
+ expr = './/%s' % (adept('encryptedKey'),)
+ bookkey = ''.join(rights.findtext(expr))
+ if len(bookkey) == 64:
+ return True
+ except:
+ pass
+
+ return False
+
+# Checks the license file and returns the UUID the book is licensed for.
+# This is used so that the Calibre plugin can pick the correct decryption key
+# first try without having to loop through all possible keys.
+def adeptGetUserUUID(inpath):
+ with closing(ZipFile(open(inpath, 'rb'))) as inf:
+ try:
+ rights = etree.fromstring(inf.read('META-INF/rights.xml'))
+ adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
+ expr = './/%s' % (adept('user'),)
+ user_uuid = ''.join(rights.findtext(expr))
+ if user_uuid[:9] != "urn:uuid:":
+ return None
+ return user_uuid[9:]
+ except:
+ return None
+
+def removeHardening(rights, keytype, keydata):
+ adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
+ textGetter = lambda name: ''.join(rights.findtext('.//%s' % (adept(name),)))
+
+ # Gather what we need, and generate the IV
+ resourceuuid = UUID(textGetter("resource"))
+ deviceuuid = UUID(textGetter("device"))
+ fullfillmentuuid = UUID(textGetter("fulfillment")[:36])
+ kekiv = UUID(int=resourceuuid.int ^ deviceuuid.int ^ fullfillmentuuid.int).bytes
+
+ # Derive kek from just "keytype"
+ rem = int(keytype, 10) % 16
+ H = hashlib.sha256(keytype.encode("ascii")).digest()
+ kek = H[2*rem : 16 + rem] + H[rem : 2*rem]
+
+ return unpad(AES.new(kek, AES.MODE_CBC, kekiv).decrypt(keydata), 16) # PKCS#7
+
+def decryptBook(userkey, inpath, outpath):
+ with closing(ZipFile(open(inpath, 'rb'))) as inf:
+ namelist = inf.namelist()
+ if 'META-INF/rights.xml' not in namelist or \
+ 'META-INF/encryption.xml' not in namelist:
+ print("{0:s} is DRM-free.".format(os.path.basename(inpath)))
+ return 1
+ for name in META_NAMES:
+ namelist.remove(name)
+ try:
+ rights = etree.fromstring(inf.read('META-INF/rights.xml'))
+ adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
+ expr = './/%s' % (adept('encryptedKey'),)
+ bookkeyelem = rights.find(expr)
+ bookkey = bookkeyelem.text
+ keytype = bookkeyelem.attrib.get('keyType', '0')
+ if len(bookkey) >= 172 and int(keytype, 10) > 2:
+ print("{0:s} is a secure Adobe Adept ePub with hardening.".format(os.path.basename(inpath)))
+ elif len(bookkey) == 172:
+ print("{0:s} is a secure Adobe Adept ePub.".format(os.path.basename(inpath)))
+ elif len(bookkey) == 64:
+ print("{0:s} is a secure Adobe PassHash (B&N) ePub.".format(os.path.basename(inpath)))
+ else:
+ print("{0:s} is not an Adobe-protected ePub!".format(os.path.basename(inpath)))
+ return 1
+
+ if len(bookkey) != 64:
+ # Normal or "hardened" Adobe ADEPT
+ rsakey = RSA.importKey(userkey) # parses the ASN1 structure
+ bookkey = base64.b64decode(bookkey)
+ if int(keytype, 10) > 2:
+ bookkey = removeHardening(rights, keytype, bookkey)
+ try:
+ bookkey = PKCS1_v1_5.new(rsakey).decrypt(bookkey, None) # automatically unpads
+ except ValueError:
+ bookkey = None
+
+ if bookkey is None:
+ print("Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
+ return 2
+ else:
+ # Adobe PassHash / B&N
+ key = base64.b64decode(userkey)[:16]
+ bookkey = base64.b64decode(bookkey)
+ bookkey = unpad(AES.new(key, AES.MODE_CBC, b'\x00'*16).decrypt(bookkey), 16) # PKCS#7
+
+ if len(bookkey) > 16:
+ bookkey = bookkey[-16:]
+
+ encryption = inf.read('META-INF/encryption.xml')
+ decryptor = Decryptor(bookkey, encryption)
+ kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
+ with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
+
+ for path in (["mimetype"] + namelist):
+ data = inf.read(path)
+ zi = ZipInfo(path)
+ zi.compress_type=ZIP_DEFLATED
+
+ if path == "mimetype":
+ zi.compress_type = ZIP_STORED
+
+ elif path == "META-INF/encryption.xml":
+ # Check if there's still something in there
+ if (decryptor.check_if_remaining()):
+ data = decryptor.get_xml()
+ print("Adding encryption.xml for the remaining embedded files.")
+ # We removed DRM, but there's still stuff like obfuscated fonts.
+ else:
+ continue
+
+
+ try:
+ # get the file info, including time-stamp
+ oldzi = inf.getinfo(path)
+ # copy across useful fields
+ zi.date_time = oldzi.date_time
+ zi.comment = oldzi.comment
+ zi.extra = oldzi.extra
+ zi.internal_attr = oldzi.internal_attr
+ # external attributes are dependent on the create system, so copy both.
+ zi.external_attr = oldzi.external_attr
+
+ zi.volume = oldzi.volume
+ zi.create_system = oldzi.create_system
+ zi.create_version = oldzi.create_version
+
+ if any(ord(c) >= 128 for c in path) or any(ord(c) >= 128 for c in zi.comment):
+ # If the file name or the comment contains any non-ASCII char, set the UTF8-flag
+ zi.flag_bits |= 0x800
+ except:
+ pass
+
+ # Python 3 has a bug where the external_attr is reset to `0o600 << 16`
+ # if it's NULL, so we need a workaround:
+ if zi.external_attr == 0:
+ zi = ZeroedZipInfo(zi)
+
+
+ if path == "META-INF/encryption.xml":
+ outf.writestr(zi, data)
+ else:
+ outf.writestr(zi, decryptor.decrypt(path, data))
+ except:
+ 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
+
+
+def decryptEPUB(inpath):
+ keypath = KEYPATH
+ outpath = os.path.basename(inpath).removesuffix(".epub") + "_decrypted.epub"
+ userkey = open(keypath,'rb').read()
+ result = decryptBook(userkey, inpath, outpath)
+ if result == 0:
+ print("Successfully decrypted")
+ return outpath
+ else:
+ print("Decryption failed")
+ return None
diff --git a/decrypt/decodePDF.py b/DeGourou/decrypt/decodePDF.py
similarity index 99%
rename from decrypt/decodePDF.py
rename to DeGourou/decrypt/decodePDF.py
index 271f859..0838aaf 100644
--- a/decrypt/decodePDF.py
+++ b/DeGourou/decrypt/decodePDF.py
@@ -13,7 +13,7 @@
Decrypts Adobe ADEPT-encrypted PDF files.
"""
-from decrypt.params import KEYPATH
+from .params import KEYPATH
__license__ = 'GPL v3'
__version__ = "10.0.4"
diff --git a/decrypt/params.py b/DeGourou/decrypt/params.py
similarity index 100%
rename from decrypt/params.py
rename to DeGourou/decrypt/params.py
diff --git a/decrypt/zeroedzipinfo.py b/DeGourou/decrypt/zeroedzipinfo.py
similarity index 100%
rename from decrypt/zeroedzipinfo.py
rename to DeGourou/decrypt/zeroedzipinfo.py
diff --git a/setup/customRSA.py b/DeGourou/setup/customRSA.py
similarity index 96%
rename from setup/customRSA.py
rename to DeGourou/setup/customRSA.py
index 3c3597b..d394bf0 100644
--- a/setup/customRSA.py
+++ b/DeGourou/setup/customRSA.py
@@ -1,118 +1,118 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-'''
-Use my own small RSA code so we don't have to include the huge
-python3-rsa just for these small bits.
-The original code used blinding and this one doesn't,
-but we don't really care about side-channel attacks ...
-'''
-
-import sys
-
-try:
- from Cryptodome.PublicKey import RSA
-except ImportError:
- # Some distros still ship this as Crypto
- from Crypto.PublicKey import RSA
-
-class CustomRSA:
-
- @staticmethod
- def encrypt_for_adobe_signature(signing_key, message):
- key = RSA.importKey(signing_key)
- keylen = CustomRSA.byte_size(key.n)
- padded = CustomRSA.pad_message(message, keylen)
- payload = CustomRSA.transform_bytes2int(padded)
- encrypted = CustomRSA.normal_encrypt(key, payload)
- block = CustomRSA.transform_int2bytes(encrypted, keylen)
- return bytearray(block)
-
- @staticmethod
- def byte_size(number):
- # type: (int) -> int
- return (number.bit_length() + 7) // 8
-
- @staticmethod
- def pad_message(message, target_len):
- # type: (bytes, int) -> bytes
-
- # Padding always uses 0xFF
- # Returns: 00 01 PADDING 00 MESSAGE
-
- max_message_length = target_len - 11
- message_length = len(message)
-
- if message_length > max_message_length:
- raise OverflowError("Message too long, has %d bytes but only space for %d" % (message_length, max_message_length))
-
- padding_len = target_len - message_length - 3
-
- ret = bytearray(b"".join([b"\x00\x01", padding_len * b"\xff", b"\x00"]))
- ret.extend(bytes(message))
-
- return ret
-
- @staticmethod
- def normal_encrypt(key, message):
-
- if message < 0 or message > key.n:
- raise ValueError("Invalid message")
-
- encrypted = pow(message, key.d, key.n)
- return encrypted
-
- @staticmethod
- def py2_int_to_bytes(value, length, big_endian = True):
- result = []
-
- for i in range(0, length):
- result.append(value >> (i * 8) & 0xff)
-
- if big_endian:
- result.reverse()
-
- return result
-
- @staticmethod
- def py2_bytes_to_int(bytes, big_endian = True):
- # type: (bytes, bool) -> int
-
- my_bytes = bytes
- if not big_endian:
- my_bytes.reverse()
-
- result = 0
- for b in my_bytes:
- result = result * 256 + int(b)
-
- return result
-
- @staticmethod
- def transform_bytes2int(raw_bytes):
- # type: (bytes) -> int
-
- if sys.version_info[0] >= 3:
- return int.from_bytes(raw_bytes, "big", signed=False)
-
- return CustomRSA.py2_bytes_to_int(raw_bytes, True)
-
-
- @staticmethod
- def transform_int2bytes(number, fill_size = 0):
- # type: (int, int) -> bytes
-
- if number < 0:
- raise ValueError("Negative number")
-
- size = None
-
- if fill_size > 0:
- size = fill_size
- else:
- size = max(1, CustomRSA.byte_size(number))
-
- if sys.version_info[0] >= 3:
- return number.to_bytes(size, "big")
-
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+'''
+Use my own small RSA code so we don't have to include the huge
+python3-rsa just for these small bits.
+The original code used blinding and this one doesn't,
+but we don't really care about side-channel attacks ...
+'''
+
+import sys
+
+try:
+ from Cryptodome.PublicKey import RSA
+except ImportError:
+ # Some distros still ship this as Crypto
+ from Crypto.PublicKey import RSA
+
+class CustomRSA:
+
+ @staticmethod
+ def encrypt_for_adobe_signature(signing_key, message):
+ key = RSA.importKey(signing_key)
+ keylen = CustomRSA.byte_size(key.n)
+ padded = CustomRSA.pad_message(message, keylen)
+ payload = CustomRSA.transform_bytes2int(padded)
+ encrypted = CustomRSA.normal_encrypt(key, payload)
+ block = CustomRSA.transform_int2bytes(encrypted, keylen)
+ return bytearray(block)
+
+ @staticmethod
+ def byte_size(number):
+ # type: (int) -> int
+ return (number.bit_length() + 7) // 8
+
+ @staticmethod
+ def pad_message(message, target_len):
+ # type: (bytes, int) -> bytes
+
+ # Padding always uses 0xFF
+ # Returns: 00 01 PADDING 00 MESSAGE
+
+ max_message_length = target_len - 11
+ message_length = len(message)
+
+ if message_length > max_message_length:
+ raise OverflowError("Message too long, has %d bytes but only space for %d" % (message_length, max_message_length))
+
+ padding_len = target_len - message_length - 3
+
+ ret = bytearray(b"".join([b"\x00\x01", padding_len * b"\xff", b"\x00"]))
+ ret.extend(bytes(message))
+
+ return ret
+
+ @staticmethod
+ def normal_encrypt(key, message):
+
+ if message < 0 or message > key.n:
+ raise ValueError("Invalid message")
+
+ encrypted = pow(message, key.d, key.n)
+ return encrypted
+
+ @staticmethod
+ def py2_int_to_bytes(value, length, big_endian = True):
+ result = []
+
+ for i in range(0, length):
+ result.append(value >> (i * 8) & 0xff)
+
+ if big_endian:
+ result.reverse()
+
+ return result
+
+ @staticmethod
+ def py2_bytes_to_int(bytes, big_endian = True):
+ # type: (bytes, bool) -> int
+
+ my_bytes = bytes
+ if not big_endian:
+ my_bytes.reverse()
+
+ result = 0
+ for b in my_bytes:
+ result = result * 256 + int(b)
+
+ return result
+
+ @staticmethod
+ def transform_bytes2int(raw_bytes):
+ # type: (bytes) -> int
+
+ if sys.version_info[0] >= 3:
+ return int.from_bytes(raw_bytes, "big", signed=False)
+
+ return CustomRSA.py2_bytes_to_int(raw_bytes, True)
+
+
+ @staticmethod
+ def transform_int2bytes(number, fill_size = 0):
+ # type: (int, int) -> bytes
+
+ if number < 0:
+ raise ValueError("Negative number")
+
+ size = None
+
+ if fill_size > 0:
+ size = fill_size
+ else:
+ size = max(1, CustomRSA.byte_size(number))
+
+ if sys.version_info[0] >= 3:
+ return number.to_bytes(size, "big")
+
return CustomRSA.py2_int_to_bytes(number, size, True)
\ No newline at end of file
diff --git a/setup/data.py b/DeGourou/setup/data.py
similarity index 99%
rename from setup/data.py
rename to DeGourou/setup/data.py
index 9aa457c..a44681d 100644
--- a/setup/data.py
+++ b/DeGourou/setup/data.py
@@ -1,5 +1,5 @@
-from setup.params import FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML
-from decrypt.params import KEYPATH
+from .params import FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML
+from ..decrypt.params import KEYPATH
keyContent = b'0\x82\x02\\\x02\x01\x00\x02\x81\x81\x00\xad*E\x8e0\n\x91\xd6\xbaj\xc1t3\xc2R2h\xa6\x18\x063i\xfd\x9bR/e\xa6\xec\x87\xab\x11\n\'\xb7\x93\x14\xb6\xbbm\xfa\xf0\xf4\xe8=\x18\xa6\xe9\x15$\xdao\xb3\x8d\xf5\xddT\n\xf5\t<\xe8\xb2\x93k\x02zi\xe6\x86\x10F\x13\xc9m\xcfZ\x83\xe6=\xd6G\xf2/]3\xff\x8ch#\xea|\xa9I\x9a\xf6\xbf\x19\xd9\x10\xe0\x18\xa1\rb\x801k~\xc03f\x84\x07{v\x88\x18\x9bH\x91+o \x90\x9b\xb7\xf5\x02\x03\x01\x00\x01\x02\x81\x80\x05\xfd\x95\xd3\x886\x9a\xba\x8ck\xc1\xb5\xc21\x86\xab\x1a\xa8^\x1af%\x9b\x8a\xc0\x96\xc6\x10}\xb6\xf6\xeb\x80\xc4R\xc2@\x9d\xf9F\xa1\xf7\xe6\x06jPs\xad\xc3w\xd3\xea\xb7\xca\xec\x03\x17\xcf\xff\x01u\x96\x15\n\x0e&\xb0\xc7\x90F\xc4\xdaZ"\xc1)>\xee\x19\xf6\x05\xa5\xba\x00H)\xa8>\x1fC\x02\xd3\xba\xa8){\x06^D\xb4\xfd"\x05\x05\xec\xef\xdb.tbZ8\xabU<,+\xb6\xfaI\x98\xcc7H\xedr\xa9\xfd\x02A\x00\xc27%\xc5\xa0\xff\xd5l\xaa\x7f=\x1dx\xab?\xd8~\xf7v\x1f!\x0cCh\xc9\xb4\x1a\x8b\xb2\xaeC\xa0\xf9\x91\xcc\x99<\x11\xfbQ\xae\x8fG\xb0\xd1b\x0c=\xebR\x19\xb4\x15\xd4\x1c\xbe\xf4\xc7E\xe8\xea\xe1\xb3\x0b\x02A\x00\xe4@\xcb(\xdd\x04F\xe4jT\xe5a\xaaj\xaf=F\xa1\xaf\x1c\xa6F\x93\xc7V1\xd9\xb1\x96\xdb\x1b\xf5\x86\r\xb11\x10\x12\x18\xc5\xee\xaeD\xa3\xc1/\xe3\xf2\x8f\xaf\xad\xda\xe6\t\x8d\x9d\x99z\x04\xeeK\xdb \xff\x02A\x00\xad_\x9d\x90v\xd0\xeb->f\xa7\xa0\x0f\x80\x90V+\xc1\xac\xe8\xcd\x0f\xad}u\xd2\x19\x80k\xd9\xb4\xf5\x96\xd4\xd8\xd8R\x0f\x9bR\xa7\x89\xb0m\xdf\xfc\xaf\x00\xf7y+\x08\xe0\x13\xa25\xb5=\xce\xe2\xc6\x0b\x05Q\x02@\x18\xee\xf7\x02\\\xbaU\xe0\'\xb9da9\xd3s\x97\x16\xfb\x1c|\xdd\xb1\x01\xfd\x99m\xd2\xa0\xf2\xa0\xb6\xba(M\xa0\x98\x82o\xe7\xa2\xdf\x82\xcb\xde\xb3\x80\xbe\xbe\xc5qdep\x11\x85\x15\xbd)6\x16\xad\xd4\x9f\x13\x02@\x0f\x15\xc1Y"b\x19\x81Q\x81\x8d\x006\xe4\xf0e\xa2\xa7\xb8\x98{\x1c\x12\xe0\nw\xbe\x86A-\xd0\x1c7\xf3\x169\xadd3\x85\xaf\x13\x99\x08\x97e)c\xaf\xb1V\xf1\x15\xf6K\r\x16\xb4\xf9\xd1\x10\xe2\x92\xf9'
diff --git a/setup/fulfill.py b/DeGourou/setup/fulfill.py
similarity index 96%
rename from setup/fulfill.py
rename to DeGourou/setup/fulfill.py
index 15a99a5..dcd4fd0 100644
--- a/setup/fulfill.py
+++ b/DeGourou/setup/fulfill.py
@@ -12,9 +12,9 @@ import os, time, shutil
import zipfile
from lxml import etree
-from setup.libadobe import sendHTTPRequest_DL2FILE
-from setup.libadobeFulfill import buildRights, fulfill
-from setup.libpdf import patch_drm_into_pdf
+from .libadobe import sendHTTPRequest_DL2FILE
+from .libadobeFulfill import buildRights, fulfill
+from .libpdf import patch_drm_into_pdf
#######################################################################
diff --git a/setup/ia.py b/DeGourou/setup/ia.py
similarity index 100%
rename from setup/ia.py
rename to DeGourou/setup/ia.py
diff --git a/setup/libadobe.py b/DeGourou/setup/libadobe.py
similarity index 99%
rename from setup/libadobe.py
rename to DeGourou/setup/libadobe.py
index a02a368..ccd4a65 100644
--- a/setup/libadobe.py
+++ b/DeGourou/setup/libadobe.py
@@ -30,7 +30,7 @@ except ImportError:
#@@CALIBRE_COMPAT_CODE@@
-from setup.customRSA import CustomRSA
+from .customRSA import CustomRSA
from cryptography.hazmat.primitives.serialization.pkcs12 import load_key_and_certificates
from cryptography.hazmat.primitives import serialization
@@ -38,7 +38,7 @@ from cryptography.hazmat.primitives import serialization
VAR_ACS_SERVER_HTTP = "http://adeactivate.adobe.com/adept"
VAR_ACS_SERVER_HTTPS = "https://adeactivate.adobe.com/adept"
-from setup.params import FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML
+from .params import FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML
# Lists of different ADE "versions" we know about
VAR_VER_SUPP_CONFIG_NAMES = [ "ADE 1.7.2", "ADE 2.0.1", "ADE 3.0.1", "ADE 4.0.3", "ADE 4.5.10", "ADE 4.5.11" ]
diff --git a/setup/libadobeAccount.py b/DeGourou/setup/libadobeAccount.py
similarity index 98%
rename from setup/libadobeAccount.py
rename to DeGourou/setup/libadobeAccount.py
index be604ef..47d4b4f 100644
--- a/setup/libadobeAccount.py
+++ b/DeGourou/setup/libadobeAccount.py
@@ -15,12 +15,12 @@ except ImportError:
#@@CALIBRE_COMPAT_CODE@@
-from setup.libadobe import addNonce, sign_node, sendRequestDocu, sendHTTPRequest
-from setup.libadobe import makeFingerprint, makeSerial, encrypt_with_device_key, decrypt_with_device_key
-from setup.libadobe import get_devkey_path, get_device_path, get_activation_xml_path
-from setup.libadobe import VAR_VER_SUPP_CONFIG_NAMES, VAR_VER_HOBBES_VERSIONS, VAR_VER_OS_IDENTIFIERS
-from setup.libadobe import VAR_VER_ALLOWED_BUILD_IDS_SWITCH_TO, VAR_VER_SUPP_VERSIONS, VAR_ACS_SERVER_HTTP
-from setup.libadobe import VAR_ACS_SERVER_HTTPS, VAR_VER_BUILD_IDS, VAR_VER_NEED_HTTPS_BUILD_ID_LIMIT, VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE
+from .libadobe import addNonce, sign_node, sendRequestDocu, sendHTTPRequest
+from .libadobe import makeFingerprint, makeSerial, encrypt_with_device_key, decrypt_with_device_key
+from .libadobe import get_devkey_path, get_device_path, get_activation_xml_path
+from .libadobe import VAR_VER_SUPP_CONFIG_NAMES, VAR_VER_HOBBES_VERSIONS, VAR_VER_OS_IDENTIFIERS
+from .libadobe import VAR_VER_ALLOWED_BUILD_IDS_SWITCH_TO, VAR_VER_SUPP_VERSIONS, VAR_ACS_SERVER_HTTP
+from .libadobe import VAR_ACS_SERVER_HTTPS, VAR_VER_BUILD_IDS, VAR_VER_NEED_HTTPS_BUILD_ID_LIMIT, VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE
def createDeviceFile(randomSerial, useVersionIndex = 0):
@@ -213,7 +213,7 @@ def createUser(useVersionIndex = 0, authCert = None):
def encryptLoginCredentials(username, password, authenticationCertificate):
# type: (str, str, str) -> bytes
- from setup.libadobe import devkey_bytes as devkey_adobe
+ from .libadobe import devkey_bytes as devkey_adobe
import struct
if devkey_adobe is not None:
diff --git a/setup/libadobeFulfill.py b/DeGourou/setup/libadobeFulfill.py
similarity index 98%
rename from setup/libadobeFulfill.py
rename to DeGourou/setup/libadobeFulfill.py
index 2d2c57e..d4bd7fd 100644
--- a/setup/libadobeFulfill.py
+++ b/DeGourou/setup/libadobeFulfill.py
@@ -5,10 +5,10 @@ import time
#@@CALIBRE_COMPAT_CODE@@
-from setup.libadobe import addNonce, sign_node, get_cert_from_pkcs12, sendRequestDocu, sendRequestDocuRC, sendHTTPRequest
-from setup.libadobe import get_devkey_path, get_device_path, get_activation_xml_path
-from setup.libadobe import VAR_VER_SUPP_VERSIONS, VAR_VER_HOBBES_VERSIONS
-from setup.libadobe import VAR_VER_BUILD_IDS, VAR_VER_USE_DIFFERENT_NOTIFICATION_XML_ORDER
+from .libadobe import addNonce, sign_node, get_cert_from_pkcs12, sendRequestDocu, sendRequestDocuRC, sendHTTPRequest
+from .libadobe import get_devkey_path, get_device_path, get_activation_xml_path
+from .libadobe import VAR_VER_SUPP_VERSIONS, VAR_VER_HOBBES_VERSIONS
+from .libadobe import VAR_VER_BUILD_IDS, VAR_VER_USE_DIFFERENT_NOTIFICATION_XML_ORDER
def buildFulfillRequest(acsm):
@@ -143,7 +143,7 @@ def getDecryptedCert(pkcs12_b64_string = None):
pkcs12_data = base64.b64decode(pkcs12_b64_string)
try:
- from setup.libadobe import devkey_bytes as devkey_adobe
+ from .libadobe import devkey_bytes as devkey_adobe
except:
pass
diff --git a/setup/libpdf.py b/DeGourou/setup/libpdf.py
similarity index 100%
rename from setup/libpdf.py
rename to DeGourou/setup/libpdf.py
diff --git a/setup/loginAccount.py b/DeGourou/setup/loginAccount.py
similarity index 87%
rename from setup/loginAccount.py
rename to DeGourou/setup/loginAccount.py
index d1715c5..dbb9610 100644
--- a/setup/loginAccount.py
+++ b/DeGourou/setup/loginAccount.py
@@ -5,14 +5,14 @@
This is an experimental Python version of libgourou.
'''
-from setup.libadobe import createDeviceKeyFile, FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML
-from setup.libadobeAccount import createDeviceFile, createUser, signIn, activateDevice, exportAccountEncryptionKeyDER, getAccountUUID
+from .libadobe import createDeviceKeyFile, FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML
+from .libadobeAccount import createDeviceFile, createUser, signIn, activateDevice, exportAccountEncryptionKeyDER, getAccountUUID
from os.path import exists
VAR_MAIL = ""
VAR_PASS = ""
VAR_VER = 1 # None # 1 for ADE2.0.1, 2 for ADE3.0.1
-from decrypt.params import KEYPATH
+from ..decrypt.params import KEYPATH
#################################################################
diff --git a/setup/params.py b/DeGourou/setup/params.py
similarity index 100%
rename from setup/params.py
rename to DeGourou/setup/params.py
diff --git a/README.md b/README.md
index 3d2eb40..f303503 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ This tool is intended for educational purposes only. Its primary aim is to assis
## Usage
```
-usage: DeGourou.py [-h] [-f [F]] [-u [U]] [-t [T]] [-o [O]] [-la] [-li] [-e [E]] [-p [P]] [-lo]
+usage: DeGourou [-h] [-f [F]] [-u [U]] [-t [T]] [-o [O]] [-la] [-li] [-e [E]] [-p [P]] [-lo]
Download and Decrypt an encrypted PDF or EPUB file.
@@ -74,17 +74,28 @@ optional arguments:
C. MacOS user's accordingly with name ```DeGourou.bin```
+### For Middlemans
+
+1. Install through Pip using
+
+ ```
+ pip install git+https://gitea.com/bipinkrish/DeGourou.git
+ ```
+
+2. Use `degourou` in Terminal/CMD
+
### For Developers
1. Clone the repositary or Download zip file and extract it
2. Install requirements using pip
-3. Run "DeGourou.py" file
+3. Run "DeGourou.py" file in "DeGourou" directory
```
-git clone https://github.com/bipinkrish/DeGourou.git
+git clone https://gitea.com/bipinkrish/DeGourou.git
cd DeGourou
pip install -r requirements.txt
+cd DeGourou
python DeGourou.py
```
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..85ec2bc
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,36 @@
+from setuptools import setup
+
+setup(
+ name='DeGourou',
+ version='1.3.8',
+ description='Automate the process of getting decrypted ebook from InternetArchive without the need for Adobe Digital Editions and Calibre.',
+ url='https://gitea.com/bipinkrish/DeGourou',
+ author='Bipin krishna',
+ license='GPL3',
+ packages=['DeGourou',"DeGourou/setup","DeGourou/decrypt"],
+ install_requires=['pycryptodomex>=3.17',
+ 'cryptography>=41.0.1',
+ 'lxml>=4.9.2',
+ 'requests>=2.31.0',
+ 'charset-normalizer>=3.1.0'
+ ],
+
+ entry_points={
+ 'console_scripts': [
+ 'degourou = DeGourou.DeGourou:main'
+ ]
+ },
+
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Intended Audience :: Science/Research',
+ 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
+ 'Operating System :: MacOS',
+ 'Operating System :: Microsoft :: Windows',
+ 'Operating System :: POSIX :: Linux',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ ],
+)
\ No newline at end of file