Skip to content

cristoforows/second_brain_bot

Repository files navigation

Second Brain Telegram Bot

A Telegram bot that collects your chat messages and stores them in Google Drive for building your personal second brain knowledge base.

What It Does

This bot acts as your personal data collector:

  • Listens to messages you send in Telegram
  • Collects and structures your chat data
  • Uploads data to your Google Drive
  • Data is later processed by specialized services for filtering and organization

Think of it as the first step in building your second brain - capturing everything you communicate in Telegram.

Current Status

Functional: The bot has a working webhook infrastructure, Google OAuth 2.0 authentication, encrypted token storage in PostgreSQL (Supabase), and Google Drive integration for saving and editing messages as markdown files. It also offers /timebox — an LLM (OpenRouter) turns your next-day tasks into a timeboxed schedule, optionally published to a dedicated Google Calendar.

Requirements

  • Python 3.10 or higher (the codebase uses X | None type unions; the Docker image is built on 3.11)
  • Telegram account
  • Google account with Drive and Calendar API access
  • Telegram bot token from @BotFather
  • OpenRouter API key (required — used by /timebox)

Quick Setup

1. Get Your Bot Token

  1. Open Telegram and message @BotFather
  2. Send /newbot and follow the instructions
  3. Save the bot token you receive

2. Install Dependencies

# Create and activate virtual environment
python -m venv venv
source venv/bin/activate  # On macOS/Linux
# venv\Scripts\activate   # On Windows

# Install required packages
pip install -r requirements.txt

3. Configure Environment

# Copy example configuration
cp .env.example .env

# Edit .env with your credentials

Your .env file should contain:

TELEGRAM_BOT_TOKEN=your_bot_token_here
LOG_LEVEL=INFO

4. Run the Bot

go to DEVELOPMENT.md for local development testing. go to DEPLOY.md for remote deployment.

Usage

  • /start - Initialize conversation with the bot
  • /help - Get help information
  • /authenticate - Connect your Google Drive via OAuth 2.0
  • /timebox - Turn next-day tasks into a timeboxed schedule (optionally written to Google Calendar)
  • /status - Check authentication and Drive connection status
  • /logout - Disconnect Google Drive and remove stored tokens

Once authenticated, send any text message and it gets saved to a daily markdown file in your Google Drive. Edit a message in Telegram and the Drive file updates automatically.

Outbound Send-Message API

POST /api/send-message lets a trusted caller make the bot send a Telegram message. Disabled unless OUTBOUND_API_SECRET is set.

curl -X POST https://your-domain.com/api/send-message \
  -H "Authorization: Bearer $OUTBOUND_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": 123456789, "text": "hello from the bot"}'

Body fields: chat_id (int or string, required), text (string, required, ≤4096 chars), parse_mode (optional: Markdown, MarkdownV2, or HTML). Returns {"ok": true, "message_id": <int>} on success.

Project Structure

second_brain_bot/
├── src/
│   ├── bot.py              # Command handlers (/start, /help, /authenticate, /timebox, /status, /logout) + register_handlers
│   ├── webhook_server.py   # Flask server: Telegram webhook, OAuth callback, outbound /api/send-message
│   ├── config.py           # Configuration and environment management
│   ├── google_auth.py      # OAuth 2.0 flow (drive.file + calendar.events), token storage (PostgreSQL), CSRF protection
│   ├── drive_handler.py    # Google Drive API: file creation, message append/edit
│   ├── timebox.py          # /timebox conversation: collect next-day tasks, build & publish a schedule
│   ├── scheduler.py        # Timebox scheduling: target-date, LLM generation, rendering
│   └── calendar_handler.py # Google Calendar writes for the schedule (tag/list/clear/per-item)
├── k8s/                    # Kubernetes manifests
├── Dockerfile              # Multi-stage Docker build
├── docker-compose.yml      # Docker Compose deployment
├── requirements.txt        # Python dependencies
├── .env                   # Your credentials (create from .env.example)
├── .env.example           # Template for configuration
├── README.md              # This file (user guide)
├── DEVELOPMENT.md         # Local development guide
├── DEPLOY.md              # Docker/Kubernetes deployment guide
└── CLAUDE.md              # Technical documentation for AI agents

Configuration

Environment Variables

  • TELEGRAM_BOT_TOKEN (required) - Your bot token from @BotFather
  • WEBHOOK_URL (required) - Public HTTPS base URL (e.g. https://your-domain.com)
  • WEBHOOK_PORT (optional, default 8443) - Port for webhook server (80, 88, 443, or 8443)
  • WEBHOOK_PATH (optional, default /webhook) - Path prefix for the webhook endpoint
  • GOOGLE_CLIENT_ID (required) - OAuth client ID from Google Cloud Console
  • GOOGLE_CLIENT_SECRET (required) - OAuth client secret
  • GOOGLE_REDIRECT_URI (optional) - OAuth callback URL (defaults to {WEBHOOK_URL}/oauth/callback)
  • DATABASE_USER / DATABASE_PASSWORD / DATABASE_HOST / DATABASE_PORT / DATABASE_NAME (required) - PostgreSQL connection details
  • TOKEN_ENCRYPTION_KEY (required) - Fernet key for encrypting stored OAuth tokens
  • DRIVE_FOLDER_NAME (optional, default second_brain_bot/) - Name of the Drive folder the bot creates; daily notes are stored inside it as YYYY-MM-DD.md
  • DAY_CUTOFF_HOUR (optional, default 0) - Hour (0-23) before which messages are filed under the previous day. 0 disables it (midnight boundary)
  • OUTBOUND_API_SECRET (optional) - Shared secret for POST /api/send-message; leave unset to disable (returns 503). Generate with openssl rand -hex 32.
  • LOG_LEVEL (optional, default INFO) - Logging verbosity: DEBUG, INFO, WARNING, ERROR, CRITICAL

Timebox (/timebox)

  • OPENROUTER_API_KEY (required) - OpenRouter API key for schedule generation. Startup fails without it (get one)
  • TIMEBOX_LLM_MODEL (optional, default deepseek/deepseek-v4-flash) - OpenRouter model used for scheduling
  • TIMEBOX_TIMEZONE (optional, default Asia/Singapore) - IANA timezone for computing the target day
  • TIMEBOX_CUTOFF_HOUR (optional, default 3) - Sessions finishing before this local hour plan the current day instead of tomorrow
  • TIMEBOX_CALENDAR_ID (optional) - Calendar ID of a dedicated calendar to publish the schedule into. Never primary. Unset → schedule is replied as text only
  • TIMEBOX_DAY_START / TIMEBOX_DAY_END / TIMEBOX_LUNCH / TIMEBOX_DINNER / TIMEBOX_EAT_DURATION / TIMEBOX_COMMUTE_MORNING / TIMEBOX_COMMUTE_EVENING / TIMEBOX_COMMUTE_DURATION (optional) - Fixed daily anchors handed to the planner as overridable defaults. See .env.example for defaults

Google OAuth & Drive Setup

  1. Create a project in Google Cloud Console
  2. Enable the Google Drive API and the Google Calendar API (the latter is needed for /timebox calendar publishing)
  3. Create OAuth 2.0 credentials (Web application type)
  4. Add your redirect URI (e.g. https://your-domain.com/oauth/callback)
  5. Copy the client ID and client secret to your .env

The bot requests the drive.file and calendar.events scopes. If you authenticated before calendar support was added, re-run /authenticate to grant the new scope.

Database Setup (Supabase)

  1. Create a project at Supabase
  2. Run this migration in the SQL Editor:
    CREATE TABLE user_tokens (
        user_id BIGINT PRIMARY KEY,
        encrypted_token TEXT NOT NULL,
        token_expires_at TIMESTAMP,
        last_accessed TIMESTAMP NOT NULL,
        created_at TIMESTAMP NOT NULL DEFAULT NOW()
    );
  3. Copy the database connection details to your .env

Token Encryption

Generate a Fernet key and add it to your .env:

python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

Security

  • Never commit your .env file
  • Keep your bot token private
  • Don't share OAuth credentials
  • The .gitignore excludes sensitive files automatically

Future Improvements

1. Stateless Architecture

The OAuth CSRF state cache (_state_cache in google_auth.py) currently lives in-memory. If the process restarts between a user clicking /authenticate and completing the Google consent screen, the callback fails. This also prevents running multiple instances.

Option A: External KV store (e.g. Cloudflare KV)

  • OAuth state is short-lived (10 min TTL) — KV supports TTL natively
  • Access pattern is single-key lookup — KV's strongest use case
  • Low volume (only during auth, not per-message)

Option B: Signed state (no storage needed)

  • Encode user_id and expires_at into the state string, sign it with TOKEN_ENCRYPTION_KEY using HMAC
  • The callback validates the signature and expiry without any storage lookup
  • Tradeoff: loses one-time-use enforcement, but Google's auth code is already single-use so the practical risk is minimal

2. Batched Drive Writes

Currently every message triggers a download-append-upload cycle (2 Drive API calls). For bursts of messages this is wasteful and creates potential race conditions.

Option A: Time-window buffer

  • Collect messages in a buffer (in-memory, KV, or Redis)
  • Flush to Drive after N seconds of inactivity or when buffer hits a size threshold
  • Single download + append all + upload per flush
  • Risk: messages lost if the process dies mid-buffer; mitigate by buffering in durable storage

Option B: Pending messages table (more robust)

  • Write each message immediately to a pending_messages table in PostgreSQL
  • A periodic job (every 30-60s) collects all pending messages per user, downloads the Drive file once, appends all, uploads, then marks them as synced
  • Edits update the pending row if not yet synced, or trigger a Drive update if already synced
  • Decouples Telegram response time from Drive API latency — the bot can acknowledge instantly

3. Media Support (Images, Video, Audio, Files)

Currently only text messages are handled. Supporting media requires a different storage strategy since binary files can't live inline in markdown.

Approach:

  • Upload media files to a subfolder in the user's Google Drive (e.g. SecondBrain/media/)
  • Reference them in the markdown by Drive link:
    <!-- msg_id: 456 -->
    [Photo: sunset.jpg](https://drive.google.com/file/d/abc123/view)
    Caption text here

Considerations:

  • Telegram file download: await context.bot.get_file(file_id) then .download_as_bytearray(). Telegram stores files temporarily, so download promptly
  • File size limits: Telegram Bot API caps file downloads at 20MB
  • Drive upload: MediaInMemoryUpload for small files, MediaIoBaseUpload with streaming for larger ones
  • Handler changes: Broaden filters to include filters.PHOTO, filters.VIDEO, filters.AUDIO, filters.Document.ALL. Each type exposes file IDs differently (message.photo[-1].file_id for highest-res photo, message.document.file_id for documents, etc.)
  • Downstream compatibility: The markdown format should match what the second brain processing service expects — simple Drive links are the most portable option

Priority

  1. Batching — most immediate reliability and performance gain
  2. Stateless — matters when deploying multiple instances or moving to serverless
  3. Media support — biggest feature expansion, most implementation work

Development

See CLAUDE.md for technical details, architecture decisions, and implementation roadmap.

License

MIT License


Built for capturing ideas and building a personal knowledge base, one message at a time.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors