Telegram-native, When2Meet-style group scheduling without a separate web app.
The project is intentionally Telegram-first: organizers create an event in the bot's private chat, share it through inline mode, and participants fill availability using an inline-keyboard timetable. Shared invitations are designed to update with the current response count and best-overlap preview.
- organizer-defined 00:00–24:00 event windows at 15-minute resolution
- paged 15×4 availability grid with row/column headers and styled count buttons
- When2Meet-like event creation across multiple dates
- inline invitations with live response count and a deep link to the bot's PM
- organizer-defined response threshold notification
- SQLite persistence
uvfor dependency management- single-container
compose.ymldeployment
The current implementation includes the domain model, SQLite schema/migrations, an event-relative availability representation of up to 96 quarter-hour slots per day, and a private-chat /new wizard for creating events. Organizers choose an hourly event window, record their own availability before sharing, and can edit an event's title, dates, hours, or reply notification from its preview. Schedule edits reset submitted availability, while an organizer-only confirmation flow permanently deletes an event. Participants can view aggregate responses in a tap-to-inspect grid. /events lists an organizer's upcoming events for quick access to management controls, expired events are deleted automatically, and availability for passed days cannot be submitted.
Telegram
|
v
aiogram routers ---> keyboard / inline adapters
|
v
application/domain logic
|
v
SQLAlchemy 2 async
|
v
SQLite (WAL, foreign keys, 5s busy timeout)
Each participant/day availability is represented as an event-relative decimal bit mask: bit 0 is the event's start time, with up to 96 quarter-hour slots. Decimal storage avoids SQLite's 64-bit integer limit while keeping constant-time toggles and aggregation.
Requirements: Python 3.14+ and uv.
cp .env.example .env
# set BOT_TOKEN in .env
uv sync
uv run alembic upgrade head
uv run python -m when2gramUse /new to create an event, or /events to manage events you organized. The event wizard supports multi-date selection, an hourly event window from 00:00–24:00, and an optional organizer notification threshold. The share button opens inline mode with an invitation result; enable inline mode first in BotFather with /setinline and enable /setinlinefeedback so sent invitations can refresh their reply count.
Run checks:
uv run ruff check .
uv run pytestcp .env.example .env
# set BOT_TOKEN
docker compose up -d --buildThe SQLite database is persisted in the named when2gram-data volume at /data/when2gram.db. Long polling is used, so no inbound port, reverse proxy, or TLS setup is required.
The multi-stage image uses python:3.14-alpine and build-only uv:0.11.14
version tags. To pick up base-image and Alpine package updates, rebuild with:
docker compose build --pull --no-cache
docker compose up -dThe publish workflow uses the same fresh-build settings. Python dependencies
remain locked by uv.lock.
The image starts as UID/GID 10001:10001, with root-owned application files.
Compose drops all capabilities, enables no-new-privileges, and mounts the root
filesystem read-only. /data is persistent; /tmp is a temporary writable mount.
New named volumes inherit /data ownership from the image.
Older deployments may have a root-owned database. Stop the bot and fix ownership once before starting the non-root image. This uses a temporary maintenance container; the bot itself never starts as root or repairs permissions:
docker compose stop bot
docker compose run --rm --no-deps --user 0:0 --cap-add CHOWN bot \
chown -R 10001:10001 /data
docker compose up -dFor bind mounts, set the host directory and database files' owner to UID/GID
10001:10001 instead.
- Validate the availability grid and event-creation wizard on Telegram mobile and desktop clients.
- Add a participant availability summary and best-overlap display.
- Refresh shared invitations after submissions.
- Add exactly-once response-threshold notifications.
- Add integration tests around Telegram callbacks and SQLite concurrency.