// ==UserScript== // @name Chrome 商店链接自动替换为镜像 // @name:en Chrome Web Store Link to Mirror // @namespace https://github.com/userscripts/chrome-store-mirror // @version 1.0.1 // @description 自动将网页上的 Chrome 网上应用店链接替换为 CrxSoso 镜像链接,并提供关键词快速搜索功能 // @description:en Automatically replace Chrome Web Store links with CrxSoso mirror links, with quick search menu // @author You // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_openInTab // @run-at document-idle // @license AI生成,随意使用 // ==/UserScript== (function () { 'use strict'; // ========== 配置 ========== const CONFIG = { // 镜像站地址,可根据需要修改 mirrorBase: 'https://www.crxsoso.com/webstore/detail/', // 镜像站搜索页地址 searchBase: 'https://www.crxsoso.com/search', // Chrome 商店域名列表 storeDomains: [ 'chromewebstore.google.com', 'chrome.google.com' ], // 特征码长度(Chrome 扩展 ID 固定为 32 位小写字母) extensionIdLength: 32, // 是否在控制台输出日志 debug: false, // 搜索结果是否在新标签页打开 searchOpenInNewTab: true }; // ========== 工具函数 ========== /** * 日志输出 */ function log(...args) { if (CONFIG.debug) { console.log('[ChromeStoreMirror]', ...args); } } /** * 判断 URL 是否为 Chrome 商店链接 */ function isChromeStoreUrl(url) { try { const u = new URL(url); return CONFIG.storeDomains.some(domain => u.hostname === domain); } catch { return false; } } /** * 从 Chrome 商店链接中提取扩展 ID(特征码) * 支持格式: * https://chromewebstore.google.com/detail/{name}/{extId} * https://chromewebstore.google.com/detail/{extId} * https://chrome.google.com/webstore/detail/{name}/{extId} * https://chrome.google.com/webstore/detail/{extId} * 带 query/hash 的版本也能处理 */ function extractExtensionId(url) { try { const u = new URL(url); // 去掉 query 和 hash,只看 pathname const pathname = u.pathname; // Chrome 扩展 ID 正则:32 位小写字母(a-p) const idRegex = /([a-p]{32})/; const match = pathname.match(idRegex); if (match) { return match[1]; } return null; } catch { return null; } } /** * 根据扩展 ID 生成镜像链接 */ function buildMirrorUrl(extensionId) { return CONFIG.mirrorBase + extensionId; } /** * 根据关键词生成镜像搜索页链接 */ function buildSearchUrl(keyword) { const url = new URL(CONFIG.searchBase); url.searchParams.set('keyword', keyword); url.searchParams.set('store', 'chrome'); return url.href; } /** * 统一的页面跳转函数(优先使用 GM_openInTab,兼容新窗口/当前窗口) */ function openUrl(url, newTab) { const openInNew = newTab !== undefined ? newTab : CONFIG.searchOpenInNewTab; try { if (openInNew && typeof GM_openInTab === 'function') { GM_openInTab(url, { active: true }); } else if (openInNew) { window.open(url, '_blank'); } else { location.href = url; } } catch (err) { // 回退到 window.open if (openInNew) { window.open(url, '_blank'); } else { location.href = url; } } } /** * 处理单个链接元素 * 返回是否进行了替换 */ function processLinkElement(a) { // 已经处理过的跳过 if (a.dataset._chromeStoreMirrorProcessed === 'true') { return false; } a.dataset._chromeStoreMirrorProcessed = 'true'; const href = a.getAttribute('href'); if (!href) return false; // 处理相对/绝对 URL let absoluteUrl; try { absoluteUrl = new URL(href, location.href).href; } catch { return false; } if (!isChromeStoreUrl(absoluteUrl)) return false; const extId = extractExtensionId(absoluteUrl); if (!extId) { log('未找到扩展 ID:', href); return false; } const mirrorUrl = buildMirrorUrl(extId); // 替换 href a.setAttribute('href', mirrorUrl); // 替换文本内容(如果文本看起来像是原链接) const text = a.textContent.trim(); if (text && isChromeStoreUrl(text)) { a.textContent = mirrorUrl; } // 添加提示 tooltip const oldTitle = a.getAttribute('title') || ''; if (!oldTitle.includes('镜像')) { a.setAttribute( 'title', (oldTitle ? oldTitle + ' | ' : '') + '已替换为 CrxSoso 镜像链接' ); } // 添加样式标记 a.style.setProperty('border-bottom', '1px dashed #4caf50'); a.style.setProperty('text-decoration-color', '#4caf50'); log('替换链接:', href, '->', mirrorUrl); return true; } /** * 批量处理容器内的所有链接 */ function processLinksIn(container) { if (!container) return 0; let count = 0; // 如果容器本身就是 a 标签 if (container.tagName && container.tagName.toLowerCase() === 'a') { if (processLinkElement(container)) count++; } // 查找所有子 a 标签 const links = container.querySelectorAll && container.querySelectorAll('a[href]'); if (links) { links.forEach(a => { if (processLinkElement(a)) count++; }); } return count; } // ========== 点击拦截(双重保障,防止某些动态绑定的事件绕过 href) ========== /** * 在捕获阶段拦截点击事件,检查是否是 Chrome 商店链接 */ document.addEventListener('click', function (e) { const a = e.target.closest && e.target.closest('a[href]'); if (!a) return; const href = a.getAttribute('href'); if (!href) return; let absoluteUrl; try { absoluteUrl = new URL(href, location.href).href; } catch { return; } if (!isChromeStoreUrl(absoluteUrl)) return; const extId = extractExtensionId(absoluteUrl); if (!extId) return; // 链接还没被替换的情况下,强制跳转 if (a.dataset._chromeStoreMirrorProcessed !== 'true' || !a.getAttribute('href').startsWith(CONFIG.mirrorBase)) { e.preventDefault(); e.stopPropagation(); const mirrorUrl = buildMirrorUrl(extId); // 处理 target const target = a.getAttribute('target') || '_self'; if (target === '_blank') { window.open(mirrorUrl, '_blank'); } else { location.href = mirrorUrl; } log('点击拦截跳转:', absoluteUrl, '->', mirrorUrl); } }, true); // 捕获阶段,先于页面自身的 click handler 执行 // ========== DOM 变化监听(处理动态加载的内容) ========== // 节流,避免短时间内大量触发 let observerTimer = null; function scheduleProcess(root) { if (observerTimer) return; observerTimer = setTimeout(() => { observerTimer = null; const count = processLinksIn(root || document.body); if (count > 0) { log('DOM 变化,处理了', count, '个新链接'); } }, 100); } const observer = new MutationObserver(function (mutations) { for (const m of mutations) { if (m.type === 'childList' && m.addedNodes.length > 0) { scheduleProcess(document.body); break; } // 处理属性变化(href 被页面脚本动态改掉) if (m.type === 'attributes' && m.attributeName === 'href') { const target = m.target; if (target.tagName && target.tagName.toLowerCase() === 'a') { // 重置处理标记,让它重新被处理 delete target.dataset._chromeStoreMirrorProcessed; scheduleProcess(target); } } } }); // ========== 搜索窗口 + 油猴菜单 ========== // 搜索弹窗 DOM 引用 let searchDialog = null; /** * 从剪贴板读取文本(带回退) */ function readClipboardFallback() { return new Promise((resolve) => { if (navigator.clipboard && navigator.clipboard.readText) { navigator.clipboard.readText().then(resolve).catch(() => resolve('')); } else { resolve(''); } }); } /** * 检查字符串是否是合法的 Chrome 扩展 ID */ function isExtensionId(str) { return /^[a-p]{32}$/.test(str.trim()); } /** * 创建并显示搜索弹窗 */ async function openSearchDialog() { // 已存在则直接显示 if (searchDialog && document.body.contains(searchDialog)) { searchDialog.style.display = 'flex'; const input = searchDialog.querySelector('#csm-search-input'); if (input) { input.focus(); input.select(); } return; } // 注入样式(只注入一次) if (!document.getElementById('csm-search-style')) { const style = document.createElement('style'); style.id = 'csm-search-style'; style.textContent = ` #csm-search-mask{ position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:2147483646; display:flex;align-items:center;justify-content:center; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif; animation:csm-fadein .15s ease-out; } @keyframes csm-fadein{from{opacity:0}to{opacity:1}} @keyframes csm-slideup{from{transform:translateY(-20px);opacity:0}to{transform:translateY(0);opacity:1}} #csm-search-dialog{ width:min(560px,92vw);background:#fff;border-radius:12px; box-shadow:0 20px 60px rgba(0,0,0,.3);padding:24px; animation:csm-slideup .2s ease-out;box-sizing:border-box; } #csm-search-title{ margin:0 0 16px;font-size:18px;font-weight:600;color:#1a1a1a; display:flex;align-items:center;gap:8px; } #csm-search-title::before{ content:"";display:inline-block;width:20px;height:20px;border-radius:5px; background:linear-gradient(135deg,#4caf50,#2196f3); } #csm-search-input-wrap{position:relative;} #csm-search-input{ width:100%;padding:12px 44px 12px 14px;box-sizing:border-box; border:2px solid #e0e0e0;border-radius:8px;font-size:15px; outline:none;transition:border-color .15s,box-shadow .15s;color:#1a1a1a; background:#fafafa; } #csm-search-input:focus{ border-color:#4caf50;background:#fff; box-shadow:0 0 0 3px rgba(76,175,80,.15); } #csm-search-clear{ position:absolute;right:10px;top:50%;transform:translateY(-50%); width:28px;height:28px;border:none;background:transparent;cursor:pointer; border-radius:50%;display:none;align-items:center;justify-content:center; color:#999;font-size:18px;line-height:1; } #csm-search-clear:hover{background:#eee;color:#333;} #csm-search-tip{ margin-top:10px;font-size:12px;color:#666;line-height:1.6; } #csm-search-tip code{ background:#f0f0f0;padding:1px 5px;border-radius:4px;font-size:11px; color:#d63384; } #csm-search-btns{ margin-top:18px;display:flex;gap:10px;justify-content:flex-end; } .csm-btn{ padding:8px 18px;border-radius:6px;border:none;cursor:pointer; font-size:14px;font-weight:500;transition:all .15s; } #csm-btn-cancel{background:#f0f0f0;color:#555;} #csm-btn-cancel:hover{background:#e0e0e0;} #csm-btn-search{ background:linear-gradient(135deg,#4caf50,#43a047);color:#fff; box-shadow:0 2px 6px rgba(76,175,80,.35); } #csm-btn-search:hover{ box-shadow:0 4px 10px rgba(76,175,80,.45);transform:translateY(-1px); } #csm-btn-search:disabled{opacity:.5;cursor:not-allowed;transform:none;} `; document.head.appendChild(style); } // 创建 DOM const mask = document.createElement('div'); mask.id = 'csm-search-mask'; mask.innerHTML = `
ggglfehkglgpenacfalffmiojghklamm)、中文/英文关键词Esc 关闭 · Enter 搜索 · Ctrl+Enter 当前页打开