Skip to content

Instantly share code, notes, and snippets.

@manuc66
Last active August 13, 2025 21:44
Show Gist options
  • Select an option

  • Save manuc66/9da50e3d34b026e0a770f2842a147aef to your computer and use it in GitHub Desktop.

Select an option

Save manuc66/9da50e3d34b026e0a770f2842a147aef to your computer and use it in GitHub Desktop.
Export All DuckDuckGo AI Chats - UserScript
// ==UserScript==
// @name Export All DuckDuckGo AI Chats
// @namespace http://tampermonkey.net/
// @version 2.1
// @description Adds a minimal export button with auto-daily export when new chats are detected
// @author manuc66
// @match *://duckduckgo.com/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
// Configuration
const STORAGE_KEYS = {
LAST_EXPORT_DATE: 'aiChatExporter_lastExportDate',
LAST_MOST_RECENT_EDIT: 'aiChatExporter_lastMostRecentEdit',
DEVICE_ID_KEY: 'aiChatExporter_deviceID'
};
function getDeviceID() {
let deviceID = localStorage.getItem(STORAGE_KEYS.DEVICE_ID_KEY);
if (!deviceID) {
// Generate a new device ID (UUID format)
deviceID = 'device-' + Date.now() + '-' + Math.random().toString(36).slice(2, 11);
localStorage.setItem(STORAGE_KEYS.DEVICE_ID_KEY, deviceID);
}
return deviceID;
}
// Utility functions
function getFormattedDateTime() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day}_${hours}-${minutes}`;
}
function getTodayDateString() {
const now = new Date();
return now.toDateString(); // Returns format like "Wed Oct 25 2023"
}
function getMostRecentChatEdit(savedAIChats) {
try {
const data = JSON.parse(savedAIChats);
if (!data.chats || !Array.isArray(data.chats)) {
return null;
}
const mostRecentEdit = data.chats.reduce((max, chat) =>
chat.lastEdit > max ? chat.lastEdit : max,
"1970-01-01T00:00:00Z"
);
return new Date(mostRecentEdit);
} catch (error) {
console.error('Error getting most recent chat edit:', error);
return null;
}
}
function getExportFileName() {
const datetime = getFormattedDateTime();
const deviceID = getDeviceID();
return `savedAIChats_${datetime}_${deviceID}.json`;
}
function exportChats(isAutomatic = false) {
const savedAIChats = localStorage.getItem('savedAIChats');
if (!savedAIChats) {
if (!isAutomatic) {
alert('No savedAIChats found in local storage.');
}
return false;
}
try {
const data = JSON.parse(savedAIChats);
const filename = getExportFileName();
const blob = new Blob([JSON.stringify(data, null, 2)], {type: 'application/json'});
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
// Programmatically click the link to trigger the download
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Update tracking data
const today = getTodayDateString();
const mostRecentEdit = getMostRecentChatEdit(savedAIChats);
const chatCount = data.chats ? data.chats.length : 0;
localStorage.setItem(STORAGE_KEYS.LAST_EXPORT_DATE, today);
if (mostRecentEdit) {
localStorage.setItem(STORAGE_KEYS.LAST_MOST_RECENT_EDIT, mostRecentEdit.toISOString());
}
if (isAutomatic) {
showNotification(`Auto-exported ${chatCount} chats (${filename})`);
}
return true;
} catch (error) {
console.error('Export failed:', error);
if (!isAutomatic) {
alert('Export failed: ' + error.message);
}
return false;
}
}
function showNotification(message) {
const notification = document.createElement('div');
notification.textContent = message;
notification.style.position = 'fixed';
notification.style.top = '20px';
notification.style.right = '20px';
notification.style.backgroundColor = '#28a745';
notification.style.color = 'white';
notification.style.padding = '10px 15px';
notification.style.borderRadius = '5px';
notification.style.zIndex = '10000';
notification.style.fontSize = '14px';
notification.style.maxWidth = '300px';
notification.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)';
notification.style.opacity = '0';
notification.style.transition = 'opacity 0.3s ease';
document.body.appendChild(notification);
// Fade in
setTimeout(() => notification.style.opacity = '1', 100);
// Fade out and remove after 4 seconds
setTimeout(() => {
notification.style.opacity = '0';
setTimeout(() => document.body.removeChild(notification), 300);
}, 4000);
}
function checkForNewChats() {
const savedAIChats = localStorage.getItem('savedAIChats');
if (!savedAIChats) return false;
try {
const currentMostRecentEdit = getMostRecentChatEdit(savedAIChats);
if (!currentMostRecentEdit) return false;
const lastExportDate = localStorage.getItem(STORAGE_KEYS.LAST_EXPORT_DATE);
const lastMostRecentEditStr = localStorage.getItem(STORAGE_KEYS.LAST_MOST_RECENT_EDIT);
const today = getTodayDateString();
const isNewDay = lastExportDate !== today;
let hasNewChats = false;
if (lastMostRecentEditStr) {
const lastMostRecentEdit = new Date(lastMostRecentEditStr);
hasNewChats = currentMostRecentEdit > lastMostRecentEdit;
} else {
// First time running, consider it as having new chats
hasNewChats = true;
}
return isNewDay && hasNewChats;
} catch (error) {
console.error('Error checking for new chats:', error);
return false;
}
}
function startAutoExportCheck() {
// Check immediately
if (checkForNewChats()) {
exportChats(true);
}
// Then check every 30 minutes
setInterval(() => {
if (checkForNewChats()) {
exportChats(true);
}
}, 30 * 60 * 1000); // 30 minutes
}
// Create the export button
const exportButton = document.createElement('button');
exportButton.innerText = '💾';
exportButton.title = 'Export AI Chats (Auto-exports daily when new chats detected)';
exportButton.id = 'ai-chat-export-btn';
// Base styles for the button
exportButton.style.position = 'fixed';
exportButton.style.top = '50%';
exportButton.style.right = '10px';
exportButton.style.transform = 'translateY(-50%)';
exportButton.style.width = '40px';
exportButton.style.height = '40px';
exportButton.style.padding = '0';
exportButton.style.backgroundColor = '#007bff';
exportButton.style.color = '#fff';
exportButton.style.border = 'none';
exportButton.style.borderRadius = '50%';
exportButton.style.cursor = 'pointer';
exportButton.style.zIndex = '9999';
exportButton.style.fontSize = '16px';
exportButton.style.display = 'flex';
exportButton.style.alignItems = 'center';
exportButton.style.justifyContent = 'center';
exportButton.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
exportButton.style.transition = 'all 0.2s ease';
// Add hover effects
exportButton.addEventListener('mouseenter', function () {
this.style.backgroundColor = '#0056b3';
this.style.transform = 'translateY(-50%) scale(1.1)';
});
exportButton.addEventListener('mouseleave', function () {
this.style.backgroundColor = '#007bff';
this.style.transform = 'translateY(-50%) scale(1)';
});
// Responsive styles using CSS
const style = document.createElement('style');
style.innerHTML = `
@media (max-width: 600px) {
#ai-chat-export-btn {
width: 35px !important;
height: 35px !important;
font-size: 14px !important;
right: 8px !important;
}
}
@media (max-width: 400px) {
#ai-chat-export-btn {
width: 30px !important;
height: 30px !important;
font-size: 12px !important;
right: 5px !important;
}
}
`;
document.head.appendChild(style);
// Append the button to the body
document.body.appendChild(exportButton);
// Add click event to the button
exportButton.addEventListener('click', function () {
// Add click animation
this.style.transform = 'translateY(-50%) scale(0.95)';
setTimeout(() => {
this.style.transform = 'translateY(-50%) scale(1)';
}, 100);
exportChats(false);
});
// Start the auto-export monitoring
// Wait a bit for the page to fully load
setTimeout(startAutoExportCheck, 3000);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment