Guest User

ia book downloader

a guest
Apr 3rd, 2023
85
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.10 KB | None | 0 0
  1. import requests
  2. import random, string
  3. from concurrent import futures
  4. from tqdm import tqdm
  5. import time
  6. from datetime import datetime
  7. import argparse
  8. import os
  9. import sys
  10. import time
  11. import shutil
  12.  
  13. def display_error(response, message):
  14.     print(message)
  15.     print(response)
  16.     print(response.text)
  17.     exit()
  18.  
  19. def get_book_infos(session, url):
  20.     r = session.get(url).text
  21.     infos_url = "https:" + r.split('bookManifestUrl="')[1].split('"\n')[0]
  22.     response = session.get(infos_url)
  23.     data = response.json()['data']
  24.     title = data['brOptions']['bookTitle'].strip().replace(" ", "_")
  25.     title = ''.join( c for c in title if c not in '<>:"/\\|?*' ) # Filter forbidden chars in directory names (Windows & Linux)
  26.     title = title[:150] # Trim the title to avoid long file names  
  27.     metadata = data['metadata']
  28.     links = []
  29.     for item in data['brOptions']['data']:
  30.         for page in item:
  31.             links.append(page['uri'])
  32.  
  33.     if len(links) > 1:
  34.         print(f"[+] Found {len(links)} pages")
  35.         return title, links, metadata
  36.     else:
  37.         print(f"[-] Error while getting image links")
  38.         exit()
  39.  
  40. def format_data(content_type, fields):
  41.     data = ""
  42.     for name, value in fields.items():
  43.         data += f"--{content_type}\x0d\x0aContent-Disposition: form-data; name=\"{name}\"\x0d\x0a\x0d\x0a{value}\x0d\x0a"
  44.     data += content_type+"--"
  45.     return data
  46.  
  47. def login(email, password):
  48.     session = requests.Session()
  49.     session.get("https://archive.org/account/login")
  50.     content_type = "----WebKitFormBoundary"+"".join(random.sample(string.ascii_letters + string.digits, 16))
  51.  
  52.     headers = {'Content-Type': 'multipart/form-data; boundary='+content_type}
  53.     data = format_data(content_type, {"username":email, "password":password, "submit_by_js":"true"})
  54.  
  55.     response = session.post("https://archive.org/account/login", data=data, headers=headers)
  56.     if "bad_login" in response.text:
  57.         print("[-] Invalid credentials!")
  58.         exit()
  59.     elif "Successful login" in response.text:
  60.         print("[+] Successful login")
  61.         return session
  62.     else:
  63.         display_error(response, "[-] Error while login:")
  64.  
  65. def loan(session, book_id, verbose=True):
  66.     data = {
  67.         "action": "grant_access",
  68.         "identifier": book_id
  69.     }
  70.     response = session.post("https://archive.org/services/loans/loan/searchInside.php", data=data)
  71.     data['action'] = "browse_book"
  72.     response = session.post("https://archive.org/services/loans/loan/", data=data)
  73.  
  74.     if response.status_code == 400 :
  75.         if response.json()["error"] == "This book is not available to borrow at this time. Please try again later.":
  76.             print("This book doesn't need to be borrowed")
  77.             return session
  78.         else :
  79.             display_error(response, "Something went wrong when trying to borrow the book.")
  80.  
  81.     data['action'] = "create_token"
  82.     response = session.post("https://archive.org/services/loans/loan/", data=data)
  83.  
  84.     if "token" in response.text:
  85.         if verbose:
  86.             print("[+] Successful loan")
  87.         return session
  88.     else:
  89.         display_error(response, "Something went wrong when trying to borrow the book, maybe you can't borrow this book.")
  90.  
  91. def return_loan(session, book_id):
  92.     data = {
  93.         "action": "return_loan",
  94.         "identifier": book_id
  95.     }
  96.     response = session.post("https://archive.org/services/loans/loan/", data=data)
  97.     if response.status_code == 200 and response.json()["success"]:
  98.         print("[+] Book returned")
  99.         print("Waiting 60 seconds so you don't hammer the server.")
  100.         time.sleep(60)
  101.  
  102.     else:
  103.         display_error(response, "Something went wrong when trying to return the book")
  104.  
  105. def image_name(pages, page, directory):
  106.     return f"{directory}/{(len(str(pages)) - len(str(page))) * '0'}{page}.jpg"
  107.  
  108. def download_one_image(session, link, i, directory, book_id, pages):
  109.     headers = {
  110.         "Referer": "https://archive.org/",
  111.         "Accept": "image/avif,image/webp,image/apng,image/*,*/*;q=0.8",
  112.         "Sec-Fetch-Site": "same-site",
  113.         "Sec-Fetch-Mode": "no-cors",
  114.         "Sec-Fetch-Dest": "image",
  115.     }
  116.     retry = True
  117.     while retry:
  118.         try:
  119.             response = session.get(link, headers=headers)
  120.             if response.status_code == 403:
  121.                 session = loan(session, book_id, verbose=False)
  122.                 raise Exception("Borrow again")
  123.             elif response.status_code == 200:
  124.                 retry = False
  125.         except:
  126.             time.sleep(1)   # Wait 1 second before retrying
  127.  
  128.     image = image_name(pages, i, directory)
  129.     with open(image,"wb") as f:
  130.         f.write(response.content)
  131.  
  132.  
  133. def download(session, n_threads, directory, links, scale, book_id):
  134.     print("Downloading pages...")
  135.     links = [f"{link}&rotate=0&scale={scale}" for link in links]
  136.     pages = len(links)
  137.  
  138.     tasks = []
  139.     with futures.ThreadPoolExecutor(max_workers=n_threads) as executor:
  140.         for link in links:
  141.             i = links.index(link)
  142.             tasks.append(executor.submit(download_one_image, session=session, link=link, i=i, directory=directory, book_id=book_id, pages=pages))
  143.         for task in tqdm(futures.as_completed(tasks), total=len(tasks)):
  144.             pass
  145.    
  146.     images = [image_name(pages, i, directory) for i in range(len(links))]
  147.     return images
  148.  
  149. def make_pdf(pdf, title, directory):
  150.     file = title+".pdf"
  151.     # Handle the case where multiple books with the same name are downloaded
  152.     i = 1
  153.     while os.path.isfile(os.path.join(directory, file)):
  154.         file = f"{title}({i}).pdf"
  155.         i += 1
  156.  
  157.     with open(os.path.join(directory, file),"wb") as f:
  158.         f.write(pdf)
  159.     print(f"[+] PDF saved as \"{file}\"")
  160.  
  161. if __name__ == "__main__":
  162.  
  163.     my_parser = argparse.ArgumentParser()
  164.     my_parser.add_argument('-e', '--email', help='Your archive.org email', type=str, required=True)
  165.     my_parser.add_argument('-p', '--password', help='Your archive.org password', type=str, required=True)
  166.     my_parser.add_argument('-u', '--url', help='Link to the book (https://archive.org/details/XXXX). You can use this argument several times to download multiple books', action='append', type=str)
  167.     my_parser.add_argument('-d', '--dir', help='Output directory', type=str)
  168.     my_parser.add_argument('-f', '--file', help='File where are stored the URLs of the books to download', type=str)
  169.     my_parser.add_argument('-r', '--resolution', help='Image resolution (10 to 0, 0 is the highest), [default 3]', type=int, default=3)
  170.     my_parser.add_argument('-t', '--threads', help="Maximum number of threads, [default 50]", type=int, default=50)
  171.     my_parser.add_argument('-j', '--jpg', help="Output to individual JPG's rather than a PDF", action='store_true')
  172.  
  173.     if len(sys.argv) == 1:
  174.         my_parser.print_help(sys.stderr)
  175.         sys.exit(1)
  176.     args = my_parser.parse_args()
  177.  
  178.     if args.url is None and args.file is None:
  179.         my_parser.error("At least one of --url and --file required")
  180.  
  181.     email = args.email
  182.     password = args.password
  183.     scale = args.resolution
  184.     n_threads = args.threads
  185.     d = args.dir
  186.  
  187.     if d == None:
  188.         d = os.getcwd()
  189.     elif not os.path.isdir(d):
  190.         print(f"Output directory does not exist!")
  191.         exit()
  192.  
  193.     if args.url is not None:
  194.         urls = args.url
  195.     else:
  196.         if os.path.exists(args.file):
  197.             with open(args.file) as f:
  198.                 urls = f.read().strip().split("\n")
  199.         else:
  200.             print(f"{args.file} does not exist!")
  201.             exit()
  202.  
  203.     # Check the urls format
  204.     for url in urls:
  205.         if not url.startswith("https://archive.org/details/"):
  206.             print(f"{url} --> Invalid url. URL must starts with \"https://archive.org/details/\"")
  207.             exit()
  208.  
  209.     print(f"{len(urls)} Book(s) to download")
  210.     session = login(email, password)
  211.  
  212.     for url in urls:
  213.         book_id = list(filter(None, url.split("/")))[3]
  214.         print("="*40)
  215.         print(f"Current book: https://archive.org/details/{book_id}")
  216.         session = loan(session, book_id)
  217.         title, links, metadata = get_book_infos(session, url)
  218.  
  219.         directory = os.path.join(d, title)
  220.         # Handle the case where multiple books with the same name are downloaded
  221.         i = 1
  222.         _directory = directory
  223.         while os.path.isdir(directory):
  224.             directory = f"{_directory}({i})"
  225.             i += 1
  226.         os.makedirs(directory)
  227.  
  228.         images = download(session, n_threads, directory, links, scale, book_id)
  229.  
  230.         if not args.jpg: # Create pdf with images and remove the images folder
  231.             import img2pdf
  232.  
  233.             # prepare PDF metadata
  234.             # sometimes archive metadata is missing
  235.             pdfmeta = { }
  236.             # ensure metadata are str
  237.             for key in ["title", "creator", "associated-names"]:
  238.                 if key in metadata:
  239.                     if isinstance(metadata[key], str):
  240.                         pass
  241.                     elif isinstance(metadata[key], list):
  242.                         metadata[key] = "; ".join(metadata[key])
  243.                     else:
  244.                         raise Exception("unsupported metadata type")
  245.             # title
  246.             if 'title' in metadata:
  247.                 pdfmeta['title'] = metadata['title']
  248.             # author
  249.             if 'creator' in metadata and 'associated-names' in metadata:
  250.                 pdfmeta['author'] = metadata['creator'] + "; " + metadata['associated-names']
  251.             elif 'creator' in metadata:
  252.                 pdfmeta['author'] = metadata['creator']
  253.             elif 'associated-names' in metadata:
  254.                 pdfmeta['author'] = metadata['associated-names']
  255.             # date
  256.             if 'date' in metadata:
  257.                 try:
  258.                     pdfmeta['creationdate'] = datetime.strptime(metadata['date'][0:4], '%Y')
  259.                 except:
  260.                     pass
  261.             # keywords
  262.             pdfmeta['keywords'] = [f"https://archive.org/details/{book_id}"]
  263.  
  264.             pdf = img2pdf.convert(images, **pdfmeta)
  265.             make_pdf(pdf, title, args.dir if args.dir != None else "")
  266.             try:
  267.                 shutil.rmtree(directory)
  268.             except OSError as e:
  269.                 print ("Error: %s - %s." % (e.filename, e.strerror))
  270.  
  271.         return_loan(session, book_id)
  272.  
  273.  
  274.  
Add Comment
Please, Sign In to add comment