// ==UserScript== // @name 微信外链自动跳转 // @namespace WeChat_External_Link_Auto_Redirect // @version 1.0 // @description 自动跳转微信拦截页面,支持域名白名单管理 // @author 一身惆怅 // @match *://weixin110.qq.com/cgi-bin/redirectforward* // @match *://weixin110.qq.com/cgi-bin/mmspamsupport-bin/newredirectconfirmcgi* // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @run-at document-end // @license MIT // ==/UserScript== (() => { 'use strict'; const CONFIG = { KEY: 'wx_whitelist_domains', DELAY: 800, API_FAVICON: (domain) => `https://cn.cravatar.com/favicon/api/index.php?url=${domain}` }; const ICONS = { SAFARI: '', ARROW: '', TRASH: '', GEAR: '', CLOSE: '' }; const CSS = ` :root { --ios-bg: rgba(255, 255, 255, 0.75); --ios-border: rgba(255, 255, 255, 0.8); --ios-shadow: 0 20px 40px -8px rgba(0, 0, 0, 0.12); --ios-text: #1d1d1f; --ios-sub: #86868b; --ios-accent: #007aff; --ios-surface: rgba(255, 255, 255, 0.5); --ios-hover: rgba(0, 0, 0, 0.05); } @media (prefers-color-scheme: dark) { :root { --ios-bg: rgba(30, 30, 30, 0.8); --ios-border: rgba(255, 255, 255, 0.1); --ios-shadow: 0 24px 64px -12px rgba(0, 0, 0, 0.6); --ios-text: #f5f5f7; --ios-sub: #a1a1a6; --ios-accent: #0a84ff; --ios-surface: rgba(255, 255, 255, 0.08); --ios-hover: rgba(255, 255, 255, 0.1); } } #wx-root { position: fixed; inset: 0; z-index: 999999; display: grid; place-items: center; background: rgba(0,0,0,0.01); backdrop-filter: blur(10px); opacity: 0; animation: fade-in 0.4s ease-out forwards; font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif; } .wx-card { width: 340px; padding: 32px 24px; background: var(--ios-bg); backdrop-filter: saturate(180%) blur(24px); border: 1px solid var(--ios-border); border-radius: 28px; box-shadow: var(--ios-shadow); display: flex; flex-direction: column; align-items: center; text-align: center; position: relative; animation: scale-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; } .wx-settings-btn { position: absolute; top: 16px; right: 16px; width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center; cursor: pointer; color: var(--ios-sub); transition: 0.2s; } .wx-settings-btn:hover { background: var(--ios-hover); color: var(--ios-text); } .wx-icon { width: 72px; height: 72px; margin-bottom: 20px; background: var(--ios-bg); border-radius: 18px; box-shadow: 0 8px 24px rgba(0,0,0,0.06); display: grid; place-items: center; overflow: hidden; } .wx-icon img { width: 48px; height: 48px; object-fit: contain; } .wx-title { font-size: 20px; font-weight: 700; color: var(--ios-text); margin-bottom: 8px; } .wx-desc { font-size: 13px; color: var(--ios-sub); background: var(--ios-surface); padding: 6px 12px; border-radius: 8px; margin-bottom: 32px; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-family: 'SF Mono', monospace; } .wx-btn { width: 100%; padding: 14px; border: none; border-radius: 14px; font-size: 16px; font-weight: 600; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; transition: transform 0.1s; margin-bottom: 12px; } .wx-btn:active { transform: scale(0.97); } .wx-btn-primary { background: linear-gradient(135deg, var(--ios-accent), #0055b3); color: white; box-shadow: 0 4px 12px rgba(0, 122, 255, 0.25); } .wx-btn-secondary { background: var(--ios-surface); color: var(--ios-text); margin-bottom: 0; } .wx-btn-secondary:hover { background: var(--ios-hover); } #wx-sheet { position: fixed; inset: 0; z-index: 1000000; background: rgba(0,0,0,0.25); display: grid; place-items: center; opacity: 0; pointer-events: none; transition: opacity 0.3s; } #wx-sheet.active { opacity: 1; pointer-events: auto; } .wx-sheet-card { width: 380px; max-height: 70vh; background: var(--ios-bg); backdrop-filter: saturate(180%) blur(30px); border-radius: 24px; box-shadow: 0 40px 100px rgba(0,0,0,0.3); display: flex; flex-direction: column; transform: scale(0.95); transition: 0.3s cubic-bezier(0.16, 1, 0.3, 1); } #wx-sheet.active .wx-sheet-card { transform: scale(1); } .wx-sheet-head { padding: 18px 24px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid rgba(0,0,0,0.05); } .wx-sheet-list { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 8px; } .wx-list-item { background: var(--ios-surface); padding: 10px 14px; border-radius: 12px; display: flex; justify-content: space-between; align-items: center; } .wx-list-info { display: flex; align-items: center; gap: 10px; overflow: hidden; } .wx-list-txt { font-size: 14px; color: var(--ios-text); font-family: 'SF Mono', monospace; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; } .wx-del-btn { color: #ff453a; padding: 6px; border-radius: 6px; cursor: pointer; background: rgba(255, 69, 58, 0.1); display: flex; } .wx-del-btn:active { transform: scale(0.9); } .wx-spin { width: 16px; height: 16px; border: 2px solid rgba(255,255,255,0.3); border-top-color: #fff; border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes fade-in { to { opacity: 1; } } @keyframes scale-in { from { transform: scale(0.92); } to { transform: scale(1); } } @keyframes spin { to { transform: rotate(360deg); } } `; const Store = { get list() { return GM_getValue(CONFIG.KEY, []); }, has(domain) { return this.list.includes(domain); }, add(domain) { const current = this.list; if (!current.includes(domain)) GM_setValue(CONFIG.KEY, [...current, domain]); }, remove(domain) { GM_setValue(CONFIG.KEY, this.list.filter(d => d !== domain)); } }; class Redirector { constructor() { this.injectStyles(); this.url = this.extractUrl(); if (this.url) this.init(); } injectStyles() { const style = document.createElement('style'); style.textContent = CSS; document.head.appendChild(style); } extractUrl() { if (window.cgiData) { if (window.cgiData.desc) { const decoded = new DOMParser().parseFromString(window.cgiData.desc, 'text/html').body.textContent; if (decoded.startsWith('http')) return decoded.trim(); } if (window.cgiData.url) { return window.cgiData.url.replaceAll('&', '&'); } } const domUrl = document.querySelector('.weui-msg__desc')?.textContent; return domUrl?.startsWith('http') ? domUrl.trim() : null; } get domain() { try { return new URL(this.url).hostname; } catch { return 'Unknown Domain'; } } init() { GM_registerMenuCommand("⚙️ 管理白名单", () => this.renderSheet()); const isAuto = Store.has(this.domain); this.renderOverlay(isAuto); } renderOverlay(isAuto) { const root = document.createElement('div'); root.id = 'wx-root'; const content = isAuto ? `