Skip to content

ecwu/subscription-bot

Repository files navigation

Subscription Bot

Deploy to Cloudflare

A privacy-oriented Telegram bot for managing personal subscription services. Runs on Cloudflare Workers, stores data in Cloudflare KV, encrypts sensitive payloads at the application layer, and hashes Telegram user IDs before using them in storage keys.

Tech Stack

  • Runtime: Cloudflare Workers (nodejs_compat)
  • Language: TypeScript (strict)
  • Telegram SDK: grammY + @grammyjs/conversations
  • Storage: Cloudflare KV
  • Validation: Zod
  • Testing: Vitest
  • Crypto: Web Crypto API (AES-GCM, HKDF, HMAC-SHA-256)
  • Reports: SVG + resvg PNG rendering

Project Structure

src/
├── bot/              # Telegram bot setup, commands, conversations, callbacks, keyboards, middleware, KV session storage
├── handlers/         # Worker fetch/scheduled/health handlers
├── services/         # Subscription, reminder, report, export, privacy, Telegram API logic
├── repositories/     # KV storage access layer and config readers
├── crypto/           # Encryption, hashing, key derivation, master key parsing
├── models/           # TypeScript type definitions
├── schemas/          # Zod validation schemas
├── utils/            # Parsers, formatting, date/math helpers, report rendering
└── types/            # Shared Env and grammY context types

Features

  • Add subscriptions interactively or with one-line commands.
  • Track fixed cycles (weekly, monthly, quarterly, yearly), manual custom cycles, and interval cycles such as 30d, 4w, 6m, 2y, every 30 days, and 每30天.
  • Mark subscriptions as trial or non-auto-renewing so reports and reminder wording match the real billing state.
  • Pause and resume subscriptions. Paused subscriptions are excluded from reminders, date advancement, and spending reports.
  • View compact lists, paginated inline list management, subscription details, JSON export, PNG reports, and text reports.
  • Send scheduled renewal reminders through Cloudflare Cron Triggers.

Development

# Install dependencies
pnpm install

# Run dev server
pnpm dev

# Push Telegram slash-command menu
pnpm command:push

# Run tests
pnpm test:run

# Type check
pnpm types:check

# Lint
pnpm lint

# Format
pnpm format

Before merging a code change, run:

pnpm types:check
pnpm test:run
pnpm lint

Environment Variables

Variable Required Format / Notes
BOT_TOKEN Yes From @BotFather
TELEGRAM_WEBHOOK_SECRET Yes High-entropy random string
ENCRYPTION_KEY Yes Base64url-encoded 32-byte value
USER_HASH_SECRET Yes High-entropy random string
ADMIN_USER_ID No Telegram user ID marked as admin
APP_ENV No development (default), production, test
REMINDER_DAYS_AHEAD No Number of days ahead to send renewal reminders (default: 3)

Secrets belong in .dev.vars locally and in Wrangler secrets for production:

wrangler secret put BOT_TOKEN
wrangler secret put TELEGRAM_WEBHOOK_SECRET
wrangler secret put ENCRYPTION_KEY
wrangler secret put USER_HASH_SECRET

Report Exchange Rates

/report generates PNG reports for monthly-equivalent spending, current-month due spending, and future 12-month projected spending. /report_text generates a Telegram text version with current-month line items and 12-month projected line items.

Known currencies are converted to CNY using a manually maintained KV config item. Seed or update the fixed key config:exchange-rates:v1 with JSON like:

{ "base": "CNY", "rates": { "CNY": 1, "USD": 7.2, "EUR": 7.8 } }

Missing currencies are shown where possible but are not included in converted CNY totals. Paused subscriptions, trial subscriptions, non-auto-renewing subscriptions, custom cycles, and entries without price/currency are excluded from calculated spending totals.

Billing Cycles

Subscriptions support fixed cycles (weekly, monthly, quarterly, yearly), custom cycles that do not auto-advance, and interval cycles in days, weeks, months, or years. One-line commands accept examples such as 30d, 4w, 6m, 2y, every 30 days, every 4 weeks, every 6 months, 每30天, 每4周, 每6个月, and 每2年.

Generating ENCRYPTION_KEY

The master encryption key must be exactly 32 bytes (256 bits), base64url-encoded:

node -e "console.log(Buffer.from(crypto.randomBytes(32)).toString('base64url'))"

This key is used for application-level encryption. KV-backed session data derives a per-session AES-GCM key from it with HKDF. Never commit it to version control.

Architecture

See docs/architecture.md for system design.

Privacy

See docs/privacy.md for data handling and encryption details.

Commands

See docs/commands.md for the full command reference.

Scaffold Review

See docs/scaffold-review.md for the latest security and correctness audit.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors