sojuboy/CLAUDE.md

14 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Development Commands

Building and Running

  • go build -o sojuboy ./cmd/sojuboy - Build the binary
  • go run ./cmd/sojuboy - Run directly with Go
  • docker-compose up -d --build - Build and run with Docker Compose
  • docker build -t sojuboy . - Build Docker image

Health Checks

  • ./sojuboy --health - Local readiness check (exits 0/1)
  • curl http://localhost:8080/healthz - HTTP health check
  • curl http://localhost:8080/ready - HTTP readiness check (requires soju connection)

Testing and Validation

  • go mod tidy - Clean up module dependencies
  • go vet ./... - Run Go static analysis
  • go fmt ./... - Format Go code

Architecture Overview

This is a Go service that acts as an IRC bouncer companion for soju, providing notifications, message storage, and AI summaries.

Core Components

Entry Point: cmd/sojuboy/main.go - Main application entry point with service wiring and configuration

Core Modules (all in internal/):

  • config/ - Environment-based configuration loading
  • soju/ - IRC client for soju bouncer connection with IRCv3 capability negotiation
  • store/ - SQLite storage layer with WAL mode enabled
  • notifier/ - Pushover notification system (pluggable interface)
  • summarizer/ - OpenAI integration for AI digests and link summarization
  • scheduler/ - Cron-based digest scheduling and retention jobs
  • httpapi/ - HTTP API server with Web UI (templates + static assets)
  • logging/ - Structured logging setup

Key Architectural Patterns

IRC Integration: Uses raw IRC protocol with sorcix/irc parser, implements irssi-style authentication for soju multi-client support (username/network@client format)

Message Flow: IRC messages → storage → mention detection → notifications + AI processing

Web Architecture: Go stdlib net/http with html/template, embedded static assets, SSE for real-time updates

AI Integration: OpenAI client with GPT-5 default (fallback to GPT-4o-mini), separate prompts for conversation digests vs link summarization

Configuration

All configuration via environment variables (see README.md for complete reference). Key patterns:

  • Soju connection: SOJU_HOST, SOJU_PORT, SOJU_TLS, IRC_USERNAME format critical for per-client history
  • Required for functionality: OPENAI_API_KEY, PUSHOVER_USER_KEY, PUSHOVER_API_TOKEN
  • Default data path: /data/app.db (SQLite with WAL mode)

HTTP API Structure

  • Authentication via HTTP_TOKEN (cookie or Bearer/query param)
  • JSON APIs: /tail, /history, /trigger, /linkcard, /linksummary, /metrics
  • Web UI with SSE tail, infinite scroll, link cards, on-demand summarization
  • Channel-based operations (URL encode # as %23 in requests)

Storage Schema

SQLite with modernc.org/sqlite driver:

  • Messages table with server-time timestamps
  • Channel-based organization
  • Retention management (daily cleanup at 03:00)
  • WAL mode for concurrent access

Development Notes

  • Go 1.23+ required (see go.mod)
  • Static binary compilation for distroless Docker image
  • No external build tools (Make, npm, etc.) - pure Go toolchain
  • Configuration via .env file for local development
  • Docker health checks via --health flag

Code Review Findings

🚀 Major Refactors for iOS Features

1. Extract Message Processing Service

The alert function in main.go:143-212 is doing too much:

  • Message storage
  • Notification logic
  • Rate limiting
  • DM vs channel handling

Recommendation: Create internal/messaging/processor.go with separate concerns:

type MessageProcessor struct {
    store Store
    notifiers []Notifier  // Multi-target for iOS + Pushover
    rateLimiter RateLimiter
    broadcaster Broadcaster
}

2. Multi-Target Notification System

Current system hardcoded to single Pushover notifier. For iOS:

type NotificationDispatcher struct {
    targets []Notifier  // Pushover, APNs, etc.
}

3. Extract Rate Limiter

Move rateLimiter struct to internal/ratelimit/ package for reusability and testing.

Critical Performance Issues

1. SQLite Connection Bottleneck

store.go:30 - SetMaxOpenConns(1) is extremely restrictive:

// Current: Major bottleneck
db.SetMaxOpenConns(1)

// Better: Allow concurrent reads
db.SetMaxOpenConns(5)
db.SetMaxIdleConns(2)

2. Inefficient Array Reversal

store.go:147-150 - Manual reversal is inefficient:

-- Better: Use SQL ORDER BY instead
SELECT ... ORDER BY at ASC LIMIT ?

3. OpenAI Client Recreation

openai.go:60 - Client created per request. Should use singleton pattern with connection pooling.

🗑️ Dead Code Cleanup

  1. Entire unused package: internal/ircclient/base64.go - appears completely unused
  2. Unused imports: io package in main.go only for health check discard
  3. Dead helper: strconvI() in summarizer.go:475 - just use strconv.Itoa directly
  4. Rarely used export: config.GetEnvInt() - used once, could be private

🔧 Easy Wins

1. Fix Deprecated API

templates.go:48 - strings.Title() is deprecated:

// Replace with
data["Title"] = cases.Title(language.English).String(base)

2. Hard-coded Values

  • Health check port hardcoded to 8080 - use configured listen address
  • Text limits hardcoded in summarizer - make configurable
  • Temperature values hardcoded - make configurable

3. Database Schema

Missing indexes for performance:

CREATE INDEX IF NOT EXISTS idx_messages_author ON messages(author);
CREATE INDEX IF NOT EXISTS idx_messages_time ON messages(at);

🛡️ Error Handling Improvements

1. Silent Failures

pushover.go:24 - Returns nil on error conditions without logging:

// Better error handling
if p == nil || p.app == nil || p.userKey == "" {
    return errors.New("pushover not configured")
}

2. Message Truncation

No user feedback when messages truncated to 1024 chars.

📱 iOS-Specific Optimizations

1. Message Broadcaster Enhancement

Current SSE broadcasting is basic - needs:

  • Connection state management
  • Message queuing for disconnected clients
  • WebSocket support alongside SSE

2. Auth Token Management

Simple token auth needs enhancement for mobile:

  • JWT tokens with refresh capability
  • Device-specific sessions
  • Proper expiration handling

🎯 Implementation Priority

Phase 1 (High Impact):

  1. Fix SQLite connection limit
  2. Extract message processor service
  3. Remove dead code
  4. Fix deprecated APIs

Phase 2 (iOS Foundation):

  1. Multi-target notification system
  2. Enhanced message broadcasting
  3. JWT authentication improvements

Phase 3 (Performance):

  1. OpenAI client pooling
  2. Database index optimization
  3. Rate limiter improvements

Application Behavior & Quirks

Message Storage Logic

  • DM Handling: Direct messages TO the bot are stored under the sender's nick, not the bot's nick (main.go:146-151)
  • Message Deduplication: Uses INSERT OR IGNORE with optional msgid for soju message deduplication
  • Channel Normalization: All channel lookups are case-insensitive via lower() SQL function

Notification Logic

  • Rate Limiting: Per-channel + per-keyword combinations, not global (main.go:199)
  • Quiet Hours: Local timezone-based, supports midnight wrap-around (config.go:267-271)
  • Urgent Keywords: Bypass quiet hours but still respect rate limits
  • Backfill Suppression: Messages older than 5 minutes are not notified unless NOTIFY_BACKFILL=true

IRC Protocol Specifics

  • Authentication: Uses raw PASS/USER, not SASL (despite config hint)
  • Capability Negotiation: Requests IRCv3 draft/event-playback for automatic message replay
  • Fallback History: CHATHISTORY LATEST only used when event-playback unavailable
  • Connection Management: Exponential backoff (1s → 30s max) with automatic reconnection

AI Summarization Behavior

  • Message Grouping: Same author within SUMM_GROUP_WINDOW (default 120s) are concatenated
  • Link Processing: Extracts URLs via regex, fetches with readability for clean text
  • Image Handling: Direct image URLs sent as vision inputs to GPT-5
  • Fallback Logic: Local text-based summary if OpenAI fails
  • Model Selection: Auto-detects reasoning models (gpt-5, o1) to skip temperature setting

Web UI Implementation

  • SSE Streaming: Real-time message delivery with 15s heartbeat
  • Link Cards: Cached 24h, special handling for X/Twitter oEmbed and YouTube
  • Authentication: Cookie-based with 7-day expiration, secure flag auto-detection
  • Channel Selector: Populated from DB + config fallback for empty databases

Storage Patterns

  • WAL Mode: Enabled for concurrent read access despite single connection limit
  • Retention: Daily cleanup at 03:00 local time, configurable days
  • Timestamps: Always stored as UTC, server-time tags preferred over local time
  • Migration: Best-effort schema updates with ignored errors for missing columns

Error Handling Patterns

  • Silent Failures: Many subsystems return nil errors to prevent cascading failures
  • Graceful Degradation: Missing summarizer/notifier doesn't break core IRC functionality
  • Connection Resilience: IRC client handles ERROR messages and reconnects automatically

Security Considerations

  • Token Storage: HTTP_TOKEN stored in plain text, suitable for internal deployments
  • CORS: No CORS headers - designed for same-origin web UI access
  • TLS: Configurable per connection, defaults to enabled
  • Input Sanitization: Minimal - relies on template escaping and database parameterization

Performance Characteristics

  • Memory Usage: Link/summary caches unbounded - potential memory leak over time
  • Concurrent Requests: Severely limited by single DB connection
  • OpenAI Rate Limits: No built-in rate limiting or retry logic
  • SSE Scalability: In-memory subscriber map, no persistence across restarts

Development Workflow

  • Hot Reload: Not supported - requires full restart for config/code changes
  • Logging: Structured JSON to stdout, debug mode shows IRC protocol messages
  • Health Monitoring: Separate metrics endpoint for Prometheus integration
  • Docker: Multi-stage build with distroless final image for security

Common Gotchas

  • Channel Names: Must URL-encode # as %23 in HTTP requests
  • Time Formats: Mix of RFC3339 and RFC3339Nano depending on context
  • Case Sensitivity: IRC nicks/channels case-insensitive, but stored as-received
  • CHATHISTORY: Requires soju-specific timestamp format, may not work with other bouncers
  • Health Check: Hard-coded to port 8080 regardless of configured listen address

Planned iOS App Development

Context

User wants to build a native iOS Swift app that consumes this Go server's HTTP APIs. The server should remain in Go (excellent fit for backend services), while the iOS app provides a native mobile experience.

Required Server Enhancements for iOS

1. iOS Push Notifications (Priority 1)

  • Add Apple Push Notification service (APNs) integration alongside Pushover
  • New endpoints: POST /api/devices (device token registration)
  • Enhanced notification system supporting multiple targets
  • Environment variables: APNS_KEY_ID, APNS_TEAM_ID, APNS_BUNDLE_ID, APNS_KEY_PATH, APNS_SANDBOX

2. Enhanced Real-time Communication

  • Add WebSocket support alongside existing SSE (better for iOS background handling)
  • Message queuing for disconnected clients
  • New endpoints: GET /api/ws?channel=#chan, GET /api/missed?since=<timestamp>

3. Mobile-Optimized Authentication

  • JWT tokens with refresh capability (current simple token is too basic)
  • Device-specific authentication for multiple iOS devices
  • New endpoints: POST /api/auth/login, POST /api/auth/refresh, POST /api/auth/logout

4. Offline Support & Sync

  • Cursor-based pagination (improve current before parameter approach)
  • Message read/unread state tracking per device
  • Enhanced endpoints: GET /api/sync?cursor=<id>&limit=100, POST /api/read-receipts, GET /api/unread-count

5. iOS-Specific Optimizations

  • Batch operations: POST /api/linkcards/batch
  • Image size variants: GET /api/linkcard?url=...&size=thumb
  • Performance optimizations for mobile bandwidth

iOS App Feature Parity Goals

The native app should match current web UI functionality:

  • Live chat view with real-time updates
  • Link cards and on-demand link summarization
  • Native push notifications (replacing Pushover for mobile users)
  • On-demand AI summarization
  • Channel switching and history browsing
  • Infinite scroll for chat history

Database Schema Extensions Needed

  • devices table - iOS device tokens and user associations
  • read_receipts table - per-device read state tracking
  • message_queue table - offline message queueing

Implementation Priority for iOS Support

  1. APNs integration (native notifications)
  2. WebSocket + connection management
  3. JWT authentication system
  4. Offline sync capabilities
  5. Mobile-specific API optimizations

Architecture Decision: Keep Go Backend

Question Asked: Could this project be rewritten in Swift? Answer: Technically feasible but not recommended.

Pros of Swift rewrite: Strong type safety, modern concurrency (async/await), cross-platform potential Cons: Limited ecosystem for IRC libraries, SQLite libraries, cron scheduling, HTTP templating; larger Docker images; significant development effort to recreate working functionality

Recommended Approach: Keep Go backend (excellent fit for network services, mature ecosystem, single static binary deployment) + build native Swift iOS frontend. This leverages both platforms' strengths effectively.