Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (Brite)
  • No Skin
Collapse
A microphone in front of an orange-yellow circle. Graphic.

Podcasting Chat Community

  1. Home
  2. World
  3. I Built My Own iMessage Wrapped (And So Can You)
Podcasting.Chat Banner

I Built My Own iMessage Wrapped (And So Can You)

Scheduled Pinned Locked Moved World
appleimessagemessagesnerdystuffpython
1 Posts 1 Posters 0 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • Jake SpurlockW This user is from outside of this forum
    Jake SpurlockW This user is from outside of this forum
    Jake Spurlock
    wrote last edited by
    #1

    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,962

    Some 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:

    1. Dates are weird. Apple stores timestamps as nanoseconds since January 1, 2001 (because of course they do).
    2. Reactions are messages. When you tapback a ❤️, it’s stored as a separate message with associated_message_type set to a magic number (2000 = loved, 2001 = liked, etc.).
    3. 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

    1. Save the script as imessage-wrapped.py
    2. Grant Full Disk Access to your terminal (System Settings → Privacy & Security → Full Disk Access)
    3. Run it:
    python3 imessage-wrapped.py        # defaults to 2025
    python3 imessage-wrapped.py 2024   # or any year

    That’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
    1 Reply Last reply
    0

    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
    Reply
    • Reply as topic
    Log in to reply
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes



    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.





    Recent Posts


    • FediDeckF
      FediDeck

      Just launched the homepage for #FediDeck!

      Link Preview Image FediDeck

      A multi-column Fediverse app! Now in Beta! Come try it out and let us know what you think! It's free to use!

      favicon

      (fedideck.app)

      Share it and the app far and wide!

      I'm still looking for someone to Hunt It on Product Hunt!

      LMK

      read more

    • Renaud ChaputR
      Renaud Chaput

      @jon It's on the list of things we want to improve but is very complex. We have some ideas that we will try out and see if it improve things for real. As anything displayed to a user needs to be stored in the database, this can quickly be a lot of data, but we can often refetch it if needed

      @Mastodon @imanijoy

      read more

    • wakest ⁂L
      wakest ⁂

      @bloguers_net what do you mean I can see it just fine how is it limited

      read more

    • Ken YeungT
      Ken Yeung

      OpenAI, Google, and Perplexity near approval to host AI directly for the U.S. government (exclusive)
      https://www.fastcompany.com/91494829/openai-google-perplexity-hosting-ai-us-government-exclusive?utm_source=flipboard&utm_medium=activitypub

      Posted into The AI Economy @the-ai-economy-thekenyeung

      read more

    Hosted On NodeBB.org -- A Goldstein Media Project
    • Login

    • Don't have an account? Register

    • Login or register to search.
    • First post
      Last post
    0
    • Categories
    • Recent
    • Tags
    • Popular
    • World
    • Users
    • Groups