Telegram bot that accepts image/video uploads and returns a public URL from Cloudflare R2.
This is a pure vibe coding project.
- Upload photo/video and return:
- direct URL
- Markdown snippet
- HTML snippet
- Webhook secret validation (
X-Telegram-Bot-Api-Secret-Token) - User whitelist (
ALLOWED_USER_IDS) - Basic per-user rate limiting
- Daily usage count (
/quota) - History query (
/history) and delete by id (/delete <id>) - Persistent webhook update idempotency in SQLite (safe across restarts)
- SHA256-based upload deduplication to reduce duplicate R2 writes
- Basic magic-number sniffing to detect mime mismatch
- Graceful shutdown on
SIGTERM/SIGINT
- Telegram cloud Bot API practical download limit: 20MB
- This MVP enforces file size limit via
MAX_FILE_SIZE_MB(default20)
- Install dependencies
npm install- Configure environment
cp .env.example .env
# edit .env- Start server
npm run start- Set webhook (after your public domain is ready)
npm run set-webhookIf you deploy with systemd and nginx on the target host, fill .env and run:
./scripts/finalize-production.shThat helper script restarts image2url-bot, checks /healthz and /readyz, then runs npm run set-webhook.
Webhook endpoint is POST /webhook.
Health endpoint is GET /healthz.
Readiness endpoint is GET /readyz.
Build and run with Docker:
docker build -t image2url-bot .
docker run --rm -p 3000:3000 --env-file .env -v $(pwd)/data:/app/data image2url-botOr run with docker compose:
docker compose up -d --buildRun unit tests:
npm testRun tests with coverage:
npm run test:coverageBOT_TOKENWEBHOOK_SECRETR2_ACCOUNT_IDR2_ACCESS_KEY_IDR2_SECRET_ACCESS_KEYR2_BUCKETR2_PUBLIC_BASE_URL
WEBHOOK_URLPORT(default3000)ALLOWED_USER_IDS(comma separated; empty means allow all)MAX_FILE_SIZE_MB(default20)RATE_LIMIT_PER_MINUTE(default20)DOWNLOAD_TIMEOUT_MS(default15000)HISTORY_LIMIT(default5)LOG_LEVEL(defaultinfo)MAX_WEBHOOK_BODY_MB(default2)UPDATE_DEDUPE_WINDOW_SECONDS(default600)R2_REGION(defaultauto)SQLITE_PATH(default./data/bot.db)
- Keep
ALLOWED_USER_IDSnon-empty for private access. - Use HTTPS webhook endpoint and a long random
WEBHOOK_SECRET. - Configure R2 bucket with least privilege access key (only required bucket).
- Put a CDN/custom domain in front of R2 for stable public URL.
- Persist
SQLITE_PATHon durable volume or migrate to managed DB. - Monitor
GET /healthzand restart process on non-200. - Run behind process manager (systemd, pm2, Docker orchestrator).
- Keep logs in JSON and ship stdout/stderr to your log collector.
- On the production host, run
./scripts/finalize-production.shafter updating.env.
GitHub Actions workflow is included: ci.yml.
It runs npm ci, npm test, and npm run test:coverage on push and PR.