From 22d7a7270b2eb78e2dce1f9f7dd61d5108865a18 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 12:28:21 +0000 Subject: [PATCH 01/16] Initial plan From 8299e9bf5dbe83c0bf75f75ad0b6b33e45a4b53c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 12:33:53 +0000 Subject: [PATCH 02/16] Fix sidebar visibility to show with single chat Co-authored-by: empirea9 <85097414+empirea9@users.noreply.github.com> --- src/App.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/App.js b/src/App.js index e07424b..2a30f12 100644 --- a/src/App.js +++ b/src/App.js @@ -552,13 +552,13 @@ function App() { return name.substring(0, 2).toUpperCase(); }; - // Check if we have multiple chats loaded - const hasMultipleChats = chats.length > 1; + // Check if we have any chats loaded + const hasChats = chats.length > 0; return (
- {/* Chat List Sidebar (only show if multiple chats) */} - {hasMultipleChats && ( + {/* Chat List Sidebar (only show if any chats) */} + {hasChats && (
@@ -614,12 +614,12 @@ function App() { size={24} style={{cursor:'pointer'}} onClick={() => { - if (hasMultipleChats) { - // In multi-chat view, just deselect + if (hasChats) { + // In chat view with sidebar, just deselect setSelectedChatId(null); setMessages([]); } else { - // In single chat view, go back to setup + // This shouldn't happen, but go back to setup if needed setFileLoaded(false); setChats([]); } From f2a3c35da968e9843f7b7733ca358859e5623101 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 12:36:03 +0000 Subject: [PATCH 03/16] Improve comment clarity for back button logic Co-authored-by: empirea9 <85097414+empirea9@users.noreply.github.com> --- src/App.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/App.js b/src/App.js index 2a30f12..5a55a5e 100644 --- a/src/App.js +++ b/src/App.js @@ -615,11 +615,11 @@ function App() { style={{cursor:'pointer'}} onClick={() => { if (hasChats) { - // In chat view with sidebar, just deselect + // In chat view with sidebar, go back to chat list setSelectedChatId(null); setMessages([]); } else { - // This shouldn't happen, but go back to setup if needed + // Fallback: go back to setup (shouldn't normally happen) setFileLoaded(false); setChats([]); } From fd84a2f29c4776e3beae16fdbcff9f8ab019c526 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 13:02:39 +0000 Subject: [PATCH 04/16] Fix: Handle inbox folder uploads correctly to detect all chats Co-authored-by: empirea9 <85097414+empirea9@users.noreply.github.com> --- src/App.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/App.js b/src/App.js index 5a55a5e..0974300 100644 --- a/src/App.js +++ b/src/App.js @@ -55,12 +55,14 @@ function App() { // Helper function to strip Instagram path prefix const stripInstagramPrefix = (path) => { if (!path) return path; - // Remove "your_instagram_activity/messages/inbox/" prefix + // Remove "your_instagram_activity/messages/inbox/" prefix or just "inbox/" prefix const prefixes = [ 'your_instagram_activity/messages/inbox/', 'your_instagram_activity\\messages\\inbox\\', '../your_instagram_activity/messages/inbox/', - '../your_instagram_activity\\messages\\inbox\\' + '../your_instagram_activity\\messages\\inbox\\', + 'inbox/', + 'inbox\\' ]; let cleanPath = path; From f4cbe0a1268f5b9712e9e3ea4d450d16f13dcfdf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 13:34:30 +0000 Subject: [PATCH 05/16] Implement auto-username, reactions, settings modal, and Instagram link embeds Co-authored-by: empirea9 <85097414+empirea9@users.noreply.github.com> --- src/App.css | 261 ++++++++++++++++++++++++++++++++++++++++- src/App.js | 280 +++++++++++++++++++++++++++++++++++++++++--- src/utils/parser.js | 16 ++- 3 files changed, 537 insertions(+), 20 deletions(-) diff --git a/src/App.css b/src/App.css index 619467a..3c9ccbc 100644 --- a/src/App.css +++ b/src/App.css @@ -566,4 +566,263 @@ body, html { color: white; border: none; padding: 5px; -} \ No newline at end of file + border-radius: 4px; +} + +/* Reaction Bubbles */ +.message-reactions { + display: flex; + gap: 4px; + margin-top: 4px; + flex-wrap: wrap; +} + +.reaction-bubble { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 12px; + padding: 2px 8px; + font-size: 14px; + display: inline-block; +} + +/* Instagram Link Embed */ +.instagram-embed { + margin: 8px 0; +} + +.instagram-link { + text-decoration: none; + color: inherit; + display: block; +} + +.instagram-thumbnail { + width: 200px; + height: 200px; + background: var(--ig-bg-secondary); + border-radius: 8px; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + transition: opacity 0.2s; +} + +.instagram-link:hover .instagram-thumbnail { + opacity: 0.8; +} + +.instagram-placeholder { + text-align: center; + color: var(--ig-text-secondary); +} + +.instagram-placeholder div { + margin-top: 8px; + font-size: 14px; +} + +/* Settings Modal */ +.settings-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.8); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.settings-content { + background: var(--ig-bg-primary); + border: 1px solid var(--ig-border); + border-radius: 12px; + width: 90%; + max-width: 600px; + max-height: 80vh; + overflow-y: auto; +} + +.settings-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + border-bottom: 1px solid var(--ig-border); +} + +.settings-header h2 { + margin: 0; + font-size: 24px; +} + +.settings-body { + padding: 20px; +} + +.setting-item { + margin-bottom: 24px; +} + +.setting-item label { + display: block; + margin-bottom: 8px; + font-weight: 600; + font-size: 14px; + color: var(--ig-text-primary); +} + +.setting-item select, +.setting-item input { + width: 100%; + padding: 10px; + background: var(--ig-bg-secondary); + color: var(--ig-text-primary); + border: 1px solid var(--ig-border); + border-radius: 8px; + font-size: 14px; +} + +.theme-toggle { + display: flex; + gap: 8px; +} + +.theme-toggle button { + flex: 1; + padding: 10px; + background: var(--ig-bg-secondary); + color: var(--ig-text-primary); + border: 1px solid var(--ig-border); + border-radius: 8px; + cursor: pointer; + font-size: 14px; + transition: all 0.2s; +} + +.theme-toggle button.active { + background: var(--ig-bubble-mine); + border-color: var(--ig-bubble-mine); +} + +.theme-toggle button:hover { + background: var(--ig-bg-secondary); + opacity: 0.8; +} + +.search-container { + display: flex; + gap: 8px; +} + +.search-container input { + flex: 1; +} + +.search-container button { + padding: 10px 16px; + background: var(--ig-bubble-mine); + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 14px; + display: flex; + align-items: center; + gap: 6px; + transition: opacity 0.2s; +} + +.search-container button:hover { + opacity: 0.8; +} + +.search-results { + margin-top: 16px; + max-height: 300px; + overflow-y: auto; + border: 1px solid var(--ig-border); + border-radius: 8px; +} + +.search-result-chat { + border-bottom: 1px solid var(--ig-border); + padding: 12px; +} + +.search-result-chat:last-child { + border-bottom: none; +} + +.search-result-header { + cursor: pointer; + padding: 8px; + border-radius: 6px; + margin-bottom: 8px; + transition: background 0.2s; +} + +.search-result-header:hover { + background: var(--ig-bg-secondary); +} + +.search-result-messages { + padding-left: 12px; +} + +.search-result-message { + padding: 6px; + font-size: 13px; + color: var(--ig-text-secondary); + border-left: 2px solid var(--ig-border); + margin: 4px 0; + padding-left: 8px; +} + +.search-result-message .sender { + color: var(--ig-bubble-mine); + font-weight: 600; +} + +.search-result-more { + padding: 6px 8px; + font-size: 12px; + color: var(--ig-text-secondary); + font-style: italic; +} + +.upload-new-btn { + width: 100%; + padding: 12px; + background: var(--ig-bubble-mine); + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 14px; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + transition: opacity 0.2s; +} + +.upload-new-btn:hover { + opacity: 0.8; +} + +/* Light Theme */ +body.light-theme { + --ig-bg-primary: #FFFFFF; + --ig-bg-secondary: #F0F0F0; + --ig-text-primary: #000000; + --ig-text-secondary: #737373; + --ig-bubble-mine: #3797F0; + --ig-bubble-theirs: #EFEFEF; + --ig-border: #DBDBDB; +} diff --git a/src/App.js b/src/App.js index 0974300..e6756da 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,5 @@ import React, { useState, useRef, useEffect } from 'react'; -import { ArrowLeft, Phone, Video, Info, Maximize2, X } from 'lucide-react'; +import { ArrowLeft, Phone, Video, Info, Maximize2, X, Settings, Search, Upload } from 'lucide-react'; import './App.css'; import { parseInstagramHTML } from './utils/parser'; @@ -23,6 +23,7 @@ function App() { const [myUsername, setMyUsername] = useState(''); const [fileLoaded, setFileLoaded] = useState(false); const [timezone, setTimezone] = useState('UTC'); + const [theme, setTheme] = useState('dark'); // 'dark' or 'light' const [error, setError] = useState(''); const [mediaFiles, setMediaFiles] = useState({}); const [fullscreenMedia, setFullscreenMedia] = useState(null); @@ -30,6 +31,9 @@ function App() { const [allVideos, setAllVideos] = useState([]); const [isDragging, setIsDragging] = useState(false); const [folderName, setFolderName] = useState(''); + const [showSettings, setShowSettings] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); + const [searchResults, setSearchResults] = useState([]); const chatEndRef = useRef(null); const videoRefs = useRef({}); const fileInputRef = useRef(null); @@ -52,6 +56,15 @@ function App() { }; }, [mediaFiles]); + // Apply theme to body + useEffect(() => { + if (theme === 'light') { + document.body.classList.add('light-theme'); + } else { + document.body.classList.remove('light-theme'); + } + }, [theme]); + // Helper function to strip Instagram path prefix const stripInstagramPrefix = (path) => { if (!path) return path; @@ -75,6 +88,43 @@ function App() { return cleanPath; }; + // Auto-detect username by finding the most common sender name across all chats + const autoDetectUsername = (allChats) => { + const senderCounts = {}; + + // Count occurrences of each sender across all chats + allChats.forEach(chat => { + chat.messages.forEach(msg => { + if (msg.sender) { + senderCounts[msg.sender] = (senderCounts[msg.sender] || 0) + 1; + } + }); + }); + + // Find the sender who appears in the most chats (not just most messages) + const senderInChatCounts = {}; + allChats.forEach(chat => { + const uniqueSenders = new Set(chat.messages.map(m => m.sender).filter(Boolean)); + uniqueSenders.forEach(sender => { + senderInChatCounts[sender] = (senderInChatCounts[sender] || 0) + 1; + }); + }); + + // The username is likely the person who appears in ALL or most chats + let detectedUsername = ''; + let maxChatCount = 0; + + Object.keys(senderInChatCounts).forEach(sender => { + if (senderInChatCounts[sender] > maxChatCount) { + maxChatCount = senderInChatCounts[sender]; + detectedUsername = sender; + } + }); + + console.log('Auto-detected username:', detectedUsername, 'appears in', maxChatCount, 'chats'); + return detectedUsername; + }; + // Process entire inbox folder with multiple chat folders const processInboxFolder = (files) => { console.log('Processing inbox folder with', files.length, 'files'); @@ -124,6 +174,17 @@ function App() { if (validChats.length === 0) { setError('No valid chats found. Please check the folder structure.'); } else { + // Auto-detect username + const detectedUsername = autoDetectUsername(validChats); + setMyUsername(detectedUsername); + + // Re-process messages with detected username to correctly mark "isMine" + validChats.forEach(chat => { + chat.messages.forEach(msg => { + msg.isMine = msg.sender.toLowerCase().includes(detectedUsername.toLowerCase()); + }); + }); + setChats(validChats); setFileLoaded(true); setError(''); @@ -344,12 +405,39 @@ function App() { reader.onload = (event) => { try { const htmlContent = event.target.result; - const parsedMessages = parseInstagramHTML(htmlContent, myUsername, mediaFileMap); + // First parse with empty username to get all messages + const parsedMessages = parseInstagramHTML(htmlContent, '', mediaFileMap); if (parsedMessages.length === 0) { setError('No messages found in the file. Please check the file format.'); setFileLoaded(false); } else { + // Auto-detect username from single chat + const senderCounts = {}; + parsedMessages.forEach(msg => { + if (msg.sender) { + senderCounts[msg.sender] = (senderCounts[msg.sender] || 0) + 1; + } + }); + + // The username is likely the most frequent sender + let detectedUsername = ''; + let maxCount = 0; + Object.keys(senderCounts).forEach(sender => { + if (senderCounts[sender] > maxCount) { + maxCount = senderCounts[sender]; + detectedUsername = sender; + } + }); + + setMyUsername(detectedUsername); + console.log('Auto-detected username:', detectedUsername); + + // Re-mark messages with correct username + parsedMessages.forEach(msg => { + msg.isMine = msg.sender.toLowerCase().includes(detectedUsername.toLowerCase()); + }); + setMessages(parsedMessages); setFileLoaded(true); setError(''); @@ -465,15 +553,7 @@ function App() { >

Instagram Chat Viewer

- - setMyUsername(e.target.value)} - /> - - +
Messages - { - setFileLoaded(false); - setChats([]); - setSelectedChatId(null); - }} + onClick={() => setShowSettings(true)} />
@@ -663,7 +739,27 @@ function App() {
{msg.sender}
)}
- {msg.content &&
{msg.content}
} + {/* Check if message contains Instagram reel/post link */} + {(() => { + const instagramLinkMatch = msg.content?.match(/(https:\/\/www\.instagram\.com\/(reel|p)\/([a-zA-Z0-9_-]+))/); + if (instagramLinkMatch) { + const [fullUrl, , type, code] = instagramLinkMatch; + // For reels/posts, show thumbnail embed + return ( + + ); + } + return msg.content &&
{msg.content}
; + })()} {msg.media && msg.media.length > 0 && (
{msg.media.map((media, mediaIdx) => { @@ -758,6 +854,13 @@ function App() {
{formatTime(msg.timestamp)}
+ {msg.reactions && msg.reactions.length > 0 && ( +
+ {msg.reactions.map((reaction, rIdx) => ( + {reaction} + ))} +
+ )}
); @@ -835,6 +938,147 @@ function App() { )}
)} + + {/* Settings Modal */} + {showSettings && ( +
setShowSettings(false)}> +
e.stopPropagation()}> +
+

Settings

+ setShowSettings(false)} /> +
+ +
+ {/* Timezone Setting */} +
+ + +
+ + {/* Theme Setting */} +
+ +
+ + +
+
+ + {/* Search Function */} +
+ +
+ setSearchQuery(e.target.value)} + /> + +
+ + {/* Search Results */} + {searchResults.length > 0 && ( +
+ {searchResults.map((result, idx) => ( +
+
{ + selectChat(result.chatId); + setShowSettings(false); + }} + > + {result.chatName} - {result.count} result{result.count > 1 ? 's' : ''} +
+
+ {result.messages.slice(0, 5).map((msg, mIdx) => ( +
+ {msg.sender}: {msg.content.substring(0, 100)}... +
+ ))} + {result.messages.length > 5 && ( +
+{result.messages.length - 5} more
+ )} +
+
+ ))} +
+ )} +
+ + {/* Upload New Inbox */} +
+ +
+
+
+
+ )}
); } diff --git a/src/utils/parser.js b/src/utils/parser.js index 63f089b..ff30c20 100644 --- a/src/utils/parser.js +++ b/src/utils/parser.js @@ -352,13 +352,27 @@ export const parseInstagramHTML = (htmlString, myUsername, mediaFiles = {}, isBo } }); + // Extract reactions from the message node + const reactions = []; + // Try to find reactions - they might be in various formats + // Common class patterns: _2lrz, _2ls0, reaction, emoji + const reactionElements = node.querySelectorAll('[class*="reaction"], [class*="_2lrz"], [class*="emoji"]'); + reactionElements.forEach(elem => { + const reactionText = (elem.innerText || elem.textContent || '').trim(); + // Only keep if it looks like an emoji (1-4 characters, contains emoji-like chars) + if (reactionText.length > 0 && reactionText.length <= 4) { + reactions.push(reactionText); + } + }); + const message = { sender: sender, content: content, timestamp: date || new Date(0), // Use epoch time for sorting if no valid date originalTime: timestampStr, isMine: sender.toLowerCase().includes(myUsername.toLowerCase()), - media: mediaItems.length > 0 ? mediaItems : null + media: mediaItems.length > 0 ? mediaItems : null, + reactions: reactions.length > 0 ? reactions : null }; if (messages.length < 3) { From 530241a1470f7624c1e35b6a9104f53efd7afe25 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 13:38:54 +0000 Subject: [PATCH 06/16] Add error handling for media loading failures Co-authored-by: empirea9 <85097414+empirea9@users.noreply.github.com> --- src/App.js | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/App.js b/src/App.js index e6756da..87af0d2 100644 --- a/src/App.js +++ b/src/App.js @@ -768,7 +768,7 @@ function App() { videoRefs.current[videoKey] = React.createRef(); } - if (media.type === 'image') { + if (media.type === 'image' && media.url) { return (
{media.alt openFullscreen(media, idx, mediaIdx)} + onError={(e) => { + e.target.style.display = 'none'; + console.error('Failed to load image:', media.url); + }} />
); - } else if (media.type === 'video') { + } else if (media.type === 'video' && media.url) { return (
))}
@@ -929,7 +946,12 @@ function App() { className="sidebar-media-item" onClick={() => openFullscreen({ type: 'video', url: vid.url }, -1, idx)} > -
))} From f47239e32c1cc9c8e96e88295006fe58886bcfa4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 13:42:35 +0000 Subject: [PATCH 07/16] Fix username matching to use exact equality and improve error handling Co-authored-by: empirea9 <85097414+empirea9@users.noreply.github.com> --- src/App.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/App.js b/src/App.js index 87af0d2..58f9cbd 100644 --- a/src/App.js +++ b/src/App.js @@ -181,7 +181,7 @@ function App() { // Re-process messages with detected username to correctly mark "isMine" validChats.forEach(chat => { chat.messages.forEach(msg => { - msg.isMine = msg.sender.toLowerCase().includes(detectedUsername.toLowerCase()); + msg.isMine = msg.sender.toLowerCase() === detectedUsername.toLowerCase(); }); }); @@ -435,7 +435,7 @@ function App() { // Re-mark messages with correct username parsedMessages.forEach(msg => { - msg.isMine = msg.sender.toLowerCase().includes(detectedUsername.toLowerCase()); + msg.isMine = msg.sender.toLowerCase() === detectedUsername.toLowerCase(); }); setMessages(parsedMessages); @@ -777,8 +777,8 @@ function App() { className="media-image" onClick={() => openFullscreen(media, idx, mediaIdx)} onError={(e) => { - e.target.style.display = 'none'; console.error('Failed to load image:', media.url); + e.target.alt = 'Image failed to load'; }} />