Telegram bot for managing cleaning schedule in the local.
Go to file
Kamil 'Kamaś' Bruchal 488f0b9a9b feat: ignore unknown commands instead of replying
The bot now answers only its predefined commands and stays silent on
anything else, logging ignored commands at debug level.

Tests no longer assert exact reply/reminder wording (which broke on
copy tweaks like 37a81a2) — they check behavior instead: message
counts, the names/handles substituted in, and that /sync persists its
fetch and doesn't leak internal errors.
2026-08-19 14:44:26 +02:00
.forgejo/workflows Update .forgejo/workflows/ci.yml 2026-07-06 19:05:11 +02:00
cmd/dyzur-bot feat: auto-assign empty duty weeks via service-account sheet writes 2026-06-19 03:48:59 +02:00
.dockerignore feat: auto-assign empty duty weeks via service-account sheet writes 2026-06-19 03:48:59 +02:00
.gitignore feat: auto-assign empty duty weeks via service-account sheet writes 2026-06-19 03:48:59 +02:00
.golangci.yml Initial commit 2026-06-17 18:53:54 +02:00
.markdownlint-cli2.yaml Initial commit 2026-06-17 18:53:54 +02:00
.pre-commit-config.yaml Initial commit 2026-06-17 18:53:54 +02:00
Dockerfile refactor: address review findings (slog, fetch timeout, Docker) 2026-06-18 21:38:37 +02:00
README.md feat: ignore unknown commands instead of replying 2026-08-19 14:44:26 +02:00
doc.go refactor: address review findings (slog, fetch timeout, Docker) 2026-06-18 21:38:37 +02:00
go.mod chore: track go.mod, go.sum, and missing test files 2026-06-18 21:40:41 +02:00
go.sum chore: track go.mod, go.sum, and missing test files 2026-06-18 21:40:41 +02:00
live_auth_check_test.go feat: auto-assign empty duty weeks via service-account sheet writes 2026-06-19 03:48:59 +02:00
logo.png Asserts: logo.png and forgejo workflow added 2026-07-06 19:03:29 +02:00
scheduler.go style: minor refinings 2026-07-20 17:45:35 +02:00
scheduler_live_test.go feat: auto-assign empty duty weeks via service-account sheet writes 2026-06-19 03:48:59 +02:00
scheduler_test.go feat: ignore unknown commands instead of replying 2026-08-19 14:44:26 +02:00
service.go feat: auto-assign empty duty weeks via service-account sheet writes 2026-06-19 03:48:59 +02:00
service_test.go feat: auto-assign empty duty weeks via service-account sheet writes 2026-06-19 03:48:59 +02:00
sheet.go feat: auto-assign empty duty weeks via service-account sheet writes 2026-06-19 03:48:59 +02:00
sheet_test.go feat: auto-assign empty duty weeks via service-account sheet writes 2026-06-19 03:48:59 +02:00
store.go feat: harden rota sync and throttle manual /sync 2026-06-19 02:05:04 +02:00
store_test.go fix: lint, interval validation, corrupt-cache tolerance, background init sync, concurrent test 2026-06-17 20:45:29 +02:00
telegram.go feat: ignore unknown commands instead of replying 2026-08-19 14:44:26 +02:00
telegram_test.go feat: ignore unknown commands instead of replying 2026-08-19 14:44:26 +02:00

README.md

Dyżur-bot

A telegram bot for managing the cleaning rota in hackerspace. Bot offers weekly reminders about cleaning duties as well as on demand informations. Backend read the schedule from a Google sheet, store it in local JSON file.

Running

Requires GOOGLE_CREDENTIALS_FILE (path to a Google service-account key, shared as an editor on the sheet), GOOGLE_SHEETS_ID, and BOT_TOKEN in the environment or a .env file (see service.go/sheet.go for optional SHEET_RANGE, ROTA_CACHE_PATH, SYNC_INTERVAL). Then:

go run ./cmd/dyzur-bot

The service loads the cached rota, syncs from Google Sheets in the background on SYNC_INTERVAL (default 30m), and runs the Telegram bot via long polling until interrupted (Ctrl-C / SIGTERM).

When SHEET_RANGE is unset, the bot reads the current year's tab (e.g. 2026!A1:G100), resolved on every sync — so it follows the New Year rollover automatically without a restart. A sync that fails, or that returns no duty weeks (e.g. a next-year tab that exists but hasn't been filled in yet), keeps the last-good cache instead of clearing it; if no sync succeeds for 6h the bot logs a loud stale-cache error.

Run exactly one instance per bot token. The bot uses long polling (getUpdates); two concurrent pollers on the same token make Telegram return HTTP 409 Conflict and updates get dropped. Do not scale this to multiple replicas.

Docker

docker build -t dyzur-bot .
docker run --rm --env-file .env -v dyzur-data:/data dyzur-bot

The image is a static binary on distroless (timezone data is embedded, so reminders work without system tzdata). The rota cache lives at /data/rota.json (ROTA_CACHE_PATH); mount a volume at /data to persist it across restarts.

Commands

  • /kto_sprzata — who is on cleaning duty this week.
  • /sync — refresh the rota from Google Sheets now (throttled to once per minute across the chat to avoid spamming the Sheets API).

Any other command is ignored — the bot stays silent instead of replying "unknown command".

Reminders

If GROUP_CHAT_ID is set, the bot posts a weekly duty reminder to that chat. Schedule (all optional, with defaults): REMINDER_WEEKDAY (default Monday), REMINDER_HOUR (default 9, 023), interpreted in Europe/Warsaw time.

On each fire the bot handles three weeks relative to the fire time:

  • This week — announces who is on duty. It never auto-assigns at this point; a still-empty week is announced as unstaffed.
  • One week ahead — if both slots are empty, it picks the two people with the fewest duties (random tie-break), writes their names into the sheet, and announces the assignment.
  • Two weeks ahead — if both slots are empty, it asks people to volunteer before the week is auto-assigned the following week.

Leave GROUP_CHAT_ID unset to disable reminders.