Telegram Word Pair Reminder Bot
This is a Telegram bot built using Go that helps users learn vocabulary with onboarding, quizzes, and spaced reminders.
Features
- Onboarding wizard that initializes personal vocabulary from a multilingual base dataset.
- Safe re-initialization flow on
/start for existing users with confirmation and cancel options.
- Upload word pairs as a CSV file (
word1,word2 or tab/semicolon separated).
- Clear uploaded word pairs.
- Set the number of pairs to send in reminders.
- Set the frequency of reminders per day.
- Periodic reminders sent to users with random word pairs.
Project Structure
cmd/tg-word-reminder/main.go: application entrypoint.
pkg/bot/handlers: Telegram command/update handlers.
pkg/bot/onboarding: onboarding flow, init vocabulary refresh, and provisioning logic.
pkg/bot/game: quiz session state and matching logic.
pkg/bot/reminders: reminder scheduling and delivery.
pkg/bot/importexport: CSV parsing and export helpers.
pkg/db: GORM models and database initialization.
pkg/config: JSON config loader.
pkg/logger: slog wrapper.
pkg/ui: inline keyboard rendering and callback data parsing.
pkg/internal/testutil: shared test helpers.
Prerequisites
- Go 1.25 or newer
- PostgreSQL database
- A Telegram bot token (create one using BotFather)
Installation
-
Clone the repository:
git clone <repository-url>
cd <repository-directory>
-
Create a production .env for Docker Compose:
# .env
POSTGRES_USER=your_db_user
POSTGRES_PASSWORD=your_db_password
POSTGRES_DB=your_db_name
Keep these values in sync with the database settings in config.json.
-
Create a configuration file:
Copy config.example.json to config.json and set your Telegram bot token and database credentials to match .env.
cp config.example.json config.json
For local development values and .env.development, see Local Development (Docker Compose).
-
Run the bot with Docker Compose:
docker compose up --build
Run detached if you prefer:
docker compose up -d --build
Local Development (Docker Compose)
For local development, use Docker Compose to run the bot and PostgreSQL.
-
Create a development .env.development file:
# .env.development
POSTGRES_USER=tgwr
POSTGRES_PASSWORD=tgwr_local_password
POSTGRES_DB=tgwrdb
Keep these values in sync with the database settings in config.json.
-
Update database settings in config.json:
Set database values to match .env.development:
- host:
db
- user:
tgwr
- password:
tgwr_local_password
- dbname:
tgwrdb
- port:
5432
- sslmode:
disable
-
Start the stack with the development env file:
docker compose --env-file .env.development up --build
Run detached if you prefer:
docker compose --env-file .env.development up -d --build
Use --env-file .env.development with other Compose commands too.
-
Useful Compose commands:
Tail logs for the bot:
docker compose --env-file .env.development logs -f tg-word-reminder
-
Connect to the local database (in-container):
docker compose --env-file .env.development exec db psql -U tgwr -d tgwrdb
-
Connect to the local database (host):
psql -h 127.0.0.1 -U tgwr -d tgwrdb
Database Backups (Docker Compose)
The Compose stack includes a db-backup service that runs pg_dump every hour and keeps four days of plain SQL backups (compressed with gzip) in ./backups.
Defaults (override via .env or .env.development):
BACKUP_INTERVAL_SECONDS=3600
BACKUP_RETENTION_DAYS=4
Restore a backup:
gunzip -c backups/<backup-file>.sql.gz | psql -h 127.0.0.1 -U tgwr -d tgwrdb
Drop everything and restore:
docker compose --env-file .env.development exec -T db psql -U tgwr -d tgwrdb -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
gunzip -c backups/<backup-file>.sql.gz | docker compose --env-file .env.development exec -T db psql -U tgwr -d tgwrdb
Testing
Run unit tests locally:
go test ./...
Usage
On first /start, the bot launches onboarding and asks for:
- the language you are learning
- the language you already know
It then copies eligible pairs from the init vocabulary table to your personal vocabulary and enables default reminders (morning, afternoon, evening) with 5 cards per session.
For existing users, /start asks for confirmation before re-initialization and allows canceling the flow.
You can also send your own CSV file to upload/update personal pairs. The first two columns are used as source/target words (comma, tab, or semicolon separated). Refer to vocabularies/example.csv for format.
- Commands:
/start: initialize your account.
/getpair: get a random word pair.
/game: start a quiz session.
/review: start a review session.
/settings: configure reminders and pair counts.
/export: download your vocabulary.
/clear: remove all uploaded word pairs.
/feedback: send feedback to the admins (private chat only).
Database Setup
The bot uses a PostgreSQL database. Ensure that the database is set up and accessible based on the configuration provided in config.json. The bot will automatically create the necessary tables for storing word pairs and user settings.
Logging
The bot uses the standard library's slog package for logging. Logs are printed to stdout by default.
Optional config.json logging settings:
"logging": {
"level": "info",
"gorm_level": "warn",
"file": "/app/logs/tg-word-reminder.log"
}
level: debug, info, or error (defaults to info).
gorm_level: silent, error, warn, or info (defaults to warn).
file: when set, logs are written to both stdout and the file path.
compose.yml mounts ./logs to /app/logs so file logs persist across container restarts.
Feedback
Configure feedback delivery in config.json with admin IDs:
"feedback": {
"enabled": true,
"admin_ids": [123456789],
"timeout_minutes": 5
}
Admin IDs must be numeric Telegram user IDs. Use @Get_myidrobot (or similar) to find your numeric ID.
Onboarding
Configure init vocabulary refresh in config.json:
"onboarding": {
"init_vocabulary_path": "/app/vocabularies/multilang.csv"
}
- If
init_vocabulary_path is set, the bot attempts to refresh init_vocabularies on startup.
- Refresh uses strict required columns:
en, ru, nl, es, de, fr.
- If refresh fails (missing/invalid file, invalid schema, empty data), the error is logged and bot startup continues.
Contributing
Contributions are welcome! Please feel free to submit a pull request or open an issue for any enhancements or bug fixes.
License
This project is licensed under the MIT License. See the LICENSE file for details.
Acknowledgments