Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added the ability to download books from playlists ! #354

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ $ python3 safaribooks.py --cred "[email protected]:password01" XXXXXXXXXXXXX
The ID is the digits that you find in the URL of the book description page:
`https://www.safaribooksonline.com/library/view/book-name/XXXXXXXXXXXXX/`
Like: `https://www.safaribooksonline.com/library/view/test-driven-development-with/9781491958698/`

You can also download books from a playlist !
Simply set the `--playlist` flag and provide a playlist ID instead of the book ID.
This option will download all books in the playlist one at a time.

#### Program options:
```shell
Expand All @@ -64,10 +68,15 @@ usage: safaribooks.py [--cred <EMAIL:PASS> | --login] [--no-cookies]
Download and generate an EPUB of your favorite books from Safari Books Online.

positional arguments:
<BOOK ID> Book digits ID that you want to download. You can find
<BOOK ID> / <PLAYLIST ID>
Book digits ID that you want to download. You can find
it in the URL (X-es):
`https://learning.oreilly.com/library/view/book-
name/XXXXXXXXXXXXX/`

Alternatively, specify a playlist ID to download all books in that playlist (Private or Public)
by setting the --playlist flag
EG: --playlist "6f612b99-bebc-41e1-8fff-6b655507b7af"

optional arguments:
--cred <EMAIL:PASS> Credentials used to perform the auth login on Safari
Expand All @@ -82,6 +91,7 @@ optional arguments:
export the EPUB to E-Readers like Amazon Kindle.
--preserve-log Leave the `info_XXXXXXXXXXXXX.log` file even if there
isn't any error.
--playlist Set this flag to interpret the <BOOK ID> as <PLAYLIST ID>
--help Show this help message.
```

Expand Down
76 changes: 72 additions & 4 deletions safaribooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def save_last_request(self):
.format(*self.last_request))

def intro(self):
output = self.SH_YELLOW + ("""
output = self.SH_YELLOW + (r"""
____ ___ _
/ __/__ _/ _/__ _____(_)
_\ \/ _ `/ _/ _ `/ __/ /
Expand Down Expand Up @@ -171,7 +171,8 @@ def parse_description(self, desc):
def book_info(self, info):
description = self.parse_description(info.get("description", None)).replace("\n", " ")
for t in [
("Title", info.get("title", "")), ("Authors", ", ".join(aut.get("name", "") for aut in info.get("authors", []))),
("Title", info.get("title", "")),
("Authors", ", ".join(aut.get("name", "") for aut in info.get("authors", []))),
("Identifier", info.get("identifier", "")), ("ISBN", info.get("isbn", "")),
("Publishers", ", ".join(pub.get("name", "") for pub in info.get("publishers", []))),
("Rights", info.get("rights", "")),
Expand Down Expand Up @@ -227,6 +228,8 @@ def qsize(self):
class SafariBooks:
LOGIN_URL = ORLY_BASE_URL + "/member/auth/login/"
LOGIN_ENTRY_URL = SAFARI_BASE_URL + "/login/unified/?next=/home/"
COLLECTIONS_ENDPOINT_V3 = "/api/v3/collections/"
get_all_playlists_data_url = SAFARI_BASE_URL + COLLECTIONS_ENDPOINT_V3

API_TEMPLATE = SAFARI_BASE_URL + "/api/v1/book/{0}/"

Expand Down Expand Up @@ -338,6 +341,48 @@ def __init__(self, args):

self.check_login()

if not args.playlist:
self.download_one_book(args)

# Interpret the <BOOKID> passed as an argument as a playlist ID
args.playlist_id = args.bookid
self.download_all_in_playlist(args)

def download_all_in_playlist(self, args):
self.display.info("Will download all in playlist ! => " + args.playlist_id)
self.display.info("Retrieving playlist info...")
self.playlist_info = self.get_user_playlist_info(self.args.playlist_id)
self.display.info("Retrieving all books one at a time...")

books_downloaded = set()
# print(self.playlist_info["content"])
for book_data in self.playlist_info["content"]:
extracted_book_id = self.check_and_extract_book_id_from_ourn(book_data["ourn"])
if extracted_book_id is None or extracted_book_id in books_downloaded:
continue
self.display.info("Retrieving book \"" + extracted_book_id + "\"...")

# Set the book ID in the arguments everytime.
# TODO: Update this logic when not single threaded
args.bookid = extracted_book_id

self.download_one_book(args)
books_downloaded.add(extracted_book_id)

"""
This function will filter out and extract only book ID's. Playlists can also contain videos,
chapter snippets, courses, etc.
"""
@staticmethod
def check_and_extract_book_id_from_ourn(ourn):
pattern = r'^urn:orm:book:(\d+)(?::|$)'
match = re.match(pattern, ourn)
if match:
return match.group(1)
return None

def download_one_book(self, args):

self.book_id = args.bookid
self.api_url = self.API_TEMPLATE.format(self.book_id)

Expand Down Expand Up @@ -529,6 +574,23 @@ def check_login(self):

self.display.info("Successfully authenticated.", state=True)

def get_user_playlist_info(self, playlist_id):
response = self.requests_provider(self.get_all_playlists_data_url)
if response == 0:
self.display.exit("API: Unable to retrieve user playlist info.")

response = response.json()
if not isinstance(response, list):
self.display.exit("Some error occurred")

# API returns a list of playlists
for playlist in response:
if playlist_id == playlist["id"]:
self.display.info("Found matching playlist with name \"" + playlist["name"] +
"\" and ID " + playlist["id"])
return playlist
self.display.exit("Did not find any matching playlist !")

def get_book_info(self):
response = self.requests_provider(self.api_url)
if response == 0:
Expand Down Expand Up @@ -1087,10 +1149,16 @@ def create_epub(self):
" file even if there isn't any error."
)
arguments.add_argument("--help", action="help", default=argparse.SUPPRESS, help='Show this help message.')
arguments.add_argument("--playlist", action="store_true", dest="playlist",
help="Specify a playlist ID to download all books in that playlist (Private or Public)."
"\n EG: -- playlist \"6f612b99-bebc-41e1-8fff-6b655507b7af\"")
arguments.add_argument(
"bookid", metavar='<BOOK ID>',
"bookid", metavar='<BOOK ID / PLAYLIST ID>',
help="Book digits ID that you want to download. You can find it in the URL (X-es):"
" `" + SAFARI_BASE_URL + "/library/view/book-name/XXXXXXXXXXXXX/`"
" `" + SAFARI_BASE_URL + "/library/view/book-name/XXXXXXXXXXXXX/`" +
"\n\nAlternatively, specify a playlist ID to download all books in that playlist (Private or Public)"
" by setting a \"--playlist flag\"."
"\n EG: -- playlist \"6f612b99-bebc-41e1-8fff-6b655507b7af\""
)

args_parsed = arguments.parse_args()
Expand Down