// ==UserScript==
// @name Enhanced Grok Export v2.4
// @description Export Grok conversations with improved detection, share to X integration and more
// @version 2.4.0
// @author iikoshteruu
// @grant none
// @match *://grok.com/*
// @match *://x.com/*
// @license MIT
// @namespace https://github.com/iikoshteruu/enhanced-grok-export
// @homepageURL https://github.com/iikoshteruu/enhanced-grok-export
// @supportURL https://github.com/iikoshteruu/enhanced-grok-export/issues
// ==/UserScript==
(function() {
'use strict';
console.log('Enhanced Grok Export v2.2 starting...');
// Configuration
const CONFIG = {
buttonText: 'Export Full',
formats: ['txt', 'md', 'json', 'pdf'],
defaultFormat: 'md',
debug: true,
autoScroll: true,
scrollDelay: 1000,
maxScrollAttempts: 50,
shareToX: {
enabled: true,
maxLength: 280,
hashtagSuggestions: ['#Grok', '#AI', '#XAI']
}
};
let isExporting = false;
function debugLog(message, data = null) {
if (CONFIG.debug) {
console.log('[Grok Export v2.2]', message, data || '');
}
}
// Auto-scroll to load all conversation content
async function loadFullConversation() {
debugLog('Starting full conversation loading...');
return new Promise((resolve) => {
let scrollAttempts = 0;
let lastScrollHeight = 0;
let unchangedCount = 0;
const scrollInterval = setInterval(() => {
window.scrollTo(0, 0);
const currentScrollHeight = document.body.scrollHeight;
debugLog(`Scroll attempt ${scrollAttempts + 1}, Height: ${currentScrollHeight}`);
if (currentScrollHeight === lastScrollHeight) {
unchangedCount++;
} else {
unchangedCount = 0;
lastScrollHeight = currentScrollHeight;
}
scrollAttempts++;
if (scrollAttempts >= CONFIG.maxScrollAttempts || unchangedCount >= 3) {
clearInterval(scrollInterval);
debugLog(`Scroll complete. Total attempts: ${scrollAttempts}`);
setTimeout(() => {
window.scrollTo(0, 0);
setTimeout(() => {
window.scrollTo(0, document.body.scrollHeight);
setTimeout(() => {
resolve();
}, 1000);
}, 500);
}, 500);
}
}, CONFIG.scrollDelay);
});
}
// Enhanced conversation detection for Grok
function getConversationData() {
debugLog('Starting Grok conversation data extraction...');
const messages = [];
const strategies = [
() => {
const messageContainers = document.querySelectorAll('div[class*="css-146c3p1"]');
debugLog(`Found ${messageContainers.length} containers with css-146c3p1`);
return Array.from(messageContainers);
},
() => {
const contentSpans = document.querySelectorAll('span[class*="css-1jxf684"]');
debugLog(`Found ${contentSpans.length} content spans with css-1jxf684`);
return Array.from(contentSpans).map(span => {
let parent = span.parentElement;
while (parent && !parent.classList.toString().includes('css-146c3p1')) {
parent = parent.parentElement;
}
return parent;
}).filter(Boolean);
},
() => {
const ltrDivs = document.querySelectorAll('div[dir="ltr"]');
debugLog(`Found ${ltrDivs.length} divs with dir='ltr'`);
return Array.from(ltrDivs).filter(div => {
const text = div.textContent?.trim() || '';
return text.length > 10 && text.length < 50000;
});
}
];
let messageElements = [];
for (let i = 0; i < strategies.length; i++) {
try {
messageElements = strategies[i]();
debugLog(`Strategy ${i + 1} found ${messageElements.length} elements`);
if (messageElements.length > 0) {
messageElements = messageElements.filter(el => {
const text = el.textContent?.trim() || '';
return text.length > 10 && text.length < 50000;
});
if (messageElements.length > 0) {
debugLog(`Using strategy ${i + 1} with ${messageElements.length} valid elements`);
break;
}
}
} catch (error) {
debugLog(`Strategy ${i + 1} failed:`, error.message);
}
}
debugLog(`Processing ${messageElements.length} message elements...`);
const processedTexts = new Set();
messageElements.forEach((element, index) => {
try {
const clone = element.cloneNode(true);
const unwanted = clone.querySelectorAll(
'svg, button, input, select, nav, header, footer, script, style, ' +
'[aria-hidden="true"], [class*="icon"], [class*="button"]'
);
unwanted.forEach(el => el.remove());
const text = clone.textContent?.trim() || '';
if (text && text.length > 10 && !processedTexts.has(text)) {
processedTexts.add(text);
const speakerInfo = detectGrokSpeakerAdvanced(element, text, index, messageElements);
messages.push({
id: `msg_${index}`,
speaker: speakerInfo.speaker,
content: text,
mode: speakerInfo.mode,
timestamp: new Date().toISOString(),
index: index,
length: text.length,
element: element,
debugInfo: speakerInfo.debugInfo
});
debugLog(`Message ${index + 1}: ${speakerInfo.speaker} [${speakerInfo.mode}] (${text.length} chars)`, speakerInfo.debugInfo);
}
} catch (error) {
debugLog(`Error processing element ${index}:`, error.message);
}
});
messages.sort((a, b) => a.index - b.index);
debugLog(`Extracted ${messages.length} unique messages`);
return messages;
}
// REVISED: Speaker detection using position and content analysis
function detectGrokSpeakerAdvanced(element, text, index, allElements) {
let debugInfo = { scores: {}, reasoning: [] };
// Mode detection for Grok
let mode = 'standard';
if (text.includes('🤔') || text.includes('Let me think') || text.includes('Step ') ||
text.includes('First,') || text.includes('Then,') || text.includes('Finally,')) {
mode = 'think';
} else if (text.includes('😄') || text.includes('😂') || text.includes('LOL') ||
text.includes('haha') || text.includes('funny')) {
mode = 'fun';
} else if (text.includes('According to') || text.includes('Based on recent') ||
text.includes('Source:') || text.includes('https://')) {
mode = 'deepsearch';
}
let grokScore = 0;
let humanScore = 0;
// 1. MESSAGE LENGTH ANALYSIS (Most reliable indicator)
if (text.length > 400) {
grokScore += 4;
debugInfo.reasoning.push(`Very long message (${text.length} chars, likely GROK)`);
} else if (text.length > 200) {
grokScore += 2;
debugInfo.reasoning.push(`Long message (${text.length} chars, likely GROK)`);
} else if (text.length < 50) {
humanScore += 2;
debugInfo.reasoning.push(`Short message (${text.length} chars, likely HUMAN)`);
}
// 2. ENHANCED CONTENT PATTERN ANALYSIS
const grokIndicators = [
{ pattern: /^(I'll|I can|I'd be happy|Here's|Let me|I understand|Certainly|Absolutely|Looking at)/i, score: 3, name: 'Grok response starter' },
{ pattern: /^(Yo, I'm right here|Hey there|What's up|Oof|Thanks for sharing)/i, score: 4, name: 'Grok casual phrases' },
{ pattern: /^(From your|Based on your|Looking at your|The error|This means|Why It's Happening)/i, score: 4, name: 'Grok analysis starters' },
{ pattern: /```/, score: 3, name: 'Code block' },
{ pattern: /(docker|container|build|error|issue|problem|fix|solution)/i, score: 2, name: 'Technical terms' },
{ pattern: /^(Based on|According to|The analysis|This approach|In summary|Overview)/i, score: 2, name: 'Analytical language' },
{ pattern: /(implementation|algorithm|analysis|explanation|methodology|digital realm)/i, score: 1, name: 'Technical/AI terms' },
{ pattern: /\n\n/, score: 1, name: 'Structured paragraphs' },
{ pattern: /(fully alive|kicking in the digital realm|locked in|squash|tackle this)/i, score: 4, name: 'Grok personality phrases' },
{ pattern: /^(Let's|Why|Steps to|Here's how)/i, score: 3, name: 'Instructional language' },
{ pattern: /(requirements\.txt|Dockerfile|app\.py|netstat|TrueNAS)/i, score: 2, name: 'Project-specific terms' }
];
const humanIndicators = [
{ pattern: /^(hi|hello|hey|can you|could you|please|help|i need|i want)/i, score: 3, name: 'Human greeting/request' },
{ pattern: /^(grok|are you|do you remember)/i, score: 5, name: 'Addressing Grok directly' },
{ pattern: /\?$/, score: 3, name: 'Ends with question' },
{ pattern: /^(ok|okay|thanks|thank you|great|perfect|yes|no|good|nice)/i, score: 2, name: 'Acknowledgment' },
{ pattern: /^(let's|lets|now|next|alright|ready|this site can't be reached)/i, score: 2, name: 'Directive/status language' },
{ pattern: /\b(you|your)\b/i, score: 1, name: 'Addressing someone' },
{ pattern: /^(root@truenas|trying|nano)/i, score: 4, name: 'User commands/actions' }
];
grokIndicators.forEach(({ pattern, score, name }) => {
if (pattern.test(text)) {
grokScore += score;
debugInfo.reasoning.push(`${name} (+${score} GROK)`);
}
});
humanIndicators.forEach(({ pattern, score, name }) => {
if (pattern.test(text)) {
humanScore += score;
debugInfo.reasoning.push(`${name} (+${score} HUMAN)`);
}
});
// 3. CONVERSATIONAL CONTEXT ANALYSIS
if (index > 0 && allElements[index - 1]) {
const prevText = allElements[index - 1].textContent?.trim() || '';
// If previous message was a question and this is a long answer
if (prevText.includes('?') && prevText.length < 200 && text.length > 150) {
grokScore += 3;
debugInfo.reasoning.push('Long response to question (likely GROK)');
}
// If this is a short follow-up to a long message
if (prevText.length > 300 && text.length < 100) {
humanScore += 2;
debugInfo.reasoning.push('Short follow-up to long message (likely HUMAN)');
}
}
// 4. QUESTION vs STATEMENT ANALYSIS
const questionCount = (text.match(/\?/g) || []).length;
if (questionCount > 0 && text.length < 150) {
humanScore += questionCount * 2;
debugInfo.reasoning.push(`Contains ${questionCount} questions (likely HUMAN)`);
}
// 5. CONVERSATION POSITION ANALYSIS
const messagesSoFar = index + 1;
// First message is typically human
if (index === 0) {
humanScore += 2;
debugInfo.reasoning.push('First message (likely HUMAN)');
}
// Look at nearby message lengths for pattern
const nearbyLengths = [];
for (let i = Math.max(0, index - 2); i <= Math.min(allElements.length - 1, index + 2); i++) {
if (i !== index && allElements[i]) {
nearbyLengths.push(allElements[i].textContent?.length || 0);
}
}
const avgNearbyLength = nearbyLengths.length > 0 ?
nearbyLengths.reduce((sum, len) => sum + len, 0) / nearbyLengths.length : 0;
if (text.length > avgNearbyLength * 2 && text.length > 200) {
grokScore += 2;
debugInfo.reasoning.push('Much longer than nearby messages (likely GROK)');
}
// 6. MODE BONUS
if (mode !== 'standard') {
grokScore += 3;
debugInfo.reasoning.push(`${mode} mode detected (likely GROK)`);
}
// Store scores for debugging
debugInfo.scores = { grokScore, humanScore };
// FINAL DECISION with balanced thresholds
let speaker;
if (grokScore >= humanScore + 2) { // Require clear Grok advantage
speaker = 'Grok';
} else if (humanScore >= grokScore + 1) { // Easier for human detection
speaker = 'Human';
} else {
// BALANCED FALLBACK based on conversation patterns
// Strong human indicators
if (text.startsWith('root@') || text.includes('nano ') || text.includes('ls -l') ||
text.includes('docker run') || text.includes('docker build') || text.length < 25) {
speaker = 'Human';
debugInfo.reasoning.push('Fallback: Clear user command/input assumed HUMAN');
}
// Clear questions
else if (text.includes('?') && text.length < 100) {
speaker = 'Human';
debugInfo.reasoning.push('Fallback: Short question assumed HUMAN');
}
// Long technical explanations
else if (text.length > 300 && (text.includes('Fix:') || text.includes('Issue:') || text.includes('Solution:'))) {
speaker = 'Grok';
debugInfo.reasoning.push('Fallback: Long technical explanation assumed GROK');
}
// Medium length with technical terms
else if (text.length > 150 && (text.includes('docker') || text.includes('container') || text.includes('error'))) {
speaker = 'Grok';
debugInfo.reasoning.push('Fallback: Medium technical content assumed GROK');
}
// Short technical references or status updates
else if (text.length < 100 && !text.includes('Here\'s') && !text.includes('Let\'s')) {
speaker = 'Human';
debugInfo.reasoning.push('Fallback: Short non-explanatory content assumed HUMAN');
}
// Final alternating fallback
else {
speaker = index % 2 === 0 ? 'Human' : 'Grok';
debugInfo.reasoning.push(`Final fallback: Alternating pattern (${speaker})`);
}
}
debugInfo.finalDecision = `${speaker} (GROK: ${grokScore}, HUMAN: ${humanScore})`;
return { speaker, mode, debugInfo };
}
// FIXED PDF GENERATION - No external dependencies
function formatAsPDF(messages) {
try {
debugLog('Generating PDF document...');
// Create a rich text document formatted like a PDF
let content = '';
// PDF-style header
content += '═'.repeat(80) + '\n';
content += ' GROK CONVERSATION EXPORT\n';
content += '═'.repeat(80) + '\n\n';
// Document metadata
content += `EXPORT INFORMATION:\n`;
content += `${'─'.repeat(40)}\n`;
content += `Generated: ${new Date().toLocaleString()}\n`;
content += `Total Messages: ${messages.length}\n`;
content += `Source URL: ${window.location.href}\n`;
content += `Export Version: Enhanced Grok Export v2.2\n\n`;
// Statistics section
const stats = {
humanMessages: messages.filter(m => m.speaker === 'Human').length,
grokMessages: messages.filter(m => m.speaker === 'Grok').length,
thinkMode: messages.filter(m => m.mode === 'think').length,
funMode: messages.filter(m => m.mode === 'fun').length,
deepSearchMode: messages.filter(m => m.mode === 'deepsearch').length,
totalChars: messages.reduce((sum, m) => sum + m.content.length, 0),
avgLength: Math.round(messages.reduce((sum, m) => sum + m.content.length, 0) / messages.length)
};
content += `CONVERSATION STATISTICS:\n`;
content += `${'─'.repeat(40)}\n`;
content += `┌─────────────────────────┬─────────┐\n`;
content += `│ Human Messages │ ${stats.humanMessages.toString().padStart(7)} │\n`;
content += `│ Grok Messages │ ${stats.grokMessages.toString().padStart(7)} │\n`;
content += `├─────────────────────────┼─────────┤\n`;
content += `│ Think Mode │ ${stats.thinkMode.toString().padStart(7)} │\n`;
content += `│ Fun Mode │ ${stats.funMode.toString().padStart(7)} │\n`;
content += `│ DeepSearch Mode │ ${stats.deepSearchMode.toString().padStart(7)} │\n`;
content += `├─────────────────────────┼─────────┤\n`;
content += `│ Total Characters │ ${stats.totalChars.toString().padStart(7)} │\n`;
content += `│ Average Message Length │ ${stats.avgLength.toString().padStart(7)} │\n`;
content += `└─────────────────────────┴─────────┘\n\n`;
content += '═'.repeat(80) + '\n';
content += ' CONVERSATION CONTENT\n';
content += '═'.repeat(80) + '\n\n';
// Message content
messages.forEach((msg, index) => {
const modeIndicator = msg.mode !== 'standard' ? ` [${msg.mode.toUpperCase()}]` : '';
// Message header
content += `${index + 1}. ${msg.speaker}${modeIndicator}\n`;
content += `${'─'.repeat(Math.max(20, msg.speaker.length + modeIndicator.length + 4))}\n`;
// Message content with proper wrapping
const wrappedContent = wrapText(msg.content, 76);
content += wrappedContent + '\n\n';
// Visual separator between messages
if (index < messages.length - 1) {
content += '▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪\n\n';
}
});
// Document footer
content += '\n' + '═'.repeat(80) + '\n';
content += ` End of Document - ${messages.length} Messages\n`;
content += '═'.repeat(80) + '\n';
return new Blob([content], { type: 'text/plain;charset=utf-8' });
} catch (error) {
debugLog('PDF generation failed:', error);
throw new Error(`PDF generation failed: ${error.message}`);
}
}
// Helper function for text wrapping
function wrapText(text, maxWidth) {
const words = text.split(' ');
const lines = [];
let currentLine = '';
words.forEach(word => {
if ((currentLine + word).length <= maxWidth) {
currentLine += (currentLine ? ' ' : '') + word;
} else {
if (currentLine) lines.push(currentLine);
currentLine = word;
}
});
if (currentLine) lines.push(currentLine);
return lines.join('\n');
}
// Create Share to X modal
function createShareModal(messages) {
const modal = document.createElement('div');
modal.id = 'grok-share-modal';
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
`;
const content = document.createElement('div');
content.style.cssText = `
background: white;
border-radius: 16px;
padding: 24px;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
`;
content.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h3 style="margin: 0; color: #1DA1F2;">🐦 Share to X</h3>
<button id="close-share-modal" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #666;">×</button>
</div>
<div style="margin-bottom: 16px;">
<label style="display: block; margin-bottom: 8px; font-weight: 600;">Select Messages to Share:</label>
<div id="message-selector" style="max-height: 200px; overflow-y: auto; border: 1px solid #ddd; border-radius: 8px; padding: 12px;">
${messages.map((msg, index) => `
<div style="margin-bottom: 12px; padding: 8px; border-radius: 6px; background: ${msg.speaker === 'Human' ? '#f0f8ff' : '#f8f9fa'};">
<label style="display: flex; align-items: flex-start; cursor: pointer;">
<input type="checkbox" data-msg-id="${msg.id}" style="margin-right: 8px; margin-top: 4px;">
<div>
<strong>${msg.speaker}${msg.mode !== 'standard' ? ` [${msg.mode.toUpperCase()}]` : ''}:</strong>
<div style="margin-top: 4px; font-size: 14px; color: #333;">${msg.content.substring(0, 100)}${msg.content.length > 100 ? '...' : ''}</div>
</div>
</label>
</div>
`).join('')}
</div>
</div>
<div style="margin-bottom: 16px;">
<label style="display: block; margin-bottom: 8px; font-weight: 600;">Your Commentary (optional):</label>
<textarea id="share-commentary" placeholder="Add your thoughts about this Grok conversation..." style="width: 100%; height: 80px; border: 1px solid #ddd; border-radius: 8px; padding: 12px; resize: vertical; font-family: inherit;"></textarea>
</div>
<div style="margin-bottom: 16px;">
<label style="display: block; margin-bottom: 8px; font-weight: 600;">Preview:</label>
<div id="share-preview" style="border: 1px solid #ddd; border-radius: 8px; padding: 12px; background: #f8f9fa; min-height: 60px; font-size: 14px; color: #666;">
Select messages to see preview...
</div>
<div id="character-count" style="text-align: right; font-size: 12px; color: #666; margin-top: 4px;">0 / 280</div>
</div>
<div style="display: flex; gap: 12px; justify-content: flex-end;">
<button id="cancel-share" style="padding: 10px 20px; border: 1px solid #ddd; background: white; border-radius: 8px; cursor: pointer;">Cancel</button>
<button id="confirm-share" style="padding: 10px 20px; border: none; background: #1DA1F2; color: white; border-radius: 8px; cursor: pointer; font-weight: 600;" disabled>Share to X</button>
</div>
`;
modal.appendChild(content);
document.body.appendChild(modal);
// Event handlers
function updatePreview() {
const selected = document.querySelectorAll('#message-selector input[type="checkbox"]:checked');
const commentary = document.getElementById('share-commentary').value;
let preview = '';
if (commentary.trim()) {
preview += commentary.trim() + '\n\n';
}
const selectedMessages = Array.from(selected).map(input => {
const msgId = input.dataset.msgId;
return messages.find(m => m.id === msgId);
}).filter(Boolean);
if (selectedMessages.length > 0) {
preview += selectedMessages.map(msg => {
const modeIndicator = msg.mode !== 'standard' ? ` [${msg.mode.toUpperCase()}]` : '';
return `${msg.speaker}${modeIndicator}: ${msg.content}`;
}).join('\n\n');
}
if (preview) {
preview += '\n\n' + CONFIG.shareToX.hashtagSuggestions.join(' ');
}
if (preview.length > CONFIG.shareToX.maxLength) {
preview = preview.substring(0, CONFIG.shareToX.maxLength - 3) + '...';
}
document.getElementById('share-preview').textContent = preview || 'Select messages to see preview...';
document.getElementById('character-count').textContent = `${preview.length} / ${CONFIG.shareToX.maxLength}`;
const shareButton = document.getElementById('confirm-share');
shareButton.disabled = !preview || preview.length > CONFIG.shareToX.maxLength;
shareButton.style.opacity = shareButton.disabled ? '0.5' : '1';
}
document.getElementById('close-share-modal').onclick = () => modal.remove();
document.getElementById('cancel-share').onclick = () => modal.remove();
document.getElementById('message-selector').addEventListener('change', updatePreview);
document.getElementById('share-commentary').addEventListener('input', updatePreview);
document.getElementById('confirm-share').onclick = () => {
const preview = document.getElementById('share-preview').textContent;
if (preview && preview !== 'Select messages to see preview...') {
const tweetUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(preview)}`;
window.open(tweetUrl, '_blank');
modal.remove();
}
};
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.remove();
});
return modal;
}
// Format functions
function formatAsText(messages) {
if (messages.length === 0) return 'No conversation found.';
let output = `Grok.ai COMPLETE Conversation Export\n`;
output += `Exported: ${new Date().toLocaleString()}\n`;
output += `Total Messages: ${messages.length}\n`;
output += `URL: ${window.location.href}\n`;
output += '='.repeat(80) + '\n\n';
messages.forEach((msg, index) => {
const modeIndicator = msg.mode !== 'standard' ? ` [${msg.mode.toUpperCase()}]` : '';
output += `${msg.speaker}${modeIndicator}:\n`;
output += `${msg.content}\n\n`;
if (index < messages.length - 1) {
output += '-'.repeat(50) + '\n\n';
}
});
return output;
}
function formatAsMarkdown(messages) {
if (messages.length === 0) return '# No conversation found';
let md = `# Grok.ai Complete Conversation Export\n\n`;
md += `**Exported:** ${new Date().toLocaleString()} \n`;
md += `**Total Messages:** ${messages.length} \n`;
md += `**URL:** ${window.location.href} \n`;
md += `**Export Method:** Enhanced Grok Export v2.2\n\n`;
md += `---\n\n`;
messages.forEach(msg => {
const modeIndicator = msg.mode !== 'standard' ? ` [${msg.mode.toUpperCase()}]` : '';
md += `## ${msg.speaker}${modeIndicator}\n\n`;
md += `${msg.content}\n\n`;
});
return md;
}
function formatAsJSON(messages) {
const exportData = {
exportDate: new Date().toISOString(),
exportTimestamp: Date.now(),
exportVersion: '2.2.0',
platform: 'grok',
messageCount: messages.length,
url: window.location.href,
userAgent: navigator.userAgent,
conversation: messages.map(msg => ({
id: msg.id,
speaker: msg.speaker,
content: msg.content,
mode: msg.mode,
timestamp: msg.timestamp,
length: msg.length,
debugInfo: msg.debugInfo
})),
statistics: {
humanMessages: messages.filter(m => m.speaker === 'Human').length,
grokMessages: messages.filter(m => m.speaker === 'Grok').length,
totalCharacters: messages.reduce((sum, m) => sum + m.content.length, 0),
averageMessageLength: Math.round(messages.reduce((sum, m) => sum + m.content.length, 0) / messages.length),
modes: {
standard: messages.filter(m => m.mode === 'standard').length,
think: messages.filter(m => m.mode === 'think').length,
fun: messages.filter(m => m.mode === 'fun').length,
deepsearch: messages.filter(m => m.mode === 'deepsearch').length
}
}
};
return JSON.stringify(exportData, null, 2);
}
// Download file
function downloadFile(content, filename, type = 'text/plain') {
try {
debugLog(`Downloading: ${filename} (${content instanceof Blob ? 'Blob' : content.length + ' chars'})`);
let blob;
if (content instanceof Blob) {
blob = content;
} else {
blob = new Blob([content], { type: type + ';charset=utf-8' });
}
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.download = filename;
link.href = url;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setTimeout(() => URL.revokeObjectURL(url), 1000);
debugLog('Download completed successfully');
return true;
} catch (error) {
console.error('Download failed:', error);
alert(`Download failed: ${error.message}`);
return false;
}
}
// Export conversation with full loading
async function exportConversation(format) {
if (isExporting) {
alert('Export already in progress. Please wait...');
return;
}
isExporting = true;
try {
debugLog(`Starting Grok export in ${format} format...`);
showNotification('🔄 Loading full conversation...', 0);
if (CONFIG.autoScroll) {
await loadFullConversation();
showNotification('📝 Extracting messages...', 3000);
}
const messages = getConversationData();
if (messages.length === 0) {
alert('No conversation content found! This might be a new chat or there could be a technical issue. Check the browser console for details.');
return;
}
if (format === 'share') {
createShareModal(messages);
hideMenu();
return;
}
const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
let filename, content, mimeType;
switch (format) {
case 'md':
content = formatAsMarkdown(messages);
mimeType = 'text/markdown';
filename = `grok-FULL-conversation-${messages.length}msgs-${timestamp}.md`;
break;
case 'json':
content = formatAsJSON(messages);
mimeType = 'application/json';
filename = `grok-FULL-conversation-${messages.length}msgs-${timestamp}.json`;
break;
case 'pdf':
showNotification('📄 Generating document...', 0);
content = await formatAsPDF(messages);
mimeType = 'text/plain';
filename = `grok-FULL-conversation-${messages.length}msgs-${timestamp}.txt`;
break;
default:
content = formatAsText(messages);
mimeType = 'text/plain';
filename = `grok-FULL-conversation-${messages.length}msgs-${timestamp}.txt`;
}
const success = downloadFile(content, filename, mimeType);
if (success) {
hideMenu();
const formatName = format === 'pdf' ? 'Document' : format.toUpperCase();
showNotification(`✅ Exported ${messages.length} messages as ${formatName}`, 5000);
}
} catch (error) {
console.error('Export failed:', error);
alert(`Export failed: ${error.message}\n\nCheck the browser console for more details.`);
} finally {
isExporting = false;
}
}
// Show notification
function showNotification(message, duration = 3000) {
const existing = document.getElementById('grok-export-notification');
if (existing) existing.remove();
const notification = document.createElement('div');
notification.id = 'grok-export-notification';
notification.textContent = message;
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #1DA1F2;
color: white;
padding: 12px 20px;
border-radius: 8px;
z-index: 10000;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
max-width: 300px;
`;
document.body.appendChild(notification);
if (duration > 0) {
setTimeout(() => {
if (notification.parentElement) {
notification.parentElement.removeChild(notification);
}
}, duration);
}
}
// Create export menu
function createExportMenu() {
const menu = document.createElement('div');
menu.id = 'grok-export-menu';
menu.style.cssText = `
position: fixed;
bottom: 70px;
right: 10px;
background: #ffffff;
border: 2px solid #1DA1F2;
border-radius: 12px;
box-shadow: 0 8px 25px rgba(0,0,0,0.2);
padding: 12px;
z-index: 1000;
display: none;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
min-width: 220px;
color: #333333 !important;
`;
// Add title
const title = document.createElement('div');
title.textContent = 'Export Grok Conversation';
title.style.cssText = `
font-weight: 600;
color: #1DA1F2 !important;
margin-bottom: 8px;
padding-bottom: 8px;
border-bottom: 1px solid #eee;
font-size: 14px;
`;
menu.appendChild(title);
const formats = [
{ ext: 'md', name: 'Markdown', icon: '📝', desc: 'Rich formatting' },
{ ext: 'txt', name: 'Plain Text', icon: '📄', desc: 'Universal format' },
{ ext: 'json', name: 'JSON Data', icon: '📊', desc: 'Structured data' },
{ ext: 'pdf', name: 'Document', icon: '📋', desc: 'Formatted text file' },
{ ext: 'share', name: 'Share to X', icon: '🐦', desc: 'Post snippets to X' }
];
formats.forEach(format => {
const button = document.createElement('button');
button.innerHTML = `
<div style="display: flex; align-items: center;">
<span style="font-size: 16px; margin-right: 8px;">${format.icon}</span>
<div>
<div style="font-weight: 500; color: #333333 !important;">${format.name}</div>
<div style="font-size: 11px; color: #666 !important;">${format.desc}</div>
</div>
</div>
`;
button.style.cssText = `
display: block;
width: 100%;
padding: 10px;
margin: 4px 0;
border: none;
background: transparent;
text-align: left;
cursor: pointer;
border-radius: 6px;
font-size: 14px;
color: #333333 !important;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
transition: background-color 0.2s;
`;
button.onmouseover = () => {
button.style.background = format.ext === 'pdf' ? '#fff3cd' :
format.ext === 'share' ? '#e3f2fd' : '#f8f9ff';
};
button.onmouseout = () => {
button.style.background = 'transparent';
};
button.onclick = () => exportConversation(format.ext);
menu.appendChild(button);
});
// Debug button
if (CONFIG.debug) {
const debugButton = document.createElement('button');
debugButton.innerHTML = '🔍 Debug Info';
debugButton.style.cssText = `
display: block;
width: 100%;
padding: 8px 12px;
margin: 8px 0 4px 0;
border: none;
background: transparent;
text-align: left;
cursor: pointer;
border-radius: 4px;
font-size: 13px;
border-top: 1px solid #eee;
color: #666 !important;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
`;
debugButton.onclick = () => {
const messages = getConversationData();
const speakerStats = {
human: messages.filter(m => m.speaker === 'Human').length,
grok: messages.filter(m => m.speaker === 'Grok').length
};
console.log('Grok Debug Info:', {
messagesFound: messages.length,
speakerDistribution: speakerStats,
url: window.location.href,
sampleMessages: messages.slice(0, 5).map(m => ({
speaker: m.speaker,
length: m.length,
preview: m.content.substring(0, 50) + '...',
debugInfo: m.debugInfo
}))
});
alert(`Found ${messages.length} messages\nHuman: ${speakerStats.human}, Grok: ${speakerStats.grok}\nCheck console for details.`);
hideMenu();
};
menu.appendChild(debugButton);
}
return menu;
}
// Show/hide menu
function toggleMenu() {
const menu = document.getElementById('grok-export-menu');
if (menu) {
menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
}
}
function hideMenu() {
const menu = document.getElementById('grok-export-menu');
if (menu) menu.style.display = 'none';
}
// Create main export button
function createExportButton() {
const button = document.createElement('button');
button.innerHTML = `🤖 Export Grok`;
button.id = 'grok-export-button';
button.style.cssText = `
position: fixed;
bottom: 10px;
right: 10px;
padding: 10px 16px;
background: linear-gradient(135deg, #1DA1F2 0%, #0084b4 100%);
border: 2px solid rgba(255,255,255,0.3);
color: white;
text-align: center;
display: inline-block;
font-size: 14px;
font-weight: 600;
cursor: pointer;
border-radius: 30px;
z-index: 999;
box-shadow: 0 4px 15px rgba(29,161,242,0.3);
transition: all 0.3s ease;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
`;
button.onmouseover = () => {
button.style.transform = 'translateY(-3px) scale(1.05)';
button.style.boxShadow = '0 6px 20px rgba(29,161,242,0.4)';
};
button.onmouseout = () => {
button.style.transform = 'translateY(0) scale(1)';
button.style.boxShadow = '0 4px 15px rgba(29,161,242,0.3)';
};
button.onclick = toggleMenu;
return button;
}
// Initialize the script
function init() {
debugLog('Initializing Enhanced Grok Export v2.2...');
// Remove existing elements
const existingButton = document.getElementById('grok-export-button');
const existingMenu = document.getElementById('grok-export-menu');
if (existingButton) existingButton.remove();
if (existingMenu) existingMenu.remove();
// Create UI elements
const button = createExportButton();
const menu = createExportMenu();
document.body.appendChild(button);
document.body.appendChild(menu);
// Close menu when clicking outside
document.addEventListener('click', function(e) {
if (!e.target.closest('#grok-export-menu') &&
!e.target.closest('#grok-export-button')) {
hideMenu();
}
});
debugLog('Enhanced Grok Export v2.2 initialized successfully!');
console.log('%c✅ Enhanced Grok Export v2.2 Ready!', 'color: green; font-weight: bold;');
console.log('%c🔧 Fixed: Content-Based Speaker Detection & PDF Export', 'color: blue; font-weight: bold;');
}
// Wait for page to be ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
setTimeout(init, 1000);
}
// Re-initialize on navigation
let lastUrl = location.href;
new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
setTimeout(init, 2000);
}
}).observe(document, { subtree: true, childList: true });
})();