// ==UserScript== // @name GreasyFork 优化增强 // @name:zh-CN GreasyFork 优化增强 // @name:en-US GreasyFork Optimization and Enhancement // @namespace https://github.com/shiyi312 // @version 2026.9.5.3 // @description 为 GreasyFork 提供实用增强:多主题、收藏笔记、代码复制、论坛过滤、大纲导航、搜索语法、自动登录等,界面清爽,配置简洁。 // @author 辻弌20 // @license MIT // @homepage https://github.com/shiyi312/Greasy-Fork-Optimization-and-Enhancement // @homepageURL https://github.com/shiyi312/Greasy-Fork-Optimization-and-Enhancement // @supportURL https://github.com/shiyi312/Greasy-Fork-Optimization-and-Enhancement/issues // @icon https://raw.githubusercontent.com/shiyi312/Greasy-Fork-Optimization-and-Enhancement/main/icon.png // @icon64 https://raw.githubusercontent.com/shiyi312/Greasy-Fork-Optimization-and-Enhancement/main/icon.png // @match https://greasyfork.org/* // @match https://sleazyfork.org/* // @match https://cn-greasyfork.org/* // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_listValues // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_addValueChangeListener // @grant GM_removeValueChangeListener // @grant GM_getResourceText // @grant GM_setClipboard // @grant unsafeWindow // @require https://cdn.jsdelivr.net/npm/viewerjs@1.11.8/dist/viewer.js // @require https://cdn.jsdelivr.net/npm/otpauth@9.5.1/dist/otpauth.umd.min.js // @resource ViewerCSS https://cdn.jsdelivr.net/npm/viewerjs@1.11.8/dist/viewer.min.css // @run-at document-end // ==/UserScript== /** * GreasyFork 优化增强 * 作者: 辻弌20 * 主页: https://scriptcat.org/zh-CN/users/202800 * 仓库: https://github.com/shiyi312/Greasy-Fork-Optimization-and-Enhancement */ (function() { 'use strict'; const log = { info: (...args) => console.log('[GFE]', ...args), warn: (...args) => console.warn('[GFE]', ...args), error: (...args) => console.error('[GFE]', ...args), success: (...args) => console.log('[GFE] ✅', ...args), }; const $ = (sel, ctx = document) => ctx.querySelector(sel); const $$ = (sel, ctx = document) => Array.from(ctx.querySelectorAll(sel)); const Utils = { get: (key, def) => { try { return GM_getValue(key, def); } catch { return localStorage.getItem(key) || def; } }, set: (key, val) => { try { GM_setValue(key, val); } catch { localStorage.setItem(key, val); } }, del: (key) => { try { GM_deleteValue(key); } catch { localStorage.removeItem(key); } }, list: () => { try { return GM_listValues(); } catch { return Object.keys(localStorage); } }, addStyle: (css) => { try { GM_addStyle(css); } catch { const s = document.createElement('style'); s.textContent = css; document.head.appendChild(s); } }, getResource: (name) => { try { return GM_getResourceText(name); } catch { return null; } }, registerMenu: (name, fn) => { try { GM_registerMenuCommand(name, fn); } catch { /* ignore */ } }, xhr: (details) => { try { return GM_xmlhttpRequest(details); } catch { /* ignore */ } }, waitForEl: (selector, timeout = 5000) => new Promise((resolve) => { if ($(selector)) return resolve($(selector)); const start = Date.now(); const interval = setInterval(() => { const el = $(selector); if (el || Date.now() - start > timeout) { clearInterval(interval); resolve(el); } }, 100); }), observe: (target, config, callback) => { const observer = new MutationObserver(callback); observer.observe(target, config); return observer; }, escapeHtml: (str) => { if (!str) return ''; return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); }, toJSON: (str) => { try { return JSON.parse(str); } catch { return null; } }, formatTime: (ts, fmt = 'yyyy-MM-dd HH:mm:ss') => { const d = new Date(ts); const o = { 'M+': d.getMonth() + 1, 'd+': d.getDate(), 'H+': d.getHours(), 'm+': d.getMinutes(), 's+': d.getSeconds() }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (d.getFullYear() + '').substr(4 - RegExp.$1.length)); for (let k in o) if (new RegExp('(' + k + ')').test(fmt)) fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)); return fmt; } }; // ========== 配置管理 ========== const CONFIG_KEY = 'GFE_lite_config'; const Config = { get(key, def) { const all = this.getAll(); return all.hasOwnProperty(key) ? all[key] : def; }, set(key, val) { const all = this.getAll(); all[key] = val; Utils.set(CONFIG_KEY, JSON.stringify(all)); }, getAll() { try { return JSON.parse(Utils.get(CONFIG_KEY, '{}')); } catch { return {}; } }, delete(key) { const all = this.getAll(); delete all[key]; Utils.set(CONFIG_KEY, JSON.stringify(all)); }, addListener(key, cb) { try { return GM_addValueChangeListener(CONFIG_KEY, (name, old, val) => { try { const newObj = JSON.parse(val); if (newObj.hasOwnProperty(key)) cb(key, newObj[key], old ? JSON.parse(old)[key] : undefined); } catch {} }); } catch { return null; } }, removeListener(id) { try { GM_removeValueChangeListener(id); } catch {} } }; // ========== 主题系统 ========== const THEME_KEY = 'theme'; const THEME_DEFAULT = 'system'; let currentTheme = Config.get(THEME_KEY, THEME_DEFAULT); const THEME_COLORS = { light: { bg: '#f5f7fb', text: '#111827', card: '#ffffff', border: '#e4e7ed', shadow: 'rgba(0,0,0,0.06)' }, dark: { bg: '#0b111a', text: '#e5e7eb', card: '#1e2633', border: '#2d3748', shadow: 'rgba(0,0,0,0.4)' } }; const systemDark = window.matchMedia('(prefers-color-scheme: dark)'); function resolveTheme(theme) { return theme === 'system' ? (systemDark.matches ? 'dark' : 'light') : theme; } function applyTheme(theme) { currentTheme = theme; Config.set(THEME_KEY, theme); const resolved = resolveTheme(theme); const isDark = resolved === 'dark'; const root = document.documentElement; root.classList.toggle('gfe-dark', isDark); root.style.colorScheme = isDark ? 'dark' : 'light'; const colors = isDark ? THEME_COLORS.dark : THEME_COLORS.light; Object.entries(colors).forEach(([k, v]) => root.style.setProperty('--gfe-' + k, v)); root.style.backgroundColor = colors.bg; root.style.color = colors.text; // 卡片样式通过 CSS 变量自动响应,无需遍历 } function initTheme() { applyTheme(currentTheme); systemDark.addEventListener('change', () => { if (currentTheme === 'system') applyTheme('system'); }); } // ========== 收藏管理 ========== const FAV_KEY = 'gfe_favorites'; function getFavorites() { return Config.get(FAV_KEY, {}); } function setFavorites(fav) { Config.set(FAV_KEY, fav); } function isFavorite(scriptId) { return !!getFavorites()[scriptId]; } async function toggleFavorite(scriptId, scriptName) { const fav = getFavorites(); if (fav[scriptId]) { delete fav[scriptId]; setFavorites(fav); showToast('已移除收藏', 'success'); } else { fav[scriptId] = { name: scriptName || scriptId, time: Date.now() }; setFavorites(fav); showToast('已添加到收藏', 'success'); } updateAllFavButtons(); } function updateAllFavButtons() { document.querySelectorAll('.gfe-btn-fav[data-script-id]').forEach(btn => { const id = btn.dataset.scriptId; const active = isFavorite(id); btn.classList.toggle('active', active); btn.textContent = active ? '❤️' : '🤍'; }); } // ========== 笔记管理 ========== const NOTES_KEY = 'gfe_notes'; function getNote(scriptId) { return Config.get(NOTES_KEY, {})[scriptId] || ''; } function setNote(scriptId, content) { const notes = Config.get(NOTES_KEY, {}); if (content && content.trim()) notes[scriptId] = content.trim(); else delete notes[scriptId]; Config.set(NOTES_KEY, notes); } // ========== 过滤系统 ========== const FILTER_KEY = 'gfe_filters'; const REGEX_FILTER_KEY = 'gfe_regex_filter'; function getFilters() { return Config.get(FILTER_KEY, []); } function addFilter(type, value) { const filters = getFilters(); filters.push({ type, value }); Config.set(FILTER_KEY, filters); } function removeFilter(index) { const filters = getFilters(); if (index >= 0 && index < filters.length) { filters.splice(index, 1); Config.set(FILTER_KEY, filters); } } function getRegexFilter() { return Config.get(REGEX_FILTER_KEY, ''); } function setRegexFilter(pattern) { Config.set(REGEX_FILTER_KEY, pattern); } // 修改:matchFilter 增加 try-catch 容错 function matchFilter(data) { const filters = getFilters(); for (const f of filters) { const val = data[f.type]; if (!val) continue; try { if (String(val).match(new RegExp(f.value, 'i'))) return true; } catch (_) { // 忽略无效正则,当作不匹配 } } return false; } function matchRegexFilter(data) { const pattern = getRegexFilter(); if (!pattern) return false; try { const re = new RegExp(pattern, 'i'); return re.test(data.scriptName) || re.test(data.scriptAuthor); } catch(e) { return false; } } function applyFilters() { document.querySelectorAll('.gfe-script-card[data-script-id]').forEach(card => { const info = { scriptId: card.dataset.scriptId, scriptName: card.dataset.scriptName, scriptAuthor: card.dataset.scriptAuthor, }; const hidden = matchFilter(info) || matchRegexFilter(info); card.style.display = hidden ? 'none' : ''; }); document.querySelectorAll('.discussion-list-container').forEach(container => { const title = container.querySelector('.discussion-title')?.textContent?.trim() || ''; const author = container.querySelector('.user-link')?.textContent?.trim() || ''; const info = { title, author }; if (matchFilter(info)) { container.style.display = 'none'; container.dataset.filtered = 'true'; } }); } // ========== Toast ========== let toastTimer = null; function showToast(msg, type = 'info') { const old = document.querySelector('.gfe-toast'); if (old) old.remove(); if (toastTimer) clearTimeout(toastTimer); const div = document.createElement('div'); div.className = 'gfe-toast'; const colors = { info: '#409eff', success: '#67c23a', warning: '#e6a23c', error: '#f56c6c' }; div.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 999999; padding: 12px 24px; border-radius: 8px; background: ${colors[type] || colors.info}; color: #fff; font-size: 14px; font-family: sans-serif; box-shadow: 0 4px 12px rgba(0,0,0,0.2); max-width: 400px; word-break: break-word; animation: gfeFadeIn 0.3s ease; transition: opacity 0.3s; `; div.textContent = msg; document.body.appendChild(div); if (!document.getElementById('gfe-toast-style')) { const style = document.createElement('style'); style.id = 'gfe-toast-style'; style.textContent = `@keyframes gfeFadeIn { from { opacity:0; transform:translateY(-10px); } to { opacity:1; transform:translateY(0); } }`; document.head.appendChild(style); } toastTimer = setTimeout(() => { div.style.opacity = '0'; setTimeout(() => div.remove(), 300); toastTimer = null; }, 3000); } // ========== 样式注入 ========== function injectStyles() { const css = ` .gfe-script-list { display: grid !important; grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)) !important; gap: 16px !important; padding: 0 !important; list-style: none !important; } .gfe-script-list > li { list-style: none !important; border: 1px solid var(--gfe-border, #e4e7ed) !important; border-radius: 8px !important; padding: 12px 16px !important; background: var(--gfe-card, #ffffff) !important; box-shadow: 0 2px 8px var(--gfe-shadow, rgba(0,0,0,0.06)) !important; transition: transform 0.15s, box-shadow 0.15s !important; margin: 0 !important; } .gfe-script-list > li:hover { transform: translateY(-2px) !important; box-shadow: 0 4px 16px var(--gfe-shadow, rgba(0,0,0,0.12)) !important; } .gfe-script-card-title { font-size: 16px; font-weight: 600; margin-bottom: 4px; } .gfe-script-card-title a { color: inherit; text-decoration: none; } .gfe-script-card-title a:hover { color: #409eff; } .gfe-script-meta { display: flex; flex-wrap: wrap; gap: 6px 16px; font-size: 13px; color: var(--gfe-text, #606266); opacity: 0.7; margin: 4px 0; } .gfe-script-meta span { display: inline-flex; align-items: center; gap: 4px; } .gfe-script-desc { color: var(--gfe-text, #606266); font-size: 14px; margin: 6px 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; opacity: 0.8; } .gfe-script-actions { display: flex; gap: 8px; margin-top: 8px; flex-wrap: wrap; align-items: center; } .gfe-script-actions .install-link { background: #409eff; color: #fff; border: none; padding: 4px 14px; border-radius: 4px; text-decoration: none; font-size: 13px; } .gfe-script-actions .install-link:hover { background: #66b1ff; } .gfe-script-actions button { background: transparent; border: 1px solid var(--gfe-border, #dcdfe6); padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 13px; color: var(--gfe-text, #333); } .gfe-script-actions button:hover { border-color: #409eff; color: #409eff; } .gfe-script-actions .gfe-btn-fav.active { color: #f56c6c; border-color: #f56c6c; } .gfe-note-panel { border: 1px solid var(--gfe-border, #e4e7ed); border-radius: 4px; padding: 8px 12px; margin: 6px 0; background: var(--gfe-card, #f5f7fa); } .gfe-note-panel textarea { width: 100%; border: 1px solid var(--gfe-border, #dcdfe6); border-radius: 4px; padding: 6px 10px; resize: vertical; font-size: 13px; background: var(--gfe-card, #fff); color: var(--gfe-text, #333); } .gfe-note-panel .note-actions { display: flex; gap: 8px; margin-top: 6px; } .gfe-note-panel .note-actions button { padding: 2px 12px; font-size: 12px; } /* 暗色主题下的样式覆盖 */ .gfe-dark { --gfe-bg: #0b111a; --gfe-text: #e5e7eb; --gfe-card: #1e2633; --gfe-border: #2d3748; --gfe-shadow: rgba(0,0,0,0.4); } .gfe-dark .gfe-script-actions button { color: #ffffff !important; opacity: 0.95; border-color: #4a5a7a; } .gfe-dark .gfe-script-actions button:hover { color: #409eff !important; border-color: #409eff; opacity: 1; } .gfe-dark .gfe-script-actions .gfe-btn-fav.active { color: #ff6b6b !important; border-color: #ff6b6b; } .gfe-dark .gfe-script-actions .gfe-btn-bookmark.active { color: #ffd93d !important; border-color: #ffd93d; } .gfe-dark .gfe-script-meta span { color: #c0c8d8; } .gfe-dark .gfe-script-desc { color: #b0b8c8; } .gfe-dark .gfe-script-card-title a { color: #e5e7eb !important; } .gfe-dark .gfe-script-card-title a:hover { color: #409eff !important; } .gfe-dark .gfe-note-panel { background: #252f3f; border-color: #3a4a5a; } .gfe-dark .gfe-note-panel textarea { background: #1a2230; color: #e0e8f0; border-color: #3a4a5a; } /* ===== 版本页暗色样式(新增) ===== */ .gfe-dark .gfe-version-item { color: #e5e7eb !important; } .gfe-dark .gfe-version-item .gfe-version-number a { color: #e5e7eb !important; } .gfe-dark .gfe-version-item .gfe-version-date { color: #c0c8d8 !important; } .gfe-dark .gfe-version-item .gfe-version-changelog { color: #b0b8c8 !important; } .gfe-dark .gfe-version-actions a, .gfe-dark .gfe-version-actions button { color: #ffffff !important; } .gfe-dark .gfe-version-actions .install-link { background: #409eff !important; color: #fff !important; } .gfe-dark .gfe-version-actions .install-link:hover { background: #66b1ff !important; } .gfe-dark .gfe-version-actions button { border-color: #4a5a7a !important; background: transparent; } .gfe-dark .gfe-version-actions button:hover { border-color: #409eff !important; color: #409eff !important; } /* 版本页原生的 diff-controls 等文字颜色 */ .gfe-dark .diff-controls label { color: #e5e7eb !important; } .gfe-dark .diff-controls input[type="radio"] { accent-color: #409eff; } .gfe-version-item { padding: 12px 16px; border: 1px solid var(--gfe-border, #e4e7ed); border-radius: 6px; margin: 8px 0; background: var(--gfe-card, #fafafa); } .gfe-version-header { display: flex; justify-content: space-between; align-items: center; } .gfe-version-number { font-weight: bold; font-size: 16px; } .gfe-version-date { color: #909399; font-size: 13px; } .gfe-version-changelog { margin: 8px 0 0; white-space: pre-wrap; } .gfe-version-actions { display: flex; gap: 8px; margin-top: 8px; } .gfe-code-toolbar { display: flex; justify-content: flex-end; margin-bottom: 8px; } .gfe-code-toolbar button { padding: 4px 16px; font-size: 13px; background: #409eff; color: #fff; border: none; border-radius: 4px; cursor: pointer; transition: background 0.2s; } .gfe-code-toolbar button:hover { background: #66b1ff; } .gfe-code-toolbar button:disabled { opacity: 0.6; cursor: not-allowed; } .gfe-code-toolbar .gfe-code-stats { font-size: 13px; color: #909399; margin-left: 12px; line-height: 32px; } .discussion-list-container[data-filtered="true"] { display: none !important; } .discussion-read-hidden { display: none !important; } .discussion-read-italic { font-style: italic; opacity: 0.6; color: gray; } .gfe-outline { position: sticky; float: right; padding: 0 0 0 0.5em; margin: 0 0.5em -99vh; max-height: 80vh; border: 1px solid var(--gfe-border, #ccc); background: var(--gfe-card, #fff); list-style: none; width: 12%; border-radius: 5px; overflow-y: auto; z-index: 10; } .gfe-outline a { color: var(--gfe-text, #333); text-decoration: none; display: block; padding: 2px 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .gfe-outline a:hover { background: #409eff; color: #fff; } .gfe-outline li { list-style: none; } .gfe-sticky-pagination { position: sticky; bottom: 0; backdrop-filter: blur(4px); padding: 8px; background: rgba(255,255,255,0.7); z-index: 5; } .gfe-flat-layout .script-list > li { padding-right: 0; } .gfe-flat-layout .script-list > li article { display: flex; flex-direction: row; align-items: center; } .gfe-flat-layout .script-list > li article > h2 { width: 60%; overflow: hidden; text-overflow: ellipsis; margin-right: 0.5em; border-right: 1px solid #ccc; } .gfe-flat-layout .script-list > li article > .script-meta-block { width: 40%; } @media (max-width: 600px) { .gfe-flat-layout .script-list > li article { flex-direction: column; align-items: stretch; } } .gfe-bookmark-btn { cursor: pointer; margin-left: 4px; } .gfe-bookmark-btn.active { color: #f56c6c; } .gfe-tooltip { position: fixed; padding: 4px 10px; background: rgba(0,0,0,0.75); color: #fff; border-radius: 4px; font-size: 12px; pointer-events: none; z-index: 99999; white-space: nowrap; opacity: 0; transition: opacity 0.15s ease; } .gfe-tooltip.visible { opacity: 1; } `; Utils.addStyle(css); const viewerCSS = Utils.getResource('ViewerCSS'); if (viewerCSS) Utils.addStyle(viewerCSS); } // ========== Tooltip 延迟显示逻辑 ========== let tooltipTimer = null; let tooltipEl = null; function initTooltip() { tooltipEl = document.createElement('div'); tooltipEl.className = 'gfe-tooltip'; document.body.appendChild(tooltipEl); document.addEventListener('mouseenter', (e) => { const btn = e.target.closest('[data-tooltip]'); if (!btn) return; if (tooltipTimer) { clearTimeout(tooltipTimer); tooltipTimer = null; } tooltipTimer = setTimeout(() => { const rect = btn.getBoundingClientRect(); tooltipEl.textContent = btn.dataset.tooltip; tooltipEl.style.left = (rect.left + rect.width / 2 - tooltipEl.offsetWidth / 2) + 'px'; tooltipEl.style.top = (rect.bottom + 6) + 'px'; tooltipEl.classList.add('visible'); tooltipTimer = null; }, 400); }, true); document.addEventListener('mouseleave', (e) => { const btn = e.target.closest('[data-tooltip]'); if (!btn) return; if (tooltipTimer) { clearTimeout(tooltipTimer); tooltipTimer = null; } tooltipEl.classList.remove('visible'); }, true); document.addEventListener('mouseleave', () => { if (tooltipTimer) { clearTimeout(tooltipTimer); tooltipTimer = null; } tooltipEl.classList.remove('visible'); }, true); } // ========== 通用复制函数 ========== function copyTextWithFeedback(text, btn, successMsg, failMsg) { const originalText = btn.textContent; btn.textContent = '复制中...'; btn.disabled = true; const resetBtn = () => { btn.textContent = originalText; btn.disabled = false; }; const doCopy = () => { try { if (typeof GM_setClipboard === 'function') { GM_setClipboard(text, 'text'); showToast(successMsg || '复制成功', 'success'); resetBtn(); } else { navigator.clipboard.writeText(text) .then(() => { showToast(successMsg || '复制成功', 'success'); resetBtn(); }) .catch(() => { showToast(failMsg || '复制失败', 'error'); resetBtn(); }); } } catch (e) { showToast(failMsg || '复制失败', 'error'); resetBtn(); } }; doCopy(); } // ========== 脚本卡片 ========== function enhanceScriptList() { const list = document.querySelector('ol.script-list, ul.script-list, #browse-script-list'); if (!list) { log.warn('Script list not found'); return; } list.classList.add('gfe-script-list'); const items = list.querySelectorAll('li[data-script-id]'); if (!items.length) return; items.forEach(li => { if (li.dataset.gfeEnhanced) return; li.dataset.gfeEnhanced = '1'; const scriptId = li.dataset.scriptId; const scriptName = li.dataset.scriptName || 'Unknown'; const codeUrl = li.dataset.codeUrl || ''; const rating = li.dataset.scriptRatingScore || 'N/A'; const version = li.dataset.scriptVersion || 'N/A'; const author = li.dataset.scriptAuthorName || 'unknown'; const desc = li.dataset.scriptDescription || ''; const link = li.querySelector('a.script-link'); const linkHref = link ? link.href : '#'; const html = `
${Utils.escapeHtml(scriptName)}
⭐ ${rating} 📦 ${version} 👤 ${Utils.escapeHtml(author)}
${Utils.escapeHtml(desc)}
${codeUrl ? `安装` : ''}
`; li.innerHTML = html; li.style.listStyle = 'none'; li.querySelector('.gfe-btn-fav').addEventListener('click', () => toggleFavorite(scriptId, scriptName)); const noteBtn = li.querySelector('.gfe-btn-note'); const panel = li.querySelector('.gfe-note-panel'); noteBtn.addEventListener('click', () => { const isOpen = panel.style.display !== 'none'; panel.style.display = isOpen ? 'none' : 'block'; if (!isOpen) panel.querySelector('textarea').focus(); }); panel.querySelector('.note-save').addEventListener('click', () => { const val = panel.querySelector('textarea').value; setNote(scriptId, val); showToast('笔记已保存', 'success'); }); panel.querySelector('.note-delete').addEventListener('click', () => { panel.querySelector('textarea').value = ''; setNote(scriptId, ''); showToast('笔记已删除', 'success'); panel.style.display = 'none'; }); const detailBtn = li.querySelector('.gfe-btn-detail'); const detailPanel = li.querySelector('.gfe-detail-panel'); detailBtn.addEventListener('click', async () => { if (detailPanel.style.display !== 'none') { detailPanel.style.display = 'none'; return; } detailPanel.style.display = 'block'; detailPanel.textContent = '加载详情...'; try { const info = await fetchScriptInfo(scriptId); detailPanel.innerHTML = `
描述:${info.description || '无描述'}
安装量:${info.total_installs || 0}
更新:${info.code_updated_at ? new Date(info.code_updated_at).toLocaleString() : '未知'}
`; } catch(e) { detailPanel.textContent = '加载失败'; } }); li.querySelector('.gfe-btn-filter').addEventListener('click', () => { const val = prompt('输入过滤规则 (如 scriptId=123)', 'scriptId=' + scriptId); if (val) { const parts = val.split('='); if (parts.length === 2) { addFilter(parts[0].trim(), parts[1].trim()); applyFilters(); showToast('过滤规则已添加', 'success'); } } }); const bookmarkBtn = li.querySelector('.gfe-btn-bookmark'); bookmarkBtn.addEventListener('click', () => { toggleBookmark(scriptId, scriptName, linkHref); }); updateBookmarkButton(bookmarkBtn, scriptId); }); // 卡片样式通过 CSS 变量自动应用,无需逐个设置 } async function fetchScriptInfo(scriptId) { const resp = await fetch(`/scripts/${scriptId}.json`); if (!resp.ok) throw new Error('Network error'); return await resp.json(); } // ========== 书签管理 ========== const BOOKMARK_KEY = 'gfe_bookmarks'; function getBookmarks() { return Config.get(BOOKMARK_KEY, {}); } function setBookmarks(bm) { Config.set(BOOKMARK_KEY, bm); } function isBookmarked(scriptId) { return !!getBookmarks()[scriptId]; } function toggleBookmark(scriptId, scriptName, url) { const bm = getBookmarks(); if (bm[scriptId]) { delete bm[scriptId]; setBookmarks(bm); showToast('已移除书签', 'success'); } else { bm[scriptId] = { name: scriptName || scriptId, url: url || `https://greasyfork.org/scripts/${scriptId}`, time: Date.now() }; setBookmarks(bm); showToast('已添加书签', 'success'); } updateAllBookmarkButtons(); } function updateBookmarkButton(btn, scriptId) { const active = isBookmarked(scriptId); btn.classList.toggle('active', active); btn.textContent = active ? '⭐' : '☆'; } function updateAllBookmarkButtons() { document.querySelectorAll('.gfe-btn-bookmark[data-script-id]').forEach(btn => { const id = btn.dataset.scriptId; const active = isBookmarked(id); btn.classList.toggle('active', active); btn.textContent = active ? '⭐' : '☆'; }); } // ========== 代码页增强 ========== function enhanceCodePage() { if (!location.pathname.includes('/code')) return; const container = document.querySelector('.code-container'); if (!container) return; const pre = container.querySelector('pre'); if (!pre) return; const toolbar = document.createElement('div'); toolbar.className = 'gfe-code-toolbar'; const copyBtn = document.createElement('button'); copyBtn.textContent = '复制代码'; copyBtn.addEventListener('click', function() { const text = pre.innerText || ''; copyTextWithFeedback(text, this, '复制成功', '复制失败'); }); toolbar.appendChild(copyBtn); const text = pre.innerText || ''; const lines = text.split('\n').length; const chars = text.length; const statsSpan = document.createElement('span'); statsSpan.className = 'gfe-code-stats'; statsSpan.textContent = `代码统计: ${lines} 行, ${chars} 字符`; toolbar.appendChild(statsSpan); container.prepend(toolbar); } // ========== 版本页增强 ========== function enhanceVersionsPage() { if (!location.pathname.includes('/versions')) return; document.querySelectorAll('.history_versions li').forEach(li => { if (li.dataset.gfeEnhanced) return; li.dataset.gfeEnhanced = '1'; const versionLink = li.querySelector('.version-number a'); if (!versionLink) return; const date = li.querySelector('.version-date'); const changelog = li.querySelector('.version-changelog'); const versionText = versionLink.textContent.trim(); const scriptId = location.pathname.match(/\/scripts\/(\d+)/)?.[1] || ''; const installUrl = `https://update.${location.hostname}/scripts/${scriptId}/${versionText}/`; const html = `
${versionLink.outerHTML} ${date?.textContent || ''}
${changelog?.innerHTML || ''}
安装 查看代码
`; li.innerHTML = html; li.className = 'gfe-version-item'; }); document.addEventListener('click', async (e) => { const btn = e.target.closest('.gfe-version-copy'); if (!btn) return; try { const resp = await fetch(btn.dataset.code); const text = await resp.text(); copyTextWithFeedback(text, btn, '复制成功', '复制失败'); } catch { showToast('复制失败', 'error'); } }); } // ========== 论坛增强 ========== function enhanceForum() { if (!location.pathname.includes('/discussions')) return; const seen = new Set(); document.querySelectorAll('.discussion-list-container').forEach(container => { const snippet = container.querySelector('.discussion-snippet')?.textContent?.trim(); if (!snippet) return; if (seen.has(snippet) && Config.get('filter_duplicate', true)) { container.style.display = 'none'; container.dataset.filtered = 'true'; } else { seen.add(snippet); } }); document.querySelectorAll('.discussion-list-container').forEach(container => { if (container.querySelector('.gfe-forum-filter-btn')) return; const meta = container.querySelector('.discussion-meta'); if (!meta) return; const filterBtn = document.createElement('button'); filterBtn.className = 'gfe-forum-filter-btn'; filterBtn.textContent = '过滤'; filterBtn.style.cssText = 'margin-left: 8px; padding: 2px 8px; font-size: 12px; cursor: pointer;'; filterBtn.addEventListener('click', () => { const title = container.querySelector('.discussion-title')?.textContent?.trim() || ''; const val = prompt('输入过滤规则 (如 title=xxx)', 'title=' + title); if (val) { const parts = val.split('='); if (parts.length === 2) { addFilter(parts[0].trim(), parts[1].trim()); applyFilters(); showToast('过滤规则已添加', 'success'); } } }); meta.appendChild(filterBtn); const reportBtn = document.createElement('button'); reportBtn.className = 'gfe-forum-report-btn'; reportBtn.textContent = '举报'; reportBtn.style.cssText = 'margin-left: 8px; padding: 2px 8px; font-size: 12px; cursor: pointer; color: #f56c6c;'; reportBtn.addEventListener('click', () => { const discussionId = container.dataset.discussionId || ''; if (discussionId) { window.open(`/reports/new?item_class=discussion&item_id=${discussionId}`, '_blank'); } else { showToast('无法获取讨论ID', 'error'); } }); meta.appendChild(reportBtn); }); const hideRead = Config.get('hide_read_comments', false); const italicRead = Config.get('italic_read_comments', false); if (hideRead || italicRead) { document.querySelectorAll('.discussion-list-container.discussion-read').forEach(container => { if (hideRead) container.classList.add('discussion-read-hidden'); if (italicRead) container.classList.add('discussion-read-italic'); }); } } // ========== 图片查看器 ========== function initImageViewer() { if (typeof Viewer === 'undefined') { log.warn('ViewerJS not loaded'); return; } document.addEventListener('click', (e) => { const img = e.target.closest('img'); if (!img) return; if (img.closest('.viewer-container') || img.closest('a')) return; const container = img.closest('.user-content, #script-content, .gfe-note-panel') || document; const images = container.querySelectorAll('img'); const srcs = Array.from(images).map(i => i.src || i.dataset.src); const index = srcs.indexOf(img.src); if (index === -1) return; try { const viewer = new Viewer.default(container, { url: 'src', zIndex: 99999, hidden: () => viewer.destroy() }); viewer.view(index); } catch (err) { log.error('Viewer error:', err); } }); } // ========== Markdown 复制 ========== function addMarkdownCopyButtons() { document.querySelectorAll('.user-content pre, .markdown-body pre').forEach(pre => { if (pre.querySelector('.gfe-md-copy')) return; const btn = document.createElement('button'); btn.className = 'gfe-md-copy'; btn.textContent = '复制'; btn.style.cssText = 'position:absolute; top:4px; right:4px; padding:2px 8px; font-size:12px; background:#409eff; color:#fff; border:none; border-radius:3px; cursor:pointer; opacity:0.6; transition:opacity 0.2s;'; btn.addEventListener('mouseenter', () => btn.style.opacity = '1'); btn.addEventListener('mouseleave', () => btn.style.opacity = '0.6'); btn.addEventListener('click', function() { const text = pre.innerText || ''; copyTextWithFeedback(text, this, '复制成功', '复制失败'); }); pre.style.position = 'relative'; pre.appendChild(btn); }); } // ========== 大纲导航 ========== function initOutline() { if (!Config.get('outline_enabled', true)) return; const path = location.pathname; if (!path.includes('/scripts') && !path.includes('/discussions')) return; if (path.match(/\/scripts\/\d+/)) return; const headings = document.querySelectorAll('body > div.width-constraint h1, h2, h3, h4, h5, h6'); if (headings.length < 3) return; const outline = document.createElement('ul'); outline.className = 'gfe-outline'; headings.forEach(h => { const id = h.id || h.textContent.trim().replace(/\s+/g, '-'); if (!h.id) h.id = id; const li = document.createElement('li'); const a = document.createElement('a'); a.href = '#' + id; a.textContent = h.textContent.trim(); li.appendChild(a); outline.appendChild(li); }); const container = document.querySelector('body > div.width-constraint > section'); if (container) container.prepend(outline); } // ========== 搜索语法 ========== function initSearchSyntax() { if (!Config.get('search_syntax', true)) return; const searchInput = document.querySelector('input[name="q"][type="search"]'); if (!searchInput) return; const form = searchInput.closest('form'); if (!form || form.method !== 'get') return; form.addEventListener('submit', (e) => { const raw = searchInput.value; const pairs = raw.match(/\b(\w+:[^\s]+)\b/g) || []; const cleaned = raw.replace(/\b\w+:[^\s]+\b/g, '').trim(); const parsed = {}; pairs.forEach(p => { const [key, val] = p.split(':'); parsed[key.toLowerCase()] = val; }); if (Object.keys(parsed).length === 0) return; e.preventDefault(); const url = new URL(form.action, window.location.href); url.searchParams.set('q', cleaned); if (parsed.type) { const types = { script: 'scripts', lib: 'scripts/libraries', library: 'scripts/libraries', user: 'users' }; if (types[parsed.type]) url.pathname = `/${types[parsed.type]}`; } if (parsed.lang) { const langs = { js: '', javascript: '', css: 'css', all: 'all' }; if (langs[parsed.lang] !== undefined) { if (langs[parsed.lang] === '') url.searchParams.delete('language'); else url.searchParams.set('language', langs[parsed.lang]); } } if (parsed.sort) { const sorts = { rel: '', relevant: '', day: 'daily_installs', total: 'total_installs', score: 'ratings', created: 'created', updated: 'updated', name: 'name' }; if (sorts[parsed.sort]) { if (sorts[parsed.sort] === '') url.searchParams.delete('sort'); else url.searchParams.set('sort', sorts[parsed.sort]); } } const rangeKeys = ['total', 'daily', 'rating', 'created', 'updated']; rangeKeys.forEach(k => { if (parsed[k]) { const rawVal = parsed[k]; const op = rawVal[0]; const val = rawVal.slice(1); if (['>','<','='].includes(op)) { url.searchParams.set(`${k}_operator`, op === '>' ? 'gt' : op === '<' ? 'lt' : 'eq'); url.searchParams.set(k, val); } } }); window.location.href = url.href; }); } // ========== 粘性分页 ========== function initStickyPagination() { if (!Config.get('sticky_pagination', true)) return; const pagy = document.querySelector('.sidebarred-main-content > .pagy'); if (pagy) pagy.classList.add('gfe-sticky-pagination'); } // ========== 总是显示通知 ========== function initAlwaysNotify() { if (!Config.get('always_notify', false)) return; const nav = document.querySelector('#nav-user-info'); if (!nav) return; const profile = nav.querySelector('.user-profile-link'); if (!profile) return; const existing = nav.querySelector('.notification-widget'); if (existing && existing.textContent !== '0') return; if (!existing) { const a = document.createElement('a'); a.className = 'notification-widget'; a.textContent = '0'; a.href = profile.querySelector('a').href + '/notifications'; nav.insertBefore(a, profile); } } // ========== 清理旧评论 ========== function cleanOldComments() { const days = Config.get('clean_old_comments_days', 30); if (days < 0) return; const now = Date.now(); document.querySelectorAll('#user-discussions-on-scripts-written > section > div').forEach(item => { const relTime = item.querySelector('relative-time'); if (relTime) { const date = new Date(relTime.date); if (now - date.getTime() > days * 24 * 3600 * 1000) { item.style.display = 'none'; } } }); } // ========== 用户统计 ========== function initUserStats() { if (!location.pathname.includes('/users/')) return; const userId = location.pathname.match(/\/users\/(\d+)/)?.[1]; if (!userId) return; const header = document.querySelector('#about-user h2'); if (!header) return; fetch(`/users/${userId}.json`) .then(r => r.json()) .then(data => { const scripts = data.scripts || []; const js = scripts.filter(s => s.code_url.endsWith('.js')).length; const css = scripts.filter(s => s.code_url.endsWith('.css')).length; const total = scripts.length; const daily = scripts.reduce((s, c) => s + (c.daily_installs || 0), 0); const totalInstalls = scripts.reduce((s, c) => s + (c.total_installs || 0), 0); const stats = document.createElement('div'); stats.style.cssText = 'font-size:14px; color:var(--gfe-text); margin:8px 0;'; stats.innerHTML = ` 📦 ${total} 脚本 🟢 JS: ${js} 🟡 CSS: ${css} 📊 日安装: ${daily} 📈 总安装: ${totalInstalls} `; header.parentNode.insertBefore(stats, header.nextSibling); }) .catch(() => {}); } // ========== Webhook 增强 ========== function enhanceWebhookPage() { if (!location.pathname.includes('/webhook-info')) return; document.querySelectorAll('.text-content dd, .text-content dd textarea').forEach(el => { if (el.nodeName === 'TEXTAREA' && el.value) { el.style.cssText = 'width:100%; height:80px; font-family:monospace;'; const btn = document.createElement('button'); btn.textContent = '复制'; btn.style.cssText = 'margin-top:4px; padding:2px 8px; cursor:pointer;'; btn.addEventListener('click', function() { copyTextWithFeedback(el.value, this, '复制成功', '复制失败'); }); el.parentNode.appendChild(btn); } }); } // ========== 显示版本号 ========== function showVersionInList() { if (!Config.get('show_version', false)) return; document.querySelectorAll('.script-list > li[data-script-version]').forEach(li => { const version = li.dataset.scriptVersion; if (version) { const span = document.createElement('span'); span.textContent = ` v${version}`; span.style.cssText = 'font-size:12px; color:#909399; margin-left:4px;'; const title = li.querySelector('.script-link'); if (title) title.parentNode.insertBefore(span, title.nextSibling); } }); } // ========== 扁平布局 ========== function applyFlatLayout() { if (Config.get('flat_layout', false)) { document.documentElement.classList.add('gfe-flat-layout'); } } // ========== 书签页面 ========== function showBookmarksPage() { if (location.pathname.includes('/404?Bookmarks')) { const bm = getBookmarks(); const container = document.querySelector('section.text-content'); if (!container) return; container.innerHTML = '

📖 书签

'; } } // ========== 设置面板 ========== function showSettings() { const container = document.createElement('div'); container.style.cssText = 'padding: 16px; max-width: 650px; margin: 0 auto; font-family: system-ui, -apple-system, sans-serif;'; const fields = [ // 常规 { key: 'theme', label: '主题', type: 'select', options: ['light', 'dark', 'system'], default: 'system' }, { key: 'language', label: '语言', type: 'select', options: ['zh-CN', 'en-US'], default: 'zh-CN' }, { key: 'auto_login', label: '自动登录', type: 'checkbox', default: true }, { key: 'account', label: '账号', type: 'text', default: '' }, { key: 'password', label: '密码', type: 'password', default: '' }, { key: 'secret', label: '2FA 密钥', type: 'text', default: '' }, // 显示 { key: 'dual_column', label: '双列显示', type: 'checkbox', default: true }, { key: 'image_viewer', label: '图片浏览增强', type: 'checkbox', default: true }, { key: 'markdown_copy', label: 'Markdown 复制按钮', type: 'checkbox', default: true }, { key: 'show_version', label: '显示版本号', type: 'checkbox', default: false }, // 论坛 { key: 'forum_filter', label: '论坛过滤', type: 'checkbox', default: true }, { key: 'filter_duplicate', label: '过滤重复评论', type: 'checkbox', default: true }, // 高级 { key: 'density', label: '密度', type: 'select', options: ['compact', 'comfortable', 'detailed'], default: 'comfortable' }, { key: 'sticky_pagination', label: '粘性分页', type: 'checkbox', default: true }, { key: 'flat_layout', label: '扁平布局', type: 'checkbox', default: false }, { key: 'always_notify', label: '总是显示通知', type: 'checkbox', default: false }, { key: 'hide_read_comments', label: '论坛阅读增强(隐藏/斜体已读)', type: 'checkbox', default: false }, { key: 'italic_read_comments', label: '', type: 'hidden' }, { key: 'regex_filter', label: '正则过滤', type: 'text', default: '' }, { key: 'clean_old_comments_days', label: '清理旧评论(天)', type: 'number', default: 30 }, { key: 'outline_enabled', label: '大纲导航', type: 'checkbox', default: true }, { key: 'search_syntax', label: '搜索语法', type: 'checkbox', default: true }, { key: 'code_enhance', label: '代码增强(复制+统计)', type: 'checkbox', default: true }, ]; let html = `

⚙️ 设置

`; html += `
`; // 常规 html += `
📋 常规
`; ['theme', 'language', 'auto_login'].forEach(k => { const f = fields.find(f => f.key === k); if (!f) return; const val = Config.get(f.key, f.default); const id = `gfe_${f.key}`; if (f.type === 'select') { html += `
`; } else if (f.type === 'checkbox') { html += `
`; } }); // 账号/密码/2FA ['account', 'password', 'secret'].forEach(k => { const f = fields.find(f => f.key === k); if (!f) return; const val = Config.get(f.key, f.default); const id = `gfe_${f.key}`; const type = f.type === 'password' ? 'password' : 'text'; html += `
`; }); html += `
`; // 显示 html += `
🖥️ 显示
`; ['dual_column', 'image_viewer', 'markdown_copy', 'show_version'].forEach(k => { const f = fields.find(f => f.key === k); if (!f) return; const val = Config.get(f.key, f.default); const id = `gfe_${f.key}`; html += `
`; }); html += `
`; // 论坛 html += `
💬 论坛
`; ['forum_filter', 'filter_duplicate'].forEach(k => { const f = fields.find(f => f.key === k); if (!f) return; const val = Config.get(f.key, f.default); const id = `gfe_${f.key}`; html += `
`; }); html += `
`; // 高级(折叠) html += `
⚙️ 高级设置
`; ['density', 'sticky_pagination', 'flat_layout', 'always_notify', 'hide_read_comments', 'regex_filter', 'clean_old_comments_days', 'outline_enabled', 'search_syntax', 'code_enhance'].forEach(k => { const f = fields.find(f => f.key === k); if (!f) return; const val = Config.get(f.key, f.default); const id = `gfe_${f.key}`; if (f.type === 'select') { html += `
`; } else if (f.type === 'checkbox') { html += `
`; } else if (f.type === 'text') { html += `
`; } else if (f.type === 'number') { html += `
`; } }); html += `
`; html += `
`; html += `
`; // 渲染覆盖层 const overlay = document.createElement('div'); overlay.id = 'gfe-settings-overlay'; overlay.style.cssText = `position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.5); z-index:999999; display:flex; align-items:center; justify-content:center; animation:gfeFadeIn 0.2s ease;`; const box = document.createElement('div'); box.style.cssText = `background:var(--gfe-card, #fff); color:var(--gfe-text, #333); border-radius:12px; padding:24px; max-width:650px; width:90%; max-height:80vh; overflow-y:auto; box-shadow:0 8px 32px rgba(0,0,0,0.3);`; box.innerHTML = html; overlay.appendChild(box); document.body.appendChild(overlay); // 搜索 const searchInput = box.querySelector('#gfe-settings-search'); searchInput.addEventListener('input', () => { const q = searchInput.value.toLowerCase(); box.querySelectorAll('.gfe-setting-item').forEach(item => { const label = item.querySelector('label')?.textContent?.toLowerCase() || ''; item.style.display = label.includes(q) ? '' : 'none'; }); }); // 保存 box.querySelector('#gfe-save-settings').addEventListener('click', () => { fields.forEach(f => { const el = document.getElementById(`gfe_${f.key}`); if (!el) return; let val; if (f.type === 'checkbox') val = el.checked; else if (f.type === 'select') val = el.value; else if (f.type === 'number') val = parseInt(el.value) || 0; else val = el.value; Config.set(f.key, val); if (f.key === 'theme') applyTheme(val); if (f.key === 'flat_layout') document.documentElement.classList.toggle('gfe-flat-layout', val); if (f.key === 'show_version') { if (val) showVersionInList(); else document.querySelectorAll('.script-list > li[data-script-version] span.v-version').forEach(el => el.remove()); } if (f.key === 'sticky_pagination') { const pagy = document.querySelector('.sidebarred-main-content > .pagy'); if (pagy) pagy.classList.toggle('gfe-sticky-pagination', val); } if (f.key === 'always_notify') { if (val) initAlwaysNotify(); else document.querySelector('.notification-widget')?.remove(); } if (f.key === 'clean_old_comments_days') { if (location.pathname.includes('/users/')) cleanOldComments(); } if (f.key === 'hide_read_comments' || f.key === 'italic_read_comments') { location.reload(); } if (f.key === 'regex_filter') applyFilters(); }); showToast('设置已保存', 'success'); overlay.remove(); }); // 导出 box.querySelector('#gfe-export-settings').addEventListener('click', () => { const data = Config.getAll(); const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `greasyfork_settings_${Date.now()}.json`; a.click(); URL.revokeObjectURL(url); }); // 导入 box.querySelector('#gfe-import-settings').addEventListener('click', () => { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = () => { const file = input.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { try { const data = JSON.parse(e.target.result); const all = Config.getAll(); Object.assign(all, data); Config.set(CONFIG_KEY, JSON.stringify(all)); showToast('配置导入成功', 'success'); box.querySelector('#gfe-close-settings')?.click(); setTimeout(() => showSettings(), 500); } catch(err) { showToast('配置导入失败', 'error'); } }; reader.readAsText(file); }; input.click(); }); box.querySelector('#gfe-close-settings').addEventListener('click', () => { overlay.remove(); }); overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); }); } // ========== 自动登录 ========== async function autoLogin() { if (!Config.get('auto_login', true)) return; const account = Config.get('account', ''); const password = Config.get('password', ''); const secret = Config.get('secret', ''); if (!account || !password) return; if (document.querySelector('#nav-user-info .user-profile-link')) return; const csrf = document.querySelector('meta[name="csrf-token"]'); if (!csrf) return; const token = csrf.getAttribute('content'); let otp = ''; if (secret && typeof OTPAuth !== 'undefined') { try { const totp = new OTPAuth.TOTP({ secret }); otp = totp.generate(); } catch {} } const data = new URLSearchParams(); data.append('authenticity_token', token); data.append('user[email]', account); data.append('user[password]', password); if (otp) data.append('user[otp_attempt]', otp); data.append('user[remember_me]', '1'); data.append('commit', '登录'); try { // 使用相对路径,由服务端根据当前会话自动重定向 const resp = await fetch('/users/sign_in', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: data.toString(), }); if (resp.ok && resp.url.includes('/users/')) { showToast('登录成功,即将刷新', 'success'); setTimeout(() => location.reload(), 1000); } else { showToast('登录失败,请检查账号密码', 'error'); } } catch (err) { log.error('Login error:', err); } } // ========== 脚本同步 ========== async function openMyScripts() { if (!document.querySelector('#nav-user-info .user-profile-link')) { showToast('请先登录', 'error'); return; } const profileLink = document.querySelector('#nav-user-info .user-profile-link a'); if (profileLink) { window.location.href = profileLink.href + '/scripts'; } else { showToast('无法获取用户页面', 'error'); } } // ========== 初始化 ========== function init() { log.info('Starting Companion v3.8.4...'); injectStyles(); initTheme(); initTooltip(); const path = location.pathname; showBookmarksPage(); if (path.match(/\/scripts(\/|$)/) && !path.includes('/code') && !path.includes('/versions')) { setTimeout(() => { enhanceScriptList(); applyFilters(); showVersionInList(); applyFlatLayout(); }, 300); const observer = Utils.observe(document.body, { childList: true, subtree: true }, () => { if (document.querySelector('ol.script-list, ul.script-list, #browse-script-list')) { enhanceScriptList(); applyFilters(); } }); setTimeout(() => observer.disconnect(), 10000); } else if (path.includes('/code')) { if (Config.get('code_enhance', true)) enhanceCodePage(); } else if (path.includes('/versions')) { if (Config.get('version_enhance', true)) enhanceVersionsPage(); } else if (path.includes('/discussions')) { if (Config.get('forum_filter', true)) enhanceForum(); applyFilters(); } else if (path.includes('/users/')) { initUserStats(); cleanOldComments(); } else if (path.includes('/webhook-info')) { enhanceWebhookPage(); } if (Config.get('image_viewer', true)) initImageViewer(); if (Config.get('markdown_copy', true)) { setTimeout(addMarkdownCopyButtons, 500); } initOutline(); initSearchSyntax(); initStickyPagination(); initAlwaysNotify(); Utils.registerMenu('⚙️ 设置', showSettings); Utils.registerMenu('📂 我的脚本', openMyScripts); Utils.registerMenu('📖 书签', () => { window.location.href = '/404?Bookmarks'; }); setTimeout(autoLogin, 500); let lastUrl = location.href; setInterval(() => { if (location.href !== lastUrl) { lastUrl = location.href; applyFilters(); if (location.pathname.match(/\/scripts(\/|$)/) && !location.pathname.includes('/code') && !location.pathname.includes('/versions')) { setTimeout(enhanceScriptList, 300); } } }, 1000); log.info('Init complete'); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();