I Built My Own iMessage Wrapped (And So Can You)
-
I Built My Own iMessage Wrapped (And So Can You)
My daughter was joking that someone should build a Spotify Wrapped, but for iMessage — your year in emoji and reactions. My wife said my brother-in-law should build it since he works at Apple. (Every Apple problem is his fault in our house.)
From the other room, my daughter yelled: “I bet Dad could write this!”
Challenge accepted.
The Result
Thirty minutes later, I had a working script. Here’s my 2025:
🎁 iMessage Wrapped 2025 📱 Messages Total: 35,768 Sent: 12,191 Received: 23,577 💬 Reactions Given: 1,974 Received: 5,014 🏆 Your Reaction Style 👍 982 ❤️ 398 😂 275 ‼️ 126 ❓ 19 👎 16 📈 Messages by Month Jan ███████████████ 3,052 Feb █████████████ 2,621 Mar █████████████████ 3,422 Apr ██████████████████ 3,696 May ██████████████████ 3,640 Jun ████████████████████ 3,904 Jul █████████████ 2,561 Aug ████████████ 2,448 Sep ██████████████ 2,790 Oct █████████████████ 3,466 Nov ███████████ 2,206 Dec ██████████ 1,962Some things I learned about myself:
- I’m a listener. I receive almost 2x as many messages as I send.
- People love reacting to me. 5,014 reactions received vs 1,974 given.
- I’m a thumbs-up guy.
accounts for half my reactions. Apparently I’m very agreeable. - June was my chattiest month. December was quietest (holiday break mode).
How It Works
Your iMessage history lives in a SQLite database at
~/Library/Messages/chat.db. It’s just sitting there, queryable.The tricky bits:
- Dates are weird. Apple stores timestamps as nanoseconds since January 1, 2001 (because of course they do).
- Reactions are messages. When you tapback a
️, it’s stored as a separate message with associated_message_typeset to a magic number (2000 = loved, 2001 = liked, etc.). - Custom emoji reactions landed in iOS 17 and are stored in
associated_message_emoji.
Once you know the schema, it’s just SQL.
The Script
Here’s the full thing — about 100 lines of Python, no dependencies beyond the standard library:
#!/usr/bin/env python3 """ iMessage Wrapped — Your year in emoji and reactions Usage: python3 imessage-wrapped.py [year] Requires: Full Disk Access for Terminal """ import sqlite3 import os import sys from datetime import datetime from pathlib import Path YEAR = int(sys.argv[1]) if len(sys.argv) > 1 else 2025 DB_PATH = Path.home() / "Library/Messages/chat.db" APPLE_EPOCH_OFFSET = 978307200 TAPBACKS = { 2000: "❤️", # Loved 2001: "👍", # Liked 2002: "👎", # Disliked 2003: "😂", # Laughed 2004: "‼️", # Emphasized 2005: "❓", # Questioned } def get_db(): if not DB_PATH.exists(): print(f"❌ Database not found at {DB_PATH}") sys.exit(1) return sqlite3.connect(f"file:{DB_PATH}?mode=ro", uri=True) def date_filter(year): return f""" datetime(date/1000000000 + {APPLE_EPOCH_OFFSET}, 'unixepoch') >= '{year}-01-01' AND datetime(date/1000000000 + {APPLE_EPOCH_OFFSET}, 'unixepoch') < '{year + 1}-01-01' """ def main(): print(f"\n🎁 iMessage Wrapped {YEAR}\n") db = get_db() cur = db.cursor() # Message counts cur.execute(f""" SELECT COUNT(*), SUM(CASE WHEN is_from_me = 1 THEN 1 ELSE 0 END), SUM(CASE WHEN is_from_me = 0 THEN 1 ELSE 0 END) FROM message WHERE {date_filter(YEAR)} AND associated_message_type = 0 """) total, sent, received = cur.fetchone() print(f"📱 Messages: {total:,} ({sent:,} sent, {received:,} received)") # Reaction counts cur.execute(f""" SELECT SUM(CASE WHEN is_from_me = 1 THEN 1 ELSE 0 END), SUM(CASE WHEN is_from_me = 0 THEN 1 ELSE 0 END) FROM message WHERE {date_filter(YEAR)} AND associated_message_type >= 2000 """) given, got = cur.fetchone() print(f"💬 Reactions: {given + got:,} ({given:,} given, {got:,} received)") # Your tapback style print(f"\n🏆 Your Reaction Style") cur.execute(f""" SELECT associated_message_type, COUNT(*) FROM message WHERE {date_filter(YEAR)} AND associated_message_type BETWEEN 2000 AND 2005 AND is_from_me = 1 GROUP BY associated_message_type ORDER BY COUNT(*) DESC """) for type_id, cnt in cur.fetchall(): print(f" {TAPBACKS.get(type_id, '?')} {cnt:,}") # Custom emoji cur.execute(f""" SELECT associated_message_emoji, COUNT(*) FROM message WHERE {date_filter(YEAR)} AND associated_message_emoji IS NOT NULL AND is_from_me = 1 GROUP BY associated_message_emoji ORDER BY COUNT(*) DESC LIMIT 5 """) customs = cur.fetchall() if customs: print(f"\n🎯 Custom Reactions: {', '.join(f'{e} ({c})' for e, c in customs)}") # Monthly volume print(f"\n📈 By Month") cur.execute(f""" SELECT strftime('%m', datetime(date/1000000000 + {APPLE_EPOCH_OFFSET}, 'unixepoch')), COUNT(*) FROM message WHERE {date_filter(YEAR)} AND associated_message_type = 0 GROUP BY 1 ORDER BY 1 """) months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"] data = {row[0]: row[1] for row in cur.fetchall()} max_cnt = max(data.values()) if data else 1 for i, name in enumerate(months, 1): cnt = data.get(f"{i:02d}", 0) bar = "█" * int(20 * cnt / max_cnt) print(f" {name} {bar} {cnt:,}") db.close() if __name__ == "__main__": main()Running It
- Save the script as
imessage-wrapped.py - Grant Full Disk Access to your terminal (System Settings → Privacy & Security → Full Disk Access)
- Run it:
python3 imessage-wrapped.py # defaults to 2025 python3 imessage-wrapped.py 2024 # or any yearThat’s it. Your data never leaves your machine.
What I’d Add Next
If I turn this into a proper app:
- Shareable cards — export your stats as an image
- Conversation breakdown — who do you text the most?
- Time of day patterns — are you a morning texter or a midnight scroller?
- Streak tracking — longest daily conversation streak
But honestly? The script is fun enough. Sometimes a quick hack that makes your daughter laugh is the whole point.
The script and a slightly more polished version are on GitHub if you want to grab it.
#Apple #iMessage #Messages #NerdyStuff #Python
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login
Welcome To Podcasting.Chat!
This forum is for podcasters, podcast guests, and podcast enthusiasts alike to share tips, tricks, and their love of the medium.
This forum is fully federated, so you are able to contribute to any discussion here through your own software of choice (e.g. Mastodon, Misskey, Lemmy, Piefed, etc.). So you can sign up for an account here and it federates around the Fediverse. You can also follow feeds and topics from your other Fedi-enabled accounts.
️ New Episode Of Entrepreneur's Enigma! 