可以导出 claude ai当前对话的内容,以及统计当前字数 (包括粘贴、上传、article)。
当前为
// ==UserScript==
// @name Claude对话导出和字数统计
// @version 0.5
// @description 可以导出 claude ai当前对话的内容,以及统计当前字数 (包括粘贴、上传、article)。
// @author Yearly
// @match https://claude.ai/*
// @include https://*claude*.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=claude.ai
// @license AGPL-v3.0
// @namespace https://greasyfork.org/zh-CN/scripts/502829-claude-helper
// @supportURL https://greasyfork.org/zh-CN/scripts/502829-claude-helper
// @homepageURL https://greasyfork.org/zh-CN/scripts/502829-claude-helper
// ==/UserScript==
(function() {
var last_uuid = '', last_length = 0;
function get_msg_count() {
let mainScreen = document.querySelector("body > div.flex.min-h-screen.w-full > div > div.flex.h-screen") ;
if(!mainScreen) return;
let tx_cnts = 0, tx_sz = 0;
let rx_cnts = 0, rx_sz = 0;
let fp_cnts = 0, fp_sz = 0, img_cnts = 0;
let i = 0;
let reactProps = Object.keys(mainScreen).find(key => key.startsWith('__reactProps$'));
if (!reactProps) return null;
let msgProps = mainScreen[reactProps];
let Msgs = (msgProps.children[0]?.props?.messages);
if (Msgs && Msgs.length > 0) {
let newest_msgs = Msgs[Msgs.length-1];
let uuid = newest_msgs.uuid;
let length = newest_msgs.text.length;
if (uuid == last_uuid && length == last_length) {
return null;
}
last_uuid = uuid;
last_length = length;
} else {
return null;
}
Msgs.forEach(function(msg){
if(msg.sender == "human") {
tx_cnts +=1;
tx_sz += msg.text.length;
for(i = 0; i < msg.attachments.length; i++) {
tx_sz += msg.attachments[i].file_size;
fp_cnts += 1;
fp_sz += msg.attachments[i].file_size;;
}
img_cnts += msg.files.length;
} else if(msg.sender == "assistant") {
rx_cnts +=1;
rx_sz += msg.text.length;
}
});
GM_log("msg countor");
return {
tx_cnts: tx_cnts, tx_sz: tx_sz,
rx_cnts: rx_cnts, rx_sz: rx_sz,
fp_cnts: fp_cnts, fp_sz: fp_sz,
img_cnts: img_cnts,
};
}
function msg_counter_main() {
let fieldset = document.querySelector("body > div.flex.min-h-screen.w-full fieldset");
if (fieldset) {
let ret = get_msg_count();
if(!ret) return;
let count_result = document.querySelector("#claude-msg-counter")
if(!count_result) {
count_result = document.createElement("pre");
count_result.id = "claude-msg-counter";
count_result.className="border-0.5 relative z-[5] text-text-200 border-accent-pro-100/20 bg-accent-pro-900 rounded-t-xl border-b-0"
count_result.style = "font-size:12px; padding: 5px 7px 14px; margin:-12px 0; text-wrap: pretty;";
if (fieldset.querySelector("div.flex.md\\:px-2.flex-col-reverse > div") ){
fieldset.querySelector("div.flex.md\\:px-2.flex-col-reverse > div").remove();
}
fieldset.querySelector("div.flex.md\\:px-2.flex-col-reverse").append(count_result);
}
let all_length = ret.tx_sz + ret.rx_sz ;
let file_info = ""
let img_file_info = ""
if (ret.fp_cnts) file_info = ` (包含${ret.fp_cnts}个上传或粘贴文本,${ret.fp_sz}字)`
if (ret.img_cnts) img_file_info = ` (另有${ret.img_cnts}个非文本内容的上传或粘贴,不能计量字数)`
count_result.innerText = `【统计】已发出:${ret.tx_cnts}条,${ret.tx_sz}字${file_info}; 已回复:${ret.rx_cnts}条,${ret.rx_sz}字; 总计:${all_length}字。${img_file_info}`;
}
}
setInterval(() => {
msg_counter_main();
}, 1600);
// Add Download Button
function createPersistentElement(selector, createElementCallback) {
function ensureElement() {
const targetElement = document.querySelector(selector);
if (targetElement) {
if (!targetElement.querySelector('.-added-element')) {
const newElement = createElementCallback();
newElement.classList.add('-added-element');
targetElement.appendChild(newElement);
}
}
}
ensureElement();
const observer = new MutationObserver(() => {
ensureElement();
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
function get_msg_context() {
let context = "";
let mainScreen = document.querySelector("body > div.flex.min-h-screen.w-full > div > div.flex.h-screen") ;
if(!mainScreen) return;
let tx_cnts = 0, tx_sz = 0;
let rx_cnts = 0, rx_sz = 0;
let fp_cnts = 0, fp_sz = 0;
let i = 0;
let reactProps = Object.keys(mainScreen).find(key => key.startsWith('__reactProps$'));
if (!reactProps) return null;
let msgProps = mainScreen[reactProps];
let convID = (msgProps.children[0]?.props?.conversationUUID);
let name = (msgProps.children[0]?.props?.name);
let Msgs = (msgProps.children[0]?.props?.messages);
if ( !convID || !name || !Msgs && !Msgs.length <= 0) {
return null;
}
context += `# conversation name:${name}\n# conversationUUID: ${convID}\n`;
Msgs.forEach(function(msg){
context += `\n## ${msg.sender}:\n\n`
context += msg.text + '\n'
for(i = 0; i < msg.attachments.length; i++) {
context += `file: ${msg.attachments[i].file_name}\n`
if(msg.attachments[i].extracted_content) {
context += `file_context: ${msg.attachments[i].extracted_content}\n`;
}
}
for(i = 0; i < msg.files.length; i++) {
context += `file: ${msg.files[i].file_name}\n`
if(msg.files[i].preview_url) {
context += `preview_url: ${window.location.origin + msg.files[i].preview_url}\n`;
}
}
context += `\n------------------------------------------------------\n`
});
GM_log("msg countor");
let blob = new Blob([context], {type: 'text/plain;charset=utf-8'});
let fileUrl = URL.createObjectURL(blob);
let tempLink = document.createElement('a');
tempLink.href = fileUrl;
let fileTitle = "Claude.AI_Export_" + name.replaceAll(' ','_') + ".md";
tempLink.setAttribute('download', fileTitle);
tempLink.style.display = 'none';
document.body.appendChild(tempLink);
tempLink.click();
document.body.removeChild(tempLink);
URL.revokeObjectURL(fileUrl);
return;
}
function createDownloadButton() {
const button = document.createElement("button");
button.className = "inline-flex items-center justify-center relative shrink-0 ring-offset-2 ring-offset-bg-300 ring-accent-main-100 focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none disabled:drop-shadow-none text-text-200 transition-all font-styrene active:bg-bg-400 hover:bg-bg-500/40 hover:text-text-100 h-9 w-9 rounded-md active:scale-95 shrink-0";
button.innerHTML = `<svg width="20" height="20" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" fill="none"><path stroke="#535358" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M27 7H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h22a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2"/><path stroke="#535358" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 20v-8l-4 4-4-4v8m12-3.5 3.5 3.5 3.5-3.5M22.5 20v-9"/></svg>`;
button.title="Download Conversation"
button.addEventListener("click", () => {
get_msg_context();
});
return button;
}
// 添加按钮
createPersistentElement("body > div.flex.min-h-screen.w-full div.sticky.items-center div.right-3 div.hidden.flex-row-reverse", createDownloadButton);
})();