analyse.love?
Back to the analyser
Liberate Your Instagram DMs

Get the receipts out of Instagram and into analyse.love — the tea doesn't spill itself ☕

5-minute process
Works with any Instagram DM
100% private & secure

Hey mobile scrollers — this one's a desktop job.

Instagram's web version on a desktop is required to run our export script. Bookmark this page and come back when you're at a real keyboard. (Trust us, the analysis is worth the device-switch drama 💅)

Why Bother Exporting Your DMs?

Because your DMs are a goldmine of evidence, and you deserve the verdict

The late-night texts, the inside jokes, even the "we need to talk" moments — they all form patterns that reveal what's really going on. analyse.love reads those patterns so you can:

  • See who's actually putting in the effort (and who's just sending "wyd" at 2am)
  • Catch the emotional patterns you've been scrolling past (hello, red flags 🚩)
  • Find out whether your communication styles match — or clash spectacularly
  • Track the arc from awkward first messages to whatever this is now

Instagram doesn't offer an official export, but our method lets you grab the conversation for analysing. It's a relationship detective that fits in a browser tab.

The Export Method (No Extension, No Excuses)

Four steps, your browser's developer console, and a little patient scrolling. You've got this.

1Open Your Instagram DMs

  1. Go to Instagram.com (on a desktop browser)
  2. Log in to your account (we promise we can't see your password)
  3. Navigate to your Direct Messages by clicking the paper airplane icon
  4. Open the conversation you want to analyse (pick a juicy one!)

Make sure you've opened the specific conversation you want to analyse

2Open Browser Developer Console

Chrome / Edge

Press:

Ctrl+Shift+J

(Mac: Cmd+Option+J)

Firefox

Press:

Ctrl+Shift+K

(Mac: Cmd+Option+K)

Safari

First enable Developer Tools:

Safari menu > Preferences > Advanced > "Show Develop menu"

Then press:

Cmd+Option+C

Don't panic if this looks techy — you're just opening a window to paste our script into. You don't need to understand the code. That's the script's job.

3Copy and Paste the Export Script

Important Note

Instagram's interface changes often. This script tries its best but might not perfectly capture all details like sender/time every time. Focus on getting the message text.

(() => {
  const capturedMessages = new Map(); // Use a Map to store messages, key can be a unique ID
  let messageIdCounter = 0; // To ensure unique IDs for sorting by capture order

  const updateConsoleCounter = () => {
    console.log("🟢 Captured messages: ", capturedMessages.size);
  };

  // Function to attempt parsing a Date from various timestamp formats
  const parseTimestampToDate = (timestampText) => {
    if (!timestampText || typeof timestampText !== 'string') return null;
    // Try direct parsing (e.g., "May 24 at 3:14 AM")
    let date = new Date(timestampText);
    if (!isNaN(date.getTime())) return date;

    // Add more specific regexes or parsing logic for formats like "1h", "2d", "Yesterday" if needed
    // For now, this is a basic attempt.
    return null;
  };
  
  const getMessageSender = (messageElement) => {
    // Strategy 1: Look for an image with alt text ending in "'s profile picture"
    // and traverse up to find a common ancestor, then look for a sibling with the name.
    // This is complex. Let's try simpler first.
    
    // Strategy 2: Look for a strong tag or specific span near the message text,
    // but not *within* the main text bubble itself.
    let currentElement = messageElement;
    for(let i=0; i<3; i++) { // Check current element and 2 parents
        if (!currentElement) break;
        // Look for a sibling that might be the sender name (often a div or span before the message bubble)
        const potentialSenderSibling = currentElement.previousElementSibling;
        if (potentialSenderSibling) {
            const strongSender = potentialSenderSibling.querySelector('strong');
            if (strongSender && strongSender.innerText.trim().length > 0 && strongSender.innerText.trim().length < 30) {
                return strongSender.innerText.trim();
            }
            const spanSender = potentialSenderSibling.querySelector('span[dir="auto"]');
             if (spanSender && spanSender.innerText.trim().length > 0 && spanSender.innerText.trim().length < 30 && !(/\d/.test(spanSender.innerText))) { // not a time
                return spanSender.innerText.trim();
            }
        }
        // Look within the message row for a specific sender element (less common for IG DMs)
        const senderEl = currentElement.querySelector('h3, strong'); // Example selectors
        if (senderEl && senderEl.innerText.trim().length > 0 && senderEl.innerText.trim().length < 30) {
            return senderEl.innerText.trim();
        }
        currentElement = currentElement.parentElement;
    }
    return null; // Default if not found
  };

  const getMessageTimestampText = (messageElement) => {
    // Look for a <time> element or a span that looks like a timestamp
    let currentElement = messageElement;
     for(let i=0; i<3; i++) { // Check current element and 2 parents
        if (!currentElement) break;
        const timeEl = currentElement.querySelector('time[datetime]');
        if (timeEl && timeEl.getAttribute('datetime')) {
            return new Date(timeEl.getAttribute('datetime')).toLocaleString();
        }
        // Look for spans that might contain relative time like "1h", "2d", or absolute time
        const timeSpan = Array.from(currentElement.querySelectorAll('span')).find(
            s => s.innerText.trim().length > 0 && 
                 s.innerText.trim().length < 25 && // Reasonable length for a timestamp
                 (/\d/.test(s.innerText) || /^(Seen|Sent|Delivered|Today|Yesterday)/i.test(s.innerText.trim())) &&
                 !s.closest('div[role="button"]') // Not part of a button
        );
        if (timeSpan) return timeSpan.innerText.trim();
        currentElement = currentElement.parentElement;
    }
    return null;
  };

  const getMessageText = (messageElement) => {
    // Try to find the most specific text container
    // Common pattern: text is in spans within a div with dir="auto"
    const textContainer = messageElement.querySelector('div[dir="auto"]');
    let text = '';
    if (textContainer) {
        // Concatenate all span texts within this container
        const spans = textContainer.querySelectorAll('span');
        if (spans.length > 0) {
            spans.forEach(span => { text += span.innerText + ' '; });
            text = text.trim();
        } else {
            text = textContainer.innerText.trim(); // Fallback to container's text
        }
    } else {
        // Fallback: try to get text from any span directly within the messageElement
        const directSpans = messageElement.querySelectorAll(':scope > span, :scope > div > span');
        if (directSpans.length > 0) {
             directSpans.forEach(span => { text += span.innerText + ' '; });
             text = text.trim();
        } else {
            text = messageElement.innerText.trim(); // Broadest fallback
        }
    }
    
    // Clean up common unwanted text
    text = text.replace(/\s*Edited$/i, '').trim();
    text = text.replace(/^Loading\.\.\.$/i, '').trim();
    text = text.replace(/^You sent$/i, '').trim();
    // Remove potential sender/timestamp if they got mixed in (heuristic)
    // This is tricky; for now, we rely on specific selectors above.
    
    if (text.length === 0 || text.length > 2000) return null; // Filter out empty or overly long (likely not a message)
    // Filter out very short, common UI phrases if they are the *only* text
    const uiPhrases = ['seen', 'sent', 'delivered', 'liked', 'photo', 'video', 'sticker', 'gif', 'voice message'];
    if (text.length < 20 && uiPhrases.some(p => text.toLowerCase().includes(p))) {
        // Check if it's ONLY a UI phrase
        let isOnlyUiPhrase = true;
        for(const p of uiPhrases) {
            if (text.toLowerCase() === p) break;
            if (text.toLowerCase().startsWith(p) && text.toLowerCase().length < p.length + 5) break;
        }
        if (isOnlyUiPhrase) return null;
    }
    if (/replied to you$/i.test(text) && text.length < 50) return null;


    return text;
  };


  const parseMessageElement = (el) => {
    const messageText = getMessageText(el);
    if (!messageText) return null;

    const sender = getMessageSender(el) || 'Unknown Sender';
    const timestampText = getMessageTimestampText(el) || 'Unknown Time';
    const timestampDate = parseTimestampToDate(timestampText);


    const id = `msg-${messageIdCounter++}`;
    return { id, timestampText, timestampDate, sender, text: messageText, originalOrder: capturedMessages.size };
  };

  const findAndProcessMessages = () => {
    // These selectors should target the individual message "bubble" or its immediate container
    const messageSelectors = [
        'div[role="listitem"] div[role="group"] > div > div > div[style*="max-width"]', // Often the bubble
        'div[role="row"] > div > div > div[style*="max-width"]', // Similar structure
        'div[data-testid="message-container"]',
        // Add more specific selectors if known, targeting the innermost message content wrapper
    ];
    
    let newMessagesFound = 0;
    document.querySelectorAll(messageSelectors.join(', ')).forEach(el => {
      const parsedMsg = parseMessageElement(el);
      if (parsedMsg) {
        // Use a combination of text and originalOrder for a more robust unique key
        const uniqueKey = `${parsedMsg.text.slice(0, 30)}-${parsedMsg.originalOrder}`;
        if (!capturedMessages.has(uniqueKey)) {
          capturedMessages.set(uniqueKey, parsedMsg);
          newMessagesFound++;
        }
      }
    });
    if (newMessagesFound > 0) updateConsoleCounter();
  };

  const observer = new MutationObserver(() => {
    try { findAndProcessMessages(); } 
    catch (err) { console.warn("⚠️ Error during message processing: ", err.message, err.stack); }
  });

  const findChatContainer = () => {
    const selectors = [ 'div[role="main"] div[class*="xh8yej3"]', 'main section > div:last-child', 'main'];
    for (const selector of selectors) {
      const container = document.querySelector(selector);
      if (container) return container;
    }
    return document.body;
  };

  const chatContainer = findChatContainer();
  observer.observe(chatContainer, { childList: true, subtree: true });

  console.log("👀 Script running! Scroll SLOWLY to the TOP of your conversation.");
  console.log("💡 When done, type stopCaptureAndDownload() in the console and press Enter.");
  findAndProcessMessages(); // Initial capture

  window.stopCaptureAndDownload = () => {
    console.log("🚀 Stopping capture and preparing download...");
    observer.disconnect();
    
    let messagesArray = Array.from(capturedMessages.values());
    
    // Sort by original capture order (which is reverse chronological as we scroll up)
    messagesArray.sort((a, b) => a.originalOrder - b.originalOrder);
    // Now reverse it to get chronological order (oldest first)
    const finalMessages = messagesArray.reverse();

    console.log("✅ Final message count:", finalMessages.length);
    if (finalMessages.length === 0) {
        console.warn("No messages captured. Cannot download an empty file.");
        alert("No messages were captured. Please try scrolling more or check console for errors.");
        return;
    }
    
    const header = [
      "Instagram Chat Export - analyse.love",
      "Exported on: " + new Date().toLocaleString(),
      "Total messages: " + finalMessages.length,
      "-------------------------------------------",
      "NOTE: Sender/Timestamp extraction is best-effort. Please verify.",
      "Messages are ordered from oldest to newest.",
      "-------------------------------------------",
      ""
    ].join("\n");
    
    const formattedMessages = finalMessages.map(msg => {
      return `[${msg.timestampText || 'Time N/A'}] ${msg.sender || 'Sender N/A'}: ${msg.text}`;
    }).join('\n\n'); // Double newline between messages
    
    const content = header + formattedMessages;
    console.log("📄 Preparing content for download. Approx size:", content.length);
    
    try {
      const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
      console.log("📦 Blob created, size:", blob.size);
      const url = URL.createObjectURL(blob);
      console.log("🔗 Object URL created:", url);
      
      const a = document.createElement('a');
      a.style.display = 'none';
      
      let convoName = 'InstagramChat';
      try {
        // Try to get conversation name from header
        const nameEl = document.querySelector('header div[role="button"] span[dir="auto"], header h1');
        if (nameEl) convoName = nameEl.innerText.trim();
      } catch(e) { console.warn("Could not get conversation name."); }
      const safeConvoName = convoName.replace(/[^a-zA-Z0-9_\-]+/g, '_').substring(0, 50);
      
      a.download = `${safeConvoName}_${new Date().toISOString().slice(0,10)}.txt`;
      a.href = url;
      
      document.body.appendChild(a);
      console.log("🔗 Link appended. Attempting click...");
      a.click();
      console.log("🖱️ Click simulated.");
      
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
      
      console.log("💾 Download should have started! Check your browser downloads.");
      alert(`Download initiated for ${finalMessages.length} messages! Check your browser downloads.`);

    } catch (e) {
      console.error("❌ Error during download process: ", e);
      alert("An error occurred during the download process. Please check the console for details.");
    }
  };
})();

This script attempts to extract detailed message info. Its success depends on Instagram's current HTML structure.

4Run the Script and Scroll

  1. Paste the script into your browser console and press Enter.

    You might see some errors about "DTSG response" or other warnings - these are normal and won't affect message capture.

  2. Scroll MANUALLY and SLOWLY up through your conversation, all the way to the very beginning.

    This is important! Scroll slowly to give the script time to find and process each message. Watch the "🟢 Captured messages" counter in the console.

  3. When you've reached the top and the message counter stops increasing, type this command in the console and press Enter:
    stopCaptureAndDownload()
  4. Save the file that automatically downloads to your computer.

Pro Tip: If the formatting isn't perfect (sender/time missing), the core message text should still be there. Instagram's structure is tricky!

Troubleshooting

  • Download not starting: Check the console for errors after running stopCaptureAndDownload(). Ensure your browser isn't blocking pop-ups or automatic downloads from the current site.
  • Few messages captured: Scroll much slower. Try refreshing the page and starting over if needed.
  • Incorrect sender/timestamp: This is the hardest part. The script makes a best guess. The text content is the priority.
  • Jumbled order: Ensure you scroll consistently from bottom to top. The script relies on capture order then reverses it.

You've Got the File. Now What?

The receipts are in hand — time to turn them into relationship gold

Upload to analyse.love

Head back to the analyser, paste your conversation, and let Claude tell you what's really been going on in there.

Run it through the analyser

Analyse It Yourself

Feeling old-school? Open the text file and play detective. Who texts first? Who writes paragraphs while the other replies "haha"?

Keep the Receipts

Back up the conversations that matter. For reminiscing on the good days — and for citation purposes on the other ones.

Privacy Note

This method runs entirely in your browser. Your messages are captured locally and never sent to any server unless you explicitly choose to upload them to analyse.love for analysis. The download happens directly to your computer without any intermediate storage. We're big on privacy - your spicy DMs stay between you, your chat partner, and our AI (who's sworn to secrecy).

If you encounter any issues or have questions, feel free to reach out to us at contact@analyse.love