I've been running a Discord server for a while and always wondered: "when is my community actually online?"
Discord doesn't show you this. You can see who's online right now, but there's no way to see patterns over time, no weekly view, no hourly breakdown, nothing.
So I built Heatmap Bot.
What it does
Heatmap Bot tracks your server's activity and turns it into a heatmap, a 7×24 grid showing activity by day and hour for the past week. It also supports:
- Daily bar chart view
- Monthly trend chart
- Channel breakdown
- Member leaderboard
- Week-over-week comparison
- Weekly auto-reports
- Peak activity alerts
- CSV data export
Tech stack
- Python 3.13 with discord.py 2.x
- SQLite with aiosqlite for async DB access
- matplotlib + numpy for heatmap rendering
- aiohttp for the webhook server
- Railway for hosting
- Ko-fi for payments (manual grant flow)
The interesting part, rendering heatmaps
The heatmap is a 7×24 numpy array. Each cell is the count of events at that day/hour combination, converted from UTC to the server's local timezone.
pythondef aggregate_to_heatmap_grid(events, timezone, days):
grid = np.zeros((7, 24), dtype=float)
tz = pytz.timezone(timezone)
for event in events:
ts = event.get("timestamp", 0)
dt = datetime.fromtimestamp(ts, tz=tz)
grid[dt.weekday()][dt.hour] += 1
return grid
The rendering uses matplotlib with a custom blurple→pink gradient to match Discord's aesthetic.
The hard part - Railway + Discord rate limits
The trickiest issue was Discord's Cloudflare rate limiting. Too many reconnects from the same IP gets you a 1015 error, temporarily banned. The fix was switching Railway regions to get a new IP.
If you're deploying a Discord bot, make sure your restart policy doesn't cause rapid reconnect loops.
Free vs paid tier
- Free: 7 days history, messages only, watermark
- Paid ($4.99/month): 90 days, all event types, no watermark, leaderboard, CSV export
Try it
Add Heatmap Bot to your server: https://discord.com/oauth2/authorize?client_id=1482225606761386085&permissions=2147601472&integration_type=0&scope=bot+applications.commands
Ko-fi: https://ko-fi.com/semtione
Happy to answer questions about the implementation in the comments!
Top comments (0)