|
// ==UserScript== |
|
// @name Yodayo Chat Downloader (Markdown) |
|
// @namespace http://tampermonkey.net/ |
|
// @version 2.3 |
|
// @description Adds a floating button to Yodayo chat windows to download the chat log as a Markdown file. |
|
// @match https://yodayo.com/tavern/chat/* |
|
// @grant none |
|
// @license MIT |
|
// @downloadURL https://gist.githubusercontent.com/Thomashighbaugh/8ceba38e46349c2039ba76c62fa9f2aa/raw/7b65c1a383dd8848a9a36d467c9ff64531f1e93e/yodayo-message-downloader-markdown.user.js |
|
// @updateURL https://gist.githubusercontent.com/Thomashighbaugh/8ceba38e46349c2039ba76c62fa9f2aa/raw/7b65c1a383dd8848a9a36d467c9ff64531f1e93e/yodayo-message-downloader-markdown.user.js |
|
// ==/UserScript== |
|
|
|
(function() { |
|
'use strict'; |
|
|
|
// Utility function to trigger a download |
|
function downloadString(text, fileType, fileName) { |
|
const blob = new Blob([text], { |
|
type: fileType |
|
}); |
|
const a = document.createElement('a'); |
|
a.download = fileName; |
|
a.href = URL.createObjectURL(blob); |
|
a.style.display = "none"; |
|
document.body.appendChild(a); |
|
a.click(); |
|
document.body.removeChild(a); |
|
setTimeout(() => { |
|
URL.revokeObjectURL(a.href); |
|
}, 1500); |
|
} |
|
|
|
// Creates and manages the floating download button |
|
function createFloatingDownloadButton() { |
|
if (document.getElementById('yodayo-markdown-downloader-btn')) return; |
|
console.log("Creating Yodayo download button."); |
|
|
|
const button = document.createElement('button'); |
|
button.id = 'yodayo-markdown-downloader-btn'; |
|
button.innerHTML = '⬇️ Markdown'; |
|
button.title = 'Download Chat as Markdown'; |
|
|
|
// --- Styling --- |
|
Object.assign(button.style, { |
|
position: 'fixed', |
|
bottom: '20px', |
|
right: '20px', |
|
zIndex: '9999', |
|
backgroundColor: '#6c47ff', |
|
color: 'white', |
|
border: 'none', |
|
borderRadius: '6px', |
|
width: '3rem', |
|
height: '3rem', |
|
fontSize: '16px', |
|
cursor: 'pointer', |
|
boxShadow: '0 4px 8px rgba(0,0,0,0.72)', |
|
display: 'flex', |
|
justifyContent: 'center', |
|
alignItems: 'center', |
|
transition: 'transform 0.52s ease-in-out' |
|
}); |
|
|
|
button.onmouseover = () => button.style.transform = 'scale(1.1)'; |
|
button.onmouseout = () => button.style.transform = 'scale(1.0)'; |
|
document.body.appendChild(button); |
|
|
|
// --- Click Event Handler --- |
|
button.addEventListener('click', async () => { |
|
console.log('Download button clicked.'); |
|
const url = window.location.href; |
|
const id = url.split('/').find(part => part.length === 36 && part.includes('-')); |
|
|
|
if (!id) { |
|
alert('Could not determine chat ID from URL.'); |
|
return; |
|
} |
|
|
|
const chatInfoUrl = `https://api.yodayo.com/v1/chats/${id}`; |
|
const messagesUrl = `https://api.yodayo.com/v1/chats/${id}/messages?limit=99999`; |
|
|
|
button.innerHTML = '...'; |
|
button.disabled = true; |
|
|
|
try { |
|
const [chatInfoRes, messagesRes] = await Promise.all([ |
|
fetch(chatInfoUrl, { |
|
credentials: 'include' |
|
}), |
|
fetch(messagesUrl, { |
|
credentials: 'include' |
|
}) |
|
]); |
|
|
|
if (!chatInfoRes.ok || !messagesRes.ok) { |
|
throw new Error(`API request failed. Statuses: ${chatInfoRes.status}, ${messagesRes.status}`); |
|
} |
|
|
|
const chatInfo = await chatInfoRes.json(); |
|
const messagesData = await messagesRes.json(); |
|
|
|
// --- CORRECTED LOGIC BASED ON YOUR LOG --- |
|
if (!chatInfo || !Array.isArray(chatInfo.characters) || chatInfo.characters.length === 0) { |
|
console.error("API Response (Chat Info):", chatInfo); |
|
throw new Error('Chat info response is missing the expected "characters" array.'); |
|
} |
|
if (!messagesData || !Array.isArray(messagesData.messages)) { |
|
console.error("API Response (Messages):", messagesData); |
|
throw new Error('Messages response is missing the expected "messages" array.'); |
|
} |
|
|
|
// Access the name from the first element of the 'characters' array |
|
const characterName = chatInfo.characters[0].name || 'Character'; |
|
const userName = (chatInfo.user_persona && chatInfo.user_persona.name) || 'You'; |
|
|
|
const messagesInOrder = messagesData.messages.reverse(); |
|
|
|
const markdownContent = messagesInOrder.map(message => { |
|
const sender = message.message_source === 'USER' ? userName : characterName; |
|
return `**${sender}:**\n${message.message}`; |
|
}).join('\n\n---\n\n'); |
|
|
|
const fullLog = `# Chat with ${characterName}\n\n${markdownContent}`; |
|
const safeCharName = characterName.replace(/[/\\?%*:|"<>]/g, '-'); |
|
const fileName = `${safeCharName}_${id}_chat.md`; |
|
|
|
downloadString(fullLog, 'text/markdown', fileName); |
|
|
|
} catch (error) { |
|
console.error('Error fetching or processing chat messages:', error); |
|
alert('Failed to download chat logs. Check the developer console (F12) for details.'); |
|
} finally { |
|
button.innerHTML = '⬇️ MD'; |
|
button.disabled = false; |
|
} |
|
}); |
|
} |
|
|
|
// Use a MutationObserver to wait for the chat UI to be ready |
|
const observer = new MutationObserver((mutations, obs) => { |
|
const chatInput = document.querySelector('textarea[placeholder*="Message"]'); |
|
if (chatInput) { |
|
console.log("Chat input found, initializing button."); |
|
createFloatingDownloadButton(); |
|
obs.disconnect(); |
|
} |
|
}); |
|
|
|
console.log("Yodayo Downloader: Observing for chat UI..."); |
|
observer.observe(document.body, { |
|
childList: true, |
|
subtree: true |
|
}); |
|
})(); |