您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
对axiom的推文监控,代币内推文,扫链出现的推文进行翻译
// ==UserScript== // @name Axiom推文翻译 // @namespace http://tampermonkey.net/ // @version 4.6 // @author @Gufii_666 // @description 对axiom的推文监控,代币内推文,扫链出现的推文进行翻译 // @match https://axiom.trade/pulse* // @match https://axiom.trade/trackers* // @match https://axiom.trade/meme/* // @match https://axiom.trade/discover* // @license MIT // @grant none // ==/UserScript== (function() { 'use strict'; const TRANSLATION_BASE_URL = 'https://translate.googleapis.com/translate_a/single'; const CLIENT_PARAM = 'gtx'; const SOURCE_LANG = 'auto'; const TARGET_LANG = 'zh-CN'; const DATA_TYPE = 't'; const TRANSLATED_TEXT_CLASS = 'localized-content-display'; const ORIGINAL_DATA_ATTR = 'data-translation-processed-status'; const ORIGINAL_TEXT_STORE_ATTR = 'data-original-text'; const UNIQUE_TRANSLATION_ID_ATTR = 'data-translation-id'; const ONGOING_TRANSLATIONS = new Map(); // Removed LAZY_SCAN_INTERVAL_MS and lazyScanTimer async function obtainLocalizedText(inputString) { if (!inputString || typeof inputString !== 'string') { return '[Translation Input Error]'; } const queryParams = new URLSearchParams({ client: CLIENT_PARAM, sl: SOURCE_LANG, tl: TARGET_LANG, dt: DATA_TYPE, q: inputString }); const fullUrl = `${TRANSLATION_BASE_URL}?${queryParams.toString()}`; try { const response = await fetch(fullUrl); const data = await response.json(); if (data && data[0] && Array.isArray(data[0])) { return data[0].map(segment => segment[0]).join(''); } throw new Error("Invalid translation response structure."); } catch (error) { console.error("Content localization failed:", error); return '[翻译失败]'; } } // Function to apply layout fixes to a specific element function applyLayoutFixes(element) { if (!element) return; const currentDisplay = getComputedStyle(element).display; if (currentDisplay.includes('inline') && !currentDisplay.includes('flex') && !currentDisplay.includes('grid')) { element.style.display = 'block'; } Object.assign(element.style, { maxHeight: "none", height: "auto", overflow: "visible", overflowY: "visible", overflowX: "visible", minHeight: "unset", flexShrink: "0", alignSelf: "stretch" }); const gradient = element.querySelector("div[class*='bg-gradient-to-b']"); if (gradient) { gradient.style.display = "none"; } } function updateTranslationBox(targetElement, statusOrContent, prepend = false) { if (!targetElement || !targetElement.parentElement) return; if (!targetElement.dataset.translationId) { targetElement.dataset.translationId = Math.random().toString(36).substring(2, 15); } const translationId = targetElement.dataset.translationId; let translationParagraph = targetElement.parentElement.querySelector(`p.${TRANSLATED_TEXT_CLASS}[${UNIQUE_TRANSLATION_ID_ATTR}="${translationId}"]`); if (!translationParagraph) { translationParagraph = document.createElement("p"); translationParagraph.classList.add(TRANSLATED_TEXT_CLASS); translationParagraph.setAttribute(UNIQUE_TRANSLATION_ID_ATTR, translationId); if (prepend) { targetElement.parentElement.insertBefore(translationParagraph, targetElement); } else { targetElement.parentElement.appendChild(translationParagraph); } } translationParagraph.textContent = statusOrContent; Object.assign(translationParagraph.style, { color: "#FFFFFF", fontSize: "14px", padding: "8px 12px", borderRadius: "6px", margin: "8px 0", boxShadow: "0 2px 8px rgba(0, 0, 0, 0.3)", lineHeight: "1.5", textShadow: "1px 1px 2px rgba(0,0,0,0.2)", backgroundColor: "", border: "", fontWeight: "", cursor: "", opacity: "" }); if (statusOrContent === '[翻译中...]') { Object.assign(translationParagraph.style, { backgroundColor: "#4A90E2", border: "1px solid #337AB7", fontWeight: "normal", cursor: "wait", opacity: "0.8" }); translationParagraph.title = "翻译中,请稍候..."; translationParagraph.onclick = null; } else if (statusOrContent === '[翻译失败]') { Object.assign(translationParagraph.style, { backgroundColor: "#DC3545", border: "1px solid #DC3545", fontWeight: "normal", cursor: "pointer", opacity: "1" }); translationParagraph.title = "点击重试翻译"; translationParagraph.onclick = (event) => { // Keep the event parameter // Prevent event from bubbling up to parent elements and causing navigation event.stopPropagation(); event.preventDefault(); // Also prevent default action if any // Clear related attributes and remove from ongoing translations to force re-processing targetElement.removeAttribute(ORIGINAL_DATA_ATTR); targetElement.removeAttribute(ORIGINAL_TEXT_STORE_ATTR); targetElement.removeAttribute(UNIQUE_TRANSLATION_ID_ATTR); // Clear translation ID on retry ONGOING_TRANSLATIONS.delete(targetElement); translationParagraph.remove(); // Remove the error box processElementForTranslation(targetElement); // Re-process the element }; } else { // Successful translation Object.assign(translationParagraph.style, { backgroundColor: "#2E8B57", border: "1px solid #4CAF50", fontWeight: "bold", cursor: "default", opacity: "1" }); translationParagraph.title = ""; translationParagraph.onclick = null; // No click action for successful translation } // --- Layout Adjustment Logic --- // Apply fixes to the immediate parent of the targetElement applyLayoutFixes(targetElement.parentElement); // Also apply to relevant ancestors (up to 5 levels) let currentParent = targetElement.parentElement; let depth = 0; while (currentParent && depth < 5) { if (currentParent.matches("div.hover\\:bg-primaryStroke\\/20") || currentParent.matches("article.tweet-container_article__0ERPK") || currentParent.matches("div.mt-2.border.border-secondaryStroke.rounded-\\[4px\\].relative.group.overflow-hidden") || currentParent.matches("div.flex-1.min-w-0") ) { applyLayoutFixes(currentParent); } currentParent = currentParent.parentElement; depth++; } } /** * Centralized function to determine element type and initiate translation. * This makes sure all entry points (initial scan, observer, retry) * funnel through one place for consistent logic. */ async function processElementForTranslation(el) { if (!el || ONGOING_TRANSLATIONS.has(el)) { return; } let prepend = true; // Default for tweets if (el.matches("p.break-words") && el.tagName === 'P') { prepend = false; // Append for bios } initiateTextTranslation(el, prepend); } /** * Unified function to initiate translation for a text element. * This function now handles content change detection more robustly. */ async function initiateTextTranslation(textElement, prepend) { const rawContent = textElement.innerText.trim(); if (!rawContent) { // Mark empty elements as processed to avoid re-checking them textElement.setAttribute(ORIGINAL_DATA_ATTR, 'true'); return; } const storedOriginalText = textElement.getAttribute(ORIGINAL_TEXT_STORE_ATTR); const isProcessed = textElement.getAttribute(ORIGINAL_DATA_ATTR) === 'true'; const hasTranslationBox = textElement.parentElement ? textElement.parentElement.querySelector(`p.${TRANSLATED_TEXT_CLASS}[${UNIQUE_TRANSLATION_ID_ATTR}="${textElement.dataset.translationId}"]`) : null; // --- IMPORTANT LOGIC FOR CONTENT CHANGE DETECTION AND RE-PROCESSING --- // Determine if we need to force a re-translation. let forceRetranslate = false; if (isProcessed) { // Case 1: Content has genuinely changed for an already processed element. if (rawContent !== storedOriginalText) { console.log('Content changed for element, forcing re-processing:', rawContent, 'vs', storedOriginalText); forceRetranslate = true; } // Case 2: Previous translation failed, allow retry (click handler will trigger this path). else if (hasTranslationBox && hasTranslationBox.textContent.includes('[翻译失败]')) { console.log('Previous translation failed, forcing re-processing for retry:', rawContent); forceRetranslate = true; } // Case 3: Already processed successfully and content is same. Do not re-process. else { return; } } // If not already processed (isProcessed is false), then we should always process. // If forceRetranslate is true, or it's a truly new element (isProcessed is false), // then we need to reset its state for a fresh translation attempt. if (forceRetranslate || !isProcessed) { // Remove old translation box if it exists if (hasTranslationBox) { hasTranslationBox.remove(); } // Clear all tracking attributes to treat as a brand new element textElement.removeAttribute(ORIGINAL_DATA_ATTR); textElement.removeAttribute(ORIGINAL_TEXT_STORE_ATTR); textElement.removeAttribute(UNIQUE_TRANSLATION_ID_ATTR); // Crucial for new translation box ONGOING_TRANSLATIONS.delete(textElement); // Remove from cache } // --- END NEW LOGIC --- // At this point, we are confident we need to initiate a new translation process for this element. textElement.setAttribute(ORIGINAL_TEXT_STORE_ATTR, rawContent); // Store current content as original // Immediately show "Translating..." status before API call updateTranslationBox(textElement, '[翻译中...]', prepend); // Store the translation promise to prevent multiple simultaneous requests const translationPromise = obtainLocalizedText(rawContent).then(localizedContent => { updateTranslationBox(textElement, localizedContent, prepend); textElement.setAttribute(ORIGINAL_DATA_ATTR, 'true'); // Mark as processed after API call result }).catch(() => { updateTranslationBox(textElement, '[翻译失败]', prepend); textElement.setAttribute(ORIGINAL_DATA_ATTR, 'true'); // Mark as processed even if failed }).finally(() => { ONGOING_TRANSLATIONS.delete(textElement); // Remove from cache when done }); ONGOING_TRANSLATIONS.set(textElement, translationPromise); } function performFullPageScan() { // Clear ALL previous translation boxes. document.querySelectorAll(`.${TRANSLATED_TEXT_CLASS}`).forEach(el => el.remove()); // Process all known translatable text elements. // Their internal content change detection will handle if they need actual re-translation. document.querySelectorAll( "p.tweet-body_root__ChzUj," + "p.text-textSecondary.mt-1.whitespace-pre-wrap," + "div.mt-2.border.border-secondaryStroke.rounded-\\[4px\\].relative.group.overflow-hidden p.text-textSecondary.mt-1," + "p.break-words" ).forEach(processElementForTranslation); } let lastPathname = window.location.pathname; let scanTimeoutId = null; const contentWatcher = new MutationObserver((mutations) => { // Primary trigger for full page scan on URL change (SPA navigation) if (window.location.pathname !== lastPathname) { lastPathname = window.location.pathname; clearTimeout(scanTimeoutId); // Clear any pending scan from previous URL scanTimeoutId = setTimeout(performFullPageScan, 500); // Debounce full scan return; // Don't process individual mutations for a changing page } // Process individual mutations for content added or attributes changed on the *same* page for (const mutationRecord of mutations) { // Check for added nodes (new elements appearing) if (mutationRecord.type === 'childList' && mutationRecord.addedNodes.length > 0) { for (const addedNode of mutationRecord.addedNodes) { // Only query if it's an element node and has querySelector (e.g., skip text nodes) if (addedNode.nodeType !== 1 || !addedNode.querySelector) continue; // Query for all possible translatable P elements within the added node or if addedNode is the P itself addedNode.querySelectorAll( "p.tweet-body_root__ChzUj," + "p.text-textSecondary.mt-1.whitespace-pre-wrap," + "div.mt-2.border.border-secondaryStroke.rounded-\\[4px\\].relative.group.overflow-hidden p.text-textSecondary.mt-1," + "p.break-words" ).forEach(processElementForTranslation); } } // --- NEW: Handle characterData changes for in-place text updates --- else if (mutationRecord.type === 'characterData') { // The target of characterData is the TextNode itself. // We need to get its parent element to process. const parentElement = mutationRecord.target.parentElement; if (parentElement) { // Check if this parent is one of our translatable elements if (parentElement.matches("p.tweet-body_root__ChzUj") || parentElement.matches("p.text-textSecondary.mt-1.whitespace-pre-wrap") || parentElement.matches("div.mt-2.border.border-secondaryStroke.rounded-\\[4px\\].relative.group.overflow-hidden p.text-textSecondary.mt-1") || parentElement.matches("p.break-words")) { processElementForTranslation(parentElement); } } } // --- END NEW --- } }); // Configure MutationObserver to observe characterData as well, and optimize attributeFilter contentWatcher.observe(document.body, { childList: true, subtree: true, characterData: true, // Crucial: now observe text content changes directly attributes: false, // Reverted: Only observe specific attributes if absolutely necessary, to avoid performance issues // attributeFilter: ['class', 'style', ORIGINAL_DATA_ATTR, ORIGINAL_TEXT_STORE_ATTR] // Removed as 'attributes: false' }); // Initial full scan when the script first runs performFullPageScan(); // Removed lazyScanTimer // Ensure timers are cleared on page unload window.addEventListener('beforeunload', () => { if (scanTimeoutId) clearTimeout(scanTimeoutId); // if (lazyScanTimer) clearInterval(lazyScanTimer); // Removed }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址