// ==UserScript==
// @name ChatGPT对话导出工具-轻松提取聊天记录导出至本地📄(MaynorAI)
// @namespace http://chatgpt-plus.top/
// @version 1.15
// @description 🐎一键将ChatGPT网站和国内相关镜像站的聊天记录导出为HTML或Markdown,轻松提取GPT聊天记录,让你在本地上就能使用看!
// @author @caicats
// @match https://chat.openai.com/*
// @match https://chatgpt.com/**
// @match *://chatgpt-plus.top/*
// @match *://*.chatgpt-plus.top/*
// @match *://*.maynor1024.live/*
// @license MIT
// @icon https://t1.gstatic.cn/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&size=32&url=https://chatgpt.com
// ==/UserScript==
(function() {
'use strict';
window.addEventListener('load', () => {
createFloatingMenu();
});
function createFloatingMenu() {
const menuContainer = document.createElement('div');
menuContainer.id = 'floating-menu';
menuContainer.style.position = 'fixed';
menuContainer.style.bottom = '80px';
menuContainer.style.right = '15px';
menuContainer.style.zIndex = '10000';
menuContainer.style.display = 'flex';
menuContainer.style.flexDirection = 'column';
menuContainer.style.alignItems = 'center';
menuContainer.style.gap = '10px';
// 创建提示框
const tooltip = document.createElement('div');
tooltip.id = 'tooltip';
tooltip.style.position = 'fixed';
tooltip.style.backgroundColor = 'rgba(0, 0, 0, 0.9)';
tooltip.style.color = '#fff';
tooltip.style.padding = '8px 12px';
tooltip.style.borderRadius = '6px';
tooltip.style.fontSize = '12px';
tooltip.style.visibility = 'hidden';
tooltip.style.zIndex = '10001';
tooltip.style.pointerEvents = 'none';
tooltip.style.maxWidth = '200px';
tooltip.style.wordWrap = 'break-word';
document.body.appendChild(tooltip);
// Main button as expand icon
const mainButton = document.createElement('button');
mainButton.id = 'main-button';
mainButton.style.width = '40px';
mainButton.style.height = '40px';
mainButton.style.borderRadius = '50%';
mainButton.style.border = 'none';
mainButton.style.backgroundColor = '#4cafa3';
mainButton.style.color = 'white';
mainButton.style.cursor = 'pointer';
mainButton.style.fontSize = '26px';
mainButton.textContent = '+';
let isExpanded = false;
mainButton.addEventListener('click', () => {
isExpanded = !isExpanded;
toggleMenu(isExpanded, menuContainer);
});
addTooltip(mainButton, '展开功能列表');
// Markdown button with orange background
const markdownButton = createMenuButton('📄', '导出为 Markdown', exportChatAsMarkdown, '#FFA500');
addTooltip(markdownButton.querySelector('button'), '📄 导出为 Markdown');
// HTML button with orange background
const htmlButton = createMenuButton('🌐', '导出为 HTML', exportChatAsHTML, '#FFA500');
addTooltip(htmlButton.querySelector('button'), '🌐 导出为 HTML');
// New Shop button to redirect to the specified link
const shopButton = createMenuButton('🛒', '付费GPT库系统', () => {
window.open('http://chatgpt-plus.top/', '_blank');
}, '#32CD32'); // LimeGreen color for shop button
addTooltip(shopButton.querySelector('button'), '🛒 付费GPT库系统');
// Append buttons to the menu
menuContainer.appendChild(mainButton);
menuContainer.appendChild(markdownButton);
menuContainer.appendChild(htmlButton);
menuContainer.appendChild(shopButton);
document.body.appendChild(menuContainer);
// Initially hide the menu buttons
toggleMenu(false, menuContainer);
function addTooltip(element, text) {
element.addEventListener('mouseenter', (event) => {
tooltip.textContent = text;
tooltip.style.visibility = 'visible';
// 设置提示框在按钮左侧显示
const rect = element.getBoundingClientRect();
tooltip.style.left = `${rect.left - tooltip.offsetWidth - 10}px`;
tooltip.style.top = `${rect.top + (rect.height / 2) - (tooltip.offsetHeight / 2)}px`;
});
element.addEventListener('mouseleave', () => {
tooltip.style.visibility = 'hidden';
});
}
}
function createMenuButton(icon, text, onClick, bgColor) {
const buttonContainer = document.createElement('div');
buttonContainer.style.position = 'relative';
const button = document.createElement('button');
button.className = 'menu-button';
button.style.width = '36px';
button.style.height = '36px';
button.style.borderRadius = '50%';
button.style.border = 'none';
button.style.backgroundColor = bgColor;
button.style.color = 'white';
button.style.cursor = 'pointer';
button.textContent = icon;
button.addEventListener('click', onClick);
buttonContainer.appendChild(button);
return buttonContainer;
}
function toggleMenu(expand, menuContainer) {
const buttons = menuContainer.querySelectorAll('.menu-button');
buttons.forEach(button => {
button.style.display = expand ? 'block' : 'none';
});
}
async function exportChatAsMarkdown() {
try {
let markdownContent = "# ChatGPT 对话记录\n\n";
let allElements = document.querySelectorAll('div.flex.flex-grow.flex-col.max-w-full');
allElements.forEach((element, index) => {
let text = element.textContent.trim();
if (index % 2 === 0) {
markdownContent += `## User\n${text}\n\n`;
} else {
markdownContent += `## Assistant\n${text}\n\n`;
}
});
download(markdownContent, 'chat-export.md', 'text/markdown');
} catch (error) {
console.error("导出为 Markdown 时出错:", error);
}
}
async function exportChatAsHTML() {
try {
let htmlContent = "<!DOCTYPE html><html><head><meta charset='UTF-8'><title>Chat Export</title></head><body>";
let allElements = document.querySelectorAll('div[class*="text-base"]');
allElements.forEach((element, index) => {
let text = element.innerHTML.trim();
let role = element.closest('[data-message-author-role]')?.getAttribute('data-message-author-role');
if (role === 'user') {
htmlContent += `<h2>User</h2><div>${text}</div>`;
} else if (role === 'assistant') {
htmlContent += `<h2>Assistant</h2><div>${text}</div>`;
}
});
htmlContent += "</body></html>";
download(htmlContent, 'chat-export.html', 'text/html');
} catch (error) {
console.error("导出为 HTML 时出错:", error);
}
}
function download(data, filename, type) {
const blob = new Blob([data], {type: type});
const a = document.createElement('a');
const url = URL.createObjectURL(blob);
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
})();