This project is a .NET 10 rewrite of the original SafariBooks downloader by lorenzodifuccia. All credits for the original concept and implementation go to the original author.
This project was ported using GitHub Copilot in order to test its features and development flows. Given the educational purposes of this program, I am not responsible for its use. Before any usage please read the O'Reilly's Terms of Service.
Download and generate EPUB of your favorite books from O'Reilly Learning library (Kindle-compatible).
Features:
- Cookie-based authentication (no login credentials required in the app)
- Downloads book content, images, and CSS
- Generates valid EPUB files
- Kindle-compatible output option
- Docker support with Dev Container
The application uses cookie-based authentication, which is simpler and more secure than credential handling:
- Create cookies.json file
- Log in to O'Reilly Learning in your browser at learning.oreilly.com
- Open Developer Tools (F12 or right-click → Inspect)
- Go to the Application/Storage tab
- Navigate to Cookies →
https://learning.oreilly.com - Copy the relevant cookies (especially
groot_sessionid)
Note: Cookie values and names may vary. Copy all cookies from the learning.oreilly.com domain for best compatibility.
The Book ID is the digits found in the O'Reilly Learning URL:
https://learning.oreilly.com/library/view/book-name/XXXXXXXXXXXXX/
For example, from this URL:
https://learning.oreilly.com/library/view/test-driven-development-with/9781491958698/
The Book ID would be: 9781491958698
# Option 1: Use pre-built image from GitHub Container Registry (recommended)
# Option 1a: Without Kindle optimization
docker run -v "$(pwd)/cookies.json:/app/cookies.json" \
-v "$(pwd)/Books:/app/Books" \
ghcr.io/krusty93/safaribooks:latest <BOOK_ID>
# Option 1b: With Kindle optimization
docker run -v "$(pwd)/cookies.json:/app/cookies.json" \
-v "$(pwd)/Books:/app/Books" \
ghcr.io/krusty93/safaribooks:latest --kindle <BOOK_ID>
# Option 2: Build the Docker image locally
docker build -t safaribooks-downloader .
# Download a book
docker run -v "$(pwd)/cookies.json:/app/cookies.json" \
-v "$(pwd)/Books:/app/Books" \
safaribooks-downloader <BOOK_ID>SafariBooksDownloader [--kindle] [--preserve-log] [--help] <BOOK ID>
positional arguments:
<BOOK ID> Book digits ID found in the URL:
https://learning.oreilly.com/library/view/BOOKNAME/XXXXXXXXXXXXX/
optional arguments:
--kindle Add CSS rules that improve Kindle rendering (tables, pre).
--preserve-log Keep logs (no-op placeholder).
--help Show this help message.The project includes a complete development environment configuration that works with any IDE supporting dev containers:
Visual Studio Code:
- Install the "Dev Containers" extension
- Open the project folder
- Open Command Palette (
Ctrl+Shift+P) - Select "Dev Containers: Reopen in Container"
- The environment will be automatically configured with .NET 10 SDK
JetBrains Rider/IntelliJ:
- Use the "Remote Development" feature
- Select "Dev Container" option
- Point to the project's
.devcontainer/devcontainer.json
Other IDEs:
-
Use Docker directly with the dev container:
docker build -f .devcontainer/Dockerfile -t safaribooks-dev . docker run -it -v "$(pwd):/workspaces/safaribooks" safaribooks-dev
The dev container includes:
- .NET 10 SDK
- All required extensions and tools
- Automatic project restoration
The project is organized into separate libraries for better maintainability and reusability:
src/
├── SafariBooksDownloader.Core/
│ └── SafariBooksDownloader.Core.csproj
├── SafariBooksDownloader.App/
│ └── SafariBooksDownloader.App.csproj
├── SafariBooksDownloader.UnitTests/
│ └── SafariBooksDownloader.UnitTests.csproj
SafariBooksDownloader.slnx
Benefits of this structure:
- Reusability: The Core library can be referenced in web apps, desktop apps, or other console tools
- Testability: Business logic is isolated and easier to unit test
- Maintainability: Clear separation of concerns between UI and business logic
- Modularity: Each project has a single, well-defined responsibility
To run the unit tests during development:
# Run all tests
dotnet test SafariBooksDownloader.slnx
# Run tests with detailed output
dotnet test SafariBooksDownloader.slnx --verbosity normal
# Run only unit tests project
dotnet test src/SafariBooksDownloader.UnitTests/SafariBooksDownloader.UnitTests.csprojTo run tests in a Docker container with code coverage:
# Build the test image
docker build -f Dockerfile.test -t safaribooks-tests .
# Run tests and collect coverage (mount volume to access coverage files)
docker run --rm -v "$(pwd)/coverage:/app/TestResults" safaribooks-tests
# Coverage files will be available in the ./coverage directory- Docker (for running the application)
- Valid O'Reilly Learning subscription
cookies.jsonfile with your session cookies
The application creates the following structure:
Books/
└── Book Title (BOOK_ID)/
├── BOOK_ID.epub # Final EPUB file
├── OEBPS/
│ ├── *.xhtml # Chapter files
│ ├── content.opf # EPUB metadata
│ ├── toc.ncx # Table of contents
│ ├── Styles/ # CSS files
│ └── Images/ # Book images
└── META-INF/
└── container.xml # EPUB container
The generated EPUB files are compatible with most e-readers. For optimal compatibility:
For best quality, convert the generated EPUB using Calibre:
ebook-convert "input.epub" "output.epub"Use the --kindle option for better Kindle compatibility:
docker run -v "$(pwd)/cookies.json:/app/cookies.json" \
-v "$(pwd)/Books:/app/Books" \
safaribooks-downloader --kindle <BOOK_ID>This adds CSS rules that improve rendering of tables and code blocks on Kindle devices.
To convert for Kindle:
ebook-convert "input.epub" "output.azw3"-
Authentication Failed
- Verify
cookies.jsonexists and contains valid cookies - Check if your O'Reilly session has expired
- Log out and log back in to get fresh cookies
- Verify
-
Book Not Found
- Verify the Book ID is correct
- Ensure you have access to the book with your subscription
- Check if the book URL is accessible when logged in
-
Download Errors
- Check your internet connection
- Verify O'Reilly Learning is accessible
- Try again later if the service is temporarily unavailable
-
Volume Mount Problems
# Ensure absolute paths docker run -v "$(pwd)/cookies.json:/app/cookies.json" \ -v "$(pwd)/Books:/app/Books" \ safaribooks-downloader <BOOK_ID>
-
Permission Issues
- Ensure the local directories have proper permissions
- On Linux/macOS, you might need to adjust file ownership after download
This tool is for personal and educational use only. Please:
- Respect O'Reilly's Terms of Service
- Only download books you have legitimate access to
- Do not redistribute downloaded content
- Support O'Reilly by maintaining your subscription
Feel free to open issues or submit pull requests. When contributing:
- Use the dev container environment for consistency
- Follow the existing code style
- Add tests for new functionality
- Update documentation as needed
For any issues, please don't hesitate to open an issue on GitHub.
