From 4b2ef8093c4227096ec6100963395c882134d56d Mon Sep 17 00:00:00 2001 From: wsuff Date: Tue, 11 Mar 2025 11:05:25 -0400 Subject: [PATCH 1/2] Create gemini.js JS Code to export Gemini Chat to JSON (HTML still) --- gemini.js | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 gemini.js diff --git a/gemini.js b/gemini.js new file mode 100644 index 0000000..05799be --- /dev/null +++ b/gemini.js @@ -0,0 +1,107 @@ +(function extractGeminiChat() { + function extractTextContent(element) { + if (!element) return ''; + return element.innerText.trim(); // Use innerText for consistency + } + + function extractHTMLContent(element) { + if (!element) return ''; + return element.innerHTML.trim(); // Use innerHTML for model responses + } + + function getNodePosition(element) { + let positions = []; + while (element) { + let position = 0; + let sibling = element; + while ((sibling = sibling.previousSibling)) { + position++; + } + positions.unshift(position); + element = element.parentNode; + } + return positions.join('.'); + } + + const conversationContainers = document.querySelectorAll( + 'chat-window-content .conversation-container' + ); + + let allMessages = []; + + conversationContainers.forEach((container) => { + const position = getNodePosition(container); + + const userQueryElement = container.querySelector( + 'user-query-content .query-text' + ); + const modelResponseElement = container.querySelector( + 'response-container .response-content' + ); + + if (userQueryElement) { + allMessages.push({ + role: 'user', + content: extractTextContent(userQueryElement), + position, + }); + } + + if (modelResponseElement) { + allMessages.push({ + role: 'assistant', + content: extractHTMLContent(modelResponseElement), + position, + }); + } + }); + + allMessages.sort((a, b) => { + const posA = a.position.split('.').map(Number); + const posB = b.position.split('.').map(Number); + + for (let i = 0; i < Math.min(posA.length, posB.length); i++) { + if (posA[i] !== posB[i]) { + return posA[i] - posB[i]; + } + } + return posA.length - posB.length; + }); + + allMessages.forEach((msg) => { + delete msg.position; + }); + + const chatData = { + title: document.title || 'Gemini Chat', + timestamp: new Date().toISOString(), + conversation: allMessages, + }; + + function downloadJSON(data, filename) { + const blob = new Blob([JSON.stringify(data, null, 2)], { + type: 'application/json', + }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + URL.revokeObjectURL(url); + document.body.removeChild(a); + } + + const filename = `gemini-chat-${new Date() + .toISOString() + .slice(0, 19) + .replace(/:/g, '-')}.json`; + downloadJSON(chatData, filename); + + console.log( + `Chat extracted with ${allMessages.length} messages. Downloading as ${filename}` + ); + + return `Successfully extracted ${allMessages.length} messages from the Gemini chat.`; +})(); From 41508aef136e1fb86840ec55f7d102cba9d74a1b Mon Sep 17 00:00:00 2001 From: wsuff Date: Tue, 11 Mar 2025 11:08:20 -0400 Subject: [PATCH 2/2] Update README.md Add Usage Details for Gemini.js --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index a288556..1acf5ff 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,35 @@ Contribs from: * DarkFlib * Wsuff * your name could be here... contribute some code + +## Gemini.js +**Key Adaptations** + +1. **Selectors:** + * `'chat-window-content .conversation-container'` to get each turn. + * `'user-query-content .query-text'` to get user input. + * `'response-container .response-content'` to get model output. +2. **`extractTextContent` and `extractHTMLContent`:** + * `extractTextContent` now uses `innerText` to get plain text from user queries. + * `extractHTMLContent` uses `innerHTML` to preserve formatting in model responses. +3. **Role Assignment:** + * User queries are assigned the `role: 'user'`. + * Model responses are assigned the `role: 'assistant'`. +4. **Position Sorting:** + * The `getNodePosition` function and sorting logic are retained to ensure the correct conversation order. +5. **JSON Structure:** + * The output JSON includes `title`, `timestamp`, and `conversation` (an array of messages). + + +**How to Use** + +1. Open the Gemini chat you want to extract. +2. Open the browser console. +3. Paste the script. +4. Press Enter. + +This script should now provide you with a JSON file in the desired format, consistent with the ChatArchiver's expectations. + +### Bugs +- Gemini doesn't appear to keep Conversation Title anywhere but the nav bar, so this doesn't populate that correctly yet. +- Output is still HTML as displayed. Post process likely needed but rather not write my own.