Telegram bot that processes receipt photos — extracts text via OCR, parses line items, and stores results. Built in C for minimal footprint.
- Receipt photo upload via Telegram
- OCR extraction using Google Gemini
- Receipt parsing (store name, line items, totals, payment info)
- Duplicate detection via SHA-256 hashing
- Pluggable storage backends (local filesystem, Supabase)
- Pluggable database backends (SQLite, PostgreSQL)
- Docker support (static binary, scratch image)
git clone git@github.com:yfozekosh/kaufbot.git && cd kaufbot
sudo dnf install cmake gcc libcurl-devel sqlite-devel postgresql-devel
cp .env.example .env # edit with your credentials
./build.sh
./test.sh
source .env && ./build/tgbotDebian/Ubuntu: apt install cmake gcc libcurl4-openssl-dev libsqlite3-dev libpq-dev
- Components — component interaction diagram
- Modules — source module dependency graph
- Backends — storage/database backend interfaces and data types
- Deployment — Docker and bare metal deployment options
sequenceDiagram
participant User
participant Telegram
participant Bot
participant Storage
participant DB
participant Gemini
User->>Telegram: Send receipt photo
Telegram->>Bot: Photo update (file_id)
Bot->>Telegram: Download file
Telegram-->>Bot: Image bytes
Bot->>Storage: Compute SHA-256 hash
Bot->>DB: Find by hash
DB-->>Bot: Not found
Bot->>Storage: Save image file
Bot->>DB: Insert record (ocr_pending)
Bot->>Gemini: OCR request (image)
Gemini-->>Bot: Extracted text + parsed JSON
Bot->>DB: Mark OCR done, save parsed receipt
Bot->>User: Reply with receipt summary
Note over Bot,DB: If hash exists: skip OCR, reply duplicate
Copy .env.example to .env and set:
Required:
| Variable | Description |
|---|---|
TELEGRAM_TOKEN |
Bot token from @BotFather |
GEMINI_API_KEY |
API key from Google AI Studio |
ALLOWED_USER_IDS |
Comma-separated Telegram user IDs |
Optional (storage):
| Variable | Default | Description |
|---|---|---|
STORAGE_BACKEND |
local |
local or supabase |
STORAGE_PATH |
/data/files |
Local file storage path |
SUPABASE_URL |
— | Supabase project URL |
SUPABASE_ANON_KEY |
— | Supabase anon/publishable key |
SUPABASE_SERVICE_KEY |
— | Supabase service role key (bypasses RLS) |
SUPABASE_BUCKET |
uploads |
Storage bucket name |
Optional (database):
| Variable | Default | Description |
|---|---|---|
DB_BACKEND |
sqlite |
sqlite or postgres |
DB_PATH |
/data/bot.db |
SQLite database path |
POSTGRES_HOST |
— | PostgreSQL host |
POSTGRES_PORT |
5432 |
PostgreSQL port |
POSTGRES_DB |
— | Database name |
POSTGRES_USER |
— | Database user |
POSTGRES_PASSWORD |
— | PostgreSQL password |
Optional (Gemini):
| Variable | Default | Description |
|---|---|---|
GEMINI_MODELS |
gemma-3-12b-it,gemma-3-27b-it,gemini-2.0-flash,gemini-2.5-flash |
Comma-separated available models |
GEMINI_API_BASE |
https://generativelanguage.googleapis.com/v1beta/models |
API endpoint |
GEMINI_HTTP_TIMEOUT |
600 |
HTTP timeout in seconds |
GEMINI_FALLBACK_MODEL |
gemma-3-27b-it |
Fallback model on rate limit |
GEMINI_FALLBACK_ENABLED |
0 |
Enable automatic fallback |
Send a photo to the bot on Telegram. It will:
- Download the image
- Check for duplicates (by SHA-256 hash)
- Send to Gemini for OCR
- Parse receipt data (store, items, totals)
- Save file and OCR result to storage
- Reply with parsed receipt summary
Commands:
/start— welcome message/help— show instructions/list— show recent receipts/delete <id>— delete a file by its ID/retry <id>— retry OCR parsing (re-parses existing OCR text)/models— list available Gemini models/retrywith <model> <id>— re-run OCR with a specific model (re-extracts text)
Install lint/format/analysis tools:
# System-wide (Fedora, requires sudo)
sudo dnf install clang-tools-extra cppcheck lcov
# Or locally into .tools/ (no sudo, no system pollution)
./tools/install.sh --local
source .tools/env.shcmake -DBUILD_TESTS=ON -S . -B build
cmake --build build
# Tests
cmake --build build && cd build && ctest --output-on-failure
# Lint & format
cmake --build build --target lint
cmake --build build --target cppcheck
cmake --build build --target format-check
# Coverage
cmake -DBUILD_TESTS=ON -DENABLE_COVERAGE=ON -S . -B build
cmake --build build --target test-coverage # run tests + generate report
cmake --build build --target coverage-check # + enforce 60% thresholdcompile_commands.json is generated automatically. Symlink it to the project root:
ln -sf build/compile_commands.json .Your editor's clangd extension will pick it up. The .clangd config handles cJSON include paths.
├── src/
│ ├── main.c # entry point, DI container
│ ├── config.c / .h # environment variable loading
│ ├── processor.c / .h # orchestration (hash, save, OCR, parse)
│ ├── utils.c / utils.h # shared utilities (GrowBuf, base64, URL encoding)
│ ├── telegram/
│ │ ├── bot.c / bot.h # Telegram bot (lifecycle, HTTP protocol)
│ │ ├── bot_commands.c / .h # command routing, update dispatch
│ │ └── bot_telegram.c / .h # MessageSender interface
│ ├── db/
│ │ ├── db_backend.h # database backend interface
│ │ ├── db.h # data types (FileRecord, ParsedReceipt)
│ │ ├── db_sqlite.c # SQLite implementation
│ │ └── db_postgres.c # PostgreSQL implementation
│ ├── storage/
│ │ ├── storage_backend.h # storage backend interface
│ │ ├── storage.h # storage utilities (SHA-256, filename, MIME)
│ │ ├── storage_local.c # local filesystem implementation
│ │ └── storage_supabase.c # Supabase Storage implementation
│ ├── ocr/
│ │ ├── gemini.c / .h # Gemini API client (OCR + parsing)
│ │ ├── ocr_service.c / .h # OCR abstraction layer
│ │ └── prompt_fetcher.c / .h # prompt management
│ └── infra/
│ ├── http_client.c / .h # HTTP client wrapper
│ ├── file_repository.c / .h # repository pattern (DB + storage)
│ ├── file_repository_memory.c # in-memory test implementation
│ ├── document_store.c / .h # document storage abstraction
│ └── event_bus.c / .h # publish/subscribe notification
├── tests/
│ ├── main.c # test runner (per-module binaries)
│ ├── test_runner.h # minimal test framework macros
│ ├── test_helpers.h # shared test helpers
│ ├── test_*.c # per-module test suites
│ └── test_runner.h
├── tools/
│ └── install.sh # local tool installer
├── third_party/cjson/ # cJSON library (vendored)
├── migrations/ # database schema files
├── .clang-format # code formatter config
├── .clang-tidy # static analysis config
├── .clangd # IDE config
├── build.sh # build script
├── ci.sh # local CI (all checks)
├── test.sh # test script
├── run.sh # run script
├── CMakeLists.txt # build configuration
├── Dockerfile # multi-stage Docker build
└── RULES.md # coding conventions
If the host machine's SSH key is linked to a different GitHub user, push from a disposable container with its own deploy key.
# 1. Generate a deploy key in the host home directory (not ~/.ssh)
ssh-keygen -t ed25519 -f ~/github_deploy_key -N ""
# 2. Add the public key to GitHub → Settings → SSH keys (with write access)
cat ~/github_deploy_key.pub
# 3. Push from an Alpine container using the deploy key
docker run --rm --network host \
-v "$(pwd)":/repo \
-v ~/github_deploy_key:/tmp/deploy_key:ro \
-w /repo \
alpine:latest sh -c '
apk add --no-cache git openssh-client > /dev/null 2>&1 &&
mkdir -p /root/.ssh &&
cp /tmp/deploy_key /root/.ssh/id_ed25519 &&
chmod 600 /root/.ssh/id_ed25519 &&
git config --global user.email "your@email.com" &&
git config --global user.name "Your Name" &&
git config --global --add safe.directory /repo &&
GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no" git push origin master
'Why
--network host? GitHub SSH (port 22) may be unreachable from Docker's default bridge network on some setups.
- Implement the interface (
StorageBackendOpsorDBBackendOps) - Add the
_openfactory function - Register in the
*_openfactory instorage_backend.hordb_backend.h
See RULES.md. Key points:
- Write tests alongside new code
- Never suppress compiler warnings
- 60% minimum code coverage target
MIT License
Copyright (c) 2026 Kaufbot Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.