diff --git a/components/PlayerButtons.ts b/components/PlayerButtons.ts new file mode 100644 index 0000000..2b701c8 --- /dev/null +++ b/components/PlayerButtons.ts @@ -0,0 +1,72 @@ +/** + * 创建或更新一个浮动播放器按钮。 + * @param id - 按钮元素的 ID。 + * @param name - 按钮上显示的文本。 + * @param iconClass - 按钮图标的 CSS 类。 + * @param url - 按钮点击后跳转的 URL。 + * @param topPosition - 按钮的 CSS 'top' 属性值。 + */ +function createOrUpdateButton(id: string, name:string, iconClass: string, url: string, topPosition: string) { + let button = document.getElementById(id) as HTMLAnchorElement | null; + + if (!button) { + button = document.createElement('a'); + button.id = id; + button.className = 'wxt-player-button'; // 为所有播放器按钮使用一个通用类名 + button.innerHTML = ` + + ${name} + `; + document.body.appendChild(button); + + // 添加点击事件监听器 + button.addEventListener('click', (e) => { + e.preventDefault(); // 阻止 标签的默认跳转行为 + window.location.href = button!.href; + }); + } + + // 更新按钮的链接和位置 + button.href = url; + button.style.top = topPosition; + button.style.display = 'flex'; // 确保按钮可见 + } + + /** + * 隐藏指定的播放器按钮。 + * @param id - 要隐藏的按钮的 ID。 + */ + function hideButton(id: string) { + const button = document.getElementById(id); + if (button) { + button.style.display = 'none'; + } + } + + // 定义按钮的常量 ID + const IINA_BUTTON_ID = 'wxt-iina-floating-button'; + const POTPLAYER_BUTTON_ID = 'wxt-potplayer-floating-button'; + + /** + * 添加或更新 IINA 和 PotPlayer 的浮动按钮。 + * @param missavUUID - 用于生成播放链接的 UUID。 + */ + export function addOrUpdatePlayerButtons(missavUUID: string) { + const playlistUrl = `https://surrit.com/${missavUUID}/playlist.m3u8`; + + // IINA 按钮 + const iinaUrl = `iina://weblink?url=${playlistUrl}`; + createOrUpdateButton(IINA_BUTTON_ID, 'IINA', 'icon-play', iinaUrl, '100px'); + + // PotPlayer 按钮 + const potplayerUrl = `potplayer://${playlistUrl}`; + createOrUpdateButton(POTPLAYER_BUTTON_ID, 'PotPlayer', 'icon-play', potplayerUrl, '150px'); + } + + /** + * 隐藏所有的播放器按钮。 + */ + export function hidePlayerButtons() { + hideButton(IINA_BUTTON_ID); + hideButton(POTPLAYER_BUTTON_ID); + } \ No newline at end of file diff --git a/entrypoints/content/index.ts b/entrypoints/content/index.ts index a89b177..9bbf3b7 100644 --- a/entrypoints/content/index.ts +++ b/entrypoints/content/index.ts @@ -1,44 +1,44 @@ import './style.css' +// 导入新的组件方法 +import { addOrUpdatePlayerButtons, hidePlayerButtons } from '../../components/PlayerButtons'; -// same storage key as popup +// storage key, 与 popup 中保持一致 const STORAGE_KEY = 'feature_enabled'; export default defineContentScript({ matches: ['*://*.javdb.com/v/*'], async main() { - // check if the feature is enabled + // 检查功能是否启用 const isEnabled = await storage.getItem(`sync:${STORAGE_KEY}`) ?? true; if (!isEnabled) { - console.log('❌ [JavDB Helper] Feature is disabled.'); - hideFloatingButton(); + console.log('❌ [JavDB Helper] 功能已禁用。'); + hidePlayerButtons(); // 使用新的方法隐藏按钮 return; } - console.log('🚀 [JavDB Helper] Feature is enabled, running script...'); + console.log('🚀 [JavDB Helper] 功能已启用,正在运行脚本...'); - // core logic, now includes the control of button visibility const processPage = async () => { - // check if the path is the one we care about if (window.location.pathname.startsWith('/v/')) { const videoNumber = getVideoNumber(); if (videoNumber) { const missavUUID = await getMissavUUID(videoNumber); if (missavUUID) { - // if the UUID is successfully fetched, create or update the button - addOrUpdateFloatingButton(missavUUID); + // 成功获取 UUID,创建或更新按钮 + addOrUpdatePlayerButtons(missavUUID); // 使用新的方法更新按钮 } else { - // if the UUID is not fetched, hide the button - hideFloatingButton(); + // 未获取到 UUID,隐藏按钮 + hidePlayerButtons(); } } } else { - // if not in the video detail page, hide the button - hideFloatingButton(); + // 如果不在视频详情页,隐藏按钮 + hidePlayerButtons(); } }; - // listen for URL path change + // 监听 URL 路径变化 let lastPathname = window.location.pathname; new MutationObserver(() => { const currentPathname = window.location.pathname; @@ -48,122 +48,65 @@ export default defineContentScript({ } }).observe(document.body, { childList: true, subtree: true }); - // execute once when page is loaded + // 页面加载时执行一次 processPage(); } }); -// get target video number +// 获取目标视频番号 (此函数保持不变) function getVideoNumber(): string { - // get target video number - const targetElement = document.querySelector('a.button.is-white.copy-to-clipboard') + const targetElement = document.querySelector('a.button.is-white.copy-to-clipboard'); if (!targetElement) { - console.log('not found target element') - return '' + console.log('未找到目标元素'); + return ''; } - - const targetNumber = targetElement.getAttribute('data-clipboard-text') + const targetNumber = targetElement.getAttribute('data-clipboard-text'); if (!targetNumber) { - console.log('no target number') - return '' + console.log('无目标番号'); + return ''; } - - console.log('targetNumber', targetNumber) - return targetNumber + console.log('目标番号', targetNumber); + return targetNumber; } -// get missav UUID +// 获取 missav UUID (此函数保持不变) async function getMissavUUID(videoNumber: string): Promise { - const lowerTargetNumber = videoNumber.toLowerCase() - const targetUrl = `https://missav.ws/dm1/en/${lowerTargetNumber}` + const lowerTargetNumber = videoNumber.toLowerCase(); + const targetUrl = `https://missav.ws/dm1/en/${lowerTargetNumber}`; try { - // 'response' here is the object { success: boolean, html?: string, error?: string } - // sent from your background script. It is NOT a Fetch API Response object. const response = await chrome.runtime.sendMessage({ type: 'fetchMissav', url: targetUrl - }) + }); if (!response.success) { - throw new Error(response.error) + throw new Error(response.error); } - // Directly use the 'html' property which is already a string. - // DO NOT try to call response.text(). - const parser = new DOMParser() - const doc = parser.parseFromString(response.html, 'text/html') + const parser = new DOMParser(); + const doc = parser.parseFromString(response.html, 'text/html'); - const scripts = doc.getElementsByTagName('script') - // console.log('scripts', scripts) + const scripts = doc.getElementsByTagName('script'); for (const script of scripts) { - const content = script.textContent || '' + const content = script.textContent || ''; if (content.includes('thumbnail')) { - const urlsMatch = content.match(/urls:\s*\[(.*?)\]/s) + const urlsMatch = content.match(/urls:\s*\[(.*?)\]/s); if (urlsMatch) { - const firstUrl = urlsMatch[1].split(',')[0].trim().replace(/"/g, '').replace(/\\/g, '') - const uuidMatch = firstUrl.match(/\/([0-9a-f-]+)\/seek\//i) + const firstUrl = urlsMatch[1].split(',')[0].trim().replace(/"/g, '').replace(/\\/g, ''); + const uuidMatch = firstUrl.match(/\/([0-9a-f-]+)\/seek\//i); if (uuidMatch) { - console.log('uuidMatch', uuidMatch[1]) - return uuidMatch[1] + console.log('uuidMatch', uuidMatch[1]); + return uuidMatch[1]; } } } } - console.warn('uuid not found') - return '' + console.warn('未找到 uuid'); + return ''; } catch (error) { - // This is where your error was being caught. - console.error('fetch or parse document error:', error) - return '' - } -} - -/** - * create a new floating button, or update the existing button's link - * @param missavUUID - the UUID used to generate the playback link - */ -function addOrUpdateFloatingButton(missavUUID: string) { - const BUTTON_ID = 'wxt-iina-floating-button'; - const iinaUrl = `iina://weblink?url=https://surrit.com/${missavUUID}/playlist.m3u8`; - console.log('Playlist URL:', iinaUrl); - - // check if the button already exists - let iinaButton = document.getElementById(BUTTON_ID) as HTMLAnchorElement | null; - - if (!iinaButton) { - // if the button does not exist, create it - iinaButton = document.createElement('a'); - iinaButton.id = BUTTON_ID; - iinaButton.className = 'wxt-iina-button'; // use class name to apply style - iinaButton.innerHTML = ` - - IINA - `; - - // add the button to the body - document.body.appendChild(iinaButton); - - // click event (since we use the a tag's href directly, we can omit this, but it's better to add it to prevent default behavior) - iinaButton.addEventListener('click', (e) => { - e.preventDefault(); - window.location.href = iinaButton!.href; - }); - } - - // update the href property of the button and ensure it is visible - iinaButton.href = iinaUrl; - iinaButton.style.display = 'flex'; -} - -/** - * hide the floating button - */ -function hideFloatingButton() { - const BUTTON_ID = 'wxt-iina-floating-button'; - const iinaButton = document.getElementById(BUTTON_ID); - if (iinaButton) { - iinaButton.style.display = 'none'; + console.error('获取或解析文档时出错:', error); + return ''; } } \ No newline at end of file diff --git a/entrypoints/content/style.css b/entrypoints/content/style.css index 97a3c73..8616631 100644 --- a/entrypoints/content/style.css +++ b/entrypoints/content/style.css @@ -1,29 +1,28 @@ -.wxt-iina-button { - /* 定位和层级 */ - position: fixed; - top: 100px; /* 距离顶部 150px,可以按喜好调整 */ - right: 0px; /* 距离右侧 20px */ - z-index: 9999; /* 确保在页面最上层 */ - - /* 外观和布局 */ - display: flex; /* 使用 flexbox 让图标和文字居中 */ - align-items: center; - gap: 5px; /* 图标和文字的间距 */ - background-color: #3173dc; /* 一个不错的蓝色 */ - color: white; - padding: 5px 10px; - /* border-radius: 5px; 圆角 */ - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; - font-size: 14px; - font-weight: 500; - text-decoration: none; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); /* 添加阴影增加立体感 */ - - /* 交互效果 */ - cursor: pointer; - transition: transform 0.2s ease-in-out, background-color 0.2s ease; - } +.wxt-player-button { + /* 定位和层级 */ + position: fixed; + /* 'top' 属性现在通过内联样式设置 */ + right: 0px; + z-index: 9999; - .wxt-iina-button:hover { - transform: scale(1.05); /* 轻微放大 */ - } \ No newline at end of file + /* 外观和布局 */ + display: flex; + align-items: center; + gap: 5px; + background-color: #3173dc; + color: white; + padding: 5px 10px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; + font-size: 14px; + font-weight: 500; + text-decoration: none; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + + /* 交互效果 */ + cursor: pointer; + transition: transform 0.2s ease-in-out, background-color 0.2s ease; +} + +.wxt-player-button:hover { + transform: scale(1.05); +} \ No newline at end of file