// ==UserScript== // @name OmniParser 全平台无水印视频与图集下载助手 // @namespace https://d.513344.xyz/ // @license MIT // @version 1.8.5 // @description 自动感应并支持抖音、快手、小红书、B站、黄果短剧、皮皮虾、YouTube、TikTok、X (Twitter)、Threads、Instagram、Facebook、好看视频等视频/图集免水印解析与快捷下载! // @author OmniParser Dev Team // @updateURL https://update.greasyfork.org/scripts/597911/OmniParser%20%E5%85%A8%E5%B9%B3%E5%8F%B0%E6%97%A0%E6%B0%B4%E5%8D%B0%E8%A7%86%E9%A2%91%E4%B8%8E%E5%9B%BE%E9%9B%86%E4%B8%8B%E8%BD%BD%E5%8A%A9%E6%89%8B.meta.js // @downloadURL https://update.greasyfork.org/scripts/597911/OmniParser%20%E5%85%A8%E5%B9%B3%E5%8F%B0%E6%97%A0%E6%B0%B4%E5%8D%B0%E8%A7%86%E9%A2%91%E4%B8%8E%E5%9B%BE%E9%9B%86%E4%B8%8B%E8%BD%BD%E5%8A%A9%E6%89%8B.user.js // @match *://*.douyin.com/* // @match *://douyin.com/* // @match *://*.bilibili.com/* // @match *://bilibili.com/* // @match *://*.xiaohongshu.com/* // @match *://xiaohongshu.com/* // @match *://*.kuaishou.com/* // @match *://kuaishou.com/* // @match *://*.youtube.com/* // @match *://youtube.com/* // @match *://*.youtu.be/* // @match *://youtu.be/* // @match *://*.tiktok.com/* // @match *://tiktok.com/* // @match *://*.twitter.com/* // @match *://twitter.com/* // @match *://*.x.com/* // @match *://x.com/* // @match *://*.threads.net/* // @match *://threads.net/* // @match *://*.threads.com/* // @match *://threads.com/* // @match *://*.instagram.com/* // @match *://instagram.com/* // @match *://*.facebook.com/* // @match *://facebook.com/* // @match *://*.fb.watch/* // @match *://fb.watch/* // @match *://*.haokan.baidu.com/* // @match *://*.hongguoduanju.com/* // @match *://hongguoduanju.com/* // @match *://*.novelquickapp.com/* // @match *://novelquickapp.com/* // @match *://*.fanqiesp.com/* // @match *://fanqiesp.com/* // @match *://*.pipix.com/* // @match *://pipix.com/* // @match *://*.pipigaoxiao.com/* // @match *://pipigaoxiao.com/* // @match *://*.bsky.app/* // @match *://*.bluesky.app/* // @match *://d.hel.cc.cd/* // @match https://d.hel.cc.cd/* // @connect d.hel.cc.cd // @match *://x.bpm.kdns.fr/* // @match https://x.bpm.kdns.fr/* // @connect x.bpm.kdns.fr // @match *://d.513344.xyz/* // @match https://d.513344.xyz/* // @connect d.513344.xyz // @connect api.qrserver.com // @connect quickchart.io // @connect 127.0.0.1 // @connect localhost // @connect * // @grant unsafeWindow // @grant GM_xmlhttpRequest // @grant GM_setClipboard // @grant GM_openInTab // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @icon https://d.513344.xyz/static/favicon.ico // @run-at document-end // ==/UserScript== (function () { 'use strict'; let rawServer = GM_getValue('omni_server_url', 'https://d.513344.xyz'); if (rawServer === 'http://d.hel.cc.cd' || rawServer === 'https://d.hel.cc.cd' || rawServer === 'http://d.513344.xyz') { rawServer = 'https://d.513344.xyz'; GM_setValue('omni_server_url', rawServer); } const SCRIPT_VERSION = 'v1.8.5'; let OMNI_SERVER = rawServer; let PARSE_MODE = GM_getValue('omni_parse_mode', 'auto'); // 'auto' (智能双模:VIP/已登录免跳转浮层,未登录跳主站) | 'redirect' (始终跳主站) | 'modal' (始终浮层) let VIP_TOKEN = GM_getValue('omni_vip_token', ''); let VIP_INFO = GM_getValue('omni_vip_info', null); // { username, is_vip, vip_expire_timestamp, is_disabled, checked_at } function getOrCreateDeviceId() { let devId = GM_getValue('omni_device_id', ''); if (!devId || typeof devId !== 'string') { devId = 'DEV-US_' + Math.random().toString(36).substring(2, 10) + Date.now().toString(36); GM_setValue('omni_device_id', devId); } return devId; } // 获取精准 VIP 状态与免跳转降级标识 function getVipStatus() { const token = (VIP_TOKEN || '').trim(); const isRegistered = Boolean(token && token.startsWith('USR-')); if (!isRegistered) { return { isRegistered: false, isVipActive: false, isVipExpired: false, isDegraded: false, username: '', expireDateStr: '', expireTimestamp: null }; } const now = Date.now(); const info = VIP_INFO || {}; const expireTs = info.vip_expire_timestamp; let isVipActive = false; let isVipExpired = false; let expireDateStr = ''; if (expireTs) { expireDateStr = new Date(expireTs).toLocaleString(); if (now < expireTs) { isVipActive = true; } else { isVipExpired = true; } } else if (info.is_vip) { isVipActive = true; expireDateStr = '永久有效'; } return { isRegistered: true, isVipActive: isVipActive, isVipExpired: isVipExpired, isDegraded: isVipExpired, // VIP 到期自动启用免跳转降级模式 username: info.username || '', expireDateStr: expireDateStr, expireTimestamp: expireTs }; } // 从后端异步同步真实会员信息并维护本地缓存 function syncVipStatusFromServer(force = false, callback = null) { const token = (VIP_TOKEN || '').trim(); if (!token) { VIP_INFO = null; GM_setValue('omni_vip_info', null); if (callback) callback({ success: false, message: '未绑定 Token' }); return; } const now = Date.now(); if (!force && VIP_INFO && VIP_INFO.checked_at && (now - VIP_INFO.checked_at < 10 * 60 * 1000)) { if (callback) callback({ success: true, data: VIP_INFO, cached: true }); return; } GM_xmlhttpRequest({ method: 'GET', url: `${OMNI_SERVER}/api/user/me`, headers: { 'Authorization': 'Bearer ' + token, 'Accept': 'application/json', 'X-Client-Type': 'userscript', 'X-Device-ID': getOrCreateDeviceId(), 'X-Device-Name': '油猴脚本助手 (' + (navigator.platform || 'Browser') + ')' }, timeout: 10000, onload: function (res) { try { const json = JSON.parse(res.responseText); if (res.status === 200 && json.success && json.data) { const u = json.data; VIP_INFO = { username: u.username || '', is_vip: Boolean(u.is_vip), vip_expire_timestamp: u.vip_expire_timestamp || null, is_disabled: Boolean(u.is_disabled), checked_at: Date.now() }; GM_setValue('omni_vip_info', VIP_INFO); console.log('[OmniParser]: VIP 状态已与云端对齐:', VIP_INFO); if (callback) callback({ success: true, data: VIP_INFO, raw: json }); } else { if (res.status === 401) { VIP_INFO = null; GM_setValue('omni_vip_info', null); } if (callback) callback({ success: false, status: res.status, message: json.message || '凭证校验未通过' }); } } catch (e) { if (callback) callback({ success: false, message: '数据解析失败: ' + e.message }); } }, onerror: function () { if (callback) callback({ success: false, message: '无法连接到认证服务器' }); } }); } // 启动油猴脚本席位活跃保鲜与防误踢定时心跳 (每 10 分钟自动对齐一次) function startUserscriptHeartbeat() { if (window.__omniHeartbeatStarted) return; window.__omniHeartbeatStarted = true; setInterval(() => { if (VIP_TOKEN) { checkAndRefreshVipStatus(true, (res) => { if (res && res.status === 401) { console.warn('[OmniParser]: 席位凭据已失效或已被踢下线'); } }); } }, 10 * 60 * 1000); } startUserscriptHeartbeat(); // 1. 自动同步主站与各镜像域名 (d.513344.xyz / d.hel.cc.cd / x.bpm.kdns.fr) 登录态与 VIP Token,用户无需手动复制粘贴! const OMNI_HOSTS = ['hel.cc.cd', 'bpm.kdns.fr', '513344.xyz', 'localhost', '127.0.0.1']; if (OMNI_HOSTS.some(h => window.location.hostname.includes(h))) { const syncTokenFromWeb = () => { try { const webToken = localStorage.getItem('omni_user_token'); if (webToken && webToken.trim()) { const clean = webToken.trim(); if (GM_getValue('omni_vip_token', '') !== clean) { GM_setValue('omni_vip_token', clean); VIP_TOKEN = clean; console.log('[OmniParser]: VIP Token 已自动从官网同步至油猴存储:', clean.substring(0, 10) + '...'); syncVipStatusFromServer(true); } } } catch (e) {} }; syncTokenFromWeb(); window.addEventListener('storage', syncTokenFromWeb); setInterval(syncTokenFromWeb, 1500); } // 初始化时静默核验(如距上次超过10分钟) if (VIP_TOKEN) { syncVipStatusFromServer(false); } // 注册油猴菜单命令 if (typeof GM_registerMenuCommand !== 'undefined') { GM_registerMenuCommand('💎 绑定/验证 VIP 会员 Token (到期免跳转降级)', configVipToken); GM_registerMenuCommand(`🔄 切换解析模式 (当前: ${getModeLabel(PARSE_MODE)})`, toggleParseMode); GM_registerMenuCommand('🚀 检查脚本更新 (v1.8.2)', checkManualUpdate); GM_registerMenuCommand('⚙️ 设置/修改 OmniParser 主站服务地址', configServerUrl); GM_registerMenuCommand('👑 访问 OmniParser 官网 / 会员充值', openOfficialWebsite); } function getModeLabel(m) { if (m === 'modal') return '仅页内浮层'; if (m === 'redirect') return '仅跳转主站'; return '智能双模 (推荐)'; } function openTabSafely(url) { try { if (typeof GM_openInTab === 'function') { const tab = GM_openInTab(url, { active: true, insert: true, setParent: true }); if (tab) return; } } catch (e) { console.warn('[OmniParser GM_openInTab error]:', e); } try { const win = window.open(url, '_blank'); if (win) { win.focus(); return; } } catch (e) { console.warn('[OmniParser window.open error]:', e); } // DOM Click Fallback try { const a = document.createElement('a'); a.href = url; a.target = '_blank'; a.rel = 'noopener noreferrer'; (document.body || document.documentElement).appendChild(a); a.click(); a.remove(); } catch (e) { window.location.href = url; } } function openOfficialWebsite() { openTabSafely(OMNI_SERVER); } function checkManualUpdate() { GM_xmlhttpRequest({ method: 'GET', url: `${OMNI_SERVER}/api/client/check_update?client_type=userscript¤t_version=${SCRIPT_VERSION}`, headers: { 'Accept': 'application/json' }, timeout: 8000, onload: function (res) { try { const data = JSON.parse(res.responseText); if (data.success) { if (data.has_update) { if (confirm(`🚀 OmniParser 脚本发现新版本 ${data.latest_version} (当前: ${SCRIPT_VERSION})!\n\n【${data.update_title || '版本更新'}】\n${data.update_log || ''}\n\n是否立即前往更新?`)) { openTabSafely(data.download_url || `${OMNI_SERVER}/static/clients/userscript/omniparser.user.js`); } } else { alert(`✅ 当前已是最新版本 (${SCRIPT_VERSION}),无需更新!`); } } else { alert(`ℹ️ 检查更新异常: ${data.message || '未获取到版本配置'}`); } } catch (e) { alert('❌ 解析更新响应失败,请稍后再试。'); } }, onerror: function () { alert('❌ 无法连接到服务器检查更新,请检查网络设置。'); } }); } function configVipToken() { const cur = GM_getValue('omni_vip_token', ''); const vipStatus = getVipStatus(); let statusDesc = '当前状态: 访客 (未绑定)'; if (vipStatus.isVipActive) { statusDesc = `当前状态: 👑 有效 VIP (账号: ${vipStatus.username || 'VIP 用户'}, 到期时间: ${vipStatus.expireDateStr})`; } else if (vipStatus.isVipExpired) { statusDesc = `当前状态: ⚠️ VIP 已到期 [免跳转降级模式生效中] (账号: ${vipStatus.username || '用户'}, 到期时间: ${vipStatus.expireDateStr})`; } else if (vipStatus.isRegistered) { statusDesc = `当前状态: 👤 普通注册用户 (账号: ${vipStatus.username || '用户'})`; } const tip = cur ? `已绑定 Token: ${cur.substring(0, 12)}...\n${statusDesc}` : `${statusDesc}`; const input = prompt( `${tip}\n\n请输入您在 OmniParser 官网个人中心复制的【用户 Token】:\n(绑定后在 TikTok / YouTube / 抖音 等页面无需跳转主站,直接享受原位浮层解析;VIP 到期用户亦自动开启免跳转降级特权)\n\n若需解除绑定,清空输入框并点击确定即可。`, cur ); if (input !== null) { const clean = input.trim(); if (!clean) { GM_setValue('omni_vip_token', ''); GM_setValue('omni_vip_info', null); VIP_TOKEN = ''; VIP_INFO = null; alert('ℹ️ 已清除绑定凭据,恢复为未登录访客模式(点击跳转主站)。'); return; } GM_setValue('omni_vip_token', clean); VIP_TOKEN = clean; syncVipStatusFromServer(true, (result) => { if (result.success && result.data) { const u = result.data; const now = Date.now(); const exp = u.vip_expire_timestamp; const expStr = exp ? new Date(exp).toLocaleString() : '永久有效'; if (u.is_vip && (!exp || exp > now)) { alert(`🎉 恭喜!VIP 会员身份验证成功!\n\n用户账号: ${u.username}\n会员状态: 👑 有效 VIP\n有效期至: ${expStr}\n\n您享有全域免跳转原位解析、高清直链与极速下载特权!`); } else if (exp && exp <= now) { alert(`⚠️ Token 绑定成功,检测到您的 VIP 会员已到期!\n\n用户账号: ${u.username}\n到期时间: ${expStr}\n\n系统已为您自动激活【免跳转降级模式】:\n您依然可在原页面直接弹出浮层解析,免除跳出打扰;建议前往官网续费恢复极速多线程下载!`); } else { alert(`✅ 账号绑定成功!\n\n用户账号: ${u.username}\n角色等级: 普通注册用户\n\n已为您解锁【免跳转页内原位解析】特权!如需作者作品批量打包等功能,欢迎前往官网升级 VIP。`); } } else { const errMsg = result.message || '凭据无效或网络超时'; alert(`❌ Token 验证失败:${errMsg}\n\n请检查您填写的 Token 是否正确,或前往官网个人中心重新复制。`); } }); } } function toggleParseMode() { const cur = GM_getValue('omni_parse_mode', 'auto'); const input = prompt( `请选择 OmniParser 的点击解析行为模式 (当前: ${getModeLabel(cur)}):\n\n` + `1 - 🚀 智能双模 (推荐:已开通VIP直接页内弹出浮层,免费用户跳转主站)\n` + `2 - 🌐 始终跳转主站 (新标签页打开官网大屏,支持多清晰度与网盘转存)\n` + `3 - ⚡ 始终页内浮层 (直接在网页上弹出解析卡片,需绑定 VIP Token)\n\n` + `请输入对应数字 (1 / 2 / 3):`, cur === 'auto' ? '1' : (cur === 'redirect' ? '2' : '3') ); if (input) { if (input.trim() === '1') { GM_setValue('omni_parse_mode', 'auto'); PARSE_MODE = 'auto'; alert('✅ 已设置为:🚀 智能双模 (VIP 原位浮层 / 免费跳主站)'); } else if (input.trim() === '2') { GM_setValue('omni_parse_mode', 'redirect'); PARSE_MODE = 'redirect'; alert('✅ 已设置为:🌐 始终跳转主站解析'); } else if (input.trim() === '3') { GM_setValue('omni_parse_mode', 'modal'); PARSE_MODE = 'modal'; alert('✅ 已设置为:⚡ 始终页内浮层解析'); } } } function configServerUrl() { const current = GM_getValue('omni_server_url', OMNI_SERVER); const input = prompt('请输入您的 OmniParser 后端服务完整地址 (HTTPS域名: https://d.513344.xyz ,本地: http://127.0.0.1:8000):', current); if (input && input.trim()) { let cleanUrl = input.trim().replace(/\/+$/, ''); if (!cleanUrl.startsWith('http://') && !cleanUrl.startsWith('https://')) { cleanUrl = 'https://' + cleanUrl; } GM_setValue('omni_server_url', cleanUrl); OMNI_SERVER = cleanUrl; alert(`✅ 已成功将 OmniParser 服务地址设置为:\n${cleanUrl}`); } } // Generate safe filename for proxy download function getSafeFilename(title, quality, ext) { let cleanTitle = (title || 'video').trim() .replace(/[\\/:*?"<>|]/g, '_') .replace(/\s+/g, '_') .substring(0, 60); return `${cleanTitle}_${quality || 'clear'}.${ext || 'mp4'}`; } // URL checker for Video detail pages (Floating Page-Level Button) function isPlayPage() { const url = window.location.href; const host = window.location.hostname; if (host.includes('douyin.com')) { return false; // 抖音全域采用右侧原生操作栏与详情页专属一键解析按钮,不再显示右下角悬浮条 } if (host.includes('bilibili.com')) { return false; // B站全域采用视频下方操作栏(分享按钮旁)原生解析按钮,不再显示右下角大悬浮条 } if (host.includes('xiaohongshu.com')) { return false; // 小红书全域采用内嵌官方 Logo 按钮规范,不再显示右下角大悬浮条 } if (host.includes('kuaishou.com')) { return false; // 快手全域采用右侧原生操作栏专属圆钮与详情页按钮,不再显示右下角悬浮条 } if (host.includes('youtube.com') || host.includes('youtu.be')) { return true; // YouTube 全域开启右下角免水印解析悬浮条与多锚点内嵌按钮全方位保障! } if (host.includes('tiktok.com')) { return false; // TikTok 全域采用右侧原生操作栏与详情页复制链接专属一键解析按钮,不再显示右下角悬浮球 } if (host.includes('twitter.com') || host.includes('x.com')) { return false; // X/Twitter 全域采用推文原生操作栏内嵌解析按钮,去除内容页多余大悬浮条 } if (host.includes('threads.net') || host.includes('threads.com')) { return false; // Threads 全域采用贴文操作栏专属极光星芒内嵌按钮,不再显示右下角大悬浮条 } if (host.includes('instagram.com')) { return url.includes('/p/') || url.includes('/reel/') || url.includes('/reels/') || url.includes('/stories/'); } if (host.includes('facebook.com') || host.includes('fb.watch')) { return false; // Facebook 全域采用贴文/视频操作栏原生胶囊按钮,不再显示右下角悬浮条 } if (host.includes('haokan.baidu.com')) { return url.includes('/v?') || url.includes('vid='); } if (host.includes('hongguoduanju.com') || host.includes('novelquickapp.com') || host.includes('fanqiesp.com')) { return url.includes('/player/') || url.includes('/video') || url.includes('video-animation-share') || url.includes('video-share'); } if (host.includes('pipix.com') || host.includes('pipigaoxiao.com')) { return url.includes('/item/') || url.includes('/s/') || url.includes('/ppx/'); } const chiguaKeywords = ["chigua", "51cg", "cg51", "51cge", "51cgf", "ucpy", "xhsvj", "cabinet", "heiliaox", "51hl", "hlw"]; if (chiguaKeywords.some(k => host.includes(k)) || url.includes('/archives/')) { return url.includes('/archives/') || /\/\d+/.test(url) || url.includes('.html'); } return false; } // Helper to extract Douyin Video URL function getDouyinVideoUrl(targetNode) { try { const curUrl = window.location.href; if (curUrl.includes('/video/') || curUrl.includes('/note/')) { return curUrl.split('?')[0]; } if (curUrl.includes('modal_id=')) { const m = curUrl.match(/modal_id=(\d+)/); if (m) return `https://www.douyin.com/video/${m[1]}`; } const container = targetNode ? (targetNode.closest('[data-e2e="feed-active-video"], [class*="swiper-slide-active"], [class*="slider-video"], [class*="feed-item"]') || targetNode.closest('div[class*="video"]')) : null; if (container) { const link = container.querySelector('a[href*="/video/"], a[href*="/note/"]'); if (link) { const href = link.getAttribute('href'); return href.startsWith('http') ? href.split('?')[0] : `https://www.douyin.com${href.split('?')[0]}`; } } return curUrl.split('?')[0]; } catch (e) { return window.location.href.split('?')[0]; } } // Helper to extract TikTok Video URL function getTikTokVideoUrl(targetNode) { try { if (/\/@[^/]+\/video\/\d+/.test(window.location.pathname) || /\/@[^/]+\/photo\/\d+/.test(window.location.pathname)) { return window.location.href.split('?')[0]; } let node = targetNode; if (!node) { const vids = Array.from(document.querySelectorAll('video')).filter(v => { try { const r = v.getBoundingClientRect(); return r.width > 80 && r.height > 80; } catch(e) { return false; } }); node = vids.find(v => !v.paused); if (!node && vids.length > 0) { const cy = window.innerHeight / 2; vids.sort((a, b) => { const ra = a.getBoundingClientRect(); const rb = b.getBoundingClientRect(); return Math.abs(ra.top + ra.height / 2 - cy) - Math.abs(rb.top + rb.height / 2 - cy); }); node = vids[0]; } } let cur = node; for (let i = 0; i < 20 && cur; i++) { const id = cur.getAttribute && cur.getAttribute('id'); if (id) { const m = id.match(/-(\d{10,})$/) || id.match(/(\d{15,})/); if (m) return `https://www.tiktok.com/@_/video/${m[1]}`; } const dataId = cur.getAttribute && (cur.getAttribute('data-video-id') || cur.getAttribute('data-e2e-item-id')); if (dataId && /^\d+$/.test(dataId)) return `https://www.tiktok.com/@_/video/${dataId}`; const link = cur.querySelector && (cur.querySelector('a[href*="/video/"]') || cur.querySelector('a[href*="/photo/"]')); if (link && link.href) { const m = link.href.match(/\/video\/(\d+)/) || link.href.match(/\/photo\/(\d+)/); if (m) return link.href.split('?')[0]; } cur = cur.parentElement; } const containers = document.querySelectorAll('.xgplayer-container, [id*="xgwrapper"], [data-e2e="feed-video"]'); for (const c of containers) { const id = c.getAttribute('id'); if (id) { const m = id.match(/-(\d{10,})$/) || id.match(/(\d{15,})/); if (m) return `https://www.tiktok.com/@_/video/${m[1]}`; } } } catch (e) { console.warn('[OmniParser getTikTokVideoUrl]:', e); } return window.location.href.split('?')[0]; } // 1. Inject Styles const style = document.createElement('style'); style.textContent = ` .omni-float-btn { position: fixed; bottom: 80px; right: 24px; z-index: 2147483646; background: linear-gradient(135deg, #a855f7 0%, #ec4899 100%); color: #ffffff; height: 36px; padding: 0 16px; border-radius: 18px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 13px; font-weight: 700; letter-spacing: 0.5px; box-shadow: 0 4px 16px rgba(168, 85, 247, 0.45); cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 6px; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); backdrop-filter: blur(8px); user-select: none; pointer-events: auto !important; border: 1px solid rgba(255, 255, 255, 0.15); } .omni-float-btn:hover { transform: translateY(-2px) scale(1.05); box-shadow: 0 6px 20px rgba(236, 72, 153, 0.6); } /* OmniParser 全网统一品牌 Logo 按钮规范 (Squircle 微圆角矩形 + 官方极光星芒) */ .omni-brand-btn { border: none !important; background: linear-gradient(135deg, #7c3aed 0%, #ec4899 50%, #6366f1 100%) !important; color: #ffffff !important; width: 28px !important; min-width: 28px !important; height: 28px !important; padding: 0 !important; border-radius: 8px !important; display: inline-flex !important; align-items: center !important; justify-content: center !important; cursor: pointer !important; user-select: none !important; box-shadow: 0 2px 8px rgba(124, 58, 237, 0.4) !important; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important; vertical-align: middle !important; box-sizing: border-box !important; flex-shrink: 0 !important; text-decoration: none !important; outline: none !important; } .omni-brand-btn:hover { transform: translateY(-1px) scale(1.08) !important; box-shadow: 0 4px 14px rgba(236, 72, 153, 0.6) !important; filter: brightness(1.08) !important; } .omni-brand-btn:active { transform: scale(0.95) !important; box-shadow: 0 1px 4px rgba(124, 58, 237, 0.35) !important; } .omni-brand-btn svg { width: 16px !important; height: 16px !important; stroke: #ffffff !important; stroke-width: 2 !important; stroke-linecap: round !important; stroke-linejoin: round !important; fill: none !important; flex-shrink: 0 !important; display: block !important; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.25)) !important; } /* 卡片角标微型规格 (小红书/YouTube/皮皮虾卡片) */ .omni-brand-btn-sm { width: 24px !important; min-width: 24px !important; height: 24px !important; border-radius: 6px !important; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5) !important; } .omni-brand-btn-sm svg { width: 13px !important; height: 13px !important; } /* 垂直流侧边栏规格 (抖音、TikTok、Shorts、Reels、快手全屏流) */ .omni-vertical-brand-btn { border: none !important; background: linear-gradient(135deg, #7c3aed 0%, #ec4899 50%, #6366f1 100%) !important; width: 42px !important; height: 42px !important; border-radius: 12px !important; display: flex !important; align-items: center !important; justify-content: center !important; cursor: pointer !important; margin: 0 auto !important; transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.2s !important; box-shadow: 0 4px 14px rgba(124, 58, 237, 0.45) !important; outline: none !important; box-sizing: border-box !important; } .omni-vertical-brand-btn:hover { transform: scale(1.12) !important; box-shadow: 0 6px 20px rgba(236, 72, 153, 0.65) !important; } .omni-vertical-brand-btn:active { transform: scale(0.96) !important; } .omni-vertical-brand-btn svg { width: 22px !important; height: 22px !important; stroke: #ffffff !important; stroke-width: 2 !important; stroke-linecap: round !important; stroke-linejoin: round !important; fill: none !important; flex-shrink: 0 !important; display: block !important; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3)) !important; } .omni-vertical-brand-label { display: block !important; font-size: 11px !important; color: #ffffff !important; text-align: center !important; margin-top: 4px !important; font-weight: 700 !important; letter-spacing: 0.5px !important; line-height: 1 !important; text-shadow: 0 1px 3px rgba(0,0,0,0.9) !important; user-select: none !important; } #omniScriptModal, .omni-modal-overlay { position: fixed !important; bottom: 24px !important; right: 24px !important; width: 490px !important; max-width: calc(100vw - 32px) !important; max-height: 85vh !important; z-index: 2147483647 !important; display: flex !important; align-items: flex-end !important; justify-content: flex-end !important; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important; font-size: 13px !important; line-height: 1.5 !important; color: #f8fafc !important; pointer-events: none !important; box-sizing: border-box !important; text-size-adjust: 100% !important; -webkit-text-size-adjust: 100% !important; } #omniScriptModal *, .omni-modal-overlay * { box-sizing: border-box !important; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important; letter-spacing: normal !important; text-transform: none !important; } .omni-modal-card { width: 100% !important; max-width: 490px !important; background: rgba(15, 23, 42, 0.96) !important; backdrop-filter: blur(20px) !important; border: 1px solid rgba(168, 85, 247, 0.4) !important; border-radius: 14px !important; padding: 16px 18px !important; color: #f8fafc !important; box-shadow: 0 20px 45px rgba(0, 0, 0, 0.85), 0 0 15px rgba(168, 85, 247, 0.25) !important; pointer-events: auto !important; max-height: 85vh !important; overflow-y: auto !important; font-size: 13px !important; line-height: 1.5 !important; text-align: left !important; animation: omni-slide-in 0.25s cubic-bezier(0.4, 0, 0.2, 1) !important; } .omni-modal-card h3 { font-size: 14px !important; line-height: 1.3 !important; margin: 0 !important; font-weight: 700 !important; } .omni-modal-card a { font-size: inherit !important; line-height: inherit !important; } .omni-modal-card button { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; } @keyframes omni-slide-in { from { transform: translateY(16px) scale(0.96); opacity: 0; } to { transform: translateY(0) scale(1); opacity: 1; } } .omni-btn { padding: 5px 12px; border-radius: 6px; border: none; font-weight: 700; font-size: 12px; cursor: pointer; transition: all 0.2s; display: inline-flex; align-items: center; justify-content: center; gap: 4px; } .omni-btn-primary { background: linear-gradient(135deg, #a855f7, #6366f1); color: #fff; box-shadow: 0 2px 8px rgba(168,85,247,0.3); } .omni-btn-secondary { background: rgba(255,255,255,0.08); color: #cbd5e1; border: 1px solid rgba(255,255,255,0.08); } .omni-btn:hover { opacity: 0.92; transform: translateY(-1px); } .omni-inline-btn:hover { transform: scale(1.2); } .omni-tiktok-hud-btn { position: fixed; z-index: 2147483647 !important; display: none; align-items: center; gap: 6px; background: rgba(15, 23, 42, 0.88); border: 1px solid rgba(168, 85, 247, 0.7); color: #ffffff; padding: 7px 15px; border-radius: 24px; font-size: 13px; font-weight: 700; cursor: pointer; backdrop-filter: blur(10px); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6), 0 0 14px rgba(168, 85, 247, 0.35); transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), background 0.2s, box-shadow 0.2s; user-select: none; pointer-events: auto !important; } .omni-tiktok-hud-btn:hover { transform: scale(1.08); background: linear-gradient(135deg, rgba(168, 85, 247, 0.95), rgba(236, 72, 153, 0.95)); box-shadow: 0 10px 30px rgba(168, 85, 247, 0.6); } `; (document.head || document.documentElement).appendChild(style); // OmniParser 官方 Logo 极光星芒 SVG 路径 (全平台统一品牌标识规范) const LOGO_SPARKLE_PATH = 'M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0zM20 3v4M22 5h-4M4 17v2M5 18H3'; function createBrandLogoBtn(tooltip = 'OmniParser 一键免水印解析 (右键设置 VIP Token)', extraClass = '') { const btn = document.createElement('button'); btn.type = 'button'; btn.className = `omni-brand-btn ${extraClass}`.trim(); btn.title = tooltip; btn.innerHTML = ``; return btn; } // 2. Inject Float Button on startup function checkUrlAndToggleBtn() { const isPlay = isPlayPage(); let floatBtn = document.querySelector('.omni-float-btn'); if (isPlay) { if (!floatBtn) { floatBtn = document.createElement('div'); floatBtn.className = 'omni-float-btn'; floatBtn.innerHTML = ` 免水印解析 `; floatBtn.title = '左键点击:智能解析 | 右键点击:设置/绑定 VIP Token'; floatBtn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); let targetUrl = null; if (window.location.hostname.includes('tiktok.com')) { targetUrl = getTikTokVideoUrl(null); } handleParseAction(targetUrl); }); floatBtn.addEventListener('contextmenu', (e) => { e.preventDefault(); e.stopPropagation(); configVipToken(); }); (document.body || document.documentElement).appendChild(floatBtn); } floatBtn.style.display = 'flex'; } else { if (floatBtn) { floatBtn.style.display = 'none'; } const modal = document.getElementById('omniScriptModal'); if (modal) modal.remove(); } } checkUrlAndToggleBtn(); // SPA URL change detector let lastUrl = window.location.href; setInterval(() => { if (window.location.href !== lastUrl) { lastUrl = window.location.href; checkUrlAndToggleBtn(); } }, 500); const pushState = history.pushState; history.pushState = function() { pushState.apply(history, arguments); setTimeout(checkUrlAndToggleBtn, 100); }; const replaceState = history.replaceState; history.replaceState = function() { replaceState.apply(history, arguments); setTimeout(checkUrlAndToggleBtn, 100); }; window.addEventListener('popstate', () => { setTimeout(checkUrlAndToggleBtn, 100); }); ['yt-navigate-finish', 'yt-page-data-updated', 'yt-visibility-refresh'].forEach(evt => { window.addEventListener(evt, () => { checkUrlAndToggleBtn(); injectInlineButtons(); }); document.addEventListener(evt, () => { checkUrlAndToggleBtn(); injectInlineButtons(); }); }); // Inject post-level inline buttons for list feeds function injectInlineButtons() { const host = window.location.hostname; // 0. Bilibili (普通视频播放页紧随“分享”;番剧/影视/国创页面靠右对齐至右侧工具栏) if (host.includes('bilibili.com')) { const isBangumi = window.location.pathname.includes('/bangumi/') || window.location.pathname.includes('/cheese/') || !!document.querySelector('.player-left-components, #share-container-id, #watch_together_tab'); if (isBangumi) { // 番剧/影视剧页面:左侧已有“一起看”等多个大操作项,采用用户指定的“靠右对齐”模式,挂载在右侧工具栏前端 const toolbarRight = document.querySelector( '.player-left-components .toolbar .toolbar-right, .toolbar .toolbar-right, [class*="toolbar-right"]' ); // 全局清理:彻底清除误插入在左侧栏内部 (如分享与一起看中间) 或其它位置的残留按钮 const allBiliBtns = Array.from(document.querySelectorAll('.omni-inline-btn-bilibili')); let validBtn = null; for (const b of allBiliBtns) { if (toolbarRight && b.parentElement === toolbarRight && !validBtn) { validBtn = b; } else { b.remove(); // 坚决清理左侧挤压的残留或多余按钮 } } if (toolbarRight) { if (validBtn) { if (toolbarRight.firstChild !== validBtn) { toolbarRight.insertBefore(validBtn, toolbarRight.firstChild); } } else { const btnSpan = document.createElement('span'); btnSpan.className = 'omni-inline-btn-bilibili'; btnSpan.style.cssText = 'display: inline-flex; align-items: center; justify-content: center; margin-right: 18px; vertical-align: middle; flex-shrink: 0; user-select: none; z-index: 10; cursor: pointer;'; const btn = createBrandLogoBtn('OmniParser 一键免水印解析此 B站 番剧/视频 (右键设置 VIP Token)'); btnSpan.appendChild(btn); btnSpan.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); handleParseAction(window.location.href); }); btnSpan.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); toolbarRight.insertBefore(btnSpan, toolbarRight.firstChild); } } } else { // 普通视频播放页 (BV):维持用户认可的“好的”状态,严格定位左侧主操作栏并排在“分享”按钮紧右侧 const toolbarLeft = document.querySelector( '#arc_toolbar_report .toolbar-left, .video-toolbar-left, .video-toolbar-v1 .toolbar-left, [class*="video-toolbar-left"], [class*="toolbar-left"]' ); if (toolbarLeft) { let shareItem = Array.from(toolbarLeft.children).find(child => { if (child.classList.contains('omni-inline-btn-bilibili')) return false; if (child.classList.contains('video-share') || child.className.includes('share')) return true; if (child.querySelector('.video-share-info, [class*="video-share"]')) return true; const txt = (child.textContent || '').trim(); return txt.includes('分享') && !txt.includes('解析'); }); // 全局清理:移除所有不在合法 toolbarLeft 下的残留旧按钮,或误插入在 .video-share 内部的多余按钮 const allBiliBtns = Array.from(document.querySelectorAll('.omni-inline-btn-bilibili')); let validBtn = null; for (const b of allBiliBtns) { if (b.parentElement === toolbarLeft && !validBtn) { validBtn = b; } else { b.remove(); // 坚决清除误插入或多余的历史按钮 } } if (validBtn) { if (shareItem && validBtn.previousElementSibling !== shareItem) { toolbarLeft.insertBefore(validBtn, shareItem.nextSibling); } } else { const btnDiv = document.createElement('div'); btnDiv.className = 'omni-inline-btn-bilibili'; btnDiv.style.cssText = 'display: inline-flex; align-items: center; justify-content: center; height: 100%; margin-left: 12px; margin-right: 8px; vertical-align: middle; flex-shrink: 0; user-select: none; z-index: 10; cursor: pointer;'; const btn = createBrandLogoBtn('OmniParser 一键免水印解析此 B站 视频 (右键设置 VIP Token)'); btnDiv.appendChild(btn); btnDiv.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); handleParseAction(window.location.href); }); btnDiv.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); if (shareItem) { toolbarLeft.insertBefore(btnDiv, shareItem.nextSibling); } else { toolbarLeft.appendChild(btnDiv); } } } } } // 1. X / Twitter if (host.includes('twitter.com') || host.includes('x.com')) { document.querySelectorAll('article[data-testid="tweet"]').forEach(tweet => { const actionBar = tweet.querySelector('div[role="group"]'); if (actionBar && !actionBar.querySelector('.omni-inline-btn-x')) { const hasMedia = tweet.querySelector('div[data-testid="tweetPhoto"]') || tweet.querySelector('div[data-testid="videoPlayer"]') || tweet.querySelector('video') || tweet.querySelector('div[data-testid="card.wrapper"]') || tweet.querySelector('div[data-testid="tweetPhotoAttachment"]'); if (!hasMedia) return; const linkEl = tweet.querySelector('a[href*="/status/"]'); if (linkEl) { const tweetUrl = 'https://x.com' + linkEl.getAttribute('href').split('?')[0]; const btnDiv = document.createElement('div'); btnDiv.className = 'omni-inline-btn-x'; btnDiv.style.cssText = 'display: inline-flex; align-items: center; margin-left: 8px; vertical-align: middle; flex-shrink: 0; user-select: none; z-index: 10;'; const btn = createBrandLogoBtn('OmniParser 一键免水印解析该推文媒体 (右键设置 VIP Token)'); btnDiv.appendChild(btn); btnDiv.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); handleParseAction(tweetUrl); }); btnDiv.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); actionBar.appendChild(btnDiv); } } }); } // 2. Instagram (Feed posts, Modal Popups, Profile, Reels, Stories) if (host.includes('instagram.com')) { // Helper to extract Instagram post / reel URL function getInstagramPostUrl(container) { try { const curUrl = window.location.href; if (/\/p\/[^/]+/.test(curUrl) || /\/reel\/[^/]+/.test(curUrl) || /\/reels\/[^/]+/.test(curUrl) || /\/stories\/[^/]+/.test(curUrl)) { return curUrl.split('?')[0]; } if (container) { const link = container.querySelector('a[href*="/p/"], a[href*="/reel/"], a[href*="/reels/"]'); if (link) { const href = link.getAttribute('href'); if (href) { return href.startsWith('http') ? href.split('?')[0] : `https://www.instagram.com${href.split('?')[0]}`; } } const timeLink = container.querySelector('time')?.closest('a'); if (timeLink) { const href = timeLink.getAttribute('href'); if (href) { return href.startsWith('http') ? href.split('?')[0] : `https://www.instagram.com${href.split('?')[0]}`; } } } return curUrl.split('?')[0]; } catch (e) { return window.location.href.split('?')[0]; } } // 2.1 Standard Feed Posts, Profile Post Popups (Modal Dialog), Single Post Pages const postContainers = document.querySelectorAll('article, div[role="dialog"], main section, div[role="presentation"]'); postContainers.forEach(container => { const actionSection = container.querySelector('section') || container; if (!actionSection || actionSection.querySelector('.omni-inline-btn-ig')) return; const svgs = actionSection.querySelectorAll('svg'); if (svgs.length === 0) return; let hasActionBtn = false; svgs.forEach(svg => { const label = (svg.getAttribute('aria-label') || svg.closest('button, [role="button"]')?.getAttribute('aria-label') || '').toLowerCase(); const title = (svg.querySelector('title')?.textContent || '').toLowerCase(); if (/like|赞|좋아요|いいね|comment|评论|留言|댓글|share|分享|direct|save|收藏|저장|bookmark/i.test(label + title)) { hasActionBtn = true; } }); if (!hasActionBtn && svgs.length < 3) return; if (actionSection.closest('.omni-inline-btn-ig-reel') || container.clientWidth < 120) return; const postUrl = getInstagramPostUrl(container); if (!postUrl) return; const btnDiv = document.createElement('div'); btnDiv.className = 'omni-inline-btn-ig'; btnDiv.style.cssText = 'display: inline-flex; align-items: center; margin-left: 8px; margin-right: 8px; vertical-align: middle; flex-shrink: 0; user-select: none; z-index: 10;'; const btn = createBrandLogoBtn('OmniParser 一键免水印解析此作品 (右键设置 VIP Token)'); btnDiv.appendChild(btn); btnDiv.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); handleParseAction(postUrl); }); btnDiv.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); const flexRow = actionSection.querySelector('div[class*="x78zum5"], div[style*="flex"], span[class*="x78zum5"]') || actionSection.firstElementChild || actionSection; let saveBtnWrapper = null; const allInteractive = Array.from(actionSection.querySelectorAll('button, [role="button"], svg')); for (const el of allInteractive) { const lbl = (el.getAttribute('aria-label') || '').toLowerCase(); if (/save|收藏|저装|bookmark/i.test(lbl)) { saveBtnWrapper = el.closest('div[class], span[class]') || el; break; } } if (saveBtnWrapper && saveBtnWrapper.parentElement && saveBtnWrapper.parentElement.contains(saveBtnWrapper)) { saveBtnWrapper.parentElement.insertBefore(btnDiv, saveBtnWrapper); } else if (flexRow && flexRow.appendChild) { flexRow.appendChild(btnDiv); } else { actionSection.appendChild(btnDiv); } }); // 2.2 Instagram Reels (Vertical action column on the right) const reelCandidates = document.querySelectorAll('div[role="button"], button[type="button"], svg'); reelCandidates.forEach(candidate => { const label = (candidate.getAttribute('aria-label') || candidate.closest('button, [role="button"]')?.getAttribute('aria-label') || '').toLowerCase(); const isReelAction = /like|赞|좋아요|いいね|unlike|取消赞|comment|评论|留言|댓글|share|分享/i.test(label); if (!isReelAction) return; let col = candidate.closest('div[class*="x1n2onr6"]') || candidate.parentElement; for (let depth = 0; depth < 5 && col; depth++) { if (col.tagName === 'BODY' || col.tagName === 'MAIN' || col.tagName === 'ARTICLE') break; const rect = col.getBoundingClientRect(); if (rect.height > 100 && rect.width < 90 && !col.querySelector('.omni-inline-btn-ig-reel')) { const reelContainer = col.closest('div[role="presentation"], article, section, div[class*="x1qjc9v5"]') || col.parentElement; const reelUrl = getInstagramPostUrl(reelContainer); const btnDiv = document.createElement('div'); btnDiv.className = 'omni-inline-btn-ig-reel'; btnDiv.style.cssText = 'text-align: center; margin-top: 10px; margin-bottom: 10px; cursor: pointer; display: flex; flex-direction: column; align-items: center; justify-content: center; user-select: none; z-index: 99;'; btnDiv.innerHTML = ` 解析 `; btnDiv.title = '左键:一键免水印解析此 Reel | 右键:设置 VIP Token'; btnDiv.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); handleParseAction(reelUrl); }); btnDiv.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); col.insertBefore(btnDiv, col.firstChild); break; } col = col.parentElement; } }); } // 3. Threads (threads.net & threads.com) - 严格媒体过滤与横向操作栏精准锚点 if (host.includes('threads.net') || host.includes('threads.com')) { // 辅助判断指定贴文卡片内是否包含音视频或真实大图内容 (杜绝纯文字贴文误注入) function hasThreadsMedia(card) { if (!card) return false; // 1. 含有视频元素或音视频播放/静音控件 if (card.querySelector('video, audio, [data-video-player="true"], [aria-label*="Audio"], [aria-label*="Mute"], [aria-label*="Volume"], [aria-label*="音讯"], [aria-label*="音訊"], [aria-label*="静音"], [aria-label*="靜音"]')) { return true; } // 2. 含有图集指示器 (如 1/2, 1/3) 或 轮播标记 if (card.querySelector('[aria-label*="carousel"], [class*="carousel"], [class*="indicator"], [class*="pagination"]')) { return true; } // 3. 含有真实正文配图 (严格排除用户头像、小表情等) const imgs = Array.from(card.querySelectorAll('img, picture img')).filter(img => { const alt = (img.getAttribute('alt') || '').toLowerCase(); if (alt.includes('profile') || alt.includes('avatar') || alt.includes('头像') || alt.includes('大头贴')) return false; const src = (img.src || img.getAttribute('data-src') || img.srcset || '').toLowerCase(); if (!src || src.startsWith('data:image/svg') || src.includes('emoji') || src.includes('favicon')) return false; // 排除作者头像链接下的图片 (例如 a[href^="/@"] 且不带 /post/ 或 /t/ 的链接) const parentLink = img.closest('a'); if (parentLink) { const href = parentLink.getAttribute('href') || ''; if (href.startsWith('/@') && !href.includes('/post/') && !href.includes('/t/')) { return false; } } // 排除明确的小头像尺寸 (如 s150x150, s50x50 等) if (src.includes('s150x150') || src.includes('s50x50') || src.includes('s64x64') || src.includes('s48x48') || src.includes('s36x36') || src.includes('s24x24')) { return false; } const rect = img.getBoundingClientRect(); if (rect.width > 60 && rect.height > 60) return true; if (img.naturalWidth > 80 && img.naturalHeight > 80) return true; if (src.includes('cdninstagram.com') || src.includes('fbcdn.net') || src.includes('threads.net') || src.includes('threads.com')) { return true; } return false; }); return imgs.length > 0; } // 辅助在贴文卡片内准确定位底部横向操作栏 (包含 Like, Reply, Repost, Share 等交互按钮的单行 flex 容器) function findThreadsActionRow(card) { if (!card) return null; function getActionType(el) { const txt = ( el.getAttribute('aria-label') || el.getAttribute('title') || el.parentElement?.getAttribute('aria-label') || el.parentElement?.parentElement?.getAttribute('aria-label') || '' ).toLowerCase(); if (txt.includes('like') || txt.includes('赞') || txt.includes('讚')) return 'like'; if (txt.includes('reply') || txt.includes('回复') || txt.includes('回覆')) return 'reply'; if (txt.includes('repost') || txt.includes('转发') || txt.includes('轉發') || txt.includes('转贴') || txt.includes('轉貼')) return 'repost'; if (txt.includes('share') || txt.includes('分享') || txt.includes('send')) return 'share'; return null; } const svgs = Array.from(card.querySelectorAll('svg')); const actionItems = []; for (const s of svgs) { const t = getActionType(s); if (t) { const btnEl = s.closest('button, [role="button"]') || s.parentElement; actionItems.push({ type: t, el: s, btn: btnEl }); } } // 必须匹配到至少 2 种不同语义的操作按钮 (杜绝单一点赞包装容器误判) const types = new Set(actionItems.map(a => a.type)); if (types.size >= 2) { let candidate = actionItems[0].btn ? actionItems[0].btn.parentElement : null; while (candidate && candidate !== card) { const containedTypes = new Set( actionItems.filter(a => candidate.contains(a.el)).map(a => a.type) ); if (containedTypes.size >= 2) { const rect = candidate.getBoundingClientRect(); const isHeightOk = rect.height === 0 || (rect.height >= 18 && rect.height <= 65); const hasAvatar = !!candidate.querySelector('img[alt*="profile"], img[alt*="avatar"], img[alt*="头像"]'); const hasLongText = Array.from(candidate.querySelectorAll('span, p')).some(el => (el.textContent || '').trim().length > 30); if (isHeightOk && !hasAvatar && !hasLongText) { return candidate; } } candidate = candidate.parentElement; } } // 备选兜底:查找包含 3-7 个 SVG、高度在 18-65px 且排除头像和长文本的横向容器 const candidateDivs = Array.from(card.querySelectorAll('div')).filter(d => { const svgsInDiv = d.querySelectorAll('svg'); if (svgsInDiv.length < 3 || svgsInDiv.length > 7) return false; const rect = d.getBoundingClientRect(); if (rect.height > 65 || (rect.height > 0 && rect.height < 18)) return false; if (d.querySelector('img[alt*="profile"], img[alt*="avatar"], img[alt*="头像"]')) return false; if (Array.from(d.querySelectorAll('span, p')).some(el => (el.textContent || '').trim().length > 30)) return false; return true; }); return candidateDivs.length > 0 ? candidateDivs[candidateDivs.length - 1] : null; } function createThreadsBtn(targetUrl) { const btnDiv = document.createElement('div'); btnDiv.className = 'omni-inline-btn-threads'; btnDiv.dataset.url = targetUrl; btnDiv.style.cssText = 'display: inline-flex; align-items: center; justify-content: center; vertical-align: middle; flex-shrink: 0; user-select: none; z-index: 99; margin-left: 8px; cursor: pointer;'; const btn = createBrandLogoBtn('OmniParser 一键免水印解析此 Threads 贴文 (右键设置 VIP Token)'); btnDiv.appendChild(btn); btnDiv.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const target = btnDiv.dataset.url || targetUrl; console.log('[OmniParser] Threads 解析当前贴文:', target); handleParseAction(target); }); btnDiv.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); return btnDiv; } // 提取并去重页面中的所有贴文卡片 const postCardSet = new Set(); document.querySelectorAll('article, div[data-pressable-container="true"]').forEach(el => postCardSet.add(el)); // 通过时间戳/贴文链接向上追溯卡片,确保无遗漏 document.querySelectorAll('a[href*="/post/"], a[href*="/t/"]').forEach(a => { const href = a.getAttribute('href') || ''; if ((!href.includes('/post/') && !href.includes('/t/')) || href.includes('/media')) return; let card = a.closest('article, div[data-pressable-container="true"]'); if (!card) { let cur = a.parentElement; for (let i = 0; i < 10 && cur; i++) { if (cur.tagName === 'BODY' || cur.tagName === 'MAIN') break; if (cur.querySelectorAll('svg').length >= 3) { card = cur; break; } cur = cur.parentElement; } } if (card) postCardSet.add(card); }); // 对去重后的每张贴文卡片进行单例处理 postCardSet.forEach(card => { const strayBtns = Array.from(card.querySelectorAll('.omni-inline-btn-threads')); // 1. 媒体过滤:纯文字无多媒体内容的贴文,坚决不注入,并清理所有误残留按钮 if (!hasThreadsMedia(card)) { strayBtns.forEach(b => b.remove()); return; } // 2. 定位底部横向操作栏 const actionRow = findThreadsActionRow(card); if (!actionRow) { strayBtns.forEach(b => b.remove()); return; } // 强制单行不换行并居中对齐,杜绝操作栏换行 actionRow.style.flexWrap = 'nowrap'; actionRow.style.alignItems = 'center'; // 3. 单卡片严格防重与多余清理:仅保留合规 actionRow 内的唯一按钮 let validBtn = null; for (const b of strayBtns) { if (b.parentElement === actionRow && !validBtn) { validBtn = b; } else { b.remove(); // 移除非合规位置(如头像旁、点赞列内)或多重堆叠的多余按钮 } } // 4. 解析目标链接 let postUrl = ''; const link = card.querySelector('a[href*="/post/"], a[href*="/t/"]'); if (link) { const rawHref = link.getAttribute('href') || ''; postUrl = (rawHref.startsWith('http') ? rawHref : `https://${window.location.host}${rawHref}`).split('?')[0]; } if (!postUrl && (window.location.pathname.includes('/post/') || window.location.pathname.includes('/t/'))) { postUrl = window.location.href.split('?')[0]; } if (!postUrl) return; if (validBtn) { validBtn.dataset.url = postUrl; if (actionRow.lastElementChild !== validBtn) { actionRow.appendChild(validBtn); } } else { const btnDiv = createThreadsBtn(postUrl); actionRow.appendChild(btnDiv); } }); } // 4. TikTok (Inject into right vertical action column with proper spacing and high-contrast styling) if (host.includes('tiktok.com')) { const likeButtons = document.querySelectorAll('[data-e2e="feed-like"], [data-e2e="video-player-like"], [data-e2e="like-icon"], button[aria-label*="Like"], button[aria-label*="like"]'); likeButtons.forEach(likeBtn => { let likeSlot = likeBtn.closest('[class*="DivActionItem"], [class*="ActionItem"], [class*="action-item"]'); if (!likeSlot) { likeSlot = likeBtn.parentElement; } const parentCol = likeSlot ? likeSlot.parentElement : null; if (!parentCol || parentCol.querySelector('.omni-inline-btn-tiktok')) return; const btnDiv = document.createElement('div'); btnDiv.className = 'omni-inline-btn-tiktok'; btnDiv.style.cssText = 'text-align: center; margin-top: 16px; margin-bottom: 14px; cursor: pointer; display: flex; flex-direction: column; align-items: center; justify-content: center; user-select: none; z-index: 10;'; btnDiv.innerHTML = ` 解析 `; btnDiv.title = '左键:一键免水印解析该视频 | 右键:设置 VIP Token'; btnDiv.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const videoUrl = getTikTokVideoUrl(parentCol); handleParseAction(videoUrl); }); btnDiv.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); // Insert neatly right above the Like item slot parentCol.insertBefore(btnDiv, likeSlot); }); } // 4.2 TikTok 视频内容详情页 (Plan A: 复制链接栏右侧专属一键解析按钮) if (host.includes('tiktok.com')) { const copyContainers = document.querySelectorAll( '[class*="DivCopyLinkContainer"], ' + '[class*="CopyLinkContainer"], ' + '[class*="DivShareLinkContainer"], ' + '[data-e2e="browse-copy-link"]' ); copyContainers.forEach(copyBox => { if (copyBox.querySelector('.omni-detail-parse-btn') || copyBox.parentElement?.querySelector('.omni-detail-parse-btn')) return; const btn = createBrandLogoBtn('OmniParser 一键免水印解析此视频 (右键设置 VIP Token)', 'omni-detail-parse-btn'); btn.style.marginLeft = '8px'; btn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const videoUrl = window.location.href.split('?')[0]; handleParseAction(videoUrl); }); btn.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); if (copyBox.style.display !== 'flex') { copyBox.style.display = 'flex'; copyBox.style.alignItems = 'center'; } copyBox.appendChild(btn); }); // 通用文本匹配兜底 (确保多语言版本下复制链接行均能完美注入) document.querySelectorAll('button').forEach(b => { const txt = (b.textContent || '').trim(); if ((txt === '复制链接' || txt === 'Copy link' || b.getAttribute('data-e2e') === 'copy-link-button') && !b.getAttribute('data-omni-bound')) { b.setAttribute('data-omni-bound', '1'); const p = b.parentElement; if (p && !p.querySelector('.omni-detail-parse-btn') && !p.parentElement?.querySelector('.omni-detail-parse-btn')) { const btn = createBrandLogoBtn('OmniParser 一键免水印解析此视频 (右键设置 VIP Token)', 'omni-detail-parse-btn'); btn.style.marginLeft = '8px'; btn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const videoUrl = window.location.href.split('?')[0]; handleParseAction(videoUrl); }); btn.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); p.appendChild(btn); } } }); } // 5. 抖音 (Douyin) - 注入到右侧垂直操作栏与详情/弹窗页 if (host.includes('douyin.com')) { // 5.1 智能寻找右侧操作栏垂直列容器 (支持文字特征、类名与 SVG 寻址) const actionColumns = []; // 寻址方式 A: 通过固定的 "听抖音" 或 "评论" 或 "收藏" 或 "分享" 文字向上寻址 document.querySelectorAll('span, div, p').forEach(el => { const txt = el.textContent ? el.textContent.trim() : ''; if (txt === '听抖音' || txt === '收藏' || txt === '评论' || txt === '分享') { const slot = el.closest('div[class*="item"], div[class*="action"], div[class*="slot"], div[class*="Item"]') || el.parentElement?.parentElement || el.parentElement; const col = slot ? slot.parentElement : null; if (col && !actionColumns.includes(col) && col.clientHeight > 100 && col.clientWidth < 120) { actionColumns.push(col); } } }); // 寻址方式 B: 通过互动容器通用类名或属性 if (actionColumns.length === 0) { document.querySelectorAll('[class*="interaction"], [class*="action"], [class*="sidebar"], [class*="side-bar"], [class*="control"], [class*="right-bar"]').forEach(c => { if (!actionColumns.includes(c) && c.clientHeight > 150 && c.clientWidth < 120 && c.querySelectorAll('svg').length >= 2) { actionColumns.push(c); } }); } // 寻址方式 C: 通过心形/气泡 SVG 向上寻址 if (actionColumns.length === 0) { document.querySelectorAll('svg').forEach(s => { const box = s.getBoundingClientRect(); if (box.width >= 18 && box.width <= 50 && box.height >= 18 && box.height <= 50) { const colCandidate = s.closest('div[class*="container"], div[class*="bar"], div[class*="wrapper"]') || s.parentElement?.parentElement?.parentElement; if (colCandidate && !actionColumns.includes(colCandidate) && colCandidate.clientHeight > 150 && colCandidate.clientWidth < 120) { actionColumns.push(colCandidate); } } }); } // 对找到的操作列注入专属解析按钮 actionColumns.forEach(parentCol => { if (parentCol.querySelector('.omni-inline-btn-douyin')) return; const btnDiv = document.createElement('div'); btnDiv.className = 'omni-inline-btn-douyin'; btnDiv.style.cssText = 'text-align: center; margin-top: 12px; margin-bottom: 12px; cursor: pointer; display: flex; flex-direction: column; align-items: center; justify-content: center; user-select: none; z-index: 999;'; btnDiv.innerHTML = ` 解析 `; btnDiv.title = '左键:一键免水印解析该作品 | 右键:设置 VIP Token'; btnDiv.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const videoUrl = getDouyinVideoUrl(parentCol); handleParseAction(videoUrl); }); btnDiv.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); // 插入在第 2 个槽位(头像下方 / 点赞上方)或顶部 if (parentCol.children.length > 1) { parentCol.insertBefore(btnDiv, parentCol.children[1]); } else { parentCol.appendChild(btnDiv); } }); // 5.2 抖音网页端快捷工具栏 / 复制链接栏旁边的一键解析 document.querySelectorAll('button, div[role="button"]').forEach(b => { const txt = (b.textContent || '').trim(); if ((txt === '复制链接' || txt.includes('复制链接') || b.getAttribute('data-e2e') === 'copy-link') && !b.getAttribute('data-omni-bound')) { b.setAttribute('data-omni-bound', '1'); const p = b.parentElement; if (p && !p.querySelector('.omni-detail-parse-btn') && !p.parentElement?.querySelector('.omni-detail-parse-btn')) { const btn = createBrandLogoBtn('OmniParser 一键免水印解析此作品 (右键设置 VIP Token)', 'omni-detail-parse-btn'); btn.style.marginLeft = '8px'; btn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const videoUrl = window.location.href.split('?')[0]; handleParseAction(videoUrl); }); btn.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); if (p.style.display !== 'flex') { p.style.display = 'flex'; p.style.alignItems = 'center'; } p.appendChild(btn); } } }); } // 6. 小红书 (Xiaohongshu) - 笔记详情底部互动栏(严格仅保留单个专属提取按钮) if (host.includes('xiaohongshu.com')) { // 场景 A: 小红书作者主页 (/user/profile/...) - 清理所有杂乱角标与旧胶囊,仅在作者操作栏统一挂载官方 Logo 纯图标 if (window.location.href.includes('/user/profile/')) { // 1. 彻底清除旧式带文字且折行重叠的作者批量按钮 document.querySelectorAll('.omni-xhs-author-batch-btn').forEach(b => b.remove()); // 2. 彻底清除卡片上密密麻麻的冗余角标,杜绝界面杂乱,还原博主精美封面 document.querySelectorAll('.omni-inline-btn-xhs-card').forEach(b => b.remove()); // 3. 彻底清除右下角可能残留的旧式免水印悬浮条 const strayFloat = document.querySelector('.omni-float-btn'); if (strayFloat) strayFloat.remove(); // 4. 在作者头部操作区 (与【关注】/【私信】/【更多】按钮并列排布) 挂载唯一官方 Squircle 极光星芒纯 Logo 按钮 const followBtn = Array.from(document.querySelectorAll('button, [role="button"]')).find(btn => { const t = (btn.textContent || '').trim(); return (t === '关注' || t === '已关注' || t === '相互关注') && !btn.closest('.note-item'); }); const actionArea = followBtn ? followBtn.parentElement : document.querySelector('.user-info .user-interactions, .user-interactions, .user-action, .info-part, .user-basic, .user-info'); let validAuthorBtn = document.querySelector('.omni-xhs-author-btn'); if (actionArea) { if (!validAuthorBtn) { const authorBtnDiv = document.createElement('div'); authorBtnDiv.className = 'omni-xhs-author-btn'; authorBtnDiv.style.cssText = 'display: inline-flex; align-items: center; justify-content: center; margin-left: 10px; vertical-align: middle; flex-shrink: 0; user-select: none; z-index: 99; cursor: pointer;'; const btn = createBrandLogoBtn('OmniParser 批量解析该博主全部作品 (右键设置 VIP Token)'); btn.style.width = '32px'; btn.style.height = '32px'; btn.style.borderRadius = '10px'; authorBtnDiv.appendChild(btn); authorBtnDiv.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); handleParseAction(window.location.href); }); authorBtnDiv.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); if (followBtn && followBtn.nextSibling) { followBtn.parentElement.appendChild(authorBtnDiv); } else { actionArea.appendChild(authorBtnDiv); } } } } // 场景 B: 笔记详情弹窗/独立详情页底部互动栏 const noteModals = document.querySelectorAll( '.note-container, .note-detail-mask, [class*="note-container"], [class*="modal-container"], .note-detail' ); // 如果有打开的笔记弹窗,优先在弹窗中注入;否则在当前页面中注入 const targetContainers = noteModals.length > 0 ? Array.from(noteModals) : [document.body]; targetContainers.forEach(container => { // 严格单例保障:每个弹窗/页面全局仅保留一个按钮 if (container.querySelector('.omni-inline-btn-xhs')) return; // 仅定位底部操作栏核心按钮组(点赞/收藏/评论/分享的父容器) const interactBar = container.querySelector('.interact-container .buttons') || container.querySelector('.interact-container') || container.querySelector('.engage-bar-style') || container.querySelector('[class*="interact-container"]'); if (!interactBar) return; // 避免输入框占满宽度导致按钮溢出:让输入框自适应微缩 const parentBar = interactBar.closest('.interact-container') || interactBar; if (parentBar) { parentBar.style.overflow = 'visible'; const chatWrapper = parentBar.querySelector('.chat-wrapper, [class*="chat-wrapper"], input'); if (chatWrapper) { chatWrapper.style.minWidth = '0'; chatWrapper.style.flex = '1 1 auto'; } } // 提取笔记链接 (务必保留完整 query 参数,特别是 xsec_token 和 xsec_source,这对于小红书风控解密至关重要!) function getDynamicXhsUrl() { let target = window.location.href; const link = container.querySelector('a[href*="/explore/"], a[href*="/discovery/item/"]'); if (link) { const href = link.getAttribute('href'); target = href.startsWith('http') ? href : `https://www.xiaohongshu.com${href}`; } if (!target.includes('xsec_token=') && window.location.href.includes('xsec_token=')) { const qIdx = window.location.href.indexOf('?'); if (qIdx !== -1) { const query = window.location.href.slice(qIdx + 1); target += (target.includes('?') ? '&' : '?') + query; } } return target; } const btnDiv = document.createElement('div'); btnDiv.className = 'omni-inline-btn-xhs'; btnDiv.style.cssText = 'display: inline-flex; align-items: center; margin-left: 8px; margin-right: 0; vertical-align: middle; flex-shrink: 0; user-select: none; z-index: 99;'; const btn = createBrandLogoBtn('OmniParser 一键免水印解析此笔记图集/视频 (右键设置 VIP Token)'); btnDiv.appendChild(btn); btnDiv.title = '左键:一键免水印解析此笔记图集/视频 | 右键:设置 VIP Token'; btnDiv.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const targetUrl = getDynamicXhsUrl(); handleParseAction(targetUrl); }); btnDiv.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); if (interactBar.style.display !== 'flex') { interactBar.style.display = 'flex'; interactBar.style.alignItems = 'center'; } interactBar.appendChild(btnDiv); }); } // 7. 快手 (Kuaishou) - 精准唯一挂载 (仅在作品主操作栏保留 1 个专属按钮) if (host.includes('kuaishou.com')) { // 1. 清理多余/历史遗留的悬浮和评论区按钮 document.querySelectorAll('.omni-kuaishou-overlay-btn').forEach(b => b.remove()); // 2. 严格寻找作品的唯一主操作栏 (点赞、分享、举报所在行) const mainBar = Array.from(document.querySelectorAll('div, section')).find(el => { // 坚决排除评论区、回复框、输入框 if (el.closest('[class*="comment"], [class*="reply"], [class*="chat"], .comment-list, [class*="editor"], [class*="input"]')) { return false; } const txt = el.textContent || ''; // 主操作栏特征:包含 "分享" 或 "举报" 文本,且包含点赞图标/数字,且在视频/作者信息正下方 const hasShare = txt.includes('分享') || txt.includes('举报'); const hasSvg = el.querySelectorAll('svg, [class*="icon"]').length >= 1; const rect = el.getBoundingClientRect(); return hasShare && hasSvg && rect.width > 100 && rect.height >= 20 && rect.height <= 80; }); // 3. 沉浸流垂直侧边栏备选 (若不是弹窗详情页,而是全屏视频流) const sideBar = !mainBar ? Array.from(document.querySelectorAll('div, section, aside')).find(el => { if (el.closest('[class*="comment"], [class*="reply"]')) return false; const rect = el.getBoundingClientRect(); return rect.width >= 35 && rect.width <= 120 && rect.height >= 120 && el.querySelectorAll('svg').length >= 3; }) : null; const targetEl = mainBar || sideBar; const isSidebar = Boolean(!mainBar && sideBar); // 4. 全局去重:如果页面上已经存在主按钮且父级不是当前 targetEl,先移除旧的 if (targetEl) { if (!targetEl.querySelector('.omni-inline-btn-kuaishou')) { // 清除页面上其他位置误挂载的快手按钮 document.querySelectorAll('.omni-inline-btn-kuaishou').forEach(b => { if (!targetEl.contains(b)) b.remove(); }); const btnDiv = document.createElement('div'); btnDiv.className = 'omni-inline-btn-kuaishou'; if (isSidebar) { btnDiv.style.cssText = 'text-align: center; margin-top: 10px; margin-bottom: 10px; cursor: pointer; display: flex; flex-direction: column; align-items: center; justify-content: center; user-select: none; z-index: 999;'; btnDiv.innerHTML = ` 解析 `; } else { btnDiv.style.cssText = 'display: inline-flex; align-items: center; margin-left: 8px; margin-right: 6px; vertical-align: middle; flex-shrink: 0; user-select: none; z-index: 999;'; const btn = createBrandLogoBtn('OmniParser 一键免水印解析此快手作品 (右键设置 VIP Token)'); btnDiv.appendChild(btn); } btnDiv.title = '左键:一键免水印解析该快手作品 | 右键:设置 VIP Token'; btnDiv.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); handleParseAction(window.location.href); }); btnDiv.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); // 智能插入位置:插入在点赞按钮之后,或分享按钮之前 const shareNode = Array.from(targetEl.children).find(c => (c.textContent || '').includes('分享') || (c.textContent || '').includes('举报')); if (shareNode) { targetEl.insertBefore(btnDiv, shareNode); } else if (targetEl.children.length > 1) { targetEl.insertBefore(btnDiv, targetEl.children[1]); } else { targetEl.appendChild(btnDiv); } } } else { // 如果不在主视频区域,移除可能残留的旧按钮 document.querySelectorAll('.omni-inline-btn-kuaishou').forEach(b => { if (b.closest('[class*="comment"], [class*="reply"], .comment-list')) b.remove(); }); } } // 8. Facebook (Feed Posts, Watch & Reels) - 挂载于顶部操作区/卡片顶层,彻底杜绝视频透明遮罩拦截 if (host.includes('facebook.com') || host.includes('fb.watch')) { document.querySelectorAll('video').forEach(video => { const rect = video.getBoundingClientRect(); if (rect.width > 0 && rect.width < 100 && rect.height < 100) return; const card = video.closest('div[role="article"], div[data-pagelet*="FeedUnit"], div[data-pagelet*="WatchFeed"], div[role="main"], div[data-virtualized="false"]') || video.parentElement?.parentElement?.parentElement; if (!card || card.querySelector('.omni-inline-btn-fb')) return; // 动态解析 Facebook 贴文/视频直达永久链接(智能保留视频参数,彻底告别空 watch / 空主页) const resolveFbPostUrl = () => { const cleanFbUrl = (rawHref) => { if (!rawHref || typeof rawHref !== 'string') return ''; let urlStr = rawHref.trim(); if (!urlStr.startsWith('http://') && !urlStr.startsWith('https://')) { urlStr = `https://www.facebook.com${urlStr.startsWith('/') ? '' : '/'}${urlStr}`; } try { const u = new URL(urlStr); // 1. /watch 页面必须完整保留 ?v=123456 参数! if (u.pathname.includes('/watch')) { const v = u.searchParams.get('v'); if (v) return `https://www.facebook.com/watch/?v=${v}`; } // 2. /reel/, /videos/, /posts/, /share/ 路径直接构成完整直链 if (u.pathname.includes('/reel/') || u.pathname.includes('/videos/') || u.pathname.includes('/posts/') || u.pathname.includes('/share/')) { return `https://www.facebook.com${u.pathname.replace(/\/+$/, '')}`; } // 3. story.php 或 permalink.php,保留 story_fbid / fbid 与 id if (u.pathname.includes('story.php') || u.pathname.includes('permalink.php') || u.searchParams.has('story_fbid') || u.searchParams.has('fbid')) { const storyFbid = u.searchParams.get('story_fbid') || u.searchParams.get('fbid'); const id = u.searchParams.get('id'); if (storyFbid) { return `https://www.facebook.com/permalink.php?story_fbid=${storyFbid}${id ? `&id=${id}` : ''}`; } } // 4. photo.php if (u.pathname.includes('photo.php') && u.searchParams.has('fbid')) { return `https://www.facebook.com/photo.php?fbid=${u.searchParams.get('fbid')}`; } // 5. 过滤掉常见杂质追踪参数 ['__cft__[0]', '__tn__', 'mibextid', 'rdid', 'share_url', 'ref', 'source', 'idorvanity'].forEach(k => u.searchParams.delete(k)); const res = u.toString(); return res.replace(/\/+$/, ''); } catch (e) { return urlStr; } }; // 1. 优先从卡片的时间戳或直达链接寻找 const priorityLinks = card.querySelectorAll('a[href*="/videos/"], a[href*="/watch"], a[href*="/reel/"], a[href*="/share/"], a[href*="/posts/"], a[href*="story_fbid"], a[href*="fbid="], a[href*="permalink.php"], a[href*="story.php"]'); for (const l of priorityLinks) { const h = l.getAttribute('href') || ''; const cleaned = cleanFbUrl(h); if (cleaned && !cleaned.endsWith('facebook.com') && !cleaned.endsWith('facebook.com/watch') && !cleaned.endsWith('facebook.com/story.php')) { return cleaned; } } // 2. 遍历卡片内的所有 a 标签 const allLinks = Array.from(card.querySelectorAll('a[href]')); for (const l of allLinks) { const h = l.getAttribute('href') || ''; const cleaned = cleanFbUrl(h); if (cleaned && (cleaned.includes('/videos/') || cleaned.includes('/reel/') || cleaned.includes('watch/?v=') || cleaned.includes('/posts/') || cleaned.includes('story_fbid') || cleaned.includes('/share/'))) { return cleaned; } } // 3. 当前页面如果已是详情页 const currentCleaned = cleanFbUrl(window.location.href); if (currentCleaned && (currentCleaned.includes('/videos/') || currentCleaned.includes('/reel/') || currentCleaned.includes('watch/?v=') || currentCleaned.includes('/posts/') || currentCleaned.includes('story_fbid') || currentCleaned.includes('/share/'))) { return currentCleaned; } return window.location.href; }; const btnDiv = document.createElement('div'); btnDiv.className = 'omni-inline-btn-fb'; btnDiv.style.cssText = 'display: inline-flex; align-items: center; justify-content: center; margin-right: 8px; vertical-align: middle; flex-shrink: 0; user-select: none; z-index: 2147483640 !important; pointer-events: auto !important;'; const btn = createBrandLogoBtn('OmniParser 一键免水印解析该 Facebook 视频 (右键设置 VIP Token)'); btnDiv.appendChild(btn); btnDiv.title = '左键:一键免水印解析该 Facebook 视频 | 右键:设置 VIP Token'; const stopAllEvents = (e) => { if (e) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); } }; // 捕获阶段全面拦截点击与按下事件,彻底阻止 Facebook 路由拦截和页面跳转 ['pointerdown', 'mousedown', 'mouseup', 'touchstart', 'touchend'].forEach(evtType => { btnDiv.addEventListener(evtType, stopAllEvents, { capture: true, passive: false }); if (btn) btn.addEventListener(evtType, stopAllEvents, { capture: true, passive: false }); }); const triggerParse = (e) => { stopAllEvents(e); const finalPostUrl = resolveFbPostUrl(); handleParseAction(finalPostUrl); }; btnDiv.addEventListener('click', triggerParse, { capture: true, passive: false }); if (btn) btn.addEventListener('click', triggerParse, { capture: true, passive: false }); const triggerVip = (e) => { stopAllEvents(e); configVipToken(); }; btnDiv.addEventListener('contextmenu', triggerVip, { capture: true, passive: false }); if (btn) btn.addEventListener('contextmenu', triggerVip, { capture: true, passive: false }); // 挂载点 1:优先挂载在贴文顶部作者栏与更多操作组之间(绝不侵占 ... 按钮自身的容器空间) const moreBtn = card.querySelector('div[aria-label*="更多"], div[aria-label*="More"], div[aria-label*="Action"], div[aria-label*="操作"], div[aria-label*="Options"]') || card.querySelector('div[aria-haspopup="menu"]'); if (moreBtn) { const actionsGroup = moreBtn.closest('div[class*="x6s0dn4"], div[class*="x78zum5"]') || moreBtn.parentElement; const headerRow = actionsGroup?.parentElement; if (headerRow && actionsGroup) { btnDiv.style.marginLeft = 'auto'; btnDiv.style.marginRight = '8px'; btnDiv.style.flexShrink = '0'; actionsGroup.style.flexShrink = '0'; actionsGroup.style.overflow = 'visible'; headerRow.insertBefore(btnDiv, actionsGroup); } else if (moreBtn.parentElement) { moreBtn.parentElement.style.overflow = 'visible'; moreBtn.parentElement.insertBefore(btnDiv, moreBtn); } } else { // 挂载点 2:若无顶部栏,挂载在整个卡片右上角顶层,赋予最高层级 btnDiv.style.position = 'absolute'; btnDiv.style.top = '12px'; btnDiv.style.right = '12px'; btnDiv.style.zIndex = '2147483640'; if (getComputedStyle(card).position === 'static') { card.style.position = 'relative'; } card.appendChild(btnDiv); } }); } // 9. YouTube (Watch Page, Shorts, Player Controls & Feed Video Cards) if (host.includes('youtube.com') || host.includes('youtu.be')) { const curUrl = window.location.href; // 9.1 YouTube 播放详情页 (/watch?v=...) 挂载 if (curUrl.includes('/watch') || curUrl.includes('v=')) { // 挂载点 A:作者/订阅栏 (100% 稳定,绝不被 YouTube 菜单折叠算法剔除) const ownerRow = document.querySelector('#owner, ytd-video-owner-renderer, #above-the-fold #owner, #top-row #owner, ytm-video-owner-renderer, .slim-owner-container'); if (ownerRow && !ownerRow.querySelector('.omni-inline-btn-yt-watch')) { const btnDiv = document.createElement('div'); btnDiv.className = 'omni-inline-btn-yt-watch'; btnDiv.style.cssText = 'display: inline-flex; align-items: center; margin-left: 12px; vertical-align: middle; flex-shrink: 0; user-select: none; z-index: 10;'; const btn = createBrandLogoBtn('OmniParser 一键免水印解析该 YouTube 视频 (右键设置 VIP Token)'); btnDiv.appendChild(btn); btnDiv.title = '左键:一键免水印解析下载该 YouTube 视频 | 右键:设置 VIP Token'; btnDiv.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); handleParseAction(window.location.href); }); btnDiv.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); ownerRow.appendChild(btnDiv); } // 挂载点 B:播放器右下角控制条 (.ytp-right-controls) 原生图标 const rightControls = document.querySelector('.ytp-right-controls, .html5-video-player .ytp-right-controls'); if (rightControls && !rightControls.querySelector('.omni-ytp-btn')) { const ytpBtn = document.createElement('button'); ytpBtn.className = 'ytp-button omni-ytp-btn'; ytpBtn.style.cssText = 'width: 48px; height: 100%; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; background: none; border: none; padding: 0; vertical-align: top; user-select: none;'; ytpBtn.innerHTML = ` `; ytpBtn.title = 'OmniParser 免水印解析下载该视频'; ytpBtn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); handleParseAction(window.location.href); }); ytpBtn.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); rightControls.insertBefore(ytpBtn, rightControls.firstChild); } } // 9.2 YouTube Shorts 短视频 (/shorts/...) 侧边栏专属快捷圆钮 if (curUrl.includes('/shorts')) { document.querySelectorAll('ytd-reel-player-overlay-renderer, ytd-reel-video-renderer, ytd-shorts, ytm-shorts').forEach(reel => { const actionsBar = reel.querySelector('#actions, #like-button')?.closest('#actions') || reel.querySelector('#actions'); if (actionsBar && !actionsBar.querySelector('.omni-inline-btn-yt-shorts')) { const btnDiv = document.createElement('div'); btnDiv.className = 'omni-inline-btn-yt-shorts'; btnDiv.style.cssText = 'display: flex; flex-direction: column; align-items: center; margin-top: 12px; margin-bottom: 8px; user-select: none; z-index: 100; cursor: pointer;'; btnDiv.innerHTML = ` 解析 `; btnDiv.title = '左键:一键免水印解析该 Shorts 短视频 | 右键:设置 VIP Token'; btnDiv.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); handleParseAction(window.location.href); }); btnDiv.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); actionsBar.insertBefore(btnDiv, actionsBar.firstChild); } }); } // 9.3 YouTube 首页推荐/搜索/频道视频列表卡片 (桌面端 + 移动端全覆盖) document.querySelectorAll('ytd-rich-item-renderer, ytd-video-renderer, ytd-grid-video-renderer, ytd-compact-video-renderer, ytd-rich-grid-media, ytd-playlist-video-renderer, ytm-media-item, ytm-video-with-context-renderer').forEach(card => { if (card.querySelector('.omni-inline-btn-yt-card')) return; const linkEl = card.querySelector('a#thumbnail[href], a#video-title-link[href], a#video-title[href], a.media-item-thumbnail-container[href], a[href*="/watch?v="], a[href*="/shorts/"]'); if (!linkEl) return; const href = linkEl.getAttribute('href') || ''; if (!href.includes('/watch') && !href.includes('/shorts/')) return; const videoUrl = href.startsWith('http') ? href : `https://www.youtube.com${href}`; const thumb = card.querySelector('#thumbnail, ytd-thumbnail, a#thumbnail, .media-item-thumbnail-container') || card; const btnDiv = document.createElement('div'); btnDiv.className = 'omni-inline-btn-yt-card'; btnDiv.style.cssText = 'position: absolute; top: 6px; right: 6px; z-index: 999; user-select: none; pointer-events: auto; display: inline-flex;'; const btn = createBrandLogoBtn('OmniParser 一键免水印解析该 YouTube 视频 (右键设置 VIP Token)', 'omni-brand-btn-sm'); btnDiv.appendChild(btn); btnDiv.title = '左键:一键免水印解析该 YouTube 视频 | 右键:设置 VIP Token'; const stopAllEvents = (e) => { if (e) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); } }; ['pointerdown', 'mousedown', 'mouseup', 'touchstart', 'touchend'].forEach(evtType => { btnDiv.addEventListener(evtType, stopAllEvents, { capture: true, passive: false }); if (btn) btn.addEventListener(evtType, stopAllEvents, { capture: true, passive: false }); }); const triggerParse = (e) => { stopAllEvents(e); handleParseAction(videoUrl); }; btnDiv.addEventListener('click', triggerParse, { capture: true, passive: false }); if (btn) btn.addEventListener('click', triggerParse, { capture: true, passive: false }); btnDiv.addEventListener('contextmenu', (e) => { stopAllEvents(e); configVipToken(); }, { capture: true, passive: false }); if (getComputedStyle(thumb).position === 'static') { thumb.style.position = 'relative'; } thumb.appendChild(btnDiv); }); } // 10. 黄果短剧 (Huangguo Drama) - 仅在视频播放页挂载 if (host.includes('hongguoduanju.com') || host.includes('novelquickapp.com') || host.includes('fanqiesp.com')) { const curUrl = window.location.href; if (curUrl.includes('/player/') || curUrl.includes('/video') || curUrl.includes('video-animation-share') || curUrl.includes('video-share')) { // 挂载点 1:视频播放页标题栏右侧 (h1 / pc-title) const titleEl = document.querySelector('h1[class*="pc-title"], div[class*="pc-title"], .pc-stage-rzytAx, h1'); if (titleEl && !titleEl.parentElement?.querySelector('.omni-inline-btn-hongguo') && !titleEl.querySelector('.omni-inline-btn-hongguo')) { const btnDiv = document.createElement('div'); btnDiv.className = 'omni-inline-btn-hongguo'; btnDiv.style.cssText = 'display: inline-flex; align-items: center; margin-left: 14px; vertical-align: middle; flex-shrink: 0; user-select: none; z-index: 10;'; const btn = createBrandLogoBtn('OmniParser 一键免水印解析下载该黄果短剧 (右键设置 VIP Token)'); btnDiv.appendChild(btn); btnDiv.title = '左键:一键免水印解析下载该黄果短剧 | 右键:设置 VIP Token'; btnDiv.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); handleParseAction(window.location.href); }); btnDiv.addEventListener('contextmenu', (e) => { e.stopPropagation(); e.preventDefault(); configVipToken(); }); if (titleEl.tagName === 'H1') { titleEl.parentElement.style.display = 'flex'; titleEl.parentElement.style.alignItems = 'center'; titleEl.parentElement.appendChild(btnDiv); } else { titleEl.appendChild(btnDiv); } } } } // 11. 皮皮虾 (Pipixia / 皮皮搞笑) if (host.includes('pipix.com') || host.includes('pipigaoxiao.com')) { document.querySelectorAll('[class*="item-detail"], [class*="video-container"], [class*="post-card"], [class*="feed-item"]').forEach(card => { if (!card.querySelector('.omni-inline-btn-pipix')) { const btnDiv = document.createElement('div'); btnDiv.className = 'omni-inline-btn-pipix'; btnDiv.style.cssText = 'position: absolute; top: 12px; right: 12px; z-index: 50; cursor: pointer; user-select: none;'; const btn = createBrandLogoBtn('OmniParser 一键免水印解析此作品 (右键设置 VIP Token)', 'omni-brand-btn-sm'); btnDiv.appendChild(btn); btnDiv.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const link = card.querySelector('a[href*="/item/"]') || card.querySelector('a[href*="/ppx/"]'); const targetUrl = link ? (link.href.startsWith('http') ? link.href : `https://h5.pipix.com${link.getAttribute('href')}`) : window.location.href; handleParseAction(targetUrl); }); if (getComputedStyle(card).position === 'static') { card.style.position = 'relative'; } card.appendChild(btnDiv); } }); } } // Initial and periodic injection injectInlineButtons(); setInterval(injectInlineButtons, 1200); // MutationObserver for instant SPA DOM response try { let obsTimer = null; const domObserver = new MutationObserver(() => { if (obsTimer) return; obsTimer = setTimeout(() => { obsTimer = null; injectInlineButtons(); }, 250); }); domObserver.observe(document.body || document.documentElement, { childList: true, subtree: true }); } catch (e) {} // 3. Plan B Core Action Dispatcher function handleParseAction(targetUrl) { try { const finalUrl = (targetUrl && typeof targetUrl === 'string' && targetUrl.trim()) ? targetUrl.trim() : window.location.href; const vipStatus = getVipStatus(); // 只要绑定了账号凭据(有效 VIP、到期降级模式或普通注册用户),均享有页内原位免跳转浮层解析特权! const canUseModal = vipStatus.isRegistered; console.log('[OmniParser]: Action triggered. Mode =', PARSE_MODE, ', vipStatus =', vipStatus, ', Target =', finalUrl); // 模式 1 (始终浮层) 或 智能双模且已绑定账号:页内免跳转直接弹出原位下载浮层 (包含 VIP 到期免跳转降级模式) if (PARSE_MODE === 'modal' || (PARSE_MODE === 'auto' && canUseModal)) { openParseModal(finalUrl); return; } // 模式 2 (始终跳转) 或 智能双模下的未登录游客:新标签页跳转至官网主站自动解析 const jumpUrl = `${OMNI_SERVER}/?url=${encodeURIComponent(finalUrl)}`; console.log('[OmniParser]: Navigating to main portal:', jumpUrl); openTabSafely(jumpUrl); } catch (err) { console.error('[OmniParser]: handleParseAction exception:', err); openParseModal(targetUrl || window.location.href); } } // 小红书原生页内秒级无损提取器 (与 Web 后端 100% 对齐) function parseXhsNoteObj(noteData) { if (!noteData) return null; const title = (noteData.title || noteData.desc || '小红书无水印内容').split('\n')[0].slice(0, 60); const user = noteData.user || {}; const author = user.nickname || user.nickName || user.name || '小红书用户'; const avatar = user.avatar || user.image || ''; let cover = ''; if (noteData.cover) { cover = noteData.cover.url || noteData.cover.urlDefault || ''; } if (!cover && noteData.imageList && noteData.imageList.length > 0) { cover = noteData.imageList[0].urlDefault || noteData.imageList[0].url || ''; } if (cover) { cover = cover.split('?')[0]; if (cover.startsWith('http://')) cover = 'https://' + cover.slice(7); } // 1. 优先判定视频笔记 const isVideo = (noteData.type === 'video') || Boolean(noteData.video) || Boolean(document.querySelector('.note-container video, .media-container video, video')); if (isVideo) { const formats = []; let stream = noteData.video?.media?.stream || {}; // 尝试解析 mediaV2 (小红书新版视频流存储) if ((!stream.h264 || stream.h264.length === 0) && noteData.video?.mediaV2) { try { const v2 = typeof noteData.video.mediaV2 === 'string' ? JSON.parse(noteData.video.mediaV2) : noteData.video.mediaV2; if (v2?.stream) stream = v2.stream; } catch (e) {} } const allStreams = [ ...(stream.h264 || []), ...(stream.h265 || []), ...(stream.av1 || []), ...(stream.h266 || []) ]; allStreams.forEach((st, i) => { const vurl = st.masterUrl || st.url || (st.backupUrls && st.backupUrls[0]); if (vurl) { const res = (st.width && st.height) ? `${st.width}x${st.height}` : '720x1280'; const qType = (st.qualityType || st.streamDesc || 'HD').toUpperCase(); formats.push({ format_id: `mp4_${i}`, extension: 'mp4', resolution: res, quality_label: `原画高清 (${qType})`, type_label: '完整音视频 MP4', url: vurl, is_video: true, is_audio: false, has_video: true, has_audio: true }); } }); // 检查 DOM 中的真实