- // ==UserScript==
- // @name Gemini JSON exporter
- // @namespace lugia19.com
- // @version 0.4
- // @description Export messages from Gemini share links into tavern/ooba format
- // @author lugia19
- // @match https://gemini.google.com/share/*
- // @grant none
- // @license MIT
- // ==/UserScript==
-
- //Data conversion stuff
- function extractConversationGemini() {
- const messages = [];
- // Collect all user and assistant messages
- const userQueries = document.querySelectorAll('user-query .query-text');
- const assistantResponses = document.querySelectorAll('response-container .message-content');
-
- for (let i = 0; i < userQueries.length || i < assistantResponses.length; i++) {
- if (userQueries[i]) {
- messages.push({
- parent: "",
- message: {
- author: {
- role: "user"
- },
- create_time: getCurrentUnixTimestamp(),
- content: {
- parts: [userQueries[i].textContent.trim()]
- }
- }
- });
- }
- if (assistantResponses[i]) {
- messages.push({
- parent: "",
- message: {
- author: {
- role: "assistant"
- },
- create_time: getCurrentUnixTimestamp(),
- content: {
- parts: [assistantResponses[i].textContent.trim()]
- }
- }
- });
- }
- }
-
- return messages;
- }
-
- function convertMessageToTavern(messageData) {
- if (!messageData.message) {
- return null;
- }
-
- const senderRole = messageData.message.author.role;
- if (senderRole === 'system') {
- return null;
- }
-
- const isAssistant = senderRole === 'assistant';
- const createTime = messageData.message.create_time;
- const text = messageData.message.content.parts[0];
-
- return {
- name: isAssistant ? 'Assistant' : 'You',
- is_user: !isAssistant,
- is_name: isAssistant,
- send_date: createTime,
- mes: text,
- swipes: [text],
- swipe_id: 0,
- };
- }
-
- function jsonlStringify(messageArray) {
- return messageArray.map(msg => JSON.stringify(msg)).join('\n');
- }
-
- function getTavernString() {
- const conversation = extractConversationGemini();
-
- const convertedConvo = [{
- user_name: 'You',
- character_name: 'Assistant',
- }];
-
- conversation.forEach((message) => {
- const convertedMsg = convertMessageToTavern(message);
- if (convertedMsg !== null) {
- convertedConvo.push(convertedMsg);
- }
- });
-
- return jsonlStringify(convertedConvo);
- }
-
- function getOobaString() {
- const messages = extractConversationGemini();
- const pairs = [];
- let idx = 0;
-
- while (idx < messages.length - 1) {
- const message = messages[idx];
- const nextMessage = messages[idx + 1];
- let role, text, nextRole, nextText;
-
- if (!message.message || !nextMessage.message) {
- idx += 1;
- continue;
- }
-
- try {
- role = message.message.author.role;
- text = message.message.content.parts[0];
- nextRole = nextMessage.message.author.role;
- nextText = nextMessage.message.content.parts[0];
- } catch (error) {
- idx += 1;
- continue;
- }
-
- if (role === 'system') {
- if (text !== '') {
- pairs.push(['<|BEGIN-VISIBLE-CHAT|>', text]);
- }
- idx += 1;
- continue;
- }
-
- if (role === 'user') {
- if (nextRole === 'assistant') {
- pairs.push([text, nextText]);
- idx += 2;
- continue;
- } else if (nextRole === 'user') {
- pairs.push([text, '']);
- idx += 1;
- continue;
- }
- }
-
- if (role === 'assistant') {
- pairs.push(['', text]);
- idx += 1;
- }
- }
- const oobaData = {
- internal: pairs,
- visible: JSON.parse(JSON.stringify(pairs)),
- };
-
- if (oobaData.visible[0] && oobaData.visible[0][0] === '<|BEGIN-VISIBLE-CHAT|>') {
- oobaData.visible[0][0] = '';
- }
-
- return JSON.stringify(oobaData, null, 2);
- }
-
-
- // Function to get current Unix timestamp in milliseconds
- function getCurrentUnixTimestamp() {
- return Math.floor(Date.now());
- }
-
-
- // Function to download data as JSON (Shamelessly copied from ChatGPT-Exporter)
- function downloadFile(filename, type, content) {
- const blob = content instanceof Blob ? content : new Blob([content], { type })
- const url = URL.createObjectURL(blob)
- const a = document.createElement('a')
- a.href = url
- a.download = filename
- document.body.appendChild(a)
- a.click()
- document.body.removeChild(a)
- }
-
- // Create and style button
- const menuButton = document.createElement('button');
- menuButton.textContent = 'Export Chatlog';
- Object.assign(menuButton.style, {
- position: 'fixed',
- bottom: '20px',
- right: '20px',
- zIndex: 1000,
- backgroundColor: '#131314',
- color: 'white',
- border: '1px solid white',
- padding: '10px 20px',
- cursor: 'pointer',
- borderRadius: '5px',
- boxSizing: 'border-box', // Include padding and border in the width
- textAlign: 'center', // Center the text
- });
-
- // Create menu container
- const menuContainer = document.createElement('div');
- Object.assign(menuContainer.style, {
- position: 'fixed',
- bottom: '70px', // Adjusted to not overlap the main button
- right: '20px',
- zIndex: 1001,
- display: 'none', // Initially hidden
- backgroundColor: '#131314',
- borderRadius: '0px',
- boxShadow: '0 4px 8px rgba(0,0,0,0.1)',
- });
-
- // Function to toggle menu display
- function toggleMenu() {
- menuContainer.style.display = menuContainer.style.display === 'none' ? 'block' : 'none';
- }
-
- // Add click event listener to menu button
- menuButton.addEventListener('click', toggleMenu);
-
- // Add buttons to the menu
- ['Tavern', 'Ooba'].forEach(type => {
- const button = document.createElement('button');
- button.textContent = `${type} format`;
- Object.assign(button.style, {
- backgroundColor: '#131314',
- color: 'white',
- border: '1px solid white',
- padding: '10px 20px',
- cursor: 'pointer',
- borderRadius: '5px',
- display: 'block', // Ensure each button is on its own line
- marginTop: '10px', // Space out the buttons
- width: '100%', // Ensure consistent width
- boxSizing: 'border-box', // Include padding and border in the width
- textAlign: 'center', // Center the text
- });
- button.onclick = function () {
- let title = document.title.replace(/^\W+/, '').trim().replace(/\s+/g, '_');
- if (type === 'Tavern') {
- const resultString = getTavernString();
- downloadFile(`${title}_tavern.jsonl`, 'application/json', resultString);
- } else if (type === 'Ooba') {
- const resultString = getOobaString();
- downloadFile(`${title}_ooba.json`, 'application/json', resultString);
- } else { // JSON
- const resultString = JSON.stringify(extractConversationGemini(), null, 2);
- downloadFile(`${title}.json`, 'application/json', resultString);
- }
- // Optionally close the menu after download
- toggleMenu();
- };
- menuContainer.appendChild(button);
- });
-
- // Add menu and menu button to the page
- document.body.appendChild(menuButton);
- document.body.appendChild(menuContainer);