// ==UserScript==
// @match *
// @name AI内容捕获模块
// @version 3.0
// @license MIT
// @namespace http://tampermonkey.net/ // Optional: Change to your namespace (e.g., your website or GitHub)
// @description 在页面上添加一个可拖动、可折叠、带标签切换和新控件的紫色主题交互式面板。
// ==/UserScript==
var ContentCaptureModule = (function() {
'use strict';
const SCRIPT_NAME = 'AI内容捕获模块 v1.2';
let DEBUG = false; // 可由 init 配置
// --- 默认配置 ---
const DEFAULT_CONFIG = {
debug: false,
chatContainerSelector: '.mx-auto.pt-24.w-full.max-w-\\[720px\\].px-4.relative',
aiMessageSelector: '#ai-chat-answer',
aiReplyBodySelector: '.markdown-body',
integralTextPattern: /消耗\s*\d+\s*积分/,
sectionsToExtract: [
{ key: 'thinking', selector: 'div.thinking-area', description: '思考区', extractMethod: 'innerHTML' },
{ key: 'textBody', selector: 'div.text-body', description: '回复主体', extractMethod: 'innerHTML' },
{ key: 'statusBar', selector: 'div.status-bar', description: '状态栏', extractMethod: 'innerHTML' },
{ key: 'memoryArea', selector: 'div.memory-area', description: '记忆区', extractMethod: 'innerHTML' },
{ key: 'optionsArea', selector: 'div.options-area ol', description: '选项区列表', extractMethod: 'innerHTML' } // 直接取 ol 的内容
],
optionItemSelector: 'div.options-area ol li', // 用于提取 optionTexts
optionTextRegex: /\[(.*?)\]/, // 用于提取选项文本
maxObserverRetries: 5,
observerRetryDelayBase: 2000
};
// --- 模块状态变量 ---
let mainObserver = null;
let currentContentObserver = null;
let watchingAiNode = null;
let latestFullyLoadedAiNode = null;
let observerRetryCount = 0;
let currentConfig = { ...DEFAULT_CONFIG };
let updateCallback = null; // 由 init 传入
let isRunning = false;
// --- 工具函数 ---
const log = (...args) => currentConfig.debug && console.log(`[${SCRIPT_NAME}]`, ...args);
const logInfo = (...args) => console.log(`[${SCRIPT_NAME}]`, ...args); // 关键信息
const logError = (...args) => console.error(`[${SCRIPT_NAME}] [ERROR]`, ...args);
// --- 核心功能:提取内容 (修改自 extractAndLogContent) ---
function extractContent(aiMessageNode) {
if (!aiMessageNode) { /* ... 错误处理 ... */ return null; }
log(`Starting content extraction from node: ${aiMessageNode.id}`);
const contentBody = aiMessageNode.querySelector(currentConfig.aiReplyBodySelector);
if (!contentBody) { /* ... 错误处理 ... */ return null; }
const extractedData = {
timestamp: new Date().toISOString(),
messageId: aiMessageNode.id || 'N/A',
// rawHtmlBody: contentBody.innerHTML, // 可以移除或保留用于调试
sections: {}
};
currentConfig.sectionsToExtract.forEach(config => {
const sectionElement = contentBody.querySelector(config.selector);
if (sectionElement) {
if (config.extractMethod === 'innerHTML') {
extractedData.sections[config.key] = sectionElement.innerHTML.trim();
} else { // 默认或指定 innerText
extractedData.sections[config.key] = sectionElement.innerText.trim();
}
log(` -> Extracted [${config.key}] using ${config.extractMethod}`);
} else {
extractedData.sections[config.key] = null; // 用 null 表示未找到,而不是字符串
log(` -> Section [${config.key}] not found using selector: ${config.selector}`);
}
});
// 单独提取选项文本 (使用配置)
const optionItems = contentBody.querySelectorAll(currentConfig.optionItemSelector);
if (optionItems.length > 0) {
extractedData.sections.optionTexts = Array.from(optionItems).map(li => {
const textContent = li.textContent?.trim();
const match = currentConfig.optionTextRegex ? textContent.match(currentConfig.optionTextRegex) : null;
// 如果正则匹配到,取捕获组1;否则取整个文本;如果正则为null,也取整个文本
return match ? match[1].trim() : textContent;
}).filter(text => text);
log(` -> Extracted specific option texts:`, extractedData.sections.optionTexts);
} else {
extractedData.sections.optionTexts = []; // 未找到选项 li 或列表容器
log(` -> No option items found using selector: ${currentConfig.optionItemSelector}`);
}
log("Content extraction complete:", extractedData);
return extractedData; // 返回数据,不在此处 log
}
// --- 清理内容观察器 ---
function disconnectContentObserver() { /* ... 代码基本不变 ... */ }
// --- 内部内容观察器的回调 ---
const contentObserverCallback = function(mutationsList, observer) {
if (!watchingAiNode || !updateCallback) return; // 增加 updateCallback 检查
let integralFound = currentConfig.integralTextPattern.test(watchingAiNode.textContent);
if (integralFound) {
log(`Completion pattern found in node: ${watchingAiNode.id}. Finalizing capture.`);
latestFullyLoadedAiNode = watchingAiNode;
disconnectContentObserver();
const data = extractContent(latestFullyLoadedAiNode); // 获取数据
if (data) {
log("Invoking updateCallback with extracted data.");
updateCallback(data); // **调用回调函数**
} else {
logError("Extraction failed after completion detected.");
}
}
};
// --- 主 MutationObserver 回调 ---
const mainObserverCallback = function(mutationsList, observer) {
if (!updateCallback) return; // 增加 updateCallback 检查
let newAiMessageNode = null;
// ... (查找 newAiMessageNode 的逻辑不变, 使用 currentConfig.aiMessageSelector) ...
if (newAiMessageNode) {
log(`New AI message node detected: ${newAiMessageNode.id || '(no id)'}`);
disconnectContentObserver();
watchingAiNode = newAiMessageNode;
if (currentConfig.integralTextPattern.test(watchingAiNode.textContent)) {
log(`Completion pattern found immediately in new node: ${watchingAiNode.id}.`);
latestFullyLoadedAiNode = watchingAiNode;
const data = extractContent(latestFullyLoadedAiNode);
if (data) {
log("Invoking updateCallback for immediately complete node.");
updateCallback(data); // **调用回调函数**
} else {
logError("Extraction failed for immediately complete node.");
}
watchingAiNode = null; // 处理完毕
} else {
log(`Setting up content observer for node: ${watchingAiNode.id}`);
// ... (设置 currentContentObserver 的逻辑不变) ...
}
}
};
// --- 查找并设置主观察器 ---
function setupObserver() {
const targetNode = document.querySelector(currentConfig.chatContainerSelector);
if (targetNode) {
log(`Starting Main Observer on ${currentConfig.chatContainerSelector}.`);
// ... (设置 mainObserver 的逻辑不变, 使用 mainObserverCallback) ...
mainObserver.observe(targetNode, { childList: true, subtree: false });
observerRetryCount = 0;
isRunning = true; // 标记为运行中
log('Main Observer started.');
// 初始加载检查 (逻辑不变, 但提取后调用回调)
const existingAiMessages = targetNode.querySelectorAll(currentConfig.aiMessageSelector);
if (existingAiMessages.length > 0) {
const lastExistingMessage = existingAiMessages[existingAiMessages.length - 1];
if (currentConfig.integralTextPattern.test(lastExistingMessage.textContent)) {
latestFullyLoadedAiNode = lastExistingMessage;
const data = extractContent(latestFullyLoadedAiNode);
if (data && updateCallback) { // 检查回调是否存在
log("Invoking updateCallback for existing complete message on load.");
updateCallback(data); // **调用回调函数**
} else if (!data) {
logError("Extraction failed for existing complete message on load.");
}
} else {
// ... (设置 currentContentObserver 观察现有未完成消息的逻辑不变) ...
}
}
} else {
observerRetryCount++;
logError(`Chat container not found. Retry ${observerRetryCount}/${currentConfig.maxObserverRetries}...`);
if (observerRetryCount < currentConfig.maxObserverRetries) {
setTimeout(setupObserver, currentConfig.observerRetryDelayBase * observerRetryCount);
} else {
logError(`Failed to find chat container. Observer not started.`);
isRunning = false; // 标记为未运行
}
}
}
// --- 公共接口方法 ---
function init(config = {}, callback) {
currentConfig = { ...DEFAULT_CONFIG, ...config }; // 合并配置
DEBUG = currentConfig.debug; // 更新 DEBUG 标志
if (typeof callback !== 'function') {
logError("Initialization failed: updateCallback must be a function.");
return false;
}
updateCallback = callback;
logInfo("ContentCaptureModule initialized. Config:", currentConfig);
return true;
}
function start() {
if (isRunning) {
log("Observer already running.");
return;
}
if (!updateCallback) {
logError("Cannot start: Module not initialized with a callback function.");
return;
}
logInfo("Starting content observation...");
observerRetryCount = 0; // 重置重试计数器
setupObserver(); // 开始尝试设置观察器
}
function stop() {
logInfo("Stopping content observation...");
disconnectContentObserver();
if (mainObserver) {
mainObserver.disconnect();
mainObserver = null;
log('Main Observer disconnected.');
}
isRunning = false; // 标记为停止
watchingAiNode = null;
// latestFullyLoadedAiNode 保留,refresh 可能会用到
}
function refresh() {
logInfo("Manual refresh triggered.");
if (!updateCallback) {
logError("Cannot refresh: Module not initialized.");
return Promise.reject("Module not initialized.");
}
const aiMessages = document.querySelectorAll(currentConfig.aiMessageSelector);
if (aiMessages.length > 0) {
const latestMessage = aiMessages[aiMessages.length - 1];
log(`Found latest AI message (ID: ${latestMessage.id}) for refresh.`);
const data = extractContent(latestMessage); // 直接提取最新的
if (data) {
log("Invoking updateCallback with refreshed data.");
updateCallback(data);
return Promise.resolve(data); // 返回提取到的数据
} else {
logError("Extraction failed during manual refresh.");
return Promise.reject("Extraction failed during refresh.");
}
} else {
logInfo("No AI messages found on page for refresh.");
// 可以选择调用回调传递一个空状态,或者什么都不做
// updateCallback({ timestamp: new Date().toISOString(), sections: {}, isEmpty: true });
return Promise.resolve(null); // 表示没有内容可刷新
}
}
// 清理事件监听器
window.addEventListener('beforeunload', stop);
// --- 返回模块的公共接口 ---
return {
init: init,
start: start,
stop: stop,
refresh: refresh,
isRun: () => { // 添加一个状态检查方法
return isRunning;
}
};
})();