Almanac Bot
A Twitter bot that tweets historical events (ephemeris) on their anniversary dates. Each day, it finds events that happened on that calendar date (any year) and tweets them with localized text.
Features
- Tweets historical events on their anniversary (month+day matching)
- Supports multiple events per day
- Template variables:
${date}(localized) and${years_ago}(calculated) - Idempotency: won't tweet the same event twice on the same day
- Dry-run mode for testing without sending tweets
- Stateless execution triggered by external scheduler (Ofelia)
Dependencies
- Docker
- Python 3.13
- PostgreSQL 17.3+
- just (optional, for convenience commands)
Quick Start
1. Create configuration
cp config.ini.default config.ini
Fill in the Twitter API credentials and other settings:
[language]
locale=ca_ES # or en_US, es_ES, etc.
[twitter]
bearer_token=...
consumer_key=...
consumer_secret=...
access_token_key=...
access_token_secret=...
[postgresql]
user=almanac
password=almanac
hostname=postgres
database=almanac
2. Launch with Docker Compose
just docker-up
# or: docker compose up -d
This starts:
- postgres: PostgreSQL database
- almanac-bot: The bot container (kept alive for scheduled execution)
- ofelia: Scheduler that triggers the bot daily at 8:00 AM
3. Load ephemeris data
just docker-load-data
# or: docker exec -it almanac-bot uv run python -m typer almanacbot.data_loader run
CSV format (init_db.csv):
date;text;location
1899-11-29 12:00 Europe/Madrid;El ${date}, avui fa ${years_ago} anys...;(41.38,2.17)
Usage
Manual execution
# Run once (will tweet if there are events for today)
just docker-run
# Dry-run mode (shows what would be tweeted without sending)
just docker-dry-run
Scheduled execution
The bot is automatically triggered by Ofelia at 8:00 AM daily. Check the schedule in compose.yaml:
labels:
ofelia.job-exec.almanac.schedule: "0 0 8 * * *" # Daily at 8 AM
View logs
just docker-logs # Bot logs
just docker-logs-scheduler # Scheduler logs (follow mode)
Development
Run tests
just test # Unit tests
just test-cov # With coverage report
just test-integration # Integration tests (starts postgres automatically)
Linting
just lint # Check for issues
just lint-fix # Auto-fix issues
Build Docker image
just docker-build
Database
Schema
The ephemeris table stores historical events:
CREATE TABLE almanac.ephemeris (
id serial primary key,
date timestamp with time zone not null,
text text not null,
location point default null,
last_tweeted_at timestamp with time zone default null
);
Clean database
just docker-clean-db
Just Commands Reference
just help # Show all available commands
| Command | Description |
|---|---|
just docker-up |
Start all services |
just docker-down |
Stop all services |
just docker-serve |
Start and follow logs |
just docker-load-data |
Load ephemeris from CSV |
just docker-run |
Run bot manually |
just docker-dry-run |
Run without tweeting |
just docker-logs |
View bot logs |
just docker-logs-scheduler |
View scheduler logs |
just docker-build |
Build Docker image |
just docker-clean-db |
Remove database volume |
just test |
Run unit tests |
just test-cov |
Run tests with coverage |
just test-integration |
Run integration tests |
just lint |
Check code style |
just lint-fix |
Fix code style |
Architecture
Ofelia (scheduler)
│
│ triggers daily at 8 AM
▼
┌─────────────────────────────────────────────────────┐
│ almanac-bot │
│ │
│ 1. Query ephemeris for today (month+day match) │
│ 2. Filter out already-tweeted (idempotency) │
│ 3. Tweet each event │
│ 4. Mark as tweeted (last_tweeted_at) │
│ 5. Exit │
└─────────────────────────────────────────────────────┘
│
▼
PostgreSQL
Production Deployment (Docker Swarm)
For production deployment on Docker Swarm:
1. Create Docker secrets
# Create secret from your config.ini file
docker secret create almanac_config /path/to/config.ini
2. Prepare the stack directory
mkdir -p /path/to/almanac-bot/{init_db,postgres}
# Copy the database init script (creates schema on first run)
cp postgres/init-ephemeris-db.sh /path/to/almanac-bot/postgres/
# Copy your ephemeris CSV data
cp your_data.csv /path/to/almanac-bot/init_db/init_db.csv
3. Configure the stack
# Copy the example and adjust paths
cp docker-compose.swarm.example.yaml /path/to/almanac-bot/docker-compose.swarm.yaml
# Edit the file and update volume paths to match your directory
4. Deploy
docker stack deploy -c /path/to/almanac-bot/docker-compose.swarm.yaml almanac
5. Load ephemeris data into database
After the stack is running, load the CSV data into PostgreSQL:
# Find the bot container
docker ps | grep almanac-bot
# Load the CSV into the database
docker exec <container_id> uv run python -m typer almanacbot.data_loader run
License
MIT
Release files for almanac-bot 1.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| almanac_bot-1.1.3.tar.gz | 9.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| almanac_bot-1.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 21.5 kB
Release files / almanac_bot-1.1.3.tar.gz
| Download URL | almanac_bot-1.1.3.tar.gz |
|---|---|
| Size | 9.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
92572ddad2cb24b7dd8527274b5961963ac47629ef6594ea0f23f6e4f3f4a9e3
|
|
BLAKE2b-256 checksum How to use checksums |
1dcb276ccd8711e50376566477aa838a8c77860a9403e5183dcca417d6b76ae5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Dec 27, 2025.
Transparency logRelease files / almanac_bot-1.1.3-py3-none-any.whl
| Download URL | almanac_bot-1.1.3-py3-none-any.whl |
|---|---|
| Size | 12.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
17972c8ca0f0ae78e71bf07a55cbda69300088a603c84e0afb4c37ac4cdb2919
|
|
BLAKE2b-256 checksum How to use checksums |
7c3e238337df62ce0c4c90f31320cf219eacd60f544b7f54e86136e9dc305061
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Dec 27, 2025.
Transparency log