This tool processes your X archive, evaluates each tweet against your alignment criteria (e.g., unprofessional language, specific keywords, outdated opinions), and generates a list of tweet URLs marked for manual deletion. This can be helpful to purging your X account with tweets that aren't social worthy or doesn't tell about your persona anymore.
The system follows a layered architecture with async processing:
API Layer: HTTP endpoints handle uploads, job status checks, and flagged tweet retrieval. Uploads return immediately with a job_id, avoiding long-running requests.
Storage Layer: Local file storage for archives and SQLite for job state, tweets, and flagged results. Both are designed for single-node operation with easy backup.
Worker System: Two background workers process jobs asynchronously:
- Job Loop: Parses archives, applies deterministic filters, enqueues tweets needing LLM review
- Tweet Loop: Processes tweets through Gemini API with rate limiting and circuit breaking
LLM Scoring Layer: Gemini API calls are wrapped in rate limiting (15 req/min), circuit breaker (fails fast on permanent errors), and exponential backoff retries. Falls back to mock scorer if no API key is configured.
Data Flow: Tweets flow from parser → filters → either flagged immediately or queued for Gemini → scored → persisted to database. All state is tracked in SQLite for querying and export.
-
Upload: Client uploads archive file via
POST /tweets/upload. Server saves file, creates job record, enqueues job using a channel, returnsjob_idimmediately. -
Archive Processing: Job worker picks up job, opens archive file, parses ZIP or JS format, extracts tweet records, saves all tweets to database.
-
Filtering: For each tweet, deterministic filters check for retweets (skip), clear abuse patterns (flag immediately), or pass to LLM queue.
-
LLM Scoring: Tweet worker pulls from queue, waits for rate limiter token, checks circuit breaker, calls Gemini API with moderation criteria, receives score and reason.
-
Persistence: If flagged, saves flagged tweet record with score and reason. Marks tweet as processed, updates job statistics (processed_count, flagged_count, gemini_calls).
-
Query: Client polls
GET /jobs/:idfor status, thenGET /tweets?job_id=:idto retrieve flagged tweets, orGET /tweets/exportto get URLs for deletion.
graph LR
subgraph "Layer 1: Deterministic Filters"
A[Tweet Record] --> B{Retweet?}
B -->|Yes| C[Skip]
B -->|No| D{Abusive Patterns?}
D -->|Yes| E[Flag Immediately]
D -->|No| F{Threat Patterns?}
F -->|Yes| E
F -->|No| G{Pass to Layer 2}
end
subgraph "Layer 2: LLM Scoring"
G --> H[Rate Limiter]
H --> I[Circuit Breaker]
I --> J[Gemini API]
J --> K{Score > Threshold?}
K -->|Yes| L[Flag with Reason]
K -->|No| M[Safe Tweet]
end
E --> N[(Database)]
L --> N
M --> N
style E fill:#ffcccc
style L fill:#ffcccc
style M fill:#ccffcc
style C fill:#e0e0e0
graph TB
subgraph "Rate Limiting"
RL[Token Bucket<br/>15 tokens/min]
RL -->|Token Available| Allow
RL -->|No Token| Wait
end
subgraph "Circuit Breaker"
CB[3 States:<br/>Closed/Open/Half-Open]
CB -->|Success| Closed[Closed<br/>Normal Operation]
CB -->|Failures > 5| Open[Open<br/>Fail Fast]
CB -->|Timeout| HalfOpen[Half-Open<br/>Test Recovery]
Open -->|30s timeout| HalfOpen
HalfOpen -->|2 successes| Closed
HalfOpen -->|Failure| Open
end
subgraph "Retry Logic"
Retry[Exponential Backoff]
Retry -->|429 Rate Limit| Wait60[Wait 60s]
Retry -->|5xx Error| Backoff[Backoff: 1s, 2s, 4s]
Retry -->|401/403/404| FailFast[Fail Fast<br/>No Retry]
end
Allow --> CB
Wait --> RL
Closed --> Retry
Open --> FailFast
HalfOpen --> Retry
style Closed fill:#ccffcc
style Open fill:#ffcccc
style HalfOpen fill:#ffffcc
style FailFast fill:#ffcccc
go mod download- Get a Gemini API key from Google AI Studio
- Create a
.envfile:cp .env.example .env
- Add your API key:
GEMINI_API_KEY=your_actual_api_key_here
go run cmd/tweet-audit/main.goThe server will use Gemini if GEMINI_API_KEY is set, otherwise it falls back to the mock scorer.
POST /tweets/upload- Upload archive (ZIP or JS file) with optional moderation criteriaGET /jobs/{id}- Check job processing status and progressGET /tweets?job_id={id}- List flagged tweets with paginationGET /tweets/export?job_id={id}&format=json|csv- Export flagged tweet URLsGET /swagger/- Interactive API documentation
Run all tests:
go test ./internal/tweets/...Tests use MockScorer - no Gemini API calls, no costs, no network dependency. SQLite tests use temporary databases that are cleaned up automatically.
- Async Processing: Upload returns immediately with a job_id. Heavy work (parsing + LLM) happens in background workers
- Two-Layer Detection: Fast deterministic filters catch obvious cases, LLM handles nuanced ones
- Rate Limiting: Built-in 15 req/min limit for Gemini free tier compliance
- Circuit Breaker: Prevents cascading failures when API is down
- SQLite: Zero-config database perfect for single-user/small-team use
- Local Storage: Simple file-based storage, can swap to S3 later via FileStore interface
See TRADEOFFS.md for detailed architectural decisions and tradeoffs.