// ==UserScript== // @name GitHub Repo Linker // @namespace https://github.com/jaysh/ // @version 1.0.0 // @description 点击悬浮框,自动从 GitHub Raw 链接跳转到对应的仓库页面 // @author Jaysh // @homepageURL https://github.com/jaysh/ // @supportURL https://github.com/jaysh/ // @match https://raw.githubusercontent.com/* // @icon https://github.githubassets.com/favicons/favicon-dark.svg // @inject-into content // @license MIT // @grant GM.getValue // @grant GM.setValue // @grant GM.xmlHttpRequest // @grant GM.registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @grant GM_addElement // @grant GM.addElement // @grant GM_listValues // @grant GM_deleteValue // @grant GM.listValues // @grant GM.deleteValue // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_addStyle // @grant GM.addStyle // @grant GM_openInTab // @grant GM.openInTab // @connect raw.githubusercontent.com // @connect github.com // @run-at document-end // @name:zh-CN GitHub 仓库链接跳转助手 // @description:zh-CN 点击悬浮框,自动解析 GitHub Raw 链接并跳转到对应的仓库页面 // @downloadURL https://github.com/ifsherlock/My-Moviepilot-Script/raw/refs/heads/main/GitHub%20仓库链接跳转助手.user.js // @updateURL https://github.com/ifsherlock/My-Moviepilot-Script/raw/refs/heads/main/GitHub%20仓库链接跳转助手.user.js // ==/UserScript== (() => { 'use strict'; /** * 从 GitHub Raw URL 解析出仓库地址 * 规则:https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{path...} * -> https://github.com/{owner}/{repo} */ function parseGitHubRepoUrl(rawUrl) { try { const url = new URL(rawUrl); // 匹配 raw.githubusercontent.com 域名 if (url.hostname === 'raw.githubusercontent.com') { const pathParts = url.pathname.split('/').filter(Boolean); // 路径格式: /{owner}/{repo}/{branch}/{...} if (pathParts.length >= 2) { const owner = pathParts[0]; const repo = pathParts[1]; return `https://github.com/${owner}/${repo}`; } } // 如果不是 raw URL,尝试直接匹配 github.com 的用户/仓库 if (url.hostname === 'github.com') { const pathParts = url.pathname.split('/').filter(Boolean); if (pathParts.length >= 2) { const owner = pathParts[0]; const repo = pathParts[1]; return `https://github.com/${owner}/${repo}`; } } return null; } catch (e) { console.error('[GitHub Repo Linker] URL 解析失败:', e); return null; } } /** * 从当前页面中自动发现 GitHub Raw 链接 */ function findRawUrlsOnPage() { const rawUrls = new Set(); // 1. 检查当前页面 URL 本身 if (window.location.hostname === 'raw.githubusercontent.com') { rawUrls.add(window.location.href); } // 2. 扫描页面中的所有链接 const links = document.querySelectorAll('a[href*="raw.githubusercontent.com"]'); links.forEach(link => { rawUrls.add(link.href); }); // 3. 扫描页面文本中的 GitHub Raw 链接 const bodyText = document.body.innerText || ''; const regex = /https?:\/\/raw\.githubusercontent\.com\/[^\s"'<>]+/gi; const matches = bodyText.match(regex); if (matches) { matches.forEach(m => rawUrls.add(m)); } // 4. 扫描所有的 code/pre 标签中的链接 const codeElements = document.querySelectorAll('code, pre, .highlight'); codeElements.forEach(el => { const text = el.textContent || ''; const codeMatches = text.match(/https?:\/\/raw\.githubusercontent\.com\/[^\s"'<>]+/gi); if (codeMatches) { codeMatches.forEach(m => rawUrls.add(m)); } }); return Array.from(rawUrls); } /** * 创建悬浮框样式 */ function injectStyles() { const styleId = 'github-repo-linker-styles'; if (document.getElementById(styleId)) return; const style = document.createElement('style'); style.id = styleId; style.textContent = ` #github-repo-linker-floater { position: fixed; bottom: 80px; right: 20px; z-index: 2147483647; background: linear-gradient(135deg, #24292f 0%, #57606a 100%); color: #ffffff; padding: 14px 22px; border-radius: 12px; cursor: pointer; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25), 0 1px 4px rgba(0, 0, 0, 0.1); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; font-size: 14px; font-weight: 600; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); display: flex; align-items: center; gap: 10px; user-select: none; border: 1px solid rgba(255, 255, 255, 0.15); backdrop-filter: blur(10px); } #github-repo-linker-floater:hover { background: linear-gradient(135deg, #1b1f23 0%, #444c56 100%); transform: translateY(-3px) scale(1.02); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.35), 0 2px 8px rgba(0, 0, 0, 0.15); border-color: rgba(255, 255, 255, 0.25); } #github-repo-linker-floater:active { transform: translateY(-1px) scale(0.98); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); transition: all 0.1s ease; } #github-repo-linker-floater .gl-icon { width: 22px; height: 22px; flex-shrink: 0; } #github-repo-linker-floater .gl-text { white-space: nowrap; } #github-repo-linker-floater.loading { opacity: 0.75; cursor: wait; } #github-repo-linker-floater .gl-spinner { display: none; width: 18px; height: 18px; border: 2px solid rgba(255, 255, 255, 0.3); border-top: 2px solid #ffffff; border-radius: 50%; animation: gl-spin 0.7s linear infinite; flex-shrink: 0; } #github-repo-linker-floater.loading .gl-spinner { display: inline-block; } #github-repo-linker-floater.loading .gl-icon { display: none; } @keyframes gl-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* 仓库列表弹窗 */ #github-repo-linker-modal-overlay { position: fixed; inset: 0; z-index: 2147483646; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; backdrop-filter: blur(4px); animation: gl-fadeIn 0.2s ease; } @keyframes gl-fadeIn { from { opacity: 0; } to { opacity: 1; } } #github-repo-linker-modal { background: #ffffff; border-radius: 16px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); max-width: 560px; width: 90%; max-height: 80vh; overflow-y: auto; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; animation: gl-slideUp 0.25s cubic-bezier(0.4, 0, 0.2, 1); } @keyframes gl-slideUp { from { transform: translateY(30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } #github-repo-linker-modal .gl-modal-header { padding: 20px 24px 16px; border-bottom: 1px solid #e1e4e8; display: flex; align-items: center; justify-content: space-between; } #github-repo-linker-modal .gl-modal-title { font-size: 18px; font-weight: 700; color: #24292f; display: flex; align-items: center; gap: 8px; } #github-repo-linker-modal .gl-modal-close { width: 32px; height: 32px; border-radius: 8px; border: none; background: #f6f8fa; color: #57606a; cursor: pointer; font-size: 18px; display: flex; align-items: center; justify-content: center; transition: all 0.15s ease; } #github-repo-linker-modal .gl-modal-close:hover { background: #e1e4e8; color: #24292f; } #github-repo-linker-modal .gl-modal-body { padding: 16px 24px 24px; } #github-repo-linker-modal .gl-repo-item { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-radius: 10px; margin-bottom: 8px; cursor: pointer; transition: all 0.15s ease; border: 1px solid transparent; color: #24292f; text-decoration: none; } #github-repo-linker-modal .gl-repo-item:hover { background: #f6f8fa; border-color: #d0d7de; } #github-repo-linker-modal .gl-repo-item .gl-repo-icon { width: 20px; height: 20px; flex-shrink: 0; color: #57606a; } #github-repo-linker-modal .gl-repo-item .gl-repo-info { flex: 1; min-width: 0; } #github-repo-linker-modal .gl-repo-item .gl-repo-name { font-weight: 600; font-size: 15px; color: #0969da; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #github-repo-linker-modal .gl-repo-item .gl-repo-path { font-size: 12px; color: #57606a; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px; } #github-repo-linker-modal .gl-empty { text-align: center; padding: 32px 16px; color: #57606a; } #github-repo-linker-modal .gl-empty svg { width: 48px; height: 48px; margin-bottom: 12px; color: #d0d7de; } #github-repo-linker-modal .gl-badge { display: inline-block; font-size: 11px; padding: 2px 8px; border-radius: 12px; background: #ddf4ff; color: #0969da; font-weight: 600; margin-left: 8px; white-space: nowrap; } /* 暗色模式 */ @media (prefers-color-scheme: dark) { #github-repo-linker-modal { background: #1c2128; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); } #github-repo-linker-modal .gl-modal-header { border-bottom-color: #30363d; } #github-repo-linker-modal .gl-modal-title { color: #e6edf3; } #github-repo-linker-modal .gl-modal-close { background: #30363d; color: #8b949e; } #github-repo-linker-modal .gl-modal-close:hover { background: #444c56; color: #e6edf3; } #github-repo-linker-modal .gl-repo-item { color: #e6edf3; } #github-repo-linker-modal .gl-repo-item:hover { background: #22272e; border-color: #30363d; } #github-repo-linker-modal .gl-repo-item .gl-repo-name { color: #58a6ff; } #github-repo-linker-modal .gl-repo-item .gl-repo-path { color: #8b949e; } #github-repo-linker-modal .gl-empty { color: #8b949e; } #github-repo-linker-modal .gl-empty svg { color: #30363d; } #github-repo-linker-modal .gl-badge { background: #1f3a5f; color: #58a6ff; } } `; document.head.appendChild(style); } /** * 创建悬浮按钮 */ function createFloater() { const floater = document.createElement('div'); floater.id = 'github-repo-linker-floater'; floater.title = 'GitHub 仓库链接跳转'; floater.innerHTML = ` 跳转仓库
`; return floater; } /** * 创建仓库列表弹窗 */ function createModal(repos) { // 移除已有弹窗 const existingOverlay = document.getElementById('github-repo-linker-modal-overlay'); if (existingOverlay) { existingOverlay.remove(); } const overlay = document.createElement('div'); overlay.id = 'github-repo-linker-modal-overlay'; const modal = document.createElement('div'); modal.id = 'github-repo-linker-modal'; let repoItemsHtml = ''; if (repos.length === 0) { repoItemsHtml = `未发现 GitHub Raw 链接
当前页面中没有检测到 raw.githubusercontent.com 链接