├── OpenbooksDownloader.py ├── README.md └── requirements.txt /OpenbooksDownloader.py: -------------------------------------------------------------------------------- 1 | ### 2 | # 3 | # IMPORTS SECTION 4 | # 5 | ### 6 | 7 | import requests, json, textwrap, html, os, isbnlib, statistics, subprocess, time, ebooklib 8 | from difflib import SequenceMatcher 9 | from ebooklib import epub 10 | from sys import platform 11 | import xml.etree.ElementTree as ET 12 | 13 | if platform == "win32": 14 | executable = "openbooks.exe" 15 | else: 16 | executable = "./openbooks_linux" 17 | 18 | lang = "en" 19 | 20 | ### 21 | # 22 | # FUNCTION DEFINITIONS SECTION 23 | # 24 | ### 25 | 26 | def convertToISBN13(isbn): 27 | if(isbnlib.is_isbn10(isbn)): 28 | return isbnlib.to_isbn13(isbn) 29 | else: 30 | return isbn 31 | 32 | def similar(a, b): 33 | return SequenceMatcher(None, a, b).ratio() 34 | 35 | def clearScreen(): 36 | os.system('cls' if os.name == 'nt' else 'clear') 37 | 38 | def generateOverallMatch(fuzzyMatches): 39 | titleWeight = 1.2 40 | authorWeight = 1 41 | descWeight = 0.7 42 | 43 | totalWeights = titleWeight + authorWeight + descWeight 44 | confidence = (((fuzzyMatches[0] * titleWeight) + (fuzzyMatches[1] * authorWeight) + (fuzzyMatches[2] * descWeight)) / totalWeights) 45 | return "{:.2%}".format(confidence) 46 | 47 | def search(targetTitle, targetAuthors): 48 | global executable 49 | print("\n\nRequesting", targetTitle, "from IRCHighway.") 50 | openbooksSearchReturn = subprocess.Popen([executable, "cli", "search", '"' + targetTitle + " " + targetAuthors + '"'], stdout=subprocess.PIPE) 51 | return openbooksSearchReturn.stdout.read().decode().split("Results location: ",1)[1].strip() 52 | 53 | def scrapeSearchResults(openbooksSearchFile): 54 | validBooks = [] 55 | for line in open(openbooksSearchFile, "r").read().split("\n"): 56 | if ".epub" in line: 57 | validBooks.append(line) 58 | os.remove(openbooksSearchFile) 59 | return validBooks 60 | 61 | def downloadEbook(downloadCommand): 62 | global executable 63 | openbooksDownloadReturn = subprocess.Popen([executable, "cli", "download", downloadCommand], stdout=subprocess.PIPE) 64 | return openbooksDownloadReturn.stdout.read().decode().split("File location: ",1)[1].strip() 65 | 66 | def fallBackToNextBook(): 67 | global epubFileName, validBooks, downloadCommand 68 | if epubFileName.strip() != "": 69 | os.remove(epubFileName) 70 | validBooks.remove(downloadCommand) 71 | if len(validBooks) == 0: 72 | print("Could not find any valid books that matched the criteria...") 73 | exit() 74 | downloadCommand = min(validBooks, key=len) 75 | clearScreen() 76 | 77 | def checkEmbeddedLanguage(book): 78 | if lang in book.get_metadata('DC', 'language')[0][0] or book.get_metadata('DC', 'language')[0][0].startswith(lang + "-"): 79 | return True 80 | else: 81 | return False 82 | 83 | def prepareAndRunFuzzyMatching(targetTitle, bookTitle, targetAuthors, bookAuthors, targetDesc, bookDesc): 84 | fuzzyMatches = [] 85 | fuzzyMatches.append(similar(targetTitle, bookTitle)) 86 | fuzzyMatches.append(similar(targetAuthors, bookAuthors)) 87 | fuzzyMatches.append(similar(targetDesc, bookDesc)) 88 | return generateOverallMatch(fuzzyMatches) 89 | 90 | def getTitle(data): 91 | return data["volumeInfo"]["title"] 92 | 93 | def getDesc(data): 94 | return html.unescape(data["searchInfo"]["textSnippet"]) 95 | 96 | def getAuthors(data, separator=", "): 97 | return separator.join(data["volumeInfo"]["authors"]) 98 | 99 | def getPageCount(data): 100 | return data["volumeInfo"]["pageCount"] 101 | 102 | def getLanguage(data): 103 | return data["volumeInfo"]["language"] 104 | 105 | def getISBN(data): 106 | return convertToISBN13(data["volumeInfo"]["industryIdentifiers"][0]['identifier']) 107 | 108 | def checkForSimilarISBNS(isbn): 109 | request = requests.get("https://www.librarything.com/api/thingISBN/" + isbn) 110 | root = ET.ElementTree(ET.fromstring(request.content.decode())).getroot() 111 | relatedIsbns = [] 112 | for child in root: 113 | relatedIsbns.append(child.text) 114 | return relatedIsbns 115 | 116 | 117 | ### 118 | # 119 | # START OF MAIN PROGRAM 120 | # 121 | ### 122 | 123 | searchInput = input("Enter search: ") 124 | targetISBN = -1 125 | response = requests.get("https://www.googleapis.com/books/v1/volumes?q=" + searchInput) 126 | info = json.loads(response.content.decode()) 127 | if info['totalItems'] == 0: 128 | print("No book results with query...") 129 | exit() 130 | clearScreen() 131 | 132 | for volume_info in info['items']: 133 | try: 134 | print("\nTitle:", getTitle(volume_info)) 135 | print("\nSummary:\n") 136 | print(textwrap.fill(getDesc(volume_info), width=65)) 137 | print("\nAuthor(s):", getAuthors(volume_info)) 138 | print("\nPage count:", getPageCount(volume_info)) 139 | print("\nLanguage:", getLanguage(volume_info)) 140 | print("\n***") 141 | except: 142 | pass 143 | else: 144 | isCorrectBook = input("\n\nIs this your book? (y/n):") 145 | if isCorrectBook.lower().startswith("y"): 146 | targetISBN = getISBN(volume_info) 147 | targetTitle = getTitle(volume_info) 148 | targetAuthors = getAuthors(volume_info) 149 | targetDesc = getDesc(volume_info) 150 | break 151 | else: 152 | clearScreen() 153 | 154 | if targetISBN == -1: 155 | clearScreen() 156 | print("No book results remaining...") 157 | exit() 158 | 159 | openbooksSearchFile = search(targetTitle, targetAuthors) 160 | clearScreen() 161 | validBooks = scrapeSearchResults(openbooksSearchFile) 162 | if len(validBooks) == 0: 163 | print("No results for this book that we could download...") 164 | exit() 165 | downloadCommand = min(validBooks, key=len) 166 | 167 | while True: 168 | try: 169 | epubFileName = downloadEbook(downloadCommand) 170 | book = epub.read_epub(epubFileName) 171 | except: 172 | #print("\n\nFAILED TO DOWNLOAD, FALLING BACK TO NEXT BOOK.") 173 | fallBackToNextBook() 174 | else: 175 | try: 176 | isbn = convertToISBN13(isbnlib.canonical(isbnlib.get_isbnlike(str(book.get_metadata('DC', 'identifier')))[0])) 177 | if isbn.strip() == "": 178 | raise 179 | except: 180 | #print("\n\nEXTRACTING METADATA FAILED, FALLING BACK TO NEXT BOOK.") 181 | fallBackToNextBook() 182 | else: 183 | if checkEmbeddedLanguage(book): 184 | similarISBNS = checkForSimilarISBNS(isbn) 185 | if isbn == targetISBN or targetISBN in similarISBNS or isbnlib.to_isbn10(targetISBN) in similarISBNS: 186 | break 187 | else: 188 | response = requests.get("https://www.googleapis.com/books/v1/volumes?q=isbn:" + isbn) 189 | info = json.loads(response.content.decode()) 190 | if info['totalItems'] != 0: 191 | volume_info = info["items"][0] 192 | if getLanguage(volume_info) == lang: 193 | matchPercentage = prepareAndRunFuzzyMatching(targetTitle, getTitle(volume_info), targetAuthors, getAuthors(volume_info, " "), targetDesc, getDesc(volume_info)) 194 | 195 | print("\n\n***") 196 | print("\nTitle:", getTitle(volume_info)) 197 | print("\nSummary:\n") 198 | print(textwrap.fill(getDesc(volume_info), width=65)) 199 | print("\nAuthor(s):", getAuthors(volume_info)) 200 | print("\nPage count:", getPageCount(volume_info)) 201 | print("\nLanguage:", getLanguage(volume_info)) 202 | print("\n***") 203 | 204 | correctBookQuery = input("This book is only a", matchPercentage, "match, does this still seem like the correct book? (y/n)") 205 | if correctBookQuery.lower().startswith("y"): 206 | break 207 | else: 208 | fallBackToNextBook() 209 | else: 210 | #print("\n\nBOOK DOES NOT MEET REQUIRED LANGUAGE, FALLING BACK TO NEXT BOOK.") 211 | fallBackToNextBook() 212 | else: 213 | #print("\n\nMETADATA QUERY FAILED, FALLING BACK TO NEXT BOOK.") 214 | fallBackToNextBook() 215 | else: 216 | #print("\n\nBOOK DOES NOT MEET REQUIRED LANGUAGE, FALLING BACK TO NEXT BOOK.") 217 | fallBackToNextBook() 218 | 219 | clearScreen() 220 | print("Book successfully downloaded!") 221 | addToCalibre = input("\n\nAdd this book to calibre? (y/n):") 222 | if addToCalibre.lower().startswith("y"): 223 | subprocess.run(["calibredb", "add", epubFileName]) 224 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Openbooks-Downloader 2 | Easy automated ebook downloader using openbooks as the backend 3 | ![image](https://user-images.githubusercontent.com/12180913/136681752-d801e60d-8d0f-4a70-9a09-4dd549840caa.png) 4 | ![image](https://user-images.githubusercontent.com/12180913/136681833-8a7396c8-db22-4031-aa40-76f3ae20eee6.png) 5 | 6 | Download the latest version of [Openbooks](https://github.com/evan-buss/openbooks/releases/latest) and place it in the same directory as the python program. (If you are using Linux make sure its named openbooks_linux and is marked as executable with chmod) 7 | 8 | ``` 9 | pip3 install -r requirements.txt 10 | python3 OpenbooksDownloader.py 11 | ``` 12 | 13 | **DISCLAIMER: I MADE THIS LAST NIGHT WHEN I WAS INTOXICATED AND ONLY CLEANED UP THE CODE TODAY- EXPECT CRASHES WITH EDGE CASES** 14 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | isbnlib 2 | ebooklib 3 | requests 4 | certifi 5 | lxml 6 | --------------------------------------------------------------------------------