Metadata-Version: 2.5
Name: orlydown
Version: 0.2.0
Summary: Download O'Reilly books as epub files you can read in any reader
Project-URL: Homepage, https://github.com/ashleyconnor/orlydown
Project-URL: Issues, https://github.com/ashleyconnor/orlydown/issues
Author: Ashley Connor, Tobias Bengfort
License-Expression: MIT
License-File: LICENSE
Keywords: downloader,ebook,epub,oreilly
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Utilities
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# orlydown

[![PyPI](https://img.shields.io/pypi/v/orlydown)](https://pypi.org/project/orlydown/)
[![Python versions](https://img.shields.io/pypi/pyversions/orlydown)](https://pypi.org/project/orlydown/)
[![License](https://img.shields.io/pypi/l/orlydown)](LICENSE)

O'Reilly provides all of their books in epub format, but only through their own
reader. `orlydown` downloads the individual files and assembles them back into
a complete epub, so you can read your books in any reader (Apple Books, Calibre,
KOReader, …), e.g. for accessibility reasons.

Before using it, please read the [O'Reilly Terms of
Service](https://learning.oreilly.com/terms/).

This is a fork of [xi/oreilly-downloader](https://github.com/xi/oreilly-downloader)
that fixes epubs which didn't render properly:

-   Stylesheets are linked from every chapter, so books keep their formatting
    and fonts.
-   Images and links work in books that keep chapters in a subdirectory
    (e.g. `Text/`, `Images/`).
-   Books whose package file isn't called `content.opf` (e.g. Springer titles)
    open correctly.
-   SVG covers are scaled to fit the page instead of being cropped.
-   A `--title` option names the epub after the book instead of its ID.
-   A `--require-auth` option stops straight away if your token doesn't work.
-   No dependencies: it only uses the Python standard library.

## Installation

You need Python 3.9 or newer. The easiest way to install `orlydown` is with
[pipx](https://pipx.pypa.io/), which puts it in its own environment and adds
the `orlydown` command to your `PATH`:

```
pipx install orlydown
```

Upgrade later with `pipx upgrade orlydown`. To run it once without installing
anything:

```
pipx run orlydown 9781491958698 --jwt 'XYZ'
```

With [uv](https://docs.astral.sh/uv/), the equivalents are
`uv tool install orlydown` and `uvx orlydown …`.

Since there are no dependencies, you can also download
[`oreilly_downloader.py`](oreilly_downloader.py) and run it directly with
`python3 oreilly_downloader.py …`.

## Getting your JWT

Without a valid JWT every chapter is cut short. To get one:

1.  Log in to <https://learning.oreilly.com> in your browser.
2.  Open the developer tools and find the cookies for `learning.oreilly.com`.
3.  Copy the value of the `orm-jwt` cookie.

If `orlydown` reports `Authentication failed`, log in again and copy a fresh
cookie.

## Usage

Find the book ID in the book's URL, e.g. `9781491958698` in
`https://learning.oreilly.com/library/view/test-driven-development-with/9781491958698/`.

```
$ orlydown 9781491958698 --jwt 'XYZ'
Authentication successful.
fetching https://learning.oreilly.com/api/v2/epubs/urn:orm:book:9781491958698/files/
…
created 9781491958698.epub
```

To name the file after the book instead of its ID, add `--title`:

```
$ orlydown 9781491958698 --jwt 'XYZ' --title
…
created Test-Driven Development with Python, 2nd Edition.epub
```

A `:` in the title becomes ` -`, `/` and `\` become `-`, and other characters
that aren't allowed in filenames on Windows (`<>|?*`) are removed, so the name
works on every platform. Long titles are shortened.

The epub is written to a `.partial` file first and only renamed once every
file has downloaded, so a failed run never leaves an incomplete book behind.
Requests that fail with a 403, 429 or 5xx error, or because of a network
problem, are retried a few times with increasing waits.

By default, a missing or rejected token only prints a warning and the download
carries on with truncated chapters. Add `--require-auth` to stop with an error
instead, which is useful in scripts:

```
$ orlydown 9781491958698 --jwt 'expired' --require-auth
Authentication failed.
$ echo $?
1
```

### Options

| Option           | Description                                                                   |
| ---------------- | ----------------------------------------------------------------------------- |
| `--jwt TOKEN`    | Your `orm-jwt` cookie. Without it, chapters are cut short.                    |
| `--require-auth` | Exit with an error if no JWT is given or it isn't accepted.                   |
| `--title`        | Name the epub after the book title instead of its ID.                         |
| `--delay N`      | Wait `N` seconds between pages of the file list. Try `5` if you get 403 errors. |

## Development

The project is managed with [uv](https://docs.astral.sh/uv/):

```
uv run orlydown --help
uv run python -m unittest discover -s tests
uv build
```

### Releasing

Pushing a `v*` tag publishes that version to PyPI. The version is the tag name
without the `v`, so `v1.0.0` publishes `1.0.0`:

```
git tag v1.0.0
git push origin v1.0.0
```

Publishing uses PyPI [trusted
publishing](https://docs.pypi.org/trusted-publishers/), so no API token is
stored in the repository. It needs a one-time setup: on PyPI, add a trusted
publisher for project `orlydown` with owner `ashleyconnor`, repository
`orlydown`, workflow `publish.yml` and environment `pypi`.

## Similar projects

-   <https://github.com/lorenzodifuccia/safaribooks> (python)
-   <https://github.com/hurlenko/orly> (rust)
-   <https://github.com/jenni/obooks> (javascript)
-   <https://github.com/rahulvramesh/oreilly-books-grabber> (go)

## License

MIT, see [LICENSE](LICENSE).
