Dev.to WebDev 🛠 Dev 👁 0 📖 6 min read

My Instagram tool's traffic was 82% Indonesian — so I taught it WhatsApp

The Search Console export that rewrote my roadmap Stats Insta is a small tool I built for myself and then put online: upload an Instagram chat export, get statistics about your conversations — who texts most, when you'

The Search Console export that rewrote my roadmap

Stats Insta is a small tool I built for myself and then put online: upload an Instagram chat export, get statistics about your conversations — who texts most, when you're active, your top emojis, Wrapped-style story cards you can share. Everything runs in the browser; nothing is uploaded anywhere.

A few weeks ago I finally did the boring thing and exported sixteen months of Google Search Console data to actually look at it. Around 6,500 clicks. And 82% of them from Indonesia.

It gets stranger. Most of my top search queries weren't keywords at all — they were people typing my old Vercel domain name into Google to find their way back. Traffic had peaked back in December 2025 (Wrapped season, of course) and was slowly sliding. In other words: the tool had found an audience almost by accident, the audience was Indonesian, and it was loyal enough to Google a URL.

Here's the part that stung: Indonesia is a WhatsApp-first country. For most of my actual users, WhatsApp is not "an app" — it's the default layer for talking to everyone. And my tool supported exactly zero WhatsApp. Every one of those users was analyzing the chats they have on their secondary app, because the app they actually live in wasn't supported at all.

So the roadmap rewrote itself. Before any new features: teach the tool WhatsApp.

Two parsers, two completely different worlds

Instagram exports are a gift. You request your data, wait, and get message_1.json — real structured JSON with sender_name, timestamp_ms, a reactions array, the works. Parsing it is an afternoon.

A WhatsApp export is a .txt file that looks like a chat transcript, because it is one:

14/08/2025, 21:03 - Messages are end-to-end encrypted
14/08/2025, 21:04 - Priya: ok so about tomorrow
[14/08/2025, 21:04:37] Arjun: wait, which tomorrow?

That first line is Android format, the third is iOS. Neither is a standard — the comma is optional, seconds come and go, and the clock is 12-hour in some regions and 24-hour in others. The parser ended up starting with two regexes just to recognize a line:

const IOS_LINE =
  /^\[(\d{1,2})[./](\d{1,2})[./](\d{2,4}),?\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\s*([AaPp]\.?[Mm]\.?)?\]\s?(.*)$/;
const ANDROID_LINE =
  /^(\d{1,2})[./](\d{1,2})[./](\d{2,4}),?\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\s*([AaPp]\.?[Mm]\.?)?\s*[-\u2010-\u2015]\s+(.*)$/;

Then the real problems start:

Half the lines aren't messages. The encryption notice, "Priya added Arjun", subject changes, "security code changed", missed calls — all of it sits in the file wearing a timestamp, and all of it has to be excluded or your statistics count WhatsApp itself as a very talkative participant.

Some system lines disguise themselves as messages. Alice changed the subject to "Trip: Friday plans" contains a ": " — so a naive split happily parses it as a message from a person named "Alice changed the subject to "Trip". I ended up treating a "sender" as fake if it's implausibly long or contains a system verb before the colon.

Multi-line messages have no prefix at all. If someone sends a message with line breaks, the continuation lines arrive naked. The rule is simple — a line with no timestamp belongs to the previous message — but you only discover that after your message count doesn't match the chat you exported by hand.

The file is full of invisible characters. A BOM at the start, narrow no-break spaces before AM/PM, left-to-right marks wrapped around lines. Normalize them away first, or your regexes fail on files that look identical to the ones that work.

And the worst one: nobody tells you the date order. Is 03/04/2025 the 3rd of April or March 4th? WhatsApp doesn't label it, and guessing wrong swaps days and months across the entire history. The parser scans the whole file before parsing anything:

let dayFirst = true;
let sawMonthFirstEvidence = false;
for (const line of lines) {
  const raw = matchTimestampLine(line);
  if (!raw) continue;
  if (raw.first > 12) { dayFirst = true; break; } // 13+ can't be a month
  if (raw.second > 12) sawMonthFirstEvidence = true;
}
if (sawMonthFirstEvidence) dayFirst = false;

If any date's first number is above 12, it must be the day. If only the second number ever goes above 12, the file is month-first. If the file never gives unambiguous evidence — a short chat from the first twelve days of a month — it defaults to day-first, which matches the export locales of most of my users: Indonesia, India, Europe, Latin America. It's a heuristic, and I wrote it down as one in the code comments, because pretending it's a guarantee would be lying.

One stats engine, two front doors

The best decision in the whole project was refusing to build a second analyzer. The WhatsApp parser's only job is to normalize a messy text file into the exact same shape the Instagram path already produces:

{ [sender_name: string]: Message[] }

Because the model is identical, every existing statistic, chart and story card — totals, distribution, the activity heatmap, night owls, conversation starters — worked on WhatsApp chats the moment the parser did. Zero changes downstream.

The only additions were three optional flags on Message, and they turned out to be where the fun was:

  • isDeleted — "This message was deleted" arrives under the sender's name, so instead of discarding it, the parser keeps it as a flagged, content-less message. It counts toward that person's totals but is invisible to text statistics. That's what powers a Deleted Messages leaderboard — who in your group chat deletes the most.
  • isMedia — <Media omitted>, <attached: IMG-2041.jpg>, IMG-2041.jpg (file attached) (captions kept, when there are any). Powers Media Shared counts.
  • isEdited — WhatsApp appends <This message was edited> to edited messages, sometimes on its own line. The parser strips the marker and flags the message.

From those flags plus the plain text fell out a set of stats that make no sense for Instagram but are pure WhatsApp: Top Link Sharers, Questions Asked (who asks the most questions), Biggest Double-Texter, plus Signature Emoji, Fastest Responder (average reply gaps) and Morning Person. One graceful accident: WhatsApp exports carry no reaction data, so reaction-based stats simply find nothing — exactly what they already did for an Instagram chat where nobody reacted. Nothing needed special-casing.

What shipped

The tool now takes Instagram JSON and WhatsApp .txt/ZIP exports through the same upload box, tells them apart on its own, and runs the same engine on both. There's a dedicated WhatsApp page, WhatsApp-specific cards, a downloadable report, and Wrapped story cards that now say "WhatsApp" when they're about a WhatsApp chat (a detail I got wrong in the first version, and a user caught within hours — platform support means the copy, not just the parser).

Is it too early for a victory lap? Completely. The honest result so far is: the tool finally supports the app its audience actually uses. Whether the December slide reverses is a question the next Search Console export will answer, and I've learned my lesson about reading those more often.

If you want to try it — everything runs locally in your browser, your chats never leave your device:

And if you've ever parsed WhatsApp exports yourself and fought the date-order problem differently, I genuinely want to hear how. The comments are open.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.