// ==UserScript== // @name X/Twitter 年龄限制解除 // @namespace http://tampermonkey.net/ // @version 2.4.0 // @description [UI重构] 悬浮球控制 | 独立接口监听开关 | 暗黑模式 | 自动解析 X/Twitter 高清原图/视频 | 自动多级目录 | 发送至 Motrix | 🚫 彻底去除敏感内容遮罩 // @author Gemini & JHC_Style // @match https://x.com/* // @match https://twitter.com/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_registerMenuCommand // @connect localhost // @connect 127.0.0.1 // @run-at document-start // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/564639/XTwitter%20%E5%B9%B4%E9%BE%84%E9%99%90%E5%88%B6%E8%A7%A3%E9%99%A4.user.js // @updateURL https://update.greasyfork.icu/scripts/564639/XTwitter%20%E5%B9%B4%E9%BE%84%E9%99%90%E5%88%B6%E8%A7%A3%E9%99%A4.meta.js // ==/UserScript== (function() { 'use strict'; // ================= 状态与配置管理 ================= const STATE_COLORS = { IDLE: 'rgb(108, 92, 231)', // 空闲/就绪 (紫色) SUCCESS: 'rgb(46, 204, 113)', // 成功 (绿色) ERROR: 'rgb(231, 76, 60)', // 错误 (红色) PROCESSING: 'rgb(241, 196, 15)' // 处理中 (黄色) }; const DEFAULTS = { enabled: true, rpcUrl: 'http://localhost:16800/jsonrpc', rpcToken: '', debugMode: false, darkMode: false, // 暗黑模式 listenUserTweets: true, // 监听用户主页/时间线 listenTweetDetail: true, // 监听推文详情 listenSearch: true, // 监听搜索结果 unlockSensitive: true // 🟢 新增:解除敏感内容限制 }; function getSettings() { return { enabled: GM_getValue('x_enabled', DEFAULTS.enabled), rpcUrl: GM_getValue('x_rpcUrl', DEFAULTS.rpcUrl), rpcToken: GM_getValue('x_rpcToken', DEFAULTS.rpcToken), debugMode: GM_getValue('x_debugMode', DEFAULTS.debugMode), darkMode: GM_getValue('x_darkMode', DEFAULTS.darkMode), listenUserTweets: GM_getValue('x_listenUserTweets', DEFAULTS.listenUserTweets), listenTweetDetail: GM_getValue('x_listenTweetDetail', DEFAULTS.listenTweetDetail), listenSearch: GM_getValue('x_listenSearch', DEFAULTS.listenSearch), unlockSensitive: GM_getValue('x_unlockSensitive', DEFAULTS.unlockSensitive) }; } let isUiReady = false; // ================= 工具函数 ================= function log(msg, data) { const s = getSettings(); if (s.debugMode) { console.log(`%c[X-Motrix] ${msg}`, 'color: #0984e3; font-weight: bold;', data || ''); } } function prettyLog(title, sub, msg, color) { if (isUiReady) { showToast(title, sub, msg, color); } else { console.log(`[${title}] ${sub}: ${msg}`); } } function showToast(t, s, m, c) { const container = document.getElementById('x-toast-container'); if (container) { const d = document.createElement('div'); d.className = 'x-toast-msg'; // 实时同步暗黑模式:优先读取 UI 复选框的实时状态,如果 UI 未加载则读取存储配置 const uiCheckbox = document.getElementById('x-ui-dark-mode'); const isDark = uiCheckbox ? uiCheckbox.checked : getSettings().darkMode; if (isDark) { d.classList.add('x-dark-mode'); } d.style.borderColor = c; d.innerHTML = `${t} ${s}
${m}`; container.appendChild(d); setTimeout(() => { d.style.opacity = '0'; d.style.transform = 'translateX(100%)'; setTimeout(() => d.remove(), 300); }, 3000); } } function updateBallColor(stateKey) { const btn = document.getElementById('x-helper-btn'); if (btn && STATE_COLORS[stateKey]) { btn.style.background = STATE_COLORS[stateKey]; // 1.5秒后恢复为空闲色 if (stateKey !== 'IDLE') { setTimeout(() => { btn.style.background = STATE_COLORS.IDLE; }, 1500); } } } /** * 格式化当前时间 YYYYMMDD_HHmmss */ function getFormattedTime() { const now = new Date(); return now.getFullYear().toString() + (now.getMonth() + 1).toString().padStart(2, '0') + now.getDate().toString().padStart(2, '0') + "_" + now.getHours().toString().padStart(2, '0') + now.getMinutes().toString().padStart(2, '0') + now.getSeconds().toString().padStart(2, '0'); } /** * 文件名非法字符清理 */ function sanitizeFileName(str) { return (str || "").replace(/[\\/:*?"<>|]/g, "_").replace(/\s+/g, " ").trim(); } /** * 获取高清原图链接 */ function getHighResUrl(url) { if (!url) return null; if (url.includes('name=orig')) return url; const match = url.match(/^(https?:\/\/.*)\.([a-zA-Z0-9]+)(\?.*)?$/); if (match) { return `${match[1]}?format=${match[2]}&name=orig`; } return url; } // ================= 核心逻辑:推送到 Motrix ================= function sendToMotrix(mediaUrl, context, index, type) { const s = getSettings(); if (!s.enabled) return; if (!mediaUrl) return; // 1. 构造路径: 域名/作者名/描述_时间.后缀 const domain = location.hostname; const author = sanitizeFileName(context.userName || "Unknown_User"); // 截取描述前50个字符作为文件名一部分 let desc = sanitizeFileName(context.text || context.tweetId); if (desc.length > 50) desc = desc.substring(0, 50); const timeStr = getFormattedTime(); // 提取扩展名 let ext = 'jpg'; if (type === 'video') { ext = 'mp4'; } else { const extMatch = mediaUrl.match(/[?&]format=([a-zA-Z0-9]+)/); if (extMatch) { ext = extMatch[1]; } else { const dotMatch = mediaUrl.match(/\.([a-zA-Z0-9]+)$/); if (dotMatch) ext = dotMatch[1]; } } const filename = `${domain}/${author}/${desc}_${timeStr}_p${index}.${ext}`; // 2. 构造 RPC 请求 const payload = { jsonrpc: '2.0', id: 'X-Motrix-' + Date.now(), method: 'aria2.addUri', params: [ [mediaUrl], { "out": filename, "header": [ "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", "Referer: https://x.com/", "Origin: https://x.com" ], "check-certificate": "false", "connect-timeout": "10", "timeout": "20", "max-tries": "0", "retry-wait": "3", "split": "8", // 连接数建议 4-8,不要过大 "min-split-size": "1M" } ] }; if (s.rpcToken) { payload.params.unshift(`token:${s.rpcToken}`); } updateBallColor('PROCESSING'); GM_xmlhttpRequest({ method: 'POST', url: s.rpcUrl, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(payload), onload: function(response) { if (response.status === 200) { log(`✅ 推送成功: ${filename}`); prettyLog("✅", "推送成功", `文件: ${filename.split('/').pop()}`, STATE_COLORS.SUCCESS); updateBallColor('SUCCESS'); } else { log(`❌ 推送失败: ${response.responseText}`); prettyLog("❌", "推送失败", "RPC 返回错误,请检查配置", STATE_COLORS.ERROR); updateBallColor('ERROR'); } }, onerror: function(err) { log(`❌ RPC 连接错误:`, err); prettyLog("❌", "连接错误", "无法连接 Motrix,请检查运行状态", STATE_COLORS.ERROR); updateBallColor('ERROR'); } }); } // ================= 解析逻辑 ================= function extractMediaFromTweet(tweet) { if (!tweet || !tweet.legacy) return 0; const legacy = tweet.legacy; const extendedEntities = legacy.extended_entities; if (!extendedEntities || !extendedEntities.media || !Array.isArray(extendedEntities.media)) return 0; // 提取推文上下文信息 const userName = tweet.core?.user_results?.result?.core?.name || legacy.user_id_str || 'unknown'; const fullText = legacy.full_text || ""; const tweetId = legacy.rest_id; const context = { userName: userName, text: fullText, tweetId: tweetId }; let extractedCount = 0; extendedEntities.media.forEach((media, index) => { // 图片 if (media.type === 'photo' && media.media_url_https) { const highResUrl = getHighResUrl(media.media_url_https); sendToMotrix(highResUrl, context, index, 'photo'); extractedCount++; log(`📷 发现图片: ${highResUrl}`); } // 视频 else if (media.type === 'video' && media.video_info) { const variants = media.video_info.variants; if (variants) { const mp4Variants = variants.filter(v => v.content_type === 'video/mp4'); // 按码率排序取最大 mp4Variants.sort((a, b) => (b.bitrate || 0) - (a.bitrate || 0)); if (mp4Variants.length > 0) { const videoUrl = mp4Variants[0].url; sendToMotrix(videoUrl, context, index, 'video'); extractedCount++; log(`🎥 发现视频: ${videoUrl}`); } } } }); return extractedCount; } function handleApiResponse(jsonStr) { const s = getSettings(); if (!s.enabled) return; try { const data = JSON.parse(jsonStr); let instructions = null; let totalExtracted = 0; // 路径匹配与独立开关判断 // 1. UserTweets (主页/时间线) if (data?.data?.user?.result?.timeline?.timeline?.instructions) { if (!s.listenUserTweets) { log('🚫 UserTweets 接口被禁用,跳过解析'); return; } instructions = data.data.user.result.timeline.timeline.instructions; log('检测到 UserTweets 数据结构'); } // 2. TweetDetail (详情页) else if (data?.data?.threaded_conversation_with_injections_v2?.instructions) { if (!s.listenTweetDetail) { log('🚫 TweetDetail 接口被禁用,跳过解析'); return; } instructions = data.data.threaded_conversation_with_injections_v2.instructions; log('检测到 TweetDetail 数据结构'); } // 3. Search (搜索结果) else if (data?.data?.search_by_raw_query?.search_timeline?.timeline?.instructions) { if (!s.listenSearch) { log('🚫 Search 接口被禁用,跳过解析'); return; } instructions = data.data.search_by_raw_query.search_timeline.timeline.instructions; log('检测到 Search 数据结构'); } if (!instructions || !Array.isArray(instructions)) return; const processTweetResult = (result) => { if (!result) return 0; const tweet = result.tweet || result; // 排除广告 if (tweet.legacy && tweet.legacy.extended_entities) { return extractMediaFromTweet(tweet); } return 0; }; instructions.forEach(instruction => { if (instruction.type === 'TimelineAddEntries' && Array.isArray(instruction.entries)) { instruction.entries.forEach(entry => { // 标准推文 const tweetResult = entry?.content?.itemContent?.tweet_results?.result; totalExtracted += processTweetResult(tweetResult); // 评论/对话 const items = entry?.content?.items; if (items && Array.isArray(items)) { items.forEach(item => { const itemTweetResult = item?.item?.itemContent?.tweet_results?.result; totalExtracted += processTweetResult(itemTweetResult); }); } }); } else if (instruction.type === 'TimelineAddToModule' && Array.isArray(instruction.moduleItems)) { instruction.moduleItems.forEach(item => { const tweetResult = item?.item?.itemContent?.tweet_results?.result; totalExtracted += processTweetResult(tweetResult); }); } }); if (totalExtracted > 0) { log(`📊 本批次共解析 ${totalExtracted} 个媒体文件`); } } catch (e) { console.error('[X-Motrix] JSON 解析异常:', e); } } // ================= Hook XHR (核心修改) ================= const originalOpen = XMLHttpRequest.prototype.open; const originalSend = XMLHttpRequest.prototype.send; // 获取原始的 responseText 描述符,用于后续在 getter 中调用 const originalResponseTextDesc = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, 'responseText'); XMLHttpRequest.prototype.open = function(method, url) { this._url = url; return originalOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function() { // 1. 监听加载完成 (被动解析用于下载) this.addEventListener('load', function() { if (this._url && ( this._url.includes('UserTweets') || this._url.includes('TweetDetail') || this._url.includes('SearchTimeline') ) && this.responseText) { handleApiResponse(this.responseText); } }); // 2. 劫持响应数据 (主动修改用于前端渲染) - 🟢 新增功能 // 只有涉及推文数据的接口才需要处理 if (this._url && ( this._url.includes('UserTweets') || this._url.includes('TweetDetail') || this._url.includes('SearchTimeline') )) { // 定义 getter 覆盖原有属性 Object.defineProperty(this, 'responseText', { get: function() { // 调用原始 getter 获取真实数据 let rawText = originalResponseTextDesc.get.call(this); const s = getSettings(); // 如果开启了解锁敏感内容,且数据是字符串,则进行替换 if (s.unlockSensitive && typeof rawText === 'string') { try { // 暴力替换: // 1. possibly_sensitive: true -> false (推文级开关) // 2. profile_interstitial_type: "sensitive_media" -> "" (主页级警告) // 3. mediaVisibilityResults -> mediaVisibilityResults_bypass (直接破坏遮罩层数据结构,防止前端读取) return rawText.replace(/"possibly_sensitive":\s*true/g, '"possibly_sensitive":false') .replace(/"profile_interstitial_type":\s*"sensitive_media"/g, '"profile_interstitial_type":""') .replace(/"mediaVisibilityResults":/g, '"mediaVisibilityResults_bypass":'); } catch (e) { console.error('[X-Motrix] 敏感内容解锁失败:', e); return rawText; } } return rawText; }, configurable: true }); } return originalSend.apply(this, arguments); }; // ================= UI 构建 ================= function createUI() { // 复刻抖音脚本样式,增加暗黑模式适配 CSS GM_addStyle(` #x-helper-btn { position: fixed; z-index: 2147483647; width: 50px; height: 50px; background: ${STATE_COLORS.IDLE}; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: grab; box-shadow: 0 8px 25px rgba(108, 92, 231, 0.4); font-weight: bold; font-family: sans-serif; transition: transform 0.1s, box-shadow 0.2s; border: 2px solid rgba(255,255,255,0.2); user-select: none; } #x-helper-btn:active { cursor: grabbing; transform: scale(0.95); } #x-helper-btn:hover { box-shadow: 0 10px 30px rgba(108, 92, 231, 0.6); } #x-settings-panel { position: fixed; z-index: 2147483647; width: 320px; background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(15px); -webkit-backdrop-filter: blur(15px); padding: 20px; display: none; border-radius: 16px; box-shadow: 0 20px 50px rgba(0,0,0,0.15); border: 1px solid rgba(255,255,255,0.6); font-family: 'Segoe UI', Roboto, sans-serif; color: #2d3436; flex-direction: column; transition: opacity 0.2s; } #x-settings-panel h3 { margin: 0 0 15px 0; font-size: 18px; color: #2d3436; border-bottom: 1px solid #eee; padding-bottom: 10px; } /* 暗黑模式适配 - 面板 */ #x-settings-panel.x-dark-mode { background: rgba(45, 52, 54, 0.95); color: #dfe6e9; border: 1px solid rgba(255,255,255,0.1); } #x-settings-panel.x-dark-mode h3 { color: #dfe6e9; border-bottom-color: rgba(255,255,255,0.1); } #x-settings-panel.x-dark-mode .x-label { color: #dfe6e9; } #x-settings-panel.x-dark-mode .x-toggle-label { color: #b2bec3; } #x-settings-panel.x-dark-mode .x-input { background: #1e272e; color: #ecf0f1; border: 1px solid #636e72; } #x-settings-panel.x-dark-mode .x-input:focus { border-color: #a29bfe; } #x-settings-panel.x-dark-mode .x-btn-secondary { background: #636e72; color: #dfe6e9; } #x-settings-panel.x-dark-mode .x-slider { background-color: #636e72; } #x-settings-panel.x-dark-mode .x-info-box { background: #2d3436; color: #b2bec3; border: 1px solid #636e72; } #x-settings-panel.x-dark-mode .x-section-box { background: rgba(255,255,255,0.05); border: 1px dashed rgba(255,255,255,0.2); } .x-group { margin-bottom: 15px; } .x-label { font-size: 12px; color: #636e72; margin-bottom: 6px; font-weight: 700; display: block; } .x-input { all: initial; display: block; width: 100%; box-sizing: border-box; padding: 10px 12px; border: 1px solid #b2bec3; border-radius: 8px; background: #fff; color: #2d3436; outline: none; transition: border 0.2s; font-size: 13px !important; font-family: sans-serif !important; margin-top: 5px; } .x-input:focus { border-color: #6c5ce7; box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.1); } .x-toggle-wrapper { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; } .x-toggle-label { font-size: 14px; color: #2d3436; font-weight: 500; } .x-toggle { position: relative; display: inline-block; width: 44px; height: 24px; } .x-toggle input { opacity: 0; width: 0; height: 0; } .x-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #b2bec3; transition: .4s; border-radius: 34px; } .x-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; box-shadow: 0 2px 4px rgba(0,0,0,0.2); } input:checked + .x-slider { background-color: #00b894 !important; } input:checked + .x-slider:before { transform: translateX(20px); } .x-actions { display: flex; gap: 10px; margin-top: 15px; padding-top: 10px; border-top: 1px solid rgba(0,0,0,0.05); } .x-btn { flex: 1; padding: 10px; border: none; border-radius: 8px; cursor: pointer; font-weight: 600; transition: opacity 0.2s; } .x-btn-primary { background: #6c5ce7; color: white; box-shadow: 0 4px 15px rgba(108, 92, 231, 0.3); } .x-btn-secondary { background: #dfe6e9; color: #636e72; } .x-btn:hover { opacity: 0.9; } .x-info-box { background:#f1f2f6; padding:10px; border-radius:8px; font-size:11px; color:#636e72; } .x-section-box { border:1px dashed #6c5ce7; padding:10px; border-radius:8px; margin-bottom:15px; background:rgba(108, 92, 231, 0.05); } #x-toast-container { position: fixed; top: 80px; right: 20px; z-index: 2147483600; pointer-events: none; } .x-toast-msg { background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(12px); margin-bottom: 10px; padding: 8px 12px; border-left: 4px solid; border-radius: 6px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); width: 220px; pointer-events: auto; animation: slideIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); border: 1px solid rgba(255,255,255,0.4); color: #2d3436; font-family: sans-serif; font-size: 12px; line-height: 1.4; } /* 暗黑模式适配 - Toast */ .x-toast-msg.x-dark-mode { background: rgba(40, 40, 40, 0.95); color: #dfe6e9; border: 1px solid rgba(255,255,255,0.1); box-shadow: 0 5px 20px rgba(0,0,0,0.3); } .x-toast-msg.x-dark-mode .x-toast-desc { color: #b2bec3 !important; } .x-toast-desc { font-size:12px; color:#636e72; } @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } `); // 等待页面加载 const initInterval = setInterval(() => { if (document.body) { clearInterval(initInterval); renderElements(); } }, 100); } function renderElements() { if (document.getElementById('x-helper-btn')) return; // 1. 悬浮球 (替换为 X Logo SVG) const btn = document.createElement('div'); btn.id = 'x-helper-btn'; // 使用 X (Twitter) 官方 SVG 路径 btn.innerHTML = ``; btn.title = "X-Motrix 下载助手"; const savedTop = GM_getValue('x_btn_top', '20%'); const savedLeft = GM_getValue('x_btn_left', 'calc(100% - 70px)'); btn.style.top = savedTop; btn.style.left = savedLeft; // 2. 拖拽逻辑 let isDragging = false; let dragStartTime = 0; let dragRaf = null; btn.onmousedown = function (event) { isDragging = false; dragStartTime = Date.now(); let shiftX = event.clientX - btn.getBoundingClientRect().left; let shiftY = event.clientY - btn.getBoundingClientRect().top; function moveAt(pageX, pageY) { isDragging = true; if (dragRaf) cancelAnimationFrame(dragRaf); dragRaf = requestAnimationFrame(() => { btn.style.left = pageX - shiftX + 'px'; btn.style.top = pageY - shiftY + 'px'; const panel = document.getElementById('x-settings-panel'); if (panel && panel.style.display !== 'none') { repositionPanel(btn, panel); } }); } function onMouseMove(event) { moveAt(event.pageX, event.pageY); } document.addEventListener('mousemove', onMouseMove); document.onmouseup = function () { document.removeEventListener('mousemove', onMouseMove); document.onmouseup = null; if (dragRaf) cancelAnimationFrame(dragRaf); if (isDragging) { GM_setValue('x_btn_top', btn.style.top); GM_setValue('x_btn_left', btn.style.left); } }; }; btn.ondragstart = () => false; // 点击打开/关闭面板 btn.addEventListener('click', (e) => { e.stopPropagation(); if (isDragging || (Date.now() - dragStartTime > 200)) return; const p = document.getElementById('x-settings-panel'); if (p) { if (p.style.display === 'none') { p.style.display = 'flex'; syncUI(); // 打开时同步数据 repositionPanel(btn, p); } else { p.style.display = 'none'; } } }); document.body.appendChild(btn); // 3. 配置面板 const panel = document.createElement('div'); panel.id = 'x-settings-panel'; // 初始应用暗黑模式 if (getSettings().darkMode) panel.classList.add('x-dark-mode'); panel.innerHTML = `

X/Twitter Motrix 助手

🟢 启用总开关
🔞 解除敏感内容限制
📡 接口监听设置
👤 主页/时间线 (UserTweets)
📄 推文详情页 (TweetDetail)
🔍 搜索结果页 (Search)
📡 Motrix RPC 地址
🔑 RPC 密钥 (Token)
🌙 暗黑模式
🐞 调试日志 (Console)
`; // 防止点击面板触发页面其他事件 panel.addEventListener('click', (e) => e.stopPropagation()); // 防止输入框触发页面快捷键 panel.querySelectorAll('input').forEach(el => { el.addEventListener('keydown', (e) => e.stopPropagation()); }); document.body.appendChild(panel); // Toast 容器 const toastContainer = document.createElement('div'); toastContainer.id = 'x-toast-container'; document.body.appendChild(toastContainer); // 绑定事件 document.getElementById('x-ui-close').onclick = () => { document.getElementById('x-settings-panel').style.display = 'none'; }; document.getElementById('x-ui-save').onclick = saveUI; // 绑定暗黑模式即时预览 document.getElementById('x-ui-dark-mode').addEventListener('change', (e) => { const p = document.getElementById('x-settings-panel'); if (e.target.checked) p.classList.add('x-dark-mode'); else p.classList.remove('x-dark-mode'); }); isUiReady = true; log('UI 初始化完成'); } // 智能定位面板 function repositionPanel(btn, panel) { const btnRect = btn.getBoundingClientRect(); const panelWidth = 320; const margin = 15; // 默认显示在左侧 let left = btnRect.left - panelWidth - margin; let top = btnRect.top; // 如果左边空间不足,显示在右边 if (left < 10) { left = btnRect.right + margin; } // 垂直防溢出 if (top + panel.offsetHeight > window.innerHeight) { top = window.innerHeight - panel.offsetHeight - 10; } if (top < 10) top = 10; panel.style.top = top + 'px'; panel.style.left = left + 'px'; } // 从存储同步到 UI function syncUI() { const s = getSettings(); document.getElementById('x-ui-enable').checked = s.enabled; document.getElementById('x-ui-listen-user').checked = s.listenUserTweets; document.getElementById('x-ui-listen-detail').checked = s.listenTweetDetail; document.getElementById('x-ui-listen-search').checked = s.listenSearch; document.getElementById('x-ui-rpc').value = s.rpcUrl; document.getElementById('x-ui-token').value = s.rpcToken; document.getElementById('x-ui-dark-mode').checked = s.darkMode; document.getElementById('x-ui-debug').checked = s.debugMode; document.getElementById('x-ui-unlock-sensitive').checked = s.unlockSensitive; // 同步面板暗黑样式 const panel = document.getElementById('x-settings-panel'); if (s.darkMode) panel.classList.add('x-dark-mode'); else panel.classList.remove('x-dark-mode'); } // 从 UI 保存到存储 function saveUI() { const enabled = document.getElementById('x-ui-enable').checked; const listenUser = document.getElementById('x-ui-listen-user').checked; const listenDetail = document.getElementById('x-ui-listen-detail').checked; const listenSearch = document.getElementById('x-ui-listen-search').checked; const rpcUrl = document.getElementById('x-ui-rpc').value; const rpcToken = document.getElementById('x-ui-token').value; const darkMode = document.getElementById('x-ui-dark-mode').checked; const debugMode = document.getElementById('x-ui-debug').checked; const unlockSensitive = document.getElementById('x-ui-unlock-sensitive').checked; GM_setValue('x_enabled', enabled); GM_setValue('x_listenUserTweets', listenUser); GM_setValue('x_listenTweetDetail', listenDetail); GM_setValue('x_listenSearch', listenSearch); GM_setValue('x_rpcUrl', rpcUrl); GM_setValue('x_rpcToken', rpcToken); GM_setValue('x_darkMode', darkMode); GM_setValue('x_debugMode', debugMode); GM_setValue('x_unlockSensitive', unlockSensitive); document.getElementById('x-settings-panel').style.display = 'none'; prettyLog("💾", "配置已保存", "新的设置即刻生效 (敏感内容需刷新页面)", STATE_COLORS.SUCCESS); } // 启动 createUI(); })();