A Ruby-based application that automatically scrapes your public library website to monitor your family's borrowing activity. Features a beautiful web dashboard and REST API for integration with Home Assistant or other home automation systems.
Designed for BiblioCommons library systems - Works with libraries using the BiblioCommons platform (e.g., lawrence.bibliocommons.com, catalog.yourlib.bibliocommons.com). The scraper can be adapted for other library systems by updating CSS selectors.
- Automated Library Scraping: Uses Playwright to log in and scrape checkout and hold data
- Family-Wide Tracking: Support for multiple library card holders
- Web Dashboard: Beautiful, responsive interface showing all library activity
- REST API: JSON endpoints for Home Assistant integration
- Due Date Alerts: Visual indicators for items due soon or overdue
- Book Thumbnails: Automatically downloads and displays cover images
- Docker Ready: Containerized for easy deployment on home servers
- GitHub Container Registry: Automated builds and publishing
This application is designed to run in a Docker container on your home lab (Proxmox, Unraid, etc.) and pulls pre-built images from GitHub Container Registry.
-
Create environment file from template:
cp .env.example .env
-
Edit
.envwith your library credentials:LIBRARY_URL=https://lawrence.bibliocommons.com PATRON_1_NAME=John Doe PATRON_1_USER=john.library.username PATRON_1_PASS=john_library_password PATRON_2_NAME=Jane Doe PATRON_2_USER=jane.library.username PATRON_2_PASS=jane_library_password SCRAPE_INTERVAL=1 LOG_LEVEL=INFO
-
Create
docker-compose.ymlfile:version: '3.8' services: dewey: image: ghcr.io/opticbob/dewey:latest container_name: dewey-library-tracker restart: unless-stopped ports: - "4567:4567" volumes: - ./data:/app/data env_file: - .env
Then run:
docker-compose up -d- Web Interface: http://your-server:4567
- API Endpoint: http://your-server:4567/api/status
- Health Check: http://your-server:4567/health
| Variable | Description | Example |
|---|---|---|
LIBRARY_URL |
Your library's website URL | https://catalog.library.org |
PATRON_1_NAME |
Display name for first family member | John Doe |
PATRON_1_USER |
Library username for first patron | john.doe |
PATRON_1_PASS |
Library password for first patron | password123 |
| Variable | Default | Description |
|---|---|---|
SCRAPE_INTERVAL |
1 |
Hours between automatic scrapes |
LOG_LEVEL |
INFO |
Logging level (DEBUG, INFO, WARN, ERROR) |
PLAYWRIGHT_HEADLESS |
true |
Set to false for debugging |
Simply add additional patron environment variables to your .env file:
PATRON_3_NAME=Kid Doe
PATRON_3_USER=kid.library.username
PATRON_3_PASS=kid_library_passwordThe scraper needs to be customized for your specific library system. The code includes detailed placeholders showing exactly what needs to be changed.
- Install the Playwright MCP tool in Claude Code
- Navigate to your library website using the tool
- Inspect the login form elements to find CSS selectors
- Examine checkout and holds pages to identify data structure
- Update the selectors in
lib/library_scraper.rb
Login Selectors (lib/library_scraper.rb lines 60-70):
USERNAME_SELECTOR = '#username' # Update this
PASSWORD_SELECTOR = '#password' # Update this
LOGIN_BUTTON_SELECTOR = '#login-btn' # Update thisCheckout Page Selectors (lines 90-110):
CHECKOUTS_CONTAINER_SELECTOR = '.checkout-items' # Update this
CHECKOUT_ITEM_SELECTOR = '.checkout-item' # Update this
TITLE_SELECTOR = '.title' # Update this
# ... and moreHolds Page Selectors (lines 140-160):
HOLDS_CONTAINER_SELECTOR = '.holds-items' # Update this
HOLD_ITEM_SELECTOR = '.hold-item' # Update this
# ... and more-
Use Playwright MCP to inspect your library:
Navigate to: https://your-library.org -
Find the login form:
- Right-click username field β Inspect β Copy CSS selector
- Right-click password field β Inspect β Copy CSS selector
- Right-click login button β Inspect β Copy CSS selector
-
Navigate to account/checkouts page:
- Look for the container holding all checkout items
- Find the pattern for individual items
- Identify selectors for title, author, due date, etc.
-
Navigate to holds/reservations page:
- Similar process for holds data
-
Update the selectors in the code:
- Edit
lib/library_scraper.rb - Replace placeholder selectors with your library's actual selectors
- Test with
PLAYWRIGHT_HEADLESS=falseto debug
- Edit
git clone https://github.com/YOUR_USERNAME/dewey.git
cd dewey
# Copy environment template and fill in your details
cp .env.example .env
# Install dependencies
bundle install
# Start development server with auto-reload
bundle exec rerun ruby app.rb# Check code style
bundle exec standard
# Auto-fix style issues
bundle exec standard --fix# Build image
docker build -t dewey .
# Run locally built image
docker run -p 4567:4567 --env-file .env dewey- Go to GitHub Settings β Developer settings β Personal access tokens β Tokens (classic)
- Click "Generate new token (classic)"
- Select scopes:
read:packageswrite:packagesdelete:packages(optional)
- Copy the token (you'll need it for deployment)
If you're contributing to the project:
- Go to your repository β Settings β Secrets and variables β Actions
- Add repository secret:
- Name:
GHCR_TOKEN - Value: Your personal access token
- Name:
The GitHub Actions workflow automatically:
- Builds Docker images on push to
mainbranch - Pushes to
ghcr.io/YOUR_USERNAME/dewey:latest - Creates tagged releases for version tags
- Supports multi-architecture builds
Add this to your Home Assistant configuration.yaml:
rest:
- resource: "http://your-server:4567/api/status" # Replace with your Dewey server IP/hostname
scan_interval: 300
sensor:
- name: "Library Checkouts"
value_template: "{{ value_json.stats.total_checkouts }}"
unit_of_measurement: "books"
- name: "Library Holds"
value_template: "{{ value_json.stats.total_holds }}"
unit_of_measurement: "books"
- name: "Books Due Soon"
value_template: "{{ value_json.stats.items_due_soon }}"
unit_of_measurement: "books"template:
- sensor:
- name: "Library Status"
state: >
{% if states('sensor.books_due_soon')|int > 0 %}
Due Soon
{% elif states('sensor.library_checkouts')|int > 0 %}
Active
{% else %}
No Activity
{% endif %}| Endpoint | Description | Example Response |
|---|---|---|
GET / |
Web dashboard | HTML interface |
GET /patron/:name |
Individual patron view | HTML interface |
GET /api/status |
Full family status | JSON with all data |
GET /api/patron/:name |
Individual patron API | JSON for specific patron |
GET /health |
Health check | {"status": "ok"} |
POST /refresh |
Manual refresh trigger | Redirects to dashboard |
{
"checkouts": [
{
"title": "The Great Gatsby",
"author": "F. Scott Fitzgerald",
"due_date": "2024-12-15",
"type": "Book",
"renewable": true,
"patron_name": "John Doe",
"thumbnail_url": "/thumbnails/abc123.jpg"
}
],
"holds": [
{
"title": "Dune",
"author": "Frank Herbert",
"status": "Ready for pickup",
"queue_position": null,
"patron_name": "Jane Doe",
"thumbnail_url": "/thumbnails/def456.jpg"
}
],
"stats": {
"total_checkouts": 1,
"total_holds": 1,
"items_due_soon": 0,
"patrons": ["John Doe", "Jane Doe"]
},
"last_updated": "2024-12-01T10:30:00Z"
}-
Login failures:
- Verify library URL and credentials
- Check if library website structure changed
- Set
PLAYWRIGHT_HEADLESS=falseto watch browser automation
-
No data appearing:
- Check application logs:
docker-compose logs dewey - Verify CSS selectors are correct for your library
- Test manual refresh from web interface
- Check application logs:
-
Missing thumbnails:
- Check if library provides thumbnail images
- Verify image URLs are accessible
- Check data volume permissions
-
Container won't start:
# Check logs docker-compose logs dewey # Verify environment variables docker-compose config
-
Permission issues:
# Fix data directory permissions sudo chown -R 1000:1000 ./data
- Login form changes: Update selectors in
login_to_librarymethod - Checkout page layout: Update selectors in
scrape_checkoutsmethod - Holds page layout: Update selectors in
scrape_holdsmethod - Date format changes: Update
parse_due_datemethod
dewey/
βββ app.rb # Main Sinatra application
βββ lib/
β βββ data_store.rb # JSON data storage handler
β βββ library_scraper.rb # Playwright web scraper
βββ views/
β βββ layout.erb # HTML layout template
β βββ dashboard.erb # Family dashboard view
β βββ patron.erb # Individual patron view
βββ config/
β βββ puma.rb # Puma server configuration
βββ data/ # Persistent data (created at runtime)
β βββ checkouts.json # Current checkout data
β βββ holds.json # Current holds data
β βββ scrape_log.json # Scraping activity log
β βββ thumbnails/ # Downloaded book cover images
βββ public/
β βββ placeholder.jpg # Fallback image for missing thumbnails
βββ .github/workflows/
β βββ docker-build.yml # GitHub Actions CI/CD
βββ Dockerfile # Container build instructions
βββ docker-compose.yml # Local deployment configuration
βββ Gemfile # Ruby dependencies
βββ README.md # This file
- Fork the repository
- Create a feature branch:
git checkout -b feature-name - Make your changes and test thoroughly
- Run StandardRB:
bundle exec standard - Commit your changes:
git commit -am 'Add new feature' - Push to the branch:
git push origin feature-name - Create a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with Sinatra web framework
- Browser automation powered by Playwright
- Designed for home lab deployment on Proxmox
- Follows StandardRB code style
- Vibe coded with Claude - Developed through pair programming with Claude AI, using conversational development and iterative refinement
Happy reading! π Use Dewey to stay on top of your family's library activity and never miss a due date again.