Problem #
After years of using Telegram, my account accumulated hundreds of unwanted chats: spam groups I was added to, channels I forgot to leave, deleted user accounts, and bot conversations I never cleaned up. The official Telegram app makes bulk management painful:
- No bulk delete — must delete chats one by one, with confirmation each time
- No spam detection — can't identify deleted accounts, inactive bots, or scam users
- No export — can't get a list of all chats for analysis
- No filtering — can't view only groups, only channels, or only users
I needed a tool to audit my Telegram account, identify clutter, and clean it up — all while keeping my data local and private.
Constraints #
- Privacy-first: No data leaves my machine — no cloud, no external APIs
- Portable: Single executable that runs anywhere without Python installed
- Cross-platform: Works on Windows, macOS, and Linux
- Official API only: Use Telegram's MTProto protocol via Telethon (no scraping)
- Session persistence: "Remember me" functionality for convenience
- Dark UI: Dark theme, responsive, works on mobile browsers too
Architecture #
(Terminal UI)"] web["default → fastapi_manager.py
(Web API)"] entry ~~~ cli ~~~ web end subgraph framework["Web Framework"] direction LR fastapi["FastAPI + Uvicorn
(async ASGI server)"] middleware["Error Handling
Middleware"] html["Embedded HTML Template
(dark glassmorphism UI)"] fastapi ~~~ middleware ~~~ html end subgraph core["Core Library"] telethon["Telethon
(Telegram MTProto API Client)"] end subgraph data["Data Layer (all local)"] direction LR config["telegram_config.json
(API credentials)"] session["*.session
(Telegram auth token, SQLite)"] exports["exports/*.json
(chat lists)"] config ~~~ session ~~~ exports end end entry_layer --> framework framework --> core core --> data
Key components:
- FastAPI: Async web framework with native
async/await— no event loop conflicts - Telethon: Official MTProto client for Telegram, handles rate limiting automatically
- Embedded HTML: UI template is a Python string, written to disk at runtime for single-file distribution
- PyInstaller: Packages everything into a ~20MB standalone executable
Decisions & Tradeoffs #
Why FastAPI over Flask?
Initially built with Flask, but hit "No event loop in thread" errors when combining Flask's sync model with Telethon's async API. Switched to FastAPI:
- Native async: Direct
await client.connect()without hacks - Auto API docs: Swagger UI at
/docsfor free - Better performance: Uvicorn ASGI server handles concurrent requests
- Tradeoff: Slightly steeper learning curve, but worth it
Why embed HTML in Python instead of separate files?
Goal was single-file distribution. Embedding the entire HTML template (~2000 lines) as a Python string means:
- One .exe: No external dependencies to bundle
- Version sync: Template always matches code version
- Tradeoff: Larger Python file, harder to edit HTML (no syntax highlighting)
Why localtest.me instead of localhost?
Used telegram-manager.localtest.me:5000 instead of localhost:5000:
- Looks cleaner in browser (vanity URL)
- No config:
*.localtest.meis a public wildcard DNS resolving to127.0.0.1 - Tradeoff: Requires internet for DNS resolution (but app works offline after first load)
Why smart port detection?
App checks if port 5000 is busy, and if so, checks if it's our app via /health endpoint:
- Single instance: If already running, just opens browser to existing instance
- Fallback: Tries ports 5001-5009 if 5000 is taken by another app
- Tradeoff: Slightly slower startup (~100ms for port check)
Failure Modes #
Session expiration
- Problem: Telegram sessions expire after inactivity or if user logs out from another device
- Mitigation: Clear error message prompting re-authentication; delete stale
.sessionfile - UX: "Session expired" toast with one-click re-login
Rate limiting
- Problem: Telegram limits API calls; bulk operations could trigger flood wait
- Mitigation: Telethon handles this automatically with exponential backoff
- Fallback: Show "Please wait X seconds" message if rate limited
2FA accounts
- Problem: Users with two-factor authentication need password after code verification
- Mitigation: Detect
SessionPasswordNeededError, prompt for 2FA password - UX: Flow is code first, then password if needed
Large chat lists
- Problem: Users with 1000+ chats experience slow load times
- Mitigation: Async iteration with
client.iter_dialogs(), streaming to frontend - Future: Add pagination and lazy loading
Results & Metrics #
- Chats cleaned: 200+ spam groups/channels deleted in first use
- Time saved: 2 hours of manual deletion → 5 minutes with bulk select
- Executable size: ~20MB (Windows), ~15MB (Linux/Mac)
- Startup time: <2 seconds to web UI ready
- Memory usage: ~50MB idle, ~100MB during large exports
- Platforms tested: Windows 10/11, Ubuntu 22.04, macOS Ventura
Lessons Learned #
What I'd do differently
- Start with FastAPI from day one — wasted time debugging Flask async issues before switching
- Add unit tests early — manual testing worked but doesn't scale; should have pytest from start
- Use a proper template engine — embedding HTML as string works but is painful to maintain
- Implement pagination upfront — assumed small chat lists, but power users have thousands
What worked well
- Telethon library — solid, well-documented, handles all MTProto complexity
- Single-file distribution — PyInstaller + embedded templates = truly portable
- Smart instance detection — prevents "port already in use" frustration
- Dark theme with JetBrains Mono — users liked the aesthetic
- Local-only architecture — no privacy concerns, no GDPR headaches