A Telegram bot integration to tidy up your finances.
Parses natural language expense messages using Claude AI and automatically registers them into a Google Sheets spreadsheet.
Note: Bot status messages are currently in pt-BR. Support for other locales is planned.
- Clone the repository:
git clone git@github.com:jhonnymoreira/financial-bot.git
cd financial-bot- Enable corepack and install dependencies:
corepack enable
pnpm install- Authenticate with Cloudflare:
pnpm exec wrangler login- Node.js v24.13.0 (use nvm with the
.nvmrcfile) - pnpm v10.28.1 via corepack
- Cloudflare account
- Anthropic account
- Google account
- Telegram account
- Go to Claude Platform
- Sign up or log in
- Navigate to API Keys
- Click Create Key and give it a name
- Copy the key — you'll need it for the Secrets Store
- Go to Google Cloud Console
- Create a new project or select an existing one
- Enable the Google Sheets API
- Go to IAM & Admin > Service Accounts and create a new service account
- Create a JSON key for the service account and save it — this is your
GOOGLE_SERVICE_ACCOUNT_CREDENTIALS - Create a Google Sheet with the following columns (in order):
| occurredAt | amount | description | category | paymentMethod | paymentIdentifier | currency | messageId | registeredAt |
|---|
- Share the spreadsheet with the service account email (found in the JSON key as
client_email) with Editor permissions - Copy the spreadsheet ID from the URL:
https://docs.google.com/spreadsheets/d/<SPREADSHEET_ID>/edit
Update the name field in wrangler.jsonc to your project identifier:
This name is used as a prefix for your secrets in the secrets store.
Create a secrets store:
pnpm exec wrangler secrets-store create <your-project-name>-secrets --remoteSave the returned store ID. Add secrets to the store:
pnpm exec wrangler secrets-store secret put <store-id> <your-project-name>_anthropic_api_key --remote
pnpm exec wrangler secrets-store secret put <store-id> <your-project-name>_telegram_bot_token --remote
pnpm exec wrangler secrets-store secret put <store-id> <your-project-name>_telegram_webhook_secret_token --remoteTo generate a webhook secret token:
openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32Save this value — you'll need it for both the secrets store and Telegram webhook registration.
Update wrangler.jsonc with your store ID and secret names:
{
"secrets_store_secrets": [
{
"binding": "ANTHROPIC_API_KEY",
"store_id": "<your-store-id>",
"secret_name": "<your-project-name>_anthropic_api_key"
},
{
"binding": "TELEGRAM_BOT_TOKEN",
"store_id": "<your-store-id>",
"secret_name": "<your-project-name>_telegram_bot_token"
},
{
"binding": "TELEGRAM_WEBHOOK_SECRET_TOKEN",
"store_id": "<your-store-id>",
"secret_name": "<your-project-name>_telegram_webhook_secret_token"
}
]
}Set the remaining secrets via wrangler secret put:
pnpm exec wrangler secret put ALLOWED_USER_IDS
# Enter: [<user-id-1>, <user-id-2>]
pnpm exec wrangler secret put ALLOWED_CHAT_IDS
# Enter: [<chat-id-1>, <chat-id-2>]
pnpm exec wrangler secret put GOOGLE_SERVICE_ACCOUNT_CREDENTIALS
# Enter: <service-account-json>
pnpm exec wrangler secret put SPREADSHEET_ID
# Enter: <spreadsheet-id>
pnpm exec wrangler secret put SPREADSHEET_BACKLOG_SHEET_NAME
# Enter: <sheet-name>- Generate Cloudflare bindings types:
pnpm cf:typegen- Deploy:
pnpm cf:deployNote the worker URL from the output (e.g., https://<your-project-name>.<account>.workers.dev).
- Open Telegram and search for @BotFather
- Send
/newbotand follow the prompts to name your bot - Save the bot token — add it to
<your-project-name>_telegram_bot_token(see Secrets Store section for reference) - Send
/setprivacy, select your bot, then chooseDisableto allow the bot to read group messages
After deploying, register the webhook with Telegram:
curl -X POST "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/setWebhook" \
-H "Content-Type: application/json" \
-d '{
"url": "https://<your-worker-url>/webhook",
"secret_token": "<TELEGRAM_WEBHOOK_SECRET_TOKEN>"
}'Send /start to your bot — it will respond with your user ID and chat ID.
For group chats, add the bot to the group and send /start there to get the group's chat ID (groups have negative IDs).
Add the bot to a group or send a direct message, then send expense messages following this format:
Note: The bot currently supports single messages only. Multi-message support is planned for the future.
{amount} {description} {temporal_ref} {payment_method} {payment_identifier} [categories]
99 Pizzaria maneira hoje débito identificador-do-banco
32.50 Besteirinhas no mercado 21/12/2025 pix identificador-do-banco
150 Cinema com a família ontem crédito identificador-do-banco entertainment,food
débito (debit), crédito (credit), pix, boleto
The bot understands temporal references in pt-BR and calculates the occurredAt date accordingly. These references are stripped from the final description:
hoje, ontem, anteontem, [N] dia(s) atrás, semana passada, mês passado, ano passado, segunda, terça, quarta, quinta, sexta, sábado, domingo
You can also use explicit dates like 21/12/2025.
You can pass one or more categories as the last argument, separated by comma. If not provided, the bot infers from the description.
appliances, candomble, car, credit-allowance, education, entertainment, food, gifts, health, market, monthly-expenses, pets, self-care, subscriptions, subscriptions-1-month, subscriptions-3-months, subscriptions-6-months, subscriptions-1-year, taxes, transport, work
Monitor real-time logs from your deployed worker:
pnpm exec wrangler tailThis project is webhook-based and designed to run on Cloudflare Workers. Local development requires tunneling your connection (e.g., ngrok, cloudflared).
Note: If the tunnel URL changes, you must re-register the webhook with Telegram.
Create a .dev.vars file with all required variables:
ALLOWED_USER_IDS="[<user-id-1>, <user-id-2>]"
ALLOWED_CHAT_IDS="[<chat-id-1>, <chat-id-2>]"
GOOGLE_SERVICE_ACCOUNT_CREDENTIALS=<service-account-json>
SPREADSHEET_ID="<spreadsheet-id>"
SPREADSHEET_BACKLOG_SHEET_NAME="<sheet-name>"
ANTHROPIC_API_KEY=<your-anthropic-api-key>
TELEGRAM_BOT_TOKEN=<your-telegram-bot-token>
TELEGRAM_WEBHOOK_SECRET_TOKEN=<your-webhook-secret-token>Start the development server:
pnpm cf:devTunnel your connection and register the webhook with the tunnel URL.
| Script | Description |
|---|---|
pnpm cf:dev |
Start local development server |
pnpm cf:deploy |
Deploy to Cloudflare Workers (minified) |
pnpm cf:typegen |
Generate TypeScript types for Cloudflare bindings |
pnpm code:lint |
Run Biome linter |
pnpm code:lint:fix |
Run Biome linter with auto-fix |
pnpm code:prettify |
Format code with Biome |
pnpm code:typecheck |
Run TypeScript type checking |
pnpm test |
Run test suite |
pnpm test:coverage |
Run tests with coverage report |
pnpm test:watch |
Run tests in watch mode |
- Add changesets
- Add commit hooks to integrate
pnpm code:lint,pnpm code:typecheck, andpnpm test - Add GitHub Actions integration
{ "name": "<your-project-name>", // ... }