Get the receipts out of Instagram and into analyse.love — the tea doesn't spill itself ☕
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
- Go to Instagram.com (on a desktop browser)
- Log in to your account (we promise we can't see your password)
- Navigate to your Direct Messages by clicking the paper airplane icon
- 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
- 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.
- 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.
- When you've reached the top and the message counter stops increasing, type this command in the console and press Enter:stopCaptureAndDownload()
- 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 analyserAnalyse 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