您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
检测并导航到 Reddit 的中文翻译帖子。自动检查 Reddit 帖子是否支持切换参数,并提供一键切换功能。
// ==UserScript== // @name Reddit Chinese Translation Detector // @name:zh-CN Reddit 中文翻译检测器 // @name:zh-TW Reddit 中文翻譯檢測器 // @namespace http://tampermonkey.net/ // @version 1.3.0 // @description Detect and navigate to Chinese translated Reddit posts. Automatically checks if Reddit posts support ?tl=zh-hans parameter and provides one-click switching. // @description:zh-CN 检测并导航到 Reddit 的中文翻译帖子。自动检查 Reddit 帖子是否支持切换参数,并提供一键切换功能。 // @description:zh-TW 檢測並導航到 Reddit 的中文翻譯貼文。自動檢查 Reddit 貼文是否支援切換參數,並提供一鍵切換功能。 // @author Will // @match https://www.reddit.com/* // @exclude https://www.reddit.com/login* // @exclude https://www.reddit.com/register* // @grant GM_xmlhttpRequest // @connect www.reddit.com // @license MIT // @run-at document-end // ==/UserScript== (function() { 'use strict'; // 配置键名 const CONFIG_KEY = 'reddit_chinese_translation_detector_enabled'; const POSITION_KEY = 'reddit_translation_button_position'; // 检查是否已启用 let isEnabled = localStorage.getItem(CONFIG_KEY) === 'true'; // 创建样式 const style = document.createElement('style'); style.textContent = ` #translation-toggle-container { position: fixed !important; top: 10px !important; left: 10px !important; z-index: 2147483647 !important; display: flex !important; gap: 10px !important; align-items: center !important; background: rgba(255, 255, 255, 0.95) !important; padding: 10px !important; border-radius: 8px !important; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2) !important; font-family: Arial, sans-serif !important; font-size: 14px !important; border: 1px solid #ccc !important; cursor: move; width: fit-content; /* 适应内容宽度 */ width: -moz-fit-content; /* Firefox兼容 */ user-select: none; } #translation-toggle-label { margin: 0 !important; color: #333 !important; white-space: nowrap !important; } #translation-toggle { margin: 0 5px !important; transform: scale(1.2) !important; } #translation-button { padding: 8px 16px !important; background-color: #0079d3 !important; color: white !important; border: none !important; border-radius: 4px !important; cursor: pointer !important; font-size: 14px !important; font-weight: bold !important; transition: all 0.2s !important; white-space: nowrap !important; /* 新增垂直居中调整 */ display: flex !important; align-items: center !important; justify-content: center !important; text-align: center !important; line-height: 1 !important; /* 防止字体因行高被拉伸 */ } #translation-button:hover:not(:disabled) { background-color: #0066b2 !important; transform: translateY(-1px) !important; } #translation-button:disabled { background-color: #cccccc !important; cursor: not-allowed !important; transform: none !important; } #translation-button.checking { background-color: #ffd700 !important; color: #333 !important; } @media (max-width: 768px) { #translation-toggle-container { top: 5px !important; left: 5px !important; padding: 8px !important; font-size: 12px !important; max-width: 90vw !important; } #translation-button { padding: 6px 12px !important; font-size: 12px !important; } } `; document.head.appendChild(style); // 创建控制面板 function createControlPanel() { // 移除可能存在的旧面板 const oldPanel = document.getElementById('translation-toggle-container'); if (oldPanel) { oldPanel.remove(); } const container = document.createElement('div'); container.id = 'translation-toggle-container'; // 设置保存的位置 const savedPosition = JSON.parse(localStorage.getItem(POSITION_KEY) || 'null'); if (savedPosition) { container.style.left = savedPosition.x + 'px'; container.style.top = savedPosition.y + 'px'; } const label = document.createElement('span'); label.id = 'translation-toggle-label'; label.textContent = '翻译检测:'; const toggle = document.createElement('input'); toggle.type = 'checkbox'; toggle.id = 'translation-toggle'; toggle.checked = isEnabled; const button = document.createElement('button'); button.id = 'translation-button'; button.textContent = isEnabled ? '检测中...' : '已禁用'; button.disabled = true; container.appendChild(label); container.appendChild(toggle); container.appendChild(button); document.body.appendChild(container); // 添加拖拽功能 makeDraggable(container); // 事件监听 toggle.addEventListener('change', function() { isEnabled = this.checked; localStorage.setItem(CONFIG_KEY, isEnabled); if (isEnabled) { button.disabled = true; button.textContent = '检测中...'; checkTranslation(); } else { button.disabled = true; button.textContent = '已禁用'; } }); button.addEventListener('click', function() { const currentUrl = window.location.href; const translatedUrl = addTranslationParam(currentUrl); window.location.href = translatedUrl; }); return { toggle, button }; } // 实现拖拽功能 function makeDraggable(element) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; element.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; // 只有在点击非按钮区域时才允许拖拽 if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'BUTTON') { e.preventDefault(); // 获取鼠标位置 pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } } function elementDrag(e) { e = e || window.event; e.preventDefault(); // 计算新位置 pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; // 设置元素新位置 element.style.top = (element.offsetTop - pos2) + "px"; element.style.left = (element.offsetLeft - pos1) + "px"; } function closeDragElement() { // 停止移动 document.onmouseup = null; document.onmousemove = null; // 保存位置到localStorage const position = { x: element.offsetLeft, y: element.offsetTop }; localStorage.setItem(POSITION_KEY, JSON.stringify(position)); } } // 添加翻译参数 function addTranslationParam(url) { const urlObj = new URL(url); // 先移除可能存在的tl参数 urlObj.searchParams.delete('tl'); urlObj.searchParams.set('tl', 'zh-hans'); return urlObj.toString(); } // 检测翻译页面是否真正支持 function checkTranslation() { // 如果已禁用,不执行检测 if (!isEnabled) { return; } const { button } = window.translationElements; // 检查是否已经是翻译页面 const currentUrl = window.location.href; const urlObj = new URL(currentUrl); if (urlObj.searchParams.get('tl') === 'zh-hans') { button.disabled = true; button.textContent = '已是中文'; return; } // 只在特定页面类型检测(帖子页面) if (!isPostPage()) { button.disabled = true; button.textContent = '仅限帖子页'; return; } button.classList.add('checking'); button.disabled = true; button.textContent = '检测中...'; const translatedUrl = addTranslationParam(currentUrl); // 使用 GM_xmlhttpRequest 进行实际验证测试 GM_xmlhttpRequest({ method: 'GET', url: translatedUrl, onload: function(response) { button.classList.remove('checking'); try { // 检查最终URL是否保持了tl=zh-hans参数 const finalUrl = response.finalUrl || translatedUrl; const finalUrlObj = new URL(finalUrl); const hasTranslationParam = finalUrlObj.searchParams.get('tl') === 'zh-hans'; // 检查响应状态 const isValidStatus = response.status === 200; // 检查是否是真正支持翻译的页面 // 通过检查页面内容中的关键特征来判断 const pageContent = response.responseText; // 检查是否重定向到了错误页面 const isNoThinkPage = ( finalUrl.includes('/no_think') || pageContent.includes('/no_think') || pageContent.includes('no_think') ); // 检查是否有网络错误 const hasNetworkError = ( response.status === 0 || pageContent.includes('ERR_TUNNEL_CONNECTION_FAILED') || pageContent.includes('net::ERR_') ); // 检查是否是有效的Reddit帖子页面(包含帖子相关内容) const hasPostContent = ( pageContent.includes('data-testid="post-container"') || pageContent.includes('class="Post"') || (pageContent.includes('post-title') && pageContent.includes('Comments')) ); // 检查页面标题是否包含翻译标识 const hasTranslationInTitle = ( pageContent.includes('>Translated<') || pageContent.includes('翻译') || pageContent.includes('translated') ); // 检查页面语言属性 const hasChineseLangAttr = ( pageContent.includes('lang="zh"') || pageContent.includes('lang="zh-CN"') || pageContent.includes('lang="zh-Hans"') ); console.log('Translation check debug info:', { status: response.status, finalUrl: finalUrl, hasTranslationParam: hasTranslationParam, isNoThinkPage: isNoThinkPage, hasNetworkError: hasNetworkError, hasPostContent: hasPostContent, hasTranslationInTitle: hasTranslationInTitle, hasChineseLangAttr: hasChineseLangAttr }); // 最严格的验证逻辑: // 1. 状态必须是200 // 2. 必须保持翻译参数 // 3. 不能是/no_think页面 // 4. 不能有网络错误 // 5. 必须包含帖子内容 // 6. (可选)必须有翻译相关的标识 const isActuallyTranslated = ( isValidStatus && hasTranslationParam && !isNoThinkPage && !hasNetworkError && hasPostContent && (hasTranslationInTitle || hasChineseLangAttr || pageContent.includes('tl=zh-hans')) ); if (isActuallyTranslated) { button.disabled = false; button.textContent = '切换到中文'; } else { button.disabled = true; button.textContent = '无中文版本'; } } catch (e) { console.error('Translation detection error:', e); button.classList.remove('checking'); button.disabled = true; button.textContent = '检测失败'; } }, onerror: function(error) { button.classList.remove('checking'); button.disabled = true; button.textContent = '检测失败'; console.error('Translation detection network error:', error); }, ontimeout: function() { button.classList.remove('checking'); button.disabled = true; button.textContent = '检测超时'; console.log('Translation detection timeout'); }, timeout: 15000 // 15秒超时 }); } // 判断是否为帖子页面 function isPostPage() { const path = window.location.pathname; // 匹配 /r/subreddit/comments/post_id/... 格式 return /^\/r\/[^\/]+\/comments\/[^\/]+/.test(path); } // 初始化 function init() { // 等待body元素存在 if (!document.body) { setTimeout(init, 100); return; } // 创建控制元素 window.translationElements = createControlPanel(); // 初始检测 if (isEnabled) { // 等待页面完全加载后再检测 setTimeout(checkTranslation, 3000); } // 监听URL变化(使用更可靠的方案) let lastUrl = location.href; const checkUrlChange = () => { const url = location.href; if (url !== lastUrl) { lastUrl = url; if (isEnabled) { setTimeout(checkTranslation, 1500); } } setTimeout(checkUrlChange, 500); }; checkUrlChange(); } // 等待DOM加载完成 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址