Bilibili & YouTube Tools

跨平台视频工具集:字幕提取、AI总结、Notion集成、笔记保存、播放速度控制、广告跳过 - 支持B站和YouTube

// ==UserScript==
// @name         Bilibili & YouTube Tools
// @namespace    http://tampermonkey.net/
// @version      1.2.0
// @author       geraldpeng & claude 4.5 sonnet
// @description  跨平台视频工具集:字幕提取、AI总结、Notion集成、笔记保存、播放速度控制、广告跳过 - 支持B站和YouTube
// @license      MIT
// @match        *://*/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/marked.min.js
// @connect      api.bilibili.com
// @connect      aisubtitle.hdslb.com
// @connect      api.notion.com
// @connect      openrouter.ai
// @connect      bsbsb.top
// @connect      *
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// @run-at       document-start
// ==/UserScript==

(function () {
	'use strict';

	const e=500,t=20,n=1500,o=500,i=500,s=2e3,r=2e3,a=500,l=12e4,c=2e3,d=1900,u=2e3,p=32,h="bilibili_notes",g=4718592,m=10,b=100,f=3e3,y="idle",v="loading",x="active",w="no-subtitle",S="error",k="subtitle:loaded",I="subtitle:failed",E="subtitle:requested",T="ai:summary:start",C="ai:summary:complete",A="ai:summary:failed",$="ai:summary:chunk",_="notion:send:start",M="notion:send:complete",P="notion:send:failed",N="ui:panel:toggle",L="ui:ball:status:change",q="video:changed",B="请用中文总结以下视频字幕内容,使用Markdown格式输出。\n\n要求:\n1. 第一行使用 # 标题,简洁概括视频主题\n2. 第二部分使用 ## TL;DR 作为标题,提供2-3句话的核心摘要\n3. 第三部分使用 --- 分隔线\n4. 后续内容按主题使用 ### 三级标题分段\n5. 使用项目符号 - 列出要点\n6. 直接输出Markdown内容,不要使用代码块包裹(不要使用```)\n\n字幕内容:\n",D='分析以下带时间戳的字幕,提取5-8个关键段落。\n\n重要:你的回复必须只包含JSON,不要有任何其他文字、解释或markdown标记。\n直接以{开始,以}结束。\n\nJSON格式要求:\n{"segments":[\n  {"timestamp":"分钟:秒","title":"标题(10字内)","summary":"内容总结(30-50字)"}\n]}\n\n示例(你的回复应该像这样):\n{"segments":[{"timestamp":"00:15","title":"开场介绍","summary":"主持人介绍今天的主题和嘉宾背景"},{"timestamp":"02:30","title":"核心观点","summary":"讨论技术发展趋势和未来展望"}]}\n\n字幕内容:\n',z={openrouter:"https://openrouter.ai/keys",openai:"https://platform.openai.com/api-keys",siliconflow:"https://cloud.siliconflow.cn/account/ak",deepseek:"https://platform.deepseek.com/api_keys",moonshot:"https://platform.moonshot.cn/console/api-keys",zhipu:"https://open.bigmodel.cn/usercenter/apikeys",yi:"https://platform.lingyiwanwu.com/apikeys",dashscope:"https://bailian.console.aliyun.com/",gemini:"https://aistudio.google.com/app/apikey"},R=[{id:"openrouter",name:"OpenRouter",url:"https://openrouter.ai/api/v1/chat/completions",apiKey:"",model:"alibaba/tongyi-deepresearch-30b-a3b:free",prompt1:B,prompt2:D,isOpenRouter:true},{id:"openai",name:"OpenAI",url:"https://api.openai.com/v1/chat/completions",apiKey:"",model:"gpt-3.5-turbo",prompt1:B,prompt2:D,isOpenRouter:false},{id:"siliconflow",name:"硅基流动",url:"https://api.siliconflow.cn/v1/chat/completions",apiKey:"",model:"Qwen/Qwen2.5-7B-Instruct",prompt1:B,prompt2:D,isOpenRouter:false},{id:"deepseek",name:"DeepSeek",url:"https://api.deepseek.com/v1/chat/completions",apiKey:"",model:"deepseek-chat",prompt1:B,prompt2:D,isOpenRouter:false},{id:"moonshot",name:"月之暗面 Kimi",url:"https://api.moonshot.cn/v1/chat/completions",apiKey:"",model:"moonshot-v1-8k",prompt1:B,prompt2:D,isOpenRouter:false},{id:"zhipu",name:"智谱清言",url:"https://open.bigmodel.cn/api/paas/v4/chat/completions",apiKey:"",model:"glm-4-flash",prompt1:B,prompt2:D,isOpenRouter:false},{id:"yi",name:"零一万物",url:"https://api.lingyiwanwu.com/v1/chat/completions",apiKey:"",model:"yi-large",prompt1:B,prompt2:D,isOpenRouter:false},{id:"dashscope",name:"通义千问",url:"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",apiKey:"",model:"qwen-plus",prompt1:B,prompt2:D,isOpenRouter:false},{id:"gemini",name:"Google Gemini",url:"https://generativelanguage.googleapis.com/v1beta/openai/chat/completions",apiKey:"",model:"gemini-1.5-flash",prompt1:B,prompt2:D,isOpenRouter:false}],O="ai_configs",V="selected_ai_config_id",j="ai_auto_summary_enabled",F="notion_api_key",U="notion_parent_page_id",H="notion_database_id",K="notion_auto_send_enabled",Y="notion_content_video_info",G="notion_content_summary",J="notion_content_segments",W="notion_content_subtitles",X="bilibili_tools_processed_videos",Q={BALL:2147483647,CONTAINER:2147483646,TOAST:2147483645,AI_MODAL:2147483643},Z="2022-06-28",ee="https://api.notion.com/v1",te=/\/video\/(BV[1-9A-Za-z]{10})/,ne=/BV[1-9A-Za-z]{10}/,oe=/([a-f0-9]{32}|[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i,ie="video",se=".bpx-player-container, #bilibili-player",re=".bpx-player-ctrl-subtitle-result",ae='.bpx-player-ctrl-subtitle-close-switch[data-action="close"]',le="h1.video-title",ce={API_URL:"https://bsbsb.top/api/skipSegments",CACHE_EXPIRY:18e5,CATEGORIES:{sponsor:{name:"广告",color:"#00d400"},selfpromo:{name:"无偿/自我推广",color:"#ffff00"},interaction:{name:"三连/订阅提醒",color:"#cc00ff"},poi_highlight:{name:"精彩时刻/重点",color:"#ff1684"},intro:{name:"过场/开场动画",color:"#00ffff"},outro:{name:"鸣谢/结束画面",color:"#0202ed"},preview:{name:"回顾/概要",color:"#008fd6"},filler:{name:"离题闲聊/玩笑",color:"#7300FF"},music_offtopic:{name:"音乐:非音乐部分",color:"#ff9900"},exclusive_access:{name:"柔性推广/品牌合作",color:"#008a5c"},mute:{name:"静音片段",color:"#B54D4B"}},DEFAULT_SETTINGS:{skipCategories:["sponsor"],showAdBadge:true,showQualityBadge:true,showProgressMarkers:true}},de=`\n  /* ==================== 小球样式 ==================== */\n  #subtitle-ball {\n    position: absolute;\n    right: -30px;\n    top: 50%;\n    transform: translateY(-50%);\n    width: 20px;\n    height: 20px;\n    border-radius: 50%;\n    background-color: #999;\n    cursor: pointer;\n    z-index: ${Q.BALL};\n    box-shadow: 0 2px 6px rgba(0,0,0,0.25);\n    transition: all 0.3s ease;\n    animation: breath-ball-normal 2s ease-in-out infinite;\n  }\n\n  #subtitle-ball:hover {\n    transform: translateY(-50%) scale(1.2);\n    box-shadow: 0 3px 10px rgba(0,0,0,0.35);\n  }\n\n  #subtitle-ball.active {\n    background-color: #feebea;\n    cursor: pointer;\n  }\n\n  #subtitle-ball.loading {\n    background-color: #3b82f6;\n    animation: breath-ball 1.2s ease-in-out infinite;\n  }\n\n  #subtitle-ball.ai-summarizing {\n    background-color: #feebea;\n    animation: breath-ball-ai 1s ease-in-out infinite;\n  }\n\n  #subtitle-ball.no-subtitle {\n    background-color: #999;\n    cursor: default;\n    opacity: 0.6;\n  }\n\n  #subtitle-ball.error {\n    background-color: #ff0000;\n    cursor: default;\n  }\n\n  @keyframes breath-ball-normal {\n    0%, 100% { transform: translateY(-50%) scale(1); }\n    50% { transform: translateY(-50%) scale(1.05); }\n  }\n\n  @keyframes breath-ball {\n    0%, 100% { transform: translateY(-50%) scale(1.1); opacity: 1; }\n    50% { transform: translateY(-50%) scale(1.4); opacity: 0.6; }\n  }\n\n  @keyframes breath-ball-ai {\n    0%, 100% { \n      transform: translateY(-50%) scale(1.3); \n      opacity: 1;\n      box-shadow: 0 0 20px rgba(254, 235, 234, 0.8);\n    }\n    50% { \n      transform: translateY(-50%) scale(1.8); \n      opacity: 0.7;\n      box-shadow: 0 0 40px rgba(254, 235, 234, 1);\n    }\n  }\n\n  /* ==================== 字幕容器样式 ==================== */\n  #subtitle-container {\n    position: absolute;\n    top: 10%;\n    left: 100%;\n    width: 500px;\n    min-width: 400px;\n    max-width: 800px;\n    height: 600px;\n    min-height: 400px;\n    max-height: 90vh;\n    background: rgba(0, 0, 0, 0.85);\n    backdrop-filter: blur(12px);\n    color: #fff;\n    border-radius: 12px;\n    font-size: 14px;\n    line-height: 1.8;\n    display: none;\n    flex-direction: column;\n    overflow: hidden;\n    box-shadow: -4px 0 24px rgba(0,0,0,0.5);\n    border: 1px solid rgba(254, 235, 234, 0.2);\n    transition: none;\n    z-index: ${Q.CONTAINER};\n    margin-left: 10px;\n  }\n\n  #subtitle-container.show {\n    display: flex;\n  }\n  \n  /* 调整大小的边缘检测区域 */\n  #subtitle-container::before {\n    content: '';\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    pointer-events: none;\n  }\n  \n  /* 鼠标悬停在边缘时的光标样式 */\n  #subtitle-container.resize-n { cursor: n-resize; }\n  #subtitle-container.resize-s { cursor: s-resize; }\n  #subtitle-container.resize-e { cursor: e-resize; }\n  #subtitle-container.resize-w { cursor: w-resize; }\n  #subtitle-container.resize-ne { cursor: ne-resize; }\n  #subtitle-container.resize-nw { cursor: nw-resize; }\n  #subtitle-container.resize-se { cursor: se-resize; }\n  #subtitle-container.resize-sw { cursor: sw-resize; }\n\n  /* ==================== 头部样式 ==================== */\n  .subtitle-header {\n    font-size: 14px;\n    font-weight: 500;\n    padding: 16px 20px;\n    border-bottom: 1px solid rgba(254, 235, 234, 0.15);\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    cursor: move;\n    user-select: none;\n    flex-shrink: 0;\n    background: linear-gradient(135deg, rgba(254, 235, 234, 0.12), rgba(254, 235, 234, 0.06));\n    color: #fff;\n    border-radius: 12px 12px 0 0;\n    user-select: none;\n  }\n\n  .subtitle-header-left {\n    display: flex;\n    align-items: center;\n    gap: 12px;\n  }\n\n  .subtitle-header-right {\n    display: flex;\n    align-items: center;\n    gap: 12px;\n    position: relative;\n    z-index: 10;\n  }\n\n  .subtitle-status-text {\n    font-size: 13px;\n    font-weight: 500;\n    color: rgba(255, 255, 255, 0.9);\n  }\n\n  .subtitle-search-box {\n    position: relative;\n    flex: 1;\n    max-width: 300px;\n    margin: 0 8px; \n    color: rgba(255, 255, 255, 0.6);\n    font-size: 14px;\n    font-weight: 500;\n    cursor: pointer;\n    transition: all 0.2s;\n  }\n\n  .subtitle-search-container {\n    position: relative;\n    display: flex;\n    align-items: center;\n    background: rgba(0, 0, 0, 0.2);\n    border-radius: 20px;\n    padding: 5px 12px;\n    border: 1px solid rgba(254, 235, 234, 0.15);\n    transition: all 0.2s;\n    width: 200px;\n  }\n\n  .subtitle-search-container:focus-within {\n    border-color: #feebea;\n    background: rgba(0, 0, 0, 0.4);\n    box-shadow: 0 0 0 2px rgba(254, 235, 234, 0.1);\n  }\n\n  .search-input {\n    flex: 1;\n    background: transparent;\n    border: none;\n    outline: none;\n    color: #fff;\n    font-size: 14px;\n    padding: 4px;\n    padding-right: 70px;\n  }\n\n  .search-input::placeholder {\n    color: rgba(255, 255, 255, 0.5);\n  }\n\n  .search-controls {\n    position: absolute;\n    right: 8px;\n    display: flex;\n    align-items: center;\n    gap: 4px;\n  }\n\n  .search-counter {\n    font-size: 11px;\n    color: rgba(255, 255, 255, 0.6);\n    min-width: 28px;\n    text-align: center;\n  }\n\n  .search-nav-btn {\n    background: transparent;\n    border: none;\n    color: rgba(255, 255, 255, 0.6);\n    cursor: pointer;\n    padding: 2px;\n    transition: all 0.2s;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    border-radius: 4px;\n  }\n\n  .search-nav-btn:hover {\n    color: #fff;\n    background: rgba(255, 255, 255, 0.1);\n  }\n\n  .search-nav-btn:active {\n    transform: scale(0.9);\n  }\n\n  .search-nav-btn:disabled {\n    opacity: 0.3;\n    cursor: not-allowed;\n  }\n  \n  .search-nav-btn svg {\n    display: block;\n  }\n\n  /* 搜索高亮样式 */\n  .search-highlight {\n    background-color: rgba(255, 255, 0, 0.4);\n    color: #000;\n    padding: 2px 0;\n    border-radius: 2px;\n  }\n\n  .search-highlight-current {\n    background-color: rgba(255, 165, 0, 0.6);\n    color: #000;\n    padding: 2px 0;\n    border-radius: 2px;\n    box-shadow: 0 0 4px rgba(255, 165, 0, 0.8);\n  }\n\n  .subtitle-header-actions {\n    display: flex;\n    gap: 12px;\n    align-items: center;\n  }\n\n  .subtitle-close {\n    cursor: pointer;\n    font-size: 20px;\n    line-height: 1;\n    color: rgba(255, 255, 255, 0.5);\n    transition: all 0.2s;\n    padding: 4px;\n    border-radius: 4px;\n  }\n\n  .subtitle-close:hover {\n    color: rgba(255, 255, 255, 0.9);\n    background: rgba(255, 255, 255, 0.1);\n  }\n\n  /* ==================== 标签页样式 ==================== */\n  .subtitle-tabs {\n    display: flex;\n    padding: 0 20px;\n    gap: 20px;\n    border-bottom: 1px solid rgba(254, 235, 234, 0.1);\n    background: rgba(0, 0, 0, 0.2);\n  }\n\n  .subtitle-tab {\n    position: relative;\n    padding: 12px 4px;\n    color: rgba(255, 255, 255, 0.6);\n    font-size: 14px;\n    font-weight: 500;\n    cursor: pointer;\n    transition: all 0.2s;\n    user-select: none;\n  }\n\n  .subtitle-tab:hover {\n    color: rgba(255, 255, 255, 0.9);\n  }\n\n  .subtitle-tab.active {\n    color: #fff;\n  }\n\n  .subtitle-tab.active::after {\n    content: '';\n    position: absolute;\n    bottom: 0;\n    left: 0;\n    right: 0;\n    height: 2px;\n    background: linear-gradient(90deg, #feebea, rgba(254, 235, 234, 0.6));\n    border-radius: 2px 2px 0 0;\n  }\n\n  /* ==================== 内容区域样式 ==================== */\n  .subtitle-content {\n    flex: 1;\n    overflow-y: auto;\n    overflow-x: hidden;\n    height: calc(100% - 120px);\n  }\n\n  .subtitle-panel {\n    padding: 20px;\n    height: 100%;\n    overflow-y: visible;\n  }\n\n  .subtitle-content::-webkit-scrollbar {\n    width: 6px;\n  }\n\n  .subtitle-content::-webkit-scrollbar-thumb {\n    background-color: rgba(254, 235, 234, 0.4);\n    border-radius: 3px;\n  }\n  \n  .subtitle-content::-webkit-scrollbar-thumb:hover {\n    background-color: rgba(254, 235, 234, 0.6);\n  }\n  \n  .subtitle-content::-webkit-scrollbar-track {\n    background-color: rgba(255, 255, 255, 0.05);\n  }\n\n  /* ==================== 字幕列表样式 ==================== */\n  .subtitle-toggle-btn {\n    padding: 8px 12px;\n    margin-bottom: 15px;\n    background: rgba(255, 255, 255, 0.1);\n    border: 1px solid rgba(254, 235, 234, 0.3);\n    border-radius: 8px;\n    color: #fff;\n    cursor: pointer;\n    font-size: 16px;\n    transition: all 0.2s;\n    display: flex;\n    align-items: center;\n    justify-content: flex-start;\n    width: 100%;\n    height: auto;\n  }\n\n  .subtitle-toggle-btn:hover {\n    background: rgba(254, 235, 234, 0.2);\n    border-color: #feebea;\n    transform: scale(1.05);\n  }\n\n  .subtitle-toggle-icon {\n    transition: transform 0.3s ease;\n    display: inline-block;\n    font-size: 12px;\n  }\n\n  .subtitle-toggle-btn.expanded .subtitle-toggle-icon {\n    transform: rotate(90deg);\n  }\n\n  .subtitle-list-container {\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n    height: auto;\n    overflow-y: visible;\n    padding: 16px;\n    position: relative;\n  }\n\n  .subtitle-item {\n    display: flex;\n    align-items: center;\n    gap: 6px;\n    margin-bottom: 2px;\n    padding: 6px 10px;\n    border-radius: 6px;\n    transition: all 0.2s;\n    cursor: pointer;\n    background: rgba(255, 255, 255, 0.05);\n    border: 1px solid rgba(254, 235, 234, 0.2);\n    position: relative;\n  }\n\n  .subtitle-item:hover {\n    background: rgba(254, 235, 234, 0.15);\n    border-color: #feebea;\n    transform: translateX(4px);\n    box-shadow: 0 2px 8px rgba(254, 235, 234, 0.2);\n  }\n\n  .subtitle-item.current {\n    background: rgba(254, 235, 234, 0.2) !important;\n    border-left: 3px solid #ff69b4;\n    padding-left: 9px;\n    box-shadow: 0 2px 8px rgba(255, 105, 180, 0.2);\n    animation: subtitleHighlight 1.5s ease infinite;\n  }\n  \n  @keyframes subtitleHighlight {\n    0%, 100% {\n      box-shadow: 0 2px 8px rgba(255, 105, 180, 0.2);\n    }\n    50% {\n      box-shadow: 0 2px 12px rgba(255, 105, 180, 0.4);\n    }\n  }\n\n  .subtitle-time {\n    color: rgba(255, 255, 255, 0.6);\n    font-size: 11px;\n    font-weight: 600;\n    flex-shrink: 0;\n    min-width: 42px;\n  }\n\n  .subtitle-text {\n    color: #e5e7eb;\n    font-size: 14px;\n    line-height: 1.5;\n    flex: 1;\n  }\n\n  /* ==================== AI图标样式 ==================== */\n  .ai-icon {\n    cursor: pointer;\n    width: 24px;\n    height: 24px;\n    opacity: 0.7;\n    transition: opacity 0.2s, transform 0.2s;\n  }\n\n  .ai-icon:hover {\n    opacity: 1;\n    transform: scale(1.1);\n  }\n\n  .ai-icon.loading {\n    animation: breath-ai 1.2s ease-in-out infinite;\n    pointer-events: none;\n  }\n\n  .ai-icon.disabled {\n    opacity: 0.3;\n    pointer-events: none;\n    cursor: not-allowed;\n  }\n\n  @keyframes breath-ai {\n    0%, 100% { transform: scale(1.05); opacity: 1; }\n    50% { transform: scale(1.35); opacity: 0.5; }\n  }\n\n  /* ==================== 下载图标样式 ==================== */\n  .download-icon {\n    cursor: pointer;\n    width: 20px;\n    height: 20px;\n    opacity: 0.7;\n    transition: opacity 0.2s, transform 0.2s;\n  }\n\n  .download-icon:hover {\n    opacity: 1;\n    transform: scale(1.1);\n  }\n\n  /* ==================== Notion图标样式 ==================== */\n  .notion-icon {\n    cursor: pointer;\n    width: 24px;\n    height: 24px;\n    opacity: 0.7;\n    transition: opacity 0.2s, transform 0.2s;\n  }\n\n  .notion-icon:hover {\n    opacity: 1;\n    transform: scale(1.1);\n  }\n\n  .notion-icon.loading {\n    animation: breath-notion 1.2s ease-in-out infinite;\n  }\n\n  @keyframes breath-notion {\n    0%, 100% { transform: scale(1.05); opacity: 1; }\n    50% { transform: scale(1.35); opacity: 0.5; }\n  }\n\n  /* ==================== Toast提示样式 ==================== */\n  .notion-toast {\n    position: fixed;\n    top: 80px;\n    left: 50%;\n    transform: translateX(-50%);\n    background-color: rgba(0, 0, 0, 0.85);\n    color: white;\n    padding: 12px 24px;\n    border-radius: 8px;\n    font-size: 14px;\n    z-index: ${Q.TOAST};\n    opacity: 0;\n    transition: opacity 0.3s;\n    box-shadow: 0 4px 12px rgba(0,0,0,0.3);\n  }\n\n  .notion-toast.show {\n    opacity: 1;\n  }\n\n  /* ==================== AI总结样式 ==================== */\n  .ai-summary-tips {\n    padding: 10px 16px;\n    background: rgba(254, 235, 234, 0.08);\n    border-radius: 8px;\n    color: rgba(255, 255, 255, 0.5);\n    font-size: 12px;\n    margin-bottom: 16px;\n  }\n\n  .ai-summary-main {\n    padding: 16px;\n    background: rgba(0, 0, 0, 0.2);\n    border-radius: 12px;\n    margin-bottom: 20px;\n    border: 1px solid rgba(255, 255, 255, 0.08);\n  }\n  \n  .summary-title {\n    font-size: 14px;\n    font-weight: bold;\n    color: rgba(255, 255, 255, 0.9);\n    margin-bottom: 12px;\n    padding-bottom: 8px;\n    border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n  }\n  \n  .summary-content {\n    font-size: 14px;\n    line-height: 1.8;\n    color: rgba(255, 255, 255, 0.85);\n    overflow-y: visible;\n    overflow-x: hidden;\n    word-wrap: break-word;\n    overflow-wrap: break-word;\n    word-break: break-word;\n  }\n  \n  /* 滚动条样式已统一由 .subtitle-content 管理 */\n  \n  /* Markdown样式 - 总结区域 */\n  .summary-content h1,\n  .summary-content h2,\n  .summary-content h3,\n  .summary-content h4,\n  .summary-content h5,\n  .summary-content h6 {\n    color: rgba(255, 255, 255, 0.9) !important;\n    margin-top: 12px;\n    margin-bottom: 8px;\n    font-weight: 600;\n  }\n  \n  .summary-content h1 { font-size: 18px; }\n  .summary-content h2 { font-size: 16px; }\n  .summary-content h3 { font-size: 15px; }\n  .summary-content h4 { font-size: 14px; }\n  \n  .summary-content p {\n    margin: 8px 0;\n    color: rgba(255, 255, 255, 0.85);\n  }\n  \n  .summary-content ul,\n  .summary-content ol {\n    margin: 8px 0;\n    padding-left: 20px;\n    color: rgba(255, 255, 255, 0.85);\n  }\n  \n  .summary-content li {\n    margin: 4px 0;\n  }\n  \n  .summary-content strong {\n    color: rgba(255, 255, 255, 0.95);\n    font-weight: 600;\n  }\n  \n  .summary-content code {\n    background: rgba(255, 255, 255, 0.1);\n    color: #feebea;\n    padding: 2px 4px;\n    border-radius: 3px;\n    font-size: 13px;\n  }\n  \n  .summary-content blockquote {\n    border-left: 3px solid #feebea;\n    background: rgba(254, 235, 234, 0.05);\n    padding: 8px 12px;\n    margin: 8px 0;\n    color: rgba(255, 255, 255, 0.8);\n  }\n\n  .ai-summary-outline {\n    margin-bottom: 20px;\n  }\n\n  .progress-switch {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    padding: 12px 16px;\n    background: rgba(0, 0, 0, 0.15);\n    border-radius: 8px;\n    margin-bottom: 16px;\n    border: 1px solid rgba(254, 235, 234, 0.1);\n  }\n  \n  .progress-switch span {\n    font-size: 14px;\n    font-weight: 500;\n    color: rgba(255, 255, 255, 0.8);\n  }\n\n  .switch-btn {\n    width: 40px;\n    height: 22px;\n    background: rgba(255, 255, 255, 0.1);\n    border-radius: 11px;\n    position: relative;\n    cursor: pointer;\n    transition: all 0.3s;\n    border: 1px solid rgba(254, 235, 234, 0.2);\n  }\n\n  .switch-btn.on {\n    background: rgba(254, 235, 234, 0.3);\n    border-color: #feebea;\n  }\n\n  .switch-block {\n    width: 16px;\n    height: 16px;\n    background: #fff;\n    border-radius: 50%;\n    position: absolute;\n    top: 2px;\n    left: 2px;\n    transition: all 0.3s;\n    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n  }\n\n  .switch-btn.on .switch-block {\n    transform: translateX(18px);\n  }\n\n  .ai-summary-sections {\n    padding: 0;\n  }\n\n  .summary-section {\n    margin-bottom: 20px;\n  }\n\n  .section-title {\n    font-size: 14px;\n    font-weight: 600;\n    color: #fff;\n    margin-bottom: 12px;\n    padding: 8px 12px;\n    background: rgba(254, 235, 234, 0.08);\n    border-radius: 8px;\n    border-left: 3px solid #feebea;\n  }\n\n  .section-items {\n    padding: 0;\n  }\n\n  .section-item {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    padding: 10px;\n    background: rgba(255, 255, 255, 0.03);\n    border-radius: 6px;\n    border: 1px solid rgba(254, 235, 234, 0.1);\n    margin-bottom: 8px;\n    transition: all 0.2s ease;\n    position: relative;\n    cursor: pointer; /* 添加鼠标指针样式,表示可点击 */\n    user-select: none; /* 禁止文字选择,防止与笔记功能冲突 */\n    -webkit-user-select: none;\n    -moz-user-select: none;\n    -ms-user-select: none;\n  }\n  \n  .section-item:hover {\n    background: rgba(254, 235, 234, 0.15);\n    border-color: #feebea;\n    transform: translateX(4px);\n    box-shadow: 0 2px 8px rgba(254, 235, 234, 0.2); /* 添加阴影效果 */\n  }\n  \n  .section-item:active {\n    transform: translateX(2px) scale(0.98); /* 点击时的反馈 */\n  }\n  \n  .section-item.clicked {\n    animation: segmentClick 0.3s ease;\n  }\n  \n  @keyframes segmentClick {\n    0% {\n      transform: scale(1);\n      background: rgba(254, 235, 234, 0.05);\n    }\n    50% {\n      transform: scale(0.98);\n      background: rgba(254, 235, 234, 0.3);\n      box-shadow: 0 0 10px rgba(254, 235, 234, 0.5);\n    }\n    100% {\n      transform: scale(1);\n      background: rgba(254, 235, 234, 0.05);\n    }\n  }\n  \n  .ai-segments-section .section-item {\n    cursor: pointer;\n  }\n  \n  .ai-segments-section .section-item:hover {\n    background: rgba(254, 235, 234, 0.2);\n  }\n  \n  .segment-item {\n    cursor: pointer;\n    transition: all 0.2s ease;\n  }\n  \n  .segment-item:hover {\n    background: rgba(254, 235, 234, 0.1);\n    transform: translateX(2px);\n  }\n\n  .time-btn {\n    padding: 2px 6px;\n    background: transparent;\n    border: none;\n    color: rgba(255, 255, 255, 0.6);\n    font-size: 11px;\n    font-weight: 600;\n    cursor: pointer;\n    transition: all 0.2s;\n    flex-shrink: 0;\n    min-width: 42px;\n    text-align: left;\n  }\n\n  .time-btn:hover {\n    color: #feebea;\n  }\n\n  .item-content {\n    flex: 1;\n    color: #e5e7eb;\n    font-size: 14px;\n    line-height: 1.5;\n    display: flex;\n    flex-direction: column;\n    gap: 2px;\n  }\n\n  .item-title {\n    font-size: 14px;\n    font-weight: 500;\n    color: rgba(255, 255, 255, 0.9);\n  }\n\n  .item-desc {\n    font-size: 12px;\n    line-height: 1.5;\n    color: rgba(255, 255, 255, 0.65);\n  }\n  \n  .item-single {\n    font-size: 14px;\n    color: #e5e7eb;\n  }\n\n  .ai-segments-section,\n  .original-subtitles-section,\n  .ai-segments-in-summary {\n    margin-bottom: 12px;\n  }\n  \n  .summary-panel-container {\n    height: auto;\n    overflow-y: visible;\n    overflow-x: hidden;\n    padding: 12px;\n  }\n  \n  /* 滚动条样式已移到 .subtitle-content */\n  \n  .ai-segments-in-summary {\n    background: #2a2a2a;\n    border-radius: 8px;\n    padding: 8px;\n  }\n\n  .segments-header {\n    font-size: 13px;\n    font-weight: 600;\n    color: rgba(255, 255, 255, 0.7);\n    padding: 8px 12px;\n    margin-bottom: 8px;\n    background: rgba(0, 0, 0, 0.2);\n    border-radius: 8px;\n    text-transform: uppercase;\n    letter-spacing: 0.5px;\n  }\n\n  .segments-divider {\n    height: 1px;\n    background: rgba(255, 255, 255, 0.1);\n    margin: 16px 0;\n  }\n\n  .ai-summary-main {\n    padding-top: 8px;\n  }\n\n  .ai-summary-empty {\n    padding: 60px 20px;\n    text-align: center;\n    color: rgba(255, 255, 255, 0.4);\n    font-size: 14px;\n  }\n\n  .ai-summary-content {\n    color: #e5e7eb;\n    font-size: 14px;\n    line-height: 1.7;\n    word-wrap: break-word;\n    overflow-wrap: break-word;\n    word-break: break-word;\n    white-space: normal;\n    max-width: 100%;\n  }\n\n  .ai-summary-loading {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    padding: 60px 20px;\n    color: rgba(255, 255, 255, 0.6);\n  }\n\n  .loading-spinner {\n    width: 40px;\n    height: 40px;\n    border: 3px solid rgba(254, 235, 234, 0.1);\n    border-top-color: #feebea;\n    border-radius: 50%;\n    animation: spin 1s linear infinite;\n    margin-bottom: 16px;\n  }\n\n  @keyframes spin {\n    to { transform: rotate(360deg); }\n  }\n\n  /* ==================== Markdown样式 ==================== */\n  .ai-summary-content h1,\n  .ai-summary-content h2,\n  .ai-summary-content h3 {\n    color: #fff;\n    margin-top: 12px;\n    margin-bottom: 8px;\n    font-weight: 700;\n    word-wrap: break-word;\n    overflow-wrap: break-word;\n  }\n\n  .ai-summary-content h1 { font-size: 17px; }\n  .ai-summary-content h2 { font-size: 16px; }\n  .ai-summary-content h3 { font-size: 15px; }\n\n  .ai-summary-content ul,\n  .ai-summary-content ol {\n    margin: 8px 0;\n    padding-left: 20px;\n    max-width: 100%;\n  }\n\n  .ai-summary-content li {\n    margin: 4px 0;\n    word-wrap: break-word;\n    overflow-wrap: break-word;\n    word-break: break-word;\n  }\n\n  .ai-summary-content p {\n    margin: 8px 0;\n    word-wrap: break-word;\n    overflow-wrap: break-word;\n    word-break: break-word;\n    white-space: normal;\n  }\n\n  .ai-summary-content code {\n    background: rgba(255, 255, 255, 0.1);\n    color: #feebea;\n    padding: 3px 6px;\n    border-radius: 4px;\n    font-family: 'Courier New', monospace;\n    font-size: 13px;\n    border: 1px solid rgba(254, 235, 234, 0.2);\n  }\n\n  .ai-summary-content pre {\n    background: rgba(0, 0, 0, 0.5);\n    padding: 12px;\n    border-radius: 8px;\n    overflow-x: auto;\n    margin: 10px 0;\n    border: 1px solid rgba(254, 235, 234, 0.2);\n    white-space: pre-wrap;\n    word-wrap: break-word;\n    max-width: 100%;\n  }\n\n  .ai-summary-content pre code {\n    background-color: transparent;\n    padding: 0;\n    border: none;\n    white-space: pre-wrap;\n    word-wrap: break-word;\n  }\n\n  .ai-summary-content blockquote {\n    border-left: 4px solid #feebea;\n    background: rgba(254, 235, 234, 0.1);\n    padding: 12px;\n    padding-left: 16px;\n    margin: 10px 0;\n    border-radius: 4px;\n    word-wrap: break-word;\n    overflow-wrap: break-word;\n  }\n\n  .ai-summary-content strong {\n    color: #fff;\n    font-weight: 700;\n  }\n\n  .ai-summary-content a {\n    color: #feebea;\n    text-decoration: underline;\n    font-weight: 600;\n  }\n  \n  .ai-summary-content a:hover {\n    color: #fff;\n  }\n\n  /* ==================== 配置模态框样式 ==================== */\n  .config-modal {\n    position: fixed;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    background-color: rgba(0, 0, 0, 0.7);\n    z-index: ${Q.AI_MODAL};\n    display: none;\n    align-items: center;\n    justify-content: center;\n  }\n\n  .config-modal.show {\n    display: flex;\n  }\n  \n  /* 配置模态框overlay样式(用于快捷键配置等) */\n  .config-modal-overlay {\n    position: fixed;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    background-color: rgba(0, 0, 0, 0.7);\n    z-index: ${Q.AI_MODAL};\n    display: none;\n    align-items: center;\n    justify-content: center;\n  }\n  \n  .config-modal-overlay.show {\n    display: flex;\n  }\n  \n  /* 快捷键配置模态框 */\n  #shortcut-config-modal {\n    background: rgba(0, 0, 0, 0.85);\n    backdrop-filter: blur(12px);\n    border-radius: 16px;\n    padding: 0;\n    width: 600px;\n    max-width: 90%;\n    max-height: 85vh;\n    overflow: hidden;\n    box-shadow: 0 20px 60px rgba(0,0,0,0.5);\n    color: #fff;\n    display: flex;\n    flex-direction: column;\n    border: 1px solid rgba(254, 235, 234, 0.2);\n  }\n\n  .config-modal-content {\n    background: rgba(0, 0, 0, 0.85);\n    backdrop-filter: blur(12px);\n    border-radius: 16px;\n    padding: 0;\n    width: 700px;\n    max-width: 90%;\n    max-height: 85vh;\n    overflow: hidden;\n    box-shadow: 0 20px 60px rgba(0,0,0,0.5);\n    color: #fff;\n    display: flex;\n    flex-direction: column;\n    border: 1px solid rgba(254, 235, 234, 0.2);\n  }\n\n  .config-modal-header {\n    padding: 24px;\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    background: rgba(254, 235, 234, 0.15);\n    border-bottom: 1px solid rgba(254, 235, 234, 0.2);\n    border-radius: 16px 16px 0 0;\n  }\n  \n  .config-modal-title {\n    font-size: 20px;\n    font-weight: 700;\n    color: white;\n  }\n  \n  .config-modal-close {\n    width: 32px;\n    height: 32px;\n    border-radius: 50%;\n    background: transparent;\n    border: none;\n    color: rgba(255, 255, 255, 0.7);\n    font-size: 24px;\n    cursor: pointer;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    transition: all 0.2s;\n  }\n  \n  .config-modal-close:hover {\n    background: rgba(255, 255, 255, 0.1);\n    color: rgba(255, 255, 255, 0.9);\n  }\n  \n  .config-modal-body {\n    flex: 1;\n    overflow-y: auto;\n    padding: 30px;\n    background-color: transparent;\n  }\n\n  .config-field {\n    margin-bottom: 20px;\n  }\n\n  .config-field label {\n    display: block;\n    margin-bottom: 10px;\n    font-weight: 600;\n    color: #e5e7eb;\n    font-size: 14px;\n    display: flex;\n    align-items: center;\n    gap: 6px;\n  }\n  \n  .config-field label::before {\n    content: '•';\n    color: #feebea;\n    font-size: 18px;\n    font-weight: bold;\n  }\n\n  .config-field input,\n  .config-field textarea {\n    width: 100%;\n    padding: 12px 14px;\n    border: 1px solid rgba(254, 235, 234, 0.3);\n    border-radius: 10px;\n    font-size: 14px;\n    box-sizing: border-box;\n    transition: all 0.2s;\n    background: rgba(255, 255, 255, 0.1);\n    color: #fff;\n  }\n\n  .config-field input:hover,\n  .config-field textarea:hover {\n    background: rgba(255, 255, 255, 0.12);\n    border-color: rgba(254, 235, 234, 0.5);\n  }\n\n  .config-field input:focus,\n  .config-field textarea:focus {\n    outline: none;\n    border-color: #feebea;\n    box-shadow: 0 0 0 3px rgba(254, 235, 234, 0.15);\n    background: rgba(255, 255, 255, 0.15);\n  }\n\n  .config-field input::placeholder,\n  .config-field textarea::placeholder {\n    color: rgba(255, 255, 255, 0.5);\n  }\n\n  .config-field textarea {\n    font-family: inherit;\n    resize: vertical;\n    min-height: 120px;\n    line-height: 1.6;\n  }\n  \n  .config-field input[type="checkbox"] {\n    width: auto;\n    margin-right: 8px;\n    cursor: pointer;\n  }\n\n  .config-help {\n    font-size: 12px;\n    color: rgba(255, 255, 255, 0.6);\n    margin-top: 5px;\n  }\n\n  .config-help a {\n    color: #feebea;\n    text-decoration: underline;\n  }\n\n  .config-help code {\n    background-color: rgba(255, 255, 255, 0.1);\n    padding: 2px 6px;\n    border-radius: 3px;\n    font-family: 'Courier New', monospace;\n    font-size: 11px;\n    color: #fff;\n  }\n\n  .config-help strong {\n    color: #feebea;\n  }\n\n  .config-footer {\n    display: flex;\n    gap: 12px;\n    justify-content: flex-end;\n    padding: 20px 30px;\n    background-color: rgba(0, 0, 0, 0.3);\n    border-top: 1px solid rgba(254, 235, 234, 0.2);\n    border-radius: 0 0 16px 16px;\n  }\n\n  .config-btn {\n    padding: 12px 24px;\n    border: none;\n    border-radius: 10px;\n    font-size: 14px;\n    font-weight: 600;\n    cursor: pointer;\n    transition: all 0.2s;\n    position: relative;\n    overflow: hidden;\n  }\n\n  .config-btn::before {\n    content: '';\n    position: absolute;\n    top: 50%;\n    left: 50%;\n    width: 0;\n    height: 0;\n    border-radius: 50%;\n    background: rgba(255, 255, 255, 0.3);\n    transform: translate(-50%, -50%);\n    transition: width 0.6s, height 0.6s;\n  }\n\n  .config-btn:hover::before {\n    width: 300px;\n    height: 300px;\n  }\n\n  .config-btn-primary {\n    background: linear-gradient(135deg, #feebea 0%, #2d2d2d 100%);\n    color: #fff;\n    box-shadow: 0 4px 12px rgba(254, 235, 234, 0.3);\n  }\n\n  .config-btn-primary:hover {\n    transform: translateY(-2px);\n    box-shadow: 0 6px 20px rgba(254, 235, 234, 0.4);\n  }\n\n  .config-btn-primary:active {\n    transform: translateY(0);\n  }\n\n  .config-btn-secondary {\n    background-color: #f3f4f6;\n    color: #6b7280;\n    border: 2px solid #e5e7eb;\n  }\n\n  .config-btn-secondary:hover {\n    background-color: #e5e7eb;\n    color: #374151;\n    border-color: #d1d5db;\n  }\n\n  .config-btn-danger {\n    background-color: #fee2e2;\n    color: #dc2626;\n    border: 2px solid #fecaca;\n  }\n\n  .config-btn-danger:hover {\n    background-color: #dc2626;\n    color: white;\n    border-color: #dc2626;\n  }\n\n  .config-status {\n    padding: 8px 12px;\n    border-radius: 6px;\n    font-size: 12px;\n    margin-top: 10px;\n  }\n\n  .config-status.success {\n    background-color: #d4edda;\n    color: #155724;\n  }\n\n  .config-status.error {\n    background-color: #f8d7da;\n    color: #721c24;\n  }\n\n  /* ==================== AI配置列表样式 ==================== */\n  .ai-config-list {\n    margin-bottom: 25px;\n    display: grid;\n    grid-template-columns: repeat(2, 1fr);\n    gap: 10px;\n  }\n\n  .ai-config-item {\n    padding: 10px 14px;\n    border: 1px solid rgba(254, 235, 234, 0.3);\n    border-radius: 10px;\n    margin-bottom: 0;\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    cursor: pointer;\n    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n    background: rgba(255, 255, 255, 0.05);\n    position: relative;\n    overflow: hidden;\n  }\n\n  .ai-config-item::before {\n    content: '';\n    position: absolute;\n    left: 0;\n    top: 0;\n    height: 100%;\n    width: 4px;\n    background: linear-gradient(135deg, #feebea 0%, #ffdbdb 100%);\n    transform: scaleY(0);\n    transition: transform 0.3s ease;\n  }\n\n  .ai-config-item:hover {\n    background: rgba(254, 235, 234, 0.15);\n    border-color: #feebea;\n    transform: translateX(4px);\n    box-shadow: 0 4px 12px rgba(254, 235, 234, 0.2);\n  }\n\n  .ai-config-item:hover::before {\n    transform: scaleY(1);\n  }\n\n  .ai-config-item.selected {\n    border-color: #feebea;\n    background: rgba(254, 235, 234, 0.2);\n    box-shadow: 0 4px 16px rgba(254, 235, 234, 0.3);\n  }\n\n  .ai-config-item.selected::before {\n    transform: scaleY(1);\n    width: 4px;\n  }\n\n  .ai-config-item-name {\n    font-weight: 600;\n    font-size: 14px;\n    color: #e5e7eb;\n  }\n\n  .ai-config-item.selected .ai-config-item-name {\n    color: #fff;\n    font-weight: 700;\n  }\n\n  .ai-config-item-actions {\n    display: flex;\n    gap: 8px;\n    z-index: 1;\n  }\n\n  .ai-config-btn-small {\n    padding: 4px 12px;\n    font-size: 12px;\n    border: none;\n    border-radius: 6px;\n    cursor: pointer;\n    transition: all 0.2s;\n    font-weight: 500;\n  }\n\n  .ai-config-btn-small:hover {\n    transform: translateY(-1px);\n    box-shadow: 0 2px 8px rgba(0,0,0,0.15);\n  }\n\n  .ai-config-btn-small.config-btn-primary {\n    background: linear-gradient(135deg, #feebea 0%, #2d2d2d 100%);\n    color: white;\n  }\n\n  .ai-config-btn-small.config-btn-secondary {\n    background-color: #f3f4f6;\n    color: #6b7280;\n  }\n\n  .ai-config-btn-small.config-btn-secondary:hover {\n    background-color: #fee2e2;\n    color: #dc2626;\n  }\n\n  .ai-config-form {\n    border-top: 1px solid rgba(254, 235, 234, 0.2);\n    padding-top: 25px;\n    margin-top: 10px;\n    background: rgba(0, 0, 0, 0.3);\n    padding: 25px;\n    border-radius: 12px;\n    border: 1px solid rgba(254, 235, 234, 0.2);\n  }\n\n  .ai-config-form.hidden {\n    display: none;\n  }\n\n  .ai-config-form .config-field {\n    margin-bottom: 20px;\n  }\n\n  /* ==================== 模型选择器样式 ==================== */\n  .model-select-wrapper {\n    margin-top: 8px;\n    position: relative;\n  }\n\n  .model-search-input {\n    width: 100%;\n    padding: 10px;\n    border: 1px solid rgba(254, 235, 234, 0.3);\n    border-radius: 6px;\n    font-size: 14px;\n    box-sizing: border-box;\n    margin-bottom: 8px;\n    background: rgba(255, 255, 255, 0.1);\n    color: #fff;\n  }\n\n  .model-search-input:focus {\n    outline: none;\n    border-color: #feebea;\n    box-shadow: 0 0 0 3px rgba(254, 235, 234, 0.15);\n    background: rgba(255, 255, 255, 0.15);\n  }\n\n  .model-search-input::placeholder {\n    color: rgba(255, 255, 255, 0.5);\n  }\n\n  .model-select-wrapper select {\n    width: 100%;\n    padding: 10px;\n    border: 1px solid rgba(254, 235, 234, 0.3);\n    border-radius: 6px;\n    font-size: 14px;\n    box-sizing: border-box;\n    max-height: 200px;\n    background: rgba(255, 255, 255, 0.1);\n    color: #fff;\n  }\n\n  .model-select-wrapper select option {\n    padding: 8px;\n    background: rgba(0, 0, 0, 0.9);\n    color: #fff;\n  }\n\n  .model-count-badge {\n    display: inline-block;\n    background: #feebea;\n    color: #1a1a1a;\n    padding: 2px 8px;\n    border-radius: 12px;\n    font-size: 12px;\n    margin-left: 8px;\n    font-weight: 600;\n  }\n\n  .model-field-with-button {\n    display: flex;\n    gap: 8px;\n    align-items: center;\n  }\n\n  .model-field-with-button input {\n    flex: 1;\n  }\n\n  .fetch-models-btn {\n    padding: 12px 20px;\n    font-size: 14px;\n    font-weight: 600;\n    border: none;\n    border-radius: 10px;\n    background: linear-gradient(135deg, #feebea 0%, #2d2d2d 100%);\n    color: white;\n    cursor: pointer;\n    white-space: nowrap;\n    transition: all 0.2s;\n    box-shadow: 0 2px 8px rgba(254, 235, 234, 0.3);\n  }\n\n  .fetch-models-btn:hover {\n    transform: translateY(-2px);\n    box-shadow: 0 4px 12px rgba(254, 235, 234, 0.4);\n  }\n\n  .fetch-models-btn:active {\n    transform: translateY(0);\n  }\n\n  .fetch-models-btn:disabled {\n    opacity: 0.6;\n    cursor: not-allowed;\n    transform: none !important;\n    box-shadow: none;\n  }\n\n  /* ==================== 速度控制样式 ==================== */\n  .speed-control-section {\n    padding: 12px;\n    margin-bottom: 15px;\n    background: linear-gradient(135deg, #fff5f5 0%, #ffe5e5 100%);\n    border-radius: 12px;\n    border: 2px solid rgba(254, 235, 234, 0.5);\n  }\n\n  .speed-control-header {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    margin-bottom: 10px;\n    padding-bottom: 8px;\n    border-bottom: 2px solid rgba(254, 235, 234, 0.5);\n  }\n\n  .speed-control-title {\n    font-size: 14px;\n    font-weight: 700;\n    color: #2d2d2d;\n  }\n\n  .speed-control-display {\n    font-size: 16px;\n    font-weight: 700;\n    color: #1a1a1a;\n    font-family: monospace;\n  }\n\n  .speed-control-buttons {\n    display: flex;\n    gap: 8px;\n    margin-bottom: 10px;\n  }\n\n  .speed-btn {\n    flex: 1;\n    padding: 8px;\n    border: 2px solid #e5e7eb;\n    border-radius: 8px;\n    background: white;\n    color: #1a1a1a;\n    cursor: pointer;\n    font-size: 14px;\n    font-weight: 600;\n    transition: all 0.2s;\n  }\n\n  .speed-btn:hover {\n    background: #feebea;\n    border-color: #feebea;\n    transform: translateY(-1px);\n  }\n\n  .speed-btn-small {\n    flex: 0 0 40px;\n    font-size: 18px;\n  }\n\n  .speed-control-advanced {\n    margin-top: 8px;\n  }\n\n  .speed-toggle-volume-btn {\n    width: 100%;\n    padding: 8px;\n    border: 2px solid #e5e7eb;\n    border-radius: 8px;\n    background: white;\n    color: #6b7280;\n    cursor: pointer;\n    font-size: 12px;\n    transition: all 0.2s;\n  }\n\n  .speed-toggle-volume-btn:hover {\n    background: #fff5f5;\n    border-color: #ffe5e5;\n  }\n\n  /* ==================== 快捷键配置面板样式 ==================== */\n\n  .shortcut-item {\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n    padding: 12px;\n    background: rgba(255, 255, 255, 0.05);\n    border-radius: 8px;\n    transition: all 0.2s;\n  }\n\n  .shortcut-item:hover {\n    background: rgba(255, 255, 255, 0.08);\n  }\n\n  .shortcut-description {\n    flex: 1;\n    font-size: 14px;\n    color: rgba(255, 255, 255, 0.9);\n  }\n\n  .shortcut-controls {\n    display: flex;\n    gap: 8px;\n    align-items: center;\n  }\n\n  .shortcut-input {\n    padding: 6px 12px;\n    border: 1px solid rgba(254, 235, 234, 0.3);\n    border-radius: 6px;\n    font-size: 13px;\n    min-width: 180px;\n    text-align: center;\n    background: rgba(255, 255, 255, 0.1);\n    color: #fff;\n    cursor: pointer;\n    transition: all 0.2s;\n  }\n\n  .shortcut-input:hover {\n    background: rgba(255, 255, 255, 0.15);\n    border-color: rgba(254, 235, 234, 0.5);\n  }\n\n  .shortcut-input.recording,\n  .shortcut-input.capturing {\n    background: rgba(254, 235, 234, 0.2);\n    border-color: #feebea;\n    animation: pulse 1.5s infinite;\n  }\n\n  @keyframes pulse {\n    0% { box-shadow: 0 0 0 0 rgba(254, 235, 234, 0.4); }\n    50% { box-shadow: 0 0 0 8px rgba(254, 235, 234, 0); }\n    100% { box-shadow: 0 0 0 0 rgba(254, 235, 234, 0); }\n  }\n\n  .shortcut-reset-btn {\n    padding: 6px 12px;\n    background: rgba(255, 255, 255, 0.1);\n    color: rgba(255, 255, 255, 0.7);\n    border: 1px solid rgba(255, 255, 255, 0.2);\n    border-radius: 6px;\n    font-size: 12px;\n    cursor: pointer;\n    transition: all 0.2s;\n  }\n\n  .shortcut-reset-btn:hover {\n    background: rgba(255, 255, 255, 0.15);\n    color: rgba(255, 255, 255, 0.9);\n  }\n\n  /* 长按和双击模式按钮 */\n  .shortcut-mode-btn {\n    padding: 4px 8px;\n    background: rgba(255, 255, 255, 0.08);\n    color: rgba(255, 255, 255, 0.6);\n    border: 1px solid rgba(255, 255, 255, 0.15);\n    border-radius: 4px;\n    font-size: 11px;\n    cursor: pointer;\n    transition: all 0.2s;\n    white-space: nowrap;\n  }\n\n  .shortcut-mode-btn:hover {\n    background: rgba(255, 255, 255, 0.12);\n    color: rgba(255, 255, 255, 0.8);\n  }\n\n  .shortcut-mode-btn.active {\n    background: rgba(254, 235, 234, 0.3);\n    color: #feebea;\n    border-color: #feebea;\n    box-shadow: 0 0 8px rgba(254, 235, 234, 0.3);\n  }\n\n  .shortcut-config-footer {\n    margin-top: 16px;\n    padding-top: 16px;\n    border-top: 1px solid rgba(255, 255, 255, 0.1);\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n  }\n  \n  .shortcut-tips {\n    font-size: 12px;\n    color: rgba(255, 255, 255, 0.6);\n    display: flex;\n    align-items: center;\n    gap: 4px;\n  }\n\n  .shortcuts-icon {\n    cursor: pointer;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    padding: 4px;\n    transition: all 0.2s;\n  }\n\n  .shortcuts-icon:hover {\n    transform: rotate(45deg);\n    filter: drop-shadow(0 0 8px rgba(254, 235, 234, 0.5));\n  }\n\n  /* ==================== 笔记面板样式 ==================== */\n  .notes-panel {\n    position: fixed;\n    right: 20px;\n    top: 50%;\n    transform: translateY(-50%);\n    width: 400px;\n    min-width: 350px;\n    max-width: 600px;\n    height: 600px;\n    min-height: 400px;\n    max-height: 80vh;\n    background: rgba(32, 32, 38, 0.95);\n    backdrop-filter: blur(20px) saturate(200%);\n    border-radius: 16px;\n    box-shadow: \n      0 20px 60px -8px rgba(255, 105, 180, 0.2),\n      0 8px 24px -4px rgba(0, 0, 0, 0.3),\n      inset 0 1px 0 rgba(255, 255, 255, 0.08),\n      inset 0 -1px 0 rgba(0, 0, 0, 0.2);\n    resize: both;\n    overflow: hidden;  /* 改为 hidden,只允许内部 body 滚动 */\n    z-index: ${Q.MODAL};\n    display: none;\n    flex-direction: column;\n  }\n\n  .notes-panel.show {\n    display: flex;\n  }\n\n  .notes-panel-content {\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n  }\n\n  .notes-panel-header {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    padding: 20px;\n    border-bottom: 1px solid rgba(254, 235, 234, 0.2);\n    background: rgba(254, 235, 234, 0.15);\n  }\n\n  .notes-panel-header h2 {\n    margin: 0;\n    font-size: 20px;\n    font-weight: 600;\n    color: #fff;\n  }\n\n  .notes-panel-close {\n    background: none;\n    border: none;\n    font-size: 24px;\n    color: rgba(255, 255, 255, 0.6);\n    cursor: pointer;\n    padding: 0;\n    width: 30px;\n    height: 30px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    border-radius: 50%;\n    transition: all 0.2s;\n  }\n\n  .notes-panel-close:hover {\n    background: rgba(255,255,255,0.1);\n    color: #fff;\n  }\n\n  /* 笔记筛选器样式 */\n  .notes-filters {\n    display: flex;\n    gap: 20px;\n    padding: 12px 20px;\n    background: rgba(0, 0, 0, 0.2);\n    border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n  }\n\n  .filter-checkbox {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    cursor: pointer;\n    user-select: none;\n  }\n\n  .filter-checkbox input[type="checkbox"] {\n    width: 16px;\n    height: 16px;\n    cursor: pointer;\n    accent-color: #feebea;\n  }\n\n  .filter-checkbox span {\n    color: rgba(255, 255, 255, 0.8);\n    font-size: 14px;\n  }\n\n  .filter-checkbox:hover span {\n    color: rgba(255, 255, 255, 0.95);\n  }\n\n  .notes-panel-body {\n    flex: 1;\n    overflow-y: auto;\n    padding: 20px;\n  }\n\n  .notes-panel-body::-webkit-scrollbar {\n    width: 6px;\n  }\n\n  .notes-panel-body::-webkit-scrollbar-thumb {\n    background-color: rgba(254, 235, 234, 0.4);\n    border-radius: 3px;\n  }\n  \n  .notes-panel-body::-webkit-scrollbar-thumb:hover {\n    background-color: rgba(254, 235, 234, 0.6);\n  }\n  \n  .notes-panel-body::-webkit-scrollbar-track {\n    background-color: rgba(255, 255, 255, 0.05);\n  }\n\n  .notes-empty-state {\n    text-align: center;\n    padding: 60px 20px;\n    color: rgba(255, 255, 255, 0.6);\n  }\n\n  .notes-empty-icon {\n    font-size: 48px;\n    margin-bottom: 16px;\n  }\n\n  .notes-empty-hint {\n    font-size: 14px;\n    margin-top: 8px;\n  }\n\n  .note-group {\n    margin-bottom: 24px;\n  }\n\n  .note-group-header {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    margin-bottom: 12px;\n    padding-bottom: 8px;\n    border-bottom: 1px solid rgba(254, 235, 234, 0.2);\n  }\n\n  .note-group-title {\n    font-size: 14px;\n    font-weight: 600;\n    color: #e5e7eb;\n  }\n\n  .note-group-actions {\n    display: flex;\n    gap: 8px;\n  }\n\n  .note-group-copy-btn,\n  .note-group-delete-btn {\n    padding: 4px 12px;\n    border-radius: 4px;\n    cursor: pointer;\n    font-size: 12px;\n    transition: all 0.2s;\n    border: 1px solid;\n  }\n\n  .note-group-copy-btn {\n    background: none;\n    border-color: #4A90E2;\n    color: #4A90E2;\n  }\n\n  .note-group-copy-btn:hover {\n    background: #4A90E2;\n    color: white;\n  }\n\n  .note-group-delete-btn {\n    background: none;\n    border-color: #e74c3c;\n    color: #e74c3c;\n  }\n\n  .note-group-delete-btn:hover {\n    background: #e74c3c;\n    color: white;\n  }\n\n  .note-item {\n    background: rgba(255, 255, 255, 0.05);\n    padding: 12px;\n    border-radius: 8px;\n    margin-bottom: 8px;\n    transition: background-color 0.2s;\n    border: 1px solid rgba(254, 235, 234, 0.1);\n  }\n\n  .note-item:hover {\n    background: rgba(255, 255, 255, 0.1);\n    border-color: rgba(254, 235, 234, 0.3);\n  }\n\n  .note-content {\n    color: #e5e7eb;\n    font-size: 14px;\n    line-height: 1.6;\n    margin-bottom: 8px;\n    word-break: break-word;\n    white-space: pre-wrap;\n  }\n\n  .note-footer {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n  }\n\n  .note-time {\n    font-size: 12px;\n    color: rgba(255, 255, 255, 0.5);\n  }\n\n  .note-actions {\n    display: flex;\n    gap: 8px;\n  }\n\n  .note-copy-btn,\n  .note-delete-btn {\n    background: none;\n    border: none;\n    cursor: pointer;\n    font-size: 12px;\n    padding: 4px 8px;\n    transition: color 0.2s;\n  }\n\n  .note-copy-btn {\n    color: #4A90E2;\n  }\n\n  .note-copy-btn:hover {\n    color: #357ABD;\n  }\n\n  .note-delete-btn {\n    color: #e74c3c;\n  }\n\n  .note-delete-btn:hover {\n    color: #c0392b;\n  }\n\n  /* ==================== AI 总结笔记样式 ==================== */\n  .note-item-ai-summary {\n    background: rgba(254, 235, 234, 0.08);\n    border: 1px solid rgba(254, 235, 234, 0.2);\n    padding: 16px;\n  }\n\n  .note-item-ai-summary:hover {\n    background: rgba(254, 235, 234, 0.12);\n    border-color: rgba(254, 235, 234, 0.4);\n  }\n\n  .ai-summary-content-wrapper {\n    display: flex;\n    flex-direction: column;\n    gap: 16px;\n    margin-bottom: 12px;\n  }\n\n  .ai-summary-section {\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n  }\n\n  .ai-summary-title {\n    font-size: 13px;\n    font-weight: 600;\n    color: #feebea;\n    text-transform: uppercase;\n    letter-spacing: 0.5px;\n  }\n\n  .ai-summary-content {\n    font-size: 13px;\n    color: rgba(255, 255, 255, 0.8);\n    line-height: 1.6;\n    padding: 8px 12px;\n    background: rgba(255, 255, 255, 0.05);\n    border-radius: 4px;\n    border-left: 3px solid rgba(254, 235, 234, 0.3);\n  }\n\n  .ai-summary-segments {\n    display: flex;\n    flex-direction: column;\n    gap: 12px;\n  }\n\n  .segment-item {\n    padding: 12px;\n    background: rgba(255, 255, 255, 0.03);\n    border-radius: 4px;\n    border: 1px solid rgba(254, 235, 234, 0.1);\n    transition: all 0.2s;\n  }\n\n  .segment-item:hover {\n    background: rgba(255, 255, 255, 0.06);\n    border-color: rgba(254, 235, 234, 0.2);\n  }\n\n  .segment-header {\n    display: flex;\n    gap: 8px;\n    margin-bottom: 8px;\n    align-items: center;\n  }\n\n  .segment-time {\n    font-size: 12px;\n    font-weight: 600;\n    color: #feebea;\n    flex-shrink: 0;\n  }\n\n  .segment-title {\n    font-size: 13px;\n    font-weight: 500;\n    color: rgba(255, 255, 255, 0.9);\n    flex: 1;\n  }\n\n  .segment-summary {\n    font-size: 12px;\n    color: rgba(255, 255, 255, 0.7);\n    line-height: 1.5;\n    margin-bottom: 8px;\n  }\n\n  .segment-screenshot {\n    margin-top: 8px;\n    padding-top: 8px;\n    border-top: 1px solid rgba(254, 235, 234, 0.1);\n  }\n\n  .segment-screenshot img {\n    max-width: 100%;\n    border-radius: 4px;\n    display: block;\n  }\n\n  .screenshot-time {\n    font-size: 11px;\n    color: rgba(255, 255, 255, 0.5);\n    margin-top: 4px;\n  }\n\n  /* ==================== 笔记选择保存点样式 ==================== */\n  #note-saver-blue-dot {\n    position: absolute;\n    cursor: pointer;\n    z-index: 2147483647; /* Maximum z-index */\n    display: none;\n    transition: transform 0.2s, filter 0.2s;\n    pointer-events: auto !important;\n    width: 24px;\n    height: 24px;\n  }\n\n  #note-saver-blue-dot:hover {\n    transform: scale(1.15);\n    filter: drop-shadow(0 2px 4px rgba(254, 235, 234, 0.5));\n  }\n\n  /* ==================== 字幕项保存按钮样式 ==================== */\n  .save-subtitle-note-btn {\n    background: linear-gradient(135deg, #feebea 0%, #2d2d2d 100%);\n    color: white;\n    border: none;\n    padding: 2px 8px;\n    border-radius: 4px;\n    font-size: 11px;\n    cursor: pointer;\n    transition: all 0.2s;\n    opacity: 0;\n    flex-shrink: 0;\n    margin-left: auto;\n  }\n\n  .subtitle-item:hover .save-subtitle-note-btn {\n    opacity: 1;\n  }\n\n  .save-subtitle-note-btn:hover {\n    transform: scale(1.05);\n  }\n  \n  .subtitle-follow-btn {\n    position: absolute;\n    bottom: 10px;\n    left: 50%;\n    transform: translateX(-50%);\n    padding: 6px 12px;\n    background: rgba(255, 105, 180, 0.8);\n    color: white;\n    border: none;\n    border-radius: 16px;\n    font-size: 12px;\n    cursor: pointer;\n    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n    transition: all 0.3s;\n    z-index: 10;\n  }\n\n  .subtitle-follow-btn:hover {\n    background: rgba(255, 105, 180, 1);\n    transform: translateX(-50%) translateY(-2px);\n    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);\n  }\n\n  /* ==================== 快捷键配置样式 ==================== */\n  .shortcut-list {\n    display: flex;\n    flex-direction: column;\n    gap: 12px;\n  }\n\n  .shortcut-item {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    padding: 12px;\n    background: rgba(255, 255, 255, 0.05);\n    border-radius: 8px;\n    border: 1px solid rgba(254, 235, 234, 0.2);\n  }\n\n  .shortcut-label {\n    font-size: 14px;\n    color: #e5e7eb;\n    font-weight: 500;\n  }\n\n  .shortcut-input-wrapper {\n    display: flex;\n    gap: 8px;\n    align-items: center;\n  }\n\n  .shortcut-input {\n    padding: 6px 12px;\n    border: 1px solid rgba(254, 235, 234, 0.3);\n    border-radius: 6px;\n    font-size: 13px;\n    min-width: 180px;\n    text-align: center;\n    background: rgba(255, 255, 255, 0.1);\n    color: #fff;\n    cursor: pointer;\n    transition: all 0.2s;\n  }\n\n  .shortcut-input:focus {\n    outline: none;\n    border-color: #feebea;\n    box-shadow: 0 0 0 3px rgba(254, 235, 234, 0.15);\n    background: rgba(255, 255, 255, 0.15);\n  }\n\n  .shortcut-input.capturing {\n    border-color: #feebea;\n    background: rgba(254, 235, 234, 0.2);\n    animation: pulse-border 1s infinite;\n  }\n\n  @keyframes pulse-border {\n    0%, 100% {\n      border-color: #feebea;\n      box-shadow: 0 0 0 3px rgba(254, 235, 234, 0.15);\n    }\n    50% {\n      border-color: #ffc9c9;\n      box-shadow: 0 0 0 3px rgba(254, 235, 234, 0.3);\n    }\n  }\n\n  .shortcut-clear-btn {\n    background: none;\n    border: none;\n    color: #999;\n    font-size: 18px;\n    cursor: pointer;\n    width: 24px;\n    height: 24px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    border-radius: 50%;\n    transition: all 0.2s;\n  }\n\n  .shortcut-clear-btn:hover {\n    background: #fee2e2;\n    color: #dc2626;\n  }\n\n  /* ==================== 调整大小手柄样式 ==================== */\n  .subtitle-resize-handle {\n    position: absolute;\n    bottom: 0;\n    right: 0;\n    width: 20px;\n    height: 20px;\n    cursor: nwse-resize;\n    z-index: 10;\n  }\n\n  .subtitle-resize-handle::after {\n    content: '';\n    position: absolute;\n    bottom: 4px;\n    right: 4px;\n    width: 12px;\n    height: 12px;\n    border-right: 3px solid rgba(254, 235, 234, 0.6);\n    border-bottom: 3px solid rgba(254, 235, 234, 0.6);\n    border-radius: 0 0 4px 0;\n  }\n\n  .subtitle-resize-handle:hover::after {\n    border-color: #feebea;\n  }\n\n  .sponsor-switch {\n    position: relative;\n    width: 48px;\n    height: 24px;\n  }\n\n  .sponsor-switch input {\n    opacity: 0;\n    width: 0;\n    height: 0;\n  }\n\n  .sponsor-switch-slider {\n    position: absolute;\n    cursor: pointer;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    background-color: #cbd5e1;\n    transition: 0.3s;\n    border-radius: 24px;\n  }\n\n  .sponsor-switch-slider:before {\n    position: absolute;\n    content: "";\n    height: 18px;\n    width: 18px;\n    left: 3px;\n    bottom: 3px;\n    background-color: white;\n    transition: 0.3s;\n    border-radius: 50%;\n  }\n\n  .sponsor-switch input:checked + .sponsor-switch-slider {\n    background-color: #feebea;\n  }\n\n  .sponsor-switch input:checked + .sponsor-switch-slider:before {\n    transform: translateX(24px);\n  }\n\n  /* ==================== SponsorBlock 标签样式 ==================== */\n  .bili-quality-tag, .bili-ad-tag {\n    display: inline-flex !important;\n    align-items: center;\n    color: white !important;\n    padding: 3px 10px !important;\n    border-radius: 15px !important;\n    margin-right: 6px !important;\n    font-size: 12px !important;\n    animation: badgeSlideIn 0.3s ease-out !important;\n    position: relative;\n    z-index: 2;\n    font-weight: 500;\n    box-shadow: 0 2px 4px rgba(0,0,0,0.2);\n    white-space: nowrap;\n    flex-shrink: 0;\n  }\n  \n  /* 只显示emoji的标签样式 */\n  .bili-quality-tag.emoji-only,\n  .bili-ad-tag.emoji-only {\n    padding: 3px 8px !important;\n    min-width: auto;\n  }\n\n  /* 视频卡片标签位置 */\n  .video-page-card-small .bili-quality-tag,\n  .video-page-card-small .bili-ad-tag,\n  .bili-video-card__wrap .bili-quality-tag,\n  .bili-video-card__wrap .bili-ad-tag {\n    position: absolute;\n    left: 8px;\n    top: 8px;\n    transform: scale(0.9);\n  }\n\n  /* UP主主页视频卡片 */\n  .up-main-video-card .bili-quality-tag,\n  .up-main-video-card .bili-ad-tag,\n  .small-item .bili-quality-tag,\n  .small-item .bili-ad-tag {\n    position: absolute !important;\n    left: 8px !important;\n    top: 8px !important;\n    z-index: 10 !important;\n    transform: scale(0.9);\n  }\n\n  .up-main-video-card .cover-container,\n  .up-main-video-card .cover,\n  .small-item .cover {\n    position: relative !important;\n  }\n\n  /* 多标签容器 */\n  .bili-tags-container {\n    display: flex;\n    flex-wrap: nowrap;\n    gap: 4px;\n    overflow: visible;\n    align-items: center;\n  }\n\n  @keyframes badgeSlideIn {\n    0% { opacity: 0; transform: translateX(-15px) scale(0.9); }\n    100% { opacity: 1; transform: translateX(0) scale(0.9); }\n  }\n\n  /* 跳过提示Toast - 视频右下角,绿色 */\n  .skip-toast {\n    position: absolute;\n    bottom: 60px;\n    right: 20px;\n    background: rgba(0, 212, 0, 0.15);\n    color: #00d400;\n    padding: 8px 16px;\n    border-radius: 4px;\n    font-size: 14px;\n    z-index: 10000;\n    animation: fadeIn 0.3s ease-out;\n    font-weight: 500;\n    backdrop-filter: blur(4px);\n    pointer-events: auto !important;\n    user-select: none;\n  }\n\n  .skip-toast.hiding {\n    animation: fadeOut 0.3s ease-out forwards;\n  }\n\n  /* 手动跳过提示 - 视频右下角 */\n  .skip-prompt {\n    position: absolute;\n    bottom: 80px;\n    right: 20px;\n    background: rgba(0, 0, 0, 0.9);\n    color: white;\n    border-radius: 8px;\n    padding: 12px 16px;\n    box-shadow: 0 4px 12px rgba(0,0,0,0.4);\n    z-index: 10000;\n    min-width: 280px;\n    animation: fadeIn 0.3s ease-out;\n    pointer-events: auto !important;\n    user-select: none;\n  }\n\n  .skip-prompt-header {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    margin-bottom: 10px;\n    font-size: 14px;\n    font-weight: 500;\n  }\n\n  .skip-prompt-icon {\n    width: 20px;\n    height: 20px;\n    flex-shrink: 0;\n  }\n\n  .skip-prompt-icon svg {\n    width: 100%;\n    height: 100%;\n  }\n\n  .skip-prompt-message {\n    flex: 1;\n  }\n\n  .skip-prompt-buttons {\n    display: flex;\n    gap: 8px;\n    justify-content: flex-end;\n  }\n\n  .skip-prompt-btn {\n    padding: 6px 14px;\n    border: none;\n    border-radius: 4px;\n    cursor: pointer;\n    font-size: 13px;\n    transition: all 0.2s;\n  }\n\n  .skip-prompt-btn-primary {\n    background: #00a1d6;\n    color: white;\n  }\n\n  .skip-prompt-btn-primary:hover {\n    background: #0087b3;\n  }\n\n  .skip-prompt-btn-secondary {\n    background: rgba(255, 255, 255, 0.1);\n    color: white;\n  }\n\n  .skip-prompt-btn-secondary:hover {\n    background: rgba(255, 255, 255, 0.2);\n  }\n\n  .skip-prompt-close {\n    background: none;\n    border: none;\n    color: #999;\n    cursor: pointer;\n    font-size: 18px;\n    padding: 0;\n    width: 20px;\n    height: 20px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    margin-left: 8px;\n  }\n\n  .skip-prompt-close:hover {\n    color: white;\n  }\n\n  .skip-prompt.hiding {\n    animation: fadeOut 0.3s ease-out forwards;\n  }\n\n  /* 进度条片段标记 */\n  #sponsorblock-preview-bar {\n    overflow: hidden;\n    padding: 0;\n    margin: 0;\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    z-index: 1;\n    pointer-events: none;\n  }\n\n  .sponsorblock-segment {\n    display: inline-block;\n    height: 100%;\n    position: absolute;\n    min-width: 1px;\n    opacity: 0.7;\n    transition: all 0.2s ease;\n    pointer-events: auto;\n    cursor: pointer;\n  }\n\n  .sponsorblock-segment:hover {\n    opacity: 0.95;\n    transform: scaleY(1.5);\n    z-index: 100;\n  }\n\n  /* 片段详情弹窗 */\n  .segment-details-popup {\n    position: fixed;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n    background: rgba(0, 0, 0, 0.95);\n    color: white;\n    border-radius: 12px;\n    padding: 24px;\n    min-width: 350px;\n    max-width: 500px;\n    box-shadow: 0 8px 32px rgba(0,0,0,0.5);\n    z-index: 10002;\n    animation: popupFadeIn 0.2s ease-out;\n  }\n\n  @keyframes popupFadeIn {\n    from {\n      opacity: 0;\n      transform: translate(-50%, -45%);\n    }\n    to {\n      opacity: 1;\n      transform: translate(-50%, -50%);\n    }\n  }\n\n  .segment-details-header {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    margin-bottom: 16px;\n    padding-bottom: 12px;\n    border-bottom: 1px solid rgba(255,255,255,0.2);\n  }\n\n  .segment-details-title {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    font-size: 18px;\n    font-weight: 500;\n  }\n\n  .segment-details-close {\n    background: none;\n    border: none;\n    color: #999;\n    font-size: 24px;\n    cursor: pointer;\n    padding: 0;\n    width: 32px;\n    height: 32px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    border-radius: 4px;\n    transition: all 0.2s;\n  }\n\n  .segment-details-close:hover {\n    background: rgba(255,255,255,0.1);\n    color: white;\n  }\n\n  .segment-details-content {\n    margin-bottom: 16px;\n  }\n\n  .segment-details-row {\n    display: flex;\n    justify-content: space-between;\n    padding: 8px 0;\n    font-size: 14px;\n  }\n\n  .segment-details-label {\n    color: #999;\n  }\n\n  .segment-details-value {\n    color: white;\n    font-weight: 500;\n  }\n\n  .segment-details-actions {\n    display: flex;\n    gap: 12px;\n    justify-content: flex-end;\n  }\n\n  .segment-details-btn {\n    padding: 8px 16px;\n    border: none;\n    border-radius: 6px;\n    cursor: pointer;\n    font-size: 14px;\n    transition: all 0.2s;\n  }\n\n  .segment-details-btn-primary {\n    background: #00a1d6;\n    color: white;\n  }\n\n  .segment-details-btn-primary:hover {\n    background: #0087b3;\n  }\n\n  .segment-details-btn-secondary {\n    background: rgba(255,255,255,0.1);\n    color: white;\n  }\n\n  .segment-details-btn-secondary:hover {\n    background: rgba(255,255,255,0.2);\n  }\n\n  .segment-details-overlay {\n    position: fixed;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    background: rgba(0,0,0,0.3);\n    z-index: 10001;\n  }\n\n  /* SponsorBlock 设置面板样式 */\n  .sponsor-settings-section {\n    margin-bottom: 24px;\n  }\n\n  .sponsor-settings-section h3 {\n    font-size: 16px;\n    color: #e5e7eb;\n    margin: 0 0 12px 0;\n  }\n\n  .sponsor-checkbox-group {\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n  }\n\n  .sponsor-checkbox-item {\n    display: flex;\n    align-items: center;\n    padding: 8px;\n    border-radius: 6px;\n    transition: background 0.2s;\n  }\n\n  .sponsor-checkbox-item:hover {\n    background: rgba(255, 255, 255, 0.05);\n  }\n\n  .sponsor-checkbox-item input[type="checkbox"] {\n    margin-right: 10px;\n    cursor: pointer;\n    width: 18px;\n    height: 18px;\n  }\n\n  .sponsor-checkbox-item label {\n    cursor: pointer;\n    flex: 1;\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    color: #e5e7eb;\n  }\n\n  .category-color-dot {\n    width: 12px;\n    height: 12px;\n    border-radius: 50%;\n    display: inline-block;\n  }\n\n  .sponsor-switch-item {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    padding: 12px;\n    border-radius: 6px;\n    background: rgba(255, 255, 255, 0.05);\n    border: 1px solid rgba(254, 235, 234, 0.2);\n    margin-bottom: 8px;\n    color: #e5e7eb;\n  }\n`;const ue='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\n    <path d="M3 21L12 12L12.2 6.2L11 5M15 4V2M15 16V14M8 9H10M20 9H22M17.8 11.8L19 13M17.8 6.2L19 5" stroke="#feebea" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>\n    <circle cx="12" cy="12" r="1.5" fill="#feebea"/>\n    <path d="M17 7L12 12L7 7" stroke="#feebea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" opacity="0.5"/>\n  </svg>',pe='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\n    <path d="M12 3V16M12 16L7 11M12 16L17 11" stroke="#feebea" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>\n    <path d="M3 17V19C3 20.1046 3.89543 21 5 21H19C20.1046 21 21 20.1046 21 19V17" stroke="#feebea" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>\n  </svg>',he='<svg viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">\n    <path d="M6.017 4.313l55.333 -4.087c6.797 -0.583 8.543 -0.19 12.817 2.917l17.663 12.443c2.913 2.14 3.883 2.723 3.883 5.053v68.243c0 4.277 -1.553 6.807 -6.99 7.193L24.467 99.967c-4.08 0.193 -6.023 -0.39 -8.16 -3.113L3.3 79.94c-2.333 -3.113 -3.3 -5.443 -3.3 -8.167V11.113c0 -3.497 1.553 -6.413 6.017 -6.8z" fill="#fff"/>\n    <path fill-rule="evenodd" clip-rule="evenodd" d="M61.35 0.227l-55.333 4.087C1.553 4.7 0 7.617 0 11.113v60.66c0 2.724 0.967 5.053 3.3 8.167l13.007 16.913c2.137 2.723 4.08 3.307 8.16 3.113l64.257 -3.89c5.433 -0.387 6.99 -2.917 6.99 -7.193V20.64c0 -2.21 -0.873 -2.847 -3.443 -4.733L74.167 3.143c-4.273 -3.107 -6.02 -3.5 -12.817 -2.917zM25.92 19.523c-5.247 0.353 -6.437 0.433 -9.417 -1.99L8.927 11.507c-0.77 -0.78 -0.383 -1.753 1.557 -1.947l53.193 -3.887c4.467 -0.39 6.793 1.167 8.54 2.527l9.123 6.61c0.39 0.197 1.36 1.36 0.193 1.36l-54.933 3.307 -0.68 0.047zM19.803 88.3V30.367c0 -2.53 0.777 -3.697 3.103 -3.893L86 22.78c2.14 -0.193 3.107 1.167 3.107 3.693v57.547c0 2.53 -0.39 4.67 -3.883 4.863l-60.377 3.5c-3.493 0.193 -5.043 -0.97 -5.043 -4.083zm59.6 -54.827c0.387 1.75 0 3.5 -1.75 3.7l-2.91 0.577v42.773c-2.527 1.36 -4.853 2.137 -6.797 2.137 -3.107 0 -3.883 -0.973 -6.21 -3.887l-19.03 -29.94v28.967l6.02 1.363s0 3.5 -4.857 3.5l-13.39 0.777c-0.39 -0.78 0 -2.723 1.357 -3.11l3.497 -0.97v-38.3L30.48 40.667c-0.39 -1.75 0.58 -4.277 3.3 -4.473l14.367 -0.967 19.8 30.327v-26.83l-5.047 -0.58c-0.39 -2.143 1.163 -3.7 3.103 -3.89l13.4 -0.78z" fill="#000"/>\n  </svg>';function ge(e){if(!e||"string"!=typeof e)return {valid:false,error:"URL不能为空"};if(!e.startsWith("http://")&&!e.startsWith("https://"))return {valid:false,error:"URL必须以 http:// 或 https:// 开头"};try{return new URL(e),{valid:!0,error:null}}catch(t){return {valid:false,error:"URL格式无效"}}}function me(e){return e&&"string"==typeof e?0===e.trim().length?{valid:false,error:"API Key不能为空"}:e.length<10?{valid:false,error:"API Key长度过短,请检查是否完整"}:{valid:true,error:null}:{valid:false,error:"API Key不能为空"}}function be(e){return e?e.bvid&&e.bvid.match(/^BV[1-9A-Za-z]{10}$/)?e.cid?{valid:true,error:null}:{valid:false,error:"CID为空"}:{valid:false,error:"BV号格式错误"}:{valid:false,error:"视频信息为空"}}const fe=new class{constructor(){this.events=new Map,this.modules=new Map,this.subscriptionId=0;}on(e,t,n=null){return this.events.has(e)||this.events.set(e,[]),this.events.get(e).push(t),n&&(this.modules.has(n)||this.modules.set(n,new Set),this.modules.get(n).add({event:e,handler:t})),()=>this.off(e,t)}once(e,t){const n=(...o)=>{t(...o),this.off(e,n);};this.on(e,n);}off(e,t){if(!this.events.has(e))return;const n=this.events.get(e),o=n.indexOf(t);o>-1&&n.splice(o,1),0===n.length&&this.events.delete(e);}emit(e,...t){if(!this.events.has(e))return;const n=[...this.events.get(e)];for(const i of n)try{i(...t);}catch(o){console.error(`[EventBus] 事件 "${e}" 处理出错:`,o);}}clear(){this.events.clear(),this.modules.clear();}clearModule(e){const t=this.modules.get(e);t&&(t.forEach(({event:e,handler:t})=>{this.off(e,t);}),this.modules.delete(e));}listenerCount(e){return this.events.has(e)?this.events.get(e).length:0}getModuleStats(){const e={};return this.modules.forEach((t,n)=>{e[n]=t.size;}),e}};const ye=new class{constructor(){this.debugMode=GM_getValue("debug_mode",false),this.prefix="[BilibiliTools]",this.LOG_LEVELS={TRACE:0,DEBUG:1,INFO:2,SUCCESS:3,WARN:4,ERROR:5},this.currentLevel=this.debugMode?this.LOG_LEVELS.TRACE:this.LOG_LEVELS.WARN;}toggleDebugMode(){return this.debugMode=!this.debugMode,GM_setValue("debug_mode",this.debugMode),this.currentLevel=this.debugMode?this.LOG_LEVELS.TRACE:this.LOG_LEVELS.WARN,console.log(`${this.prefix} 调试模式: ${this.debugMode?"开启":"关闭"}`),this.debugMode}isDebugMode(){return this.debugMode}trace(e,...t){this.currentLevel<=this.LOG_LEVELS.TRACE&&console.log(`🔍 [${e}]`,...t);}debug(e,...t){this.currentLevel<=this.LOG_LEVELS.DEBUG&&console.log(`🐛 [${e}]`,...t);}info(e,...t){this.currentLevel<=this.LOG_LEVELS.INFO&&console.info(`ℹ️ [${e}]`,...t);}success(e,...t){this.currentLevel<=this.LOG_LEVELS.SUCCESS&&console.log(`✅ [${e}]`,...t);}warn(e,...t){this.currentLevel<=this.LOG_LEVELS.WARN&&console.warn(`⚠️ [${e}]`,...t);}error(e,...t){this.currentLevel<=this.LOG_LEVELS.ERROR&&console.error(`❌ [${e}]`,...t);}group(e,t){this.debugMode&&console.group(`[${e}] ${t}`);}groupEnd(){this.debugMode&&console.groupEnd();}table(e,t){this.debugMode&&(console.log(`[${e}] 数据表格:`),console.table(t));}time(e){this.debugMode&&console.time(e);}timeEnd(e){this.debugMode&&console.timeEnd(e);}};const ve=new class{constructor(){this.activeTasks=new Map,this.processedVideos=this._loadProcessedVideos(),this.taskQueue=[],window.addEventListener("beforeunload",()=>{this._saveProcessedVideos();});}_loadProcessedVideos(){try{const e=localStorage.getItem(X);if(e){const t=JSON.parse(e),n=Date.now()-2592e6,o=Object.entries(t).filter(([,e])=>e>n).map(([e])=>e);return new Set(o)}}catch(e){ye.error("TaskManager","加载已处理视频记录失败:",e);}return new Set}_saveProcessedVideos(){try{const e={};this.processedVideos.forEach(t=>{e[t]=Date.now();}),localStorage.setItem(X,JSON.stringify(e));}catch(e){ye.error("TaskManager","保存已处理视频记录失败:",e);}}isVideoProcessed(e){return this.processedVideos.has(e)}markVideoProcessed(e){this.processedVideos.add(e),this._saveProcessedVideos();}clearVideoProcessed(e){this.processedVideos.delete(e),this._saveProcessedVideos();}createTask(e,t,n,o=false){const i=`${e}_${t.bvid}_${Date.now()}`;if(!o&&this.isVideoProcessed(t.bvid))return ye.debug("TaskManager",`视频 ${t.bvid} 已自动处理过,跳过自动任务`),null;const s={id:i,type:e,videoInfo:{...t},status:"pending",createdAt:Date.now(),isManual:o,abortController:new AbortController,result:null,error:null};return this.activeTasks.set(i,s),this._executeTask(s,n),i}async _executeTask(e,t){try{e.status="running",ye.info("TaskManager",`开始执行任务 ${e.id},视频: ${e.videoInfo.bvid}`);const n=await t({videoInfo:e.videoInfo,signal:e.abortController.signal,taskId:e.id});e.result=n,e.status="completed",e.isManual||"ai_summary"!==e.type||this.markVideoProcessed(e.videoInfo.bvid),ye.success("TaskManager",`任务 ${e.id} 完成`),setTimeout(()=>{this.activeTasks.delete(e.id);},6e4);}catch(n){e.error=n,e.status="failed","AbortError"===n.name?ye.info("TaskManager",`任务 ${e.id} 被取消`):ye.error("TaskManager",`任务 ${e.id} 失败:`,n),setTimeout(()=>{this.activeTasks.delete(e.id);},6e4);}}cancelVideoTasks(e){let t=0;return this.activeTasks.forEach((n,o)=>{n.videoInfo.bvid===e&&"running"===n.status&&(n.abortController.abort(),n.status="canceled",t++,ye.info("TaskManager",`取消任务 ${o}`));}),t>0&&ye.info("TaskManager",`已取消视频 ${e} 的 ${t} 个任务`),t}cancelTypeTasks(e){let t=0;return this.activeTasks.forEach((n,o)=>{n.type===e&&"running"===n.status&&(n.abortController.abort(),n.status="canceled",t++);}),t}getVideoTasks(e){const t=[];return this.activeTasks.forEach(n=>{n.videoInfo.bvid===e&&t.push(n);}),t}hasRunningTask(e,t=null){for(const n of this.activeTasks.values())if(n.type===e&&"running"===n.status&&(null===t||n.videoInfo.bvid===t))return  true;return  false}cleanup(){const e=[];this.activeTasks.forEach((t,n)=>{"completed"!==t.status&&"failed"!==t.status&&"canceled"!==t.status||e.push(n);}),e.forEach(e=>{this.activeTasks.delete(e);}),ye.debug("TaskManager",`清理了 ${e.length} 个已完成任务`);}reset(){this.activeTasks.forEach(e=>{"running"===e.status&&e.abortController.abort();}),this.activeTasks.clear(),this.taskQueue=[],ye.info("TaskManager","任务管理器已重置");}getStats(){const e={total:this.activeTasks.size,running:0,completed:0,failed:0,canceled:0,processedVideos:this.processedVideos.size};return this.activeTasks.forEach(t=>{"running"===t.status?e.running++:"completed"===t.status?e.completed++:"failed"===t.status?e.failed++:"canceled"===t.status&&e.canceled++;}),e}};const xe=new class{constructor(){this.reset();}reset(){this.subtitle={data:null,cache:{},capturedUrl:null},this.request={isRequesting:false,currentRequestKey:null,requestPromise:null,abortController:null},this.ai={isSummarizing:false,currentSummary:null,summaryPromise:null,abortController:null},this.notion={isSending:false,sendPromise:null,pageIds:{}},this.ui={ballStatus:y,panelVisible:false,isDragging:false,dragStart:{x:0,y:0},panelStart:{x:0,y:0}},this.video={bvid:null,cid:null,aid:null};}setVideoInfo(e){if(!be(e).valid)return  false;const t=this.video.bvid,n=e.bvid;return t&&t!==n&&(ye.info("StateManager",`检测到视频切换: ${t} -> ${n}`),ve.cancelVideoTasks(t),fe.emit(q,{oldBvid:t,newBvid:n,oldVideoInfo:{...this.video},newVideoInfo:e})),this.video.bvid=e.bvid,this.video.cid=e.cid,this.video.aid=e.aid,true}getVideoInfo(){return {...this.video}}getVideoKey(){return be(e=this.video).valid?`${e.bvid}-${e.cid}`:null;var e;}setSubtitleData(e){this.subtitle.data=e;const t=this.getVideoKey();t&&(this.subtitle.cache[t]=e),e&&e.length>0&&fe.emit(k,e,t);}getSubtitleData(e=null){const t=e||this.getVideoKey();return t?this.subtitle.cache[t]?this.subtitle.cache[t]:t===this.getVideoKey()?this.subtitle.data:null:this.subtitle.data}startRequest(){const e=this.getVideoKey();return e?this.request.isRequesting&&this.request.currentRequestKey===e?{success:false,reason:"已有相同视频的请求在进行中"}:this.subtitle.cache[e]?{success:false,reason:"已有缓存"}:(this.request.isRequesting&&this.cancelRequest(),this.request.isRequesting=true,this.request.currentRequestKey=e,{success:true,reason:null}):{success:false,reason:"视频信息无效"}}finishRequest(){this.request.isRequesting=false,this.request.currentRequestKey=null,this.request.requestPromise=null,this.request.abortController=null;}cancelRequest(){this.request.abortController&&this.request.abortController.abort(),this.finishRequest();}startAISummary(){return !this.ai.isSummarizing&&(this.ai.isSummarizing=true,this.ai.abortController=new AbortController,fe.emit(T),true)}finishAISummary(e){this.ai.isSummarizing=false,this.ai.currentSummary=e,this.ai.summaryPromise=null,this.ai.abortController=null;const t=this.getVideoKey();if(t&&e){const n="object"==typeof e?JSON.stringify(e):e;sessionStorage.setItem(`ai-summary-${t}`,n);}fe.emit(C,e,t);}cancelAISummary(){this.ai.abortController&&this.ai.abortController.abort(),this.ai.isSummarizing=false,this.ai.summaryPromise=null,this.ai.abortController=null;}getAISummary(e=null){const t=e||this.getVideoKey();if(!t)return this.ai.currentSummary;const n=sessionStorage.getItem(`ai-summary-${t}`);if(n)try{return JSON.parse(n)}catch(o){return n}return null}setBallStatus(e){this.ui.ballStatus!==e&&(this.ui.ballStatus=e,fe.emit(L,e));}getBallStatus(){return this.ui.ballStatus}togglePanel(){this.ui.panelVisible=!this.ui.panelVisible,fe.emit(N,this.ui.panelVisible);}setNotionPageId(e,t){e&&t&&(this.notion.pageIds[e]=t);}getNotionPageId(e){return this.notion.pageIds[e]||null}setPanelVisible(e){this.ui.panelVisible!==e&&(this.ui.panelVisible=e,fe.emit(N,e));}};const we=new class{getAIConfigs(){const e=GM_getValue(O,[]);if(0===e.length)return [...R];const t=e.map(e=>!e.prompt||e.prompt1&&e.prompt2?e:{...e,prompt1:e.prompt,prompt2:this._getDefaultPrompt2(),prompt:void 0});return t.some(t=>void 0===t.prompt&&e.some(e=>e.prompt))&&this.saveAIConfigs(t),t}saveAIConfigs(e){GM_setValue(O,e);}getSelectedAIConfigId(){return GM_getValue(V,"openrouter")}setSelectedAIConfigId(e){GM_setValue(V,e);}getSelectedAIConfig(){const e=this.getAIConfigs(),t=this.getSelectedAIConfigId();return e.find(e=>e.id===t)||e[0]||null}addAIConfig(e){if(!(e.name&&e.url&&e.apiKey&&e.model))return {success:false,error:"所有字段都是必填的"};const t=ge(e.url);if(!t.valid)return {success:false,error:t.error};const n=me(e.apiKey);if(!n.valid)return {success:false,error:n.error};const o=this.getAIConfigs(),i={id:Date.now().toString(),name:e.name.trim(),url:e.url.trim(),apiKey:e.apiKey.trim(),model:e.model.trim(),prompt1:e.prompt1||"",prompt2:e.prompt2||"",isOpenRouter:e.isOpenRouter||false};return o.push(i),this.saveAIConfigs(o),this.setSelectedAIConfigId(i.id),{success:true,error:null,config:i}}updateAIConfig(e,t){const n=this.getAIConfigs(),o=n.findIndex(t=>t.id===e);if(-1===o)return {success:false,error:"配置不存在"};if(void 0!==t.apiKey){const e=me(t.apiKey,n[o].isOpenRouter);if(!e.valid)return {success:false,error:e.error}}if(void 0!==t.url){const e=ge(t.url);if(!e.valid)return {success:false,error:e.error}}return !t.prompt||t.prompt1&&t.prompt2||(t.prompt1=t.prompt,t.prompt2=this._getDefaultPrompt2(),delete t.prompt),n[o]={...n[o],...t},this.saveAIConfigs(n),{success:true,error:null}}deleteAIConfig(e){if("openrouter"===e||"openai"===e)return {success:false,error:"预设配置不能删除"};let t=this.getAIConfigs();return t=t.filter(t=>t.id!==e),this.saveAIConfigs(t),this.getSelectedAIConfigId()===e&&this.setSelectedAIConfigId("openrouter"),{success:true,error:null}}getAIAutoSummaryEnabled(){return GM_getValue(j,true)}setAIAutoSummaryEnabled(e){GM_setValue(j,e);}getNotionConfig(){return {apiKey:GM_getValue(F,""),parentPageId:GM_getValue(U,""),databaseId:GM_getValue(H,"")}}isNotionConfigured(){const e=this.getNotionConfig();return !(!e.apiKey||!e.parentPageId)}saveNotionConfig(e){if(e.apiKey){const t=me(e.apiKey);if(!t.valid)return {success:false,error:t.error};GM_setValue(F,e.apiKey.trim());}if(e.parentPageId){const t=function(e){if(!e||"string"!=typeof e)return {valid:false,cleaned:null,error:"Page ID不能为空"};let t=e.split("?")[0].split("#")[0];const n=t.match(oe);return n?(t=n[1].replace(/-/g,""),t.length!==p?{valid:false,cleaned:null,error:`Page ID长度错误,需要${p}位字符`}:{valid:true,cleaned:t,error:null}):{valid:false,cleaned:null,error:"Page ID格式错误,应为32位十六进制字符"}}(e.parentPageId);if(!t.valid)return {success:false,error:t.error};GM_setValue(U,t.cleaned);}return void 0!==e.databaseId&&GM_setValue(H,e.databaseId),{success:true,error:null}}getNotionAutoSendEnabled(){return GM_getValue(K,false)}setNotionAutoSendEnabled(e){GM_setValue(K,e);}getNotionContentOptions(){return {videoInfo:GM_getValue(Y,true),summary:GM_getValue(G,true),segments:GM_getValue(J,true),subtitles:GM_getValue(W,true)}}saveNotionContentOptions(e){ void 0!==e.videoInfo&&GM_setValue(Y,e.videoInfo),void 0!==e.summary&&GM_setValue(G,e.summary),void 0!==e.segments&&GM_setValue(J,e.segments),void 0!==e.subtitles&&GM_setValue(W,e.subtitles);}fixExistingConfigPrompts(){const e=this.getAIConfigs();let t=false;const n=e.map(e=>e.prompt1&&e.prompt2&&(e.prompt1===e.prompt2||e.prompt2.includes("TL;DR"))?(t=true,{...e,prompt2:this._getDefaultPrompt2()}):e);return t&&(this.saveAIConfigs(n),ye.debug("ConfigManager","已修复配置中的prompt2为JSON格式")),t}_getDefaultPrompt2(){return '分析以下带时间戳的字幕,提取5-8个关键段落。\n\n重要:\n1. 你的回复必须是一个完整且有效的JSON\n2. 不要有任何其他文字、解释或markdown标记\n3. 直接以{开始,以}结束\n4. 数组元素之间必须用逗号分隔\n\nJSON格式要求(注意逗号):\n{\n  "segments": [\n    {"timestamp":"分钟:秒","title":"标题(10字内)","summary":"内容总结(30-50字)"},\n    {"timestamp":"分钟:秒","title":"标题(10字内)","summary":"内容总结(30-50字)"}\n  ]\n}\n\n正确示例(特别注意元素之间的逗号):\n{"segments":[{"timestamp":"00:15","title":"开场介绍","summary":"主持人介绍今天的主题和嘉宾背景"},{"timestamp":"02:30","title":"核心观点","summary":"讨论技术发展趋势和未来展望"}]}\n\n字幕内容:\n'}},Se="bilibili_shortcuts_config",ke=/Mac|iPhone|iPad|iPod/.test(navigator.platform),Ie={toggleSubtitlePanel:{key:"KeyB",meta:true,ctrl:true,alt:false,shift:false,description:"切换字幕面板"},toggleNotesPanel:{key:"Slash",meta:false,ctrl:false,alt:false,shift:true,description:"切换笔记面板"},takeScreenshot:{key:"Slash",meta:true,ctrl:true,alt:false,shift:false,description:"截图并保存到笔记"},speedIncrease:{key:"Period",meta:false,ctrl:false,alt:false,shift:false,description:"增加播放速度"},speedDecrease:{key:"Comma",meta:false,ctrl:false,alt:false,shift:false,description:"减少播放速度"},speedReset:{key:"Comma",meta:false,ctrl:false,alt:false,shift:false,doubleClick:true,description:"重置播放速度(双击)"},speedDouble:{key:"Period",meta:false,ctrl:false,alt:false,shift:false,doubleClick:true,description:"2倍速(双击)"}};const Ee=new class{constructor(){this.shortcuts=this.loadShortcuts(),this.handlers=new Map,this.isListening=false,this.pressedKeys=new Set,this.setupKeyTracking();}static get DEFAULT_SHORTCUTS(){return Ie}loadShortcuts(){try{const e=GM_getValue(Se,null);if(!e)return {...Ie};const t=JSON.parse(e),n={...Ie};for(const[o,i]of Object.entries(t))i.key&&"(双击)"!==i.key&&""!==i.key?n[o]=i:(ye.warn("ShortcutManager",`修复损坏的快捷键配置: ${o}`),Ie[o]&&(n[o]={...Ie[o]}));return n}catch(e){return console.error("加载快捷键配置失败:",e),{...Ie}}}saveShortcuts(e){try{return this.shortcuts=e,GM_setValue(Se,JSON.stringify(e)),{success:!0,error:null}}catch(t){return console.error("保存快捷键配置失败:",t),{success:false,error:t.message}}}resetToDefaults(){return this.shortcuts={...Ie},this.saveShortcuts(this.shortcuts)}getAllShortcuts(){return {...this.shortcuts}}updateShortcut(e,t){if(!this.shortcuts[e])return {success:false,error:"快捷键不存在"};const n=this.checkConflict(e,t);return n?{success:false,error:`与"${n}"冲突`}:(this.shortcuts[e]={...this.shortcuts[e],...t},this.saveShortcuts(this.shortcuts))}checkConflict(e,t){for(const[n,o]of Object.entries(this.shortcuts)){if(n===e)continue;const i=o.key===t.key,s=o.alt===t.alt,r=o.shift===t.shift,a=o.doubleClick===t.doubleClick,l=(o.meta||false)===(t.meta||false),c=(o.ctrl||false)===(t.ctrl||false);if(i&&l&&c&&s&&r&&a)return o.description}return null}register(e,t){this.handlers.set(e,t);}setupKeyTracking(){document.addEventListener("keydown",e=>{this.pressedKeys.add(e.code);},true),document.addEventListener("keyup",e=>{this.pressedKeys.delete(e.code);},true),window.addEventListener("blur",()=>{this.pressedKeys.clear();});}matches(e,t){if(t.keys&&Array.isArray(t.keys)){const n=t.keys.every(e=>this.pressedKeys.has(e)),o=!(e.ctrlKey||e.metaKey||e.altKey||e.shiftKey);return n&&o}const n=ke?e.metaKey:e.ctrlKey,o=ke?t.meta||false:t.ctrl||false;return e.code===t.key&&n===o&&e.altKey===(t.alt||false)&&e.shiftKey===(t.shift||false)}startListening(){this.isListening||(document.addEventListener("keydown",e=>this.handleKeyDown(e),true),document.addEventListener("keyup",e=>this.handleKeyUp(e),true),this.isListening=true);}handleKeyDown(e){const t="INPUT"===e.target.tagName||"TEXTAREA"===e.target.tagName||e.target.isContentEditable;for(const[n,o]of Object.entries(this.shortcuts)){if(o.holdMode){if(e.code===o.key){const t=this.handlers.get(n);t&&(e.preventDefault(),t(e));}continue}if(o.doubleClickMode){e.code===o.key&&this.handleDoubleClick(e,n,o);continue}if(o.doubleClick){"takeScreenshot"===n&&this.handleDoubleClick(e,n,o);continue}const i=o.ctrl||o.alt||o.meta;if(this.matches(e,o)){if(t&&!i)continue;const o=this.handlers.get(n);o&&(e.preventDefault(),o(e));}}}handleKeyUp(e){for(const[t,n]of Object.entries(this.shortcuts))if(n.holdMode&&e.code===n.key){const n=this.handlers.get(t);n&&n.release&&(e.preventDefault(),n.release(e));}}handleDoubleClick(e,t,n){if(e.code!==n.key)return;const o=Date.now();this.lastKeyPressTime||(this.lastKeyPressTime={});if(o-(this.lastKeyPressTime[n.key]||0)<300){const o=this.handlers.get(t);o&&(e.preventDefault(),o(e),this.lastKeyPressTime[n.key]=0);}else this.lastKeyPressTime[n.key]=o;}formatShortcut(e){if(e.keys&&Array.isArray(e.keys)){return e.keys.map(e=>"Period"===e?".":"Comma"===e?",":1===e.length?e.toUpperCase():e).join(" + ")}if(e.holdMode){let t=e.key;return "Period"===t&&(t="."),"Comma"===t&&(t=","),"Slash"===t&&(t="/"),t.startsWith("Key")&&(t=t.substring(3)),1===t.length&&(t=t.toUpperCase()),`${t} (长按)`}if(e.doubleClickMode){let t=e.key;return "Period"===t&&(t="."),"Comma"===t&&(t=","),"Slash"===t&&(t="/"),t.startsWith("Key")&&(t=t.substring(3)),1===t.length&&(t=t.toUpperCase()),`${t} (双击)`}const t=[];ke&&e.meta?t.push("Cmd"):!ke&&e.ctrl&&t.push("Ctrl"),e.alt&&t.push(ke?"Option":"Alt"),e.shift&&t.push("Shift");let n=e.key;return "Period"===n&&(n="."),"Comma"===n&&(n=","),"Slash"===n&&(n="/"),n.startsWith("Key")&&(n=n.substring(3)),1===n.length&&(n=n.toUpperCase()),t.push(n),e.doubleClick&&t.push("(双击)"),t.join(" + ")}validateConfig(e){return e.key&&"string"==typeof e.key?"boolean"!=typeof e.ctrl||"boolean"!=typeof e.alt||"boolean"!=typeof e.shift?{valid:false,error:"修饰键配置错误"}:{valid:true,error:null}:{valid:false,error:"按键不能为空"}}};class Te{constructor(){this.platform="unknown",this.video=null,this.progressBar=null;}isVideoPage(){throw new Error("子类必须实现isVideoPage方法")}getVideoId(){throw new Error("子类必须实现getVideoId方法")}getVideoElement(){throw new Error("子类必须实现getVideoElement方法")}getProgressBar(){throw new Error("子类必须实现getProgressBar方法")}getAdProgressBar(){throw new Error("子类必须实现getAdProgressBar方法")}getPlayerContainer(){throw new Error("子类必须实现getPlayerContainer方法")}async waitForVideo(){return new Promise(e=>{const t=()=>{const n=this.getVideoElement();n?(this.video=n,e(n)):setTimeout(t,500);};t();})}async waitForProgressBar(){return new Promise(e=>{const t=()=>{const n=this.getProgressBar();n?(this.progressBar=n,e(n)):setTimeout(t,500);};t();})}seekTo(e){this.video&&(this.video.currentTime=e);}getCurrentTime(){return this.video?this.video.currentTime:0}getDuration(){return this.video?this.video.duration:0}isPlaying(){return this.video&&!this.video.paused}showNotification(e,t={}){throw new Error("子类必须实现showNotification方法")}addProgressMarkers(e){throw new Error("子类必须实现addProgressMarkers方法")}destroy(){this.video=null,this.progressBar=null;}}class Ce extends Te{constructor(){super(),this.platform="bilibili";}isVideoPage(){return location.hostname.includes("bilibili.com")&&location.pathname.includes("/video/")}getVideoId(){return location.pathname.match(/video\/(BV\w+)/)?.[1]}getVideoElement(){return document.querySelector("video")||document.querySelector(".bpx-player-video-wrap video")}getProgressBar(){return document.querySelector(".bpx-player-progress-schedule")}getAdProgressBar(){return null}getPlayerContainer(){return document.querySelector(".bpx-player-video-wrap")||document.querySelector(".bpx-player-container")}showNotification(e,t={}){const{duration:n=3e3,type:o="info",position:i="top-right"}=t,s=document.createElement("div");s.className="bilibili-skip-notification",s.textContent=e;const r="warning"===o?"#ff9800":"success"===o?"#4caf50":"error"===o?"#f44336":"#2196f3";s.style.cssText=`\n      position: absolute;\n      ${i.includes("top")?"top: 70px":"bottom: 70px"};\n      ${i.includes("right")?"right: 12px":"left: 12px"};\n      background: ${r};\n      color: white;\n      padding: 12px 20px;\n      border-radius: 4px;\n      font-size: 14px;\n      font-weight: 500;\n      box-shadow: 0 2px 5px rgba(0,0,0,0.3);\n      z-index: 1000;\n      animation: fadeIn 0.3s ease;\n      pointer-events: none;\n    `;const a=this.getPlayerContainer();if(a){if(a.appendChild(s),!document.querySelector("#bilibili-notification-styles")){const e=document.createElement("style");e.id="bilibili-notification-styles",e.textContent="\n          @keyframes fadeIn {\n            from { opacity: 0; transform: translateY(-10px); }\n            to { opacity: 1; transform: translateY(0); }\n          }\n          @keyframes fadeOut {\n            from { opacity: 1; transform: translateY(0); }\n            to { opacity: 0; transform: translateY(-10px); }\n          }\n        ",document.head.appendChild(e);}setTimeout(()=>{s.style.animation="fadeOut 0.3s ease",setTimeout(()=>s.remove(),300);},n);}}addProgressMarkers(e,t={}){const{containerId:n="sponsorblock-preview-bar",className:o="sponsorblock-segment",defaultColor:i="#ff0000",opacity:s=.7}=t,r=this.getProgressBar();if(!r||!this.video)return;document.querySelectorAll(`#${n}`).forEach(e=>e.remove());const a=document.createElement("ul");a.id=n,a.style.cssText="\n      position: absolute;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      pointer-events: none;\n      z-index: 10;\n    ";const l=this.video.duration;[...e].sort((e,t)=>{const n=(e.end||e.segment?.[1]||0)-(e.start||e.segment?.[0]||0);return (t.end||t.segment?.[1]||0)-(t.start||t.segment?.[0]||0)-n}).forEach((e,t)=>{const n=e.start||e.segment?.[0]||0,r=e.end||e.segment?.[1]||0,c=n/l*100,d=100*(1-r/l),u=document.createElement("li");u.className=o,u.dataset.segmentIndex=t.toString();const p=e.color||this.getCategoryColor(e.category)||i;u.style.cssText=`\n        position: absolute;\n        left: ${c}%;\n        right: ${d}%;\n        height: 100%;\n        background: ${p};\n        opacity: ${s};\n        pointer-events: auto;\n        cursor: pointer;\n        transition: opacity 0.2s ease;\n      `,u.addEventListener("mouseenter",()=>{u.style.opacity="1";}),u.addEventListener("mouseleave",()=>{u.style.opacity=s.toString();});const h=r-n,g=this.getCategoryName(e.category);u.title=`${g}\n${this.formatTime(n)} - ${this.formatTime(r)} (${h.toFixed(1)}秒)`,u.addEventListener("click",t=>{t.stopPropagation(),"mute"!==e.actionType&&(this.seekTo(r),this.showNotification(`已跳过 ${g}`,{type:"success"}));}),a.appendChild(u);}),r.prepend(a);}getCategoryColor(e){return {sponsor:"#00d400",selfpromo:"#ffff00",interaction:"#cc00ff",intro:"#00ffff",outro:"#0202ed",preview:"#008fd6",filler:"#7300ff",music_offtopic:"#ff9900"}[e]||"#ff0000"}getCategoryName(e){return {sponsor:"赞助商",selfpromo:"自我推广",interaction:"互动提醒",intro:"开场动画",outro:"结尾推荐",preview:"预告",filler:"无关内容",music_offtopic:"非音乐部分"}[e]||e||"广告"}formatTime(e){return `${Math.floor(e/60)}:${Math.floor(e%60).toString().padStart(2,"0")}`}isVIPUser(){const e=document.querySelector(".bili-avatar-pendent-dom"),t=document.querySelector(".bpx-player-video-info-vip"),n=localStorage.getItem("bili_vip_status");return !(!e&&!t&&"true"!==n)}destroy(){super.destroy();}}class Ae extends Te{constructor(){super(),this.platform="youtube",this.adObserver=null;}isVideoPage(){return location.hostname.includes("youtube.com")&&location.pathname.includes("/watch")}getVideoId(){return new URLSearchParams(window.location.search).get("v")}getVideoElement(){return document.querySelector("video.html5-main-video")||document.querySelector("video")}getProgressBar(){return document.querySelector(".ytp-progress-bar")}getAdProgressBar(){return document.querySelector(".ytp-ad-progress-list")}getPlayerContainer(){return document.querySelector("#movie_player")||document.querySelector(".html5-video-player")}detectNativeAdMarkers(){const e=[],t=this.getProgressBar();if(!t)return e;t.querySelectorAll(".ytp-ad-progress, .ytp-play-progress").forEach(t=>{const n=window.getComputedStyle(t);if(n.backgroundColor.includes("255, 204")||n.backgroundColor.includes("254, 205")||t.classList.contains("ytp-ad-progress")){const n=parseFloat(t.style.left)||0,o=parseFloat(t.style.width)||100*(parseFloat(t.style.transform?.match(/scaleX\(([\d.]+)\)/)?.[1])||0);if(o>0&&this.video){const t=this.video.duration,i=n/100*t,s=(n+o)/100*t;e.push({start:Math.max(0,i),end:Math.min(t,s),type:"native_ad",color:"#ffcc00"});}}});return t.querySelectorAll(".ytp-ad-section-marker").forEach(t=>{const n=parseFloat(t.style.left)||0,o=parseFloat(t.style.width)||0;if(o>0&&this.video){const t=this.video.duration,i=n/100*t,s=(n+o)/100*t;e.push({start:Math.max(0,i),end:Math.min(t,s),type:"section_marker",color:"#ffcc00"});}}),e}observeAdChanges(e){const t=this.getProgressBar();t&&(this.adObserver&&this.adObserver.disconnect(),this.adObserver=new MutationObserver(t=>{let n=false;if(t.forEach(e=>{(e.target.classList.contains("ytp-ad-progress")||e.target.classList.contains("ytp-ad-progress-list")||"style"===e.attributeName)&&(n=true);}),n){const t=this.detectNativeAdMarkers();e(t);}}),this.adObserver.observe(t,{attributes:true,attributeFilter:["style","class"],childList:true,subtree:true}));}showNotification(e,t={}){const{duration:n=3e3,type:o="info",position:i="top-right"}=t,s=document.createElement("div");s.className="youtube-skip-notification",s.textContent=e,s.style.cssText=`\n      position: absolute;\n      ${i.includes("top")?"top: 70px":"bottom: 70px"};\n      ${i.includes("right")?"right: 12px":"left: 12px"};\n      background: ${"warning"===o?"#ff9800":"success"===o?"#4caf50":"#2196f3"};\n      color: white;\n      padding: 12px 20px;\n      border-radius: 4px;\n      font-size: 14px;\n      font-weight: 500;\n      box-shadow: 0 2px 5px rgba(0,0,0,0.3);\n      z-index: 1000;\n      animation: slideIn 0.3s ease;\n      pointer-events: none;\n    `;const r=this.getPlayerContainer();r&&(r.appendChild(s),setTimeout(()=>{s.style.animation="slideOut 0.3s ease",setTimeout(()=>s.remove(),300);},n));}addProgressMarkers(e,t={}){const{containerId:n="universal-ad-markers",className:o="universal-ad-marker",color:i="#ff0000",opacity:s=.7}=t,r=this.getProgressBar();if(!r||!this.video)return;const a=this.video.duration;if(!a||0===a)return ye.debug("YouTubeAdapter","视频时长无效,延迟添加标记"),void setTimeout(()=>this.addProgressMarkers(e,t),1e3);const l=document.getElementById(n);l&&l.remove();const c=document.createElement("div");c.id=n,c.style.cssText="\n      position: absolute;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      pointer-events: none;\n      z-index: 40;\n      display: flex;\n      align-items: center;\n    ";const d={sponsor:"#00d400",intro:"#00ffff",outro:"#0202ed",selfpromo:"#ffff00",interaction:"#cc00ff",preview:"#008fd6",filler:"#7300ff",music_offtopic:"#ff9900"};e.forEach((e,t)=>{const n=e.start||e.segment?.[0]||0,r=e.end||e.segment?.[1]||0;if(n>=r||n<0||r>a)return void ye.warn("YouTubeAdapter",`跳过无效段落: ${n}-${r}`);const l=n/a*100,u=(r-n)/a*100,p=document.createElement("div");p.className=o,p.dataset.segmentIndex=t.toString(),p.dataset.category=e.category||"unknown";const h=d[e.category]||e.color||i;p.style.cssText=`\n        position: absolute;\n        left: ${l}%;\n        width: ${u}%;\n        height: 100%;\n        background-color: ${h};\n        opacity: ${s};\n        pointer-events: auto;\n        cursor: pointer;\n        box-sizing: border-box;\n        transition: opacity 0.2s;\n      `,p.addEventListener("mouseenter",()=>{p.style.opacity="1";}),p.addEventListener("mouseleave",()=>{p.style.opacity=s.toString();});const g=this.getCategoryName(e.category);p.title=`${g}: ${this.formatTime(n)} - ${this.formatTime(r)}`,p.addEventListener("click",t=>{t.stopPropagation(),t.preventDefault(),"mute"!==e.actionType&&(this.seekTo(r),this.showNotification(`跳过了 ${g}`,{type:"success"}));}),c.appendChild(p);});const u=r.querySelector(".ytp-progress-bar-container"),p=r.querySelector(".ytp-play-progress"),h=r.querySelector(".ytp-load-progress");u?p&&p.parentElement?p.parentElement.insertBefore(c,p.nextSibling):h&&h.parentElement?h.parentElement.appendChild(c):u.appendChild(c):r.appendChild(c),ye.info("YouTubeAdapter",`已添加 ${e.length} 个进度条标记`);}getCategoryName(e){return {sponsor:"赞助商",selfpromo:"自我推广",interaction:"互动提醒",intro:"片头",outro:"片尾",preview:"预告",filler:"填充内容",music_offtopic:"非音乐内容"}[e]||e||"广告"}formatTime(e){return `${Math.floor(e/60)}:${Math.floor(e%60).toString().padStart(2,"0")}`}isPremiumUser(){const e=document.querySelector(".ytp-premium-badge"),t="1"===new URLSearchParams(window.location.search).get("premium"),n="true"===localStorage.getItem("yt-player-premium");return !!(e||t||n)}destroy(){this.adObserver&&(this.adObserver.disconnect(),this.adObserver=null),super.destroy();}}class $e{static decorateClass(e,t,n={}){const{includeMethods:o=[],excludeMethods:i=[],logLevel:s="trace",logArgs:r=true,logReturn:a=true,logTime:l=true}=n,c=e.prototype;return Object.getOwnPropertyNames(c).forEach(e=>{if("constructor"===e||e.startsWith("_"))return;if(i.includes(e))return;if(o.length>0&&!o.includes(e))return;const n=c[e];"function"==typeof n&&(c[e]=function(...o){const i=l?performance.now():0;ye.isDebugMode()&&(ye.trace(t,`→ ${e}()${r&&o.length>0?" with args:":""}`),r&&o.length>0&&o.forEach((e,n)=>{ye.trace(t,`  [${n}]:`,this._formatArgument(e));}));try{const s=n.apply(this,o);if(s instanceof Promise)return s.then(n=>{if(ye.isDebugMode()){const o=l?(performance.now()-i).toFixed(2):0;ye.trace(t,`← ${e}() async completed${l?` in ${o}ms`:""}`),a&&void 0!==n&&ye.trace(t,"  Return:",this._formatArgument(n));}return n}).catch(n=>{if(ye.isDebugMode()){const o=l?(performance.now()-i).toFixed(2):0;ye.error(t,`✗ ${e}() failed${l?` after ${o}ms`:""}:`,n);}throw n});if(ye.isDebugMode()){const n=l?(performance.now()-i).toFixed(2):0;ye.trace(t,`← ${e}()${l?` in ${n}ms`:""}`),a&&void 0!==s&&ye.trace(t,"  Return:",this._formatArgument(s));}return s}catch(s){if(ye.isDebugMode()){const n=l?(performance.now()-i).toFixed(2):0;ye.error(t,`✗ ${e}() failed${l?` after ${n}ms`:""}:`,s);}throw s}},Object.defineProperty(c[e],"name",{value:e,configurable:true}));}),c._formatArgument=function(e){if(null===e)return "null";if(void 0===e)return "undefined";const t=typeof e;if("string"===t)return e.length>100?`"${e.substring(0,100)}..."`:`"${e}"`;if("number"===t||"boolean"===t)return e;if("function"===t)return `[Function: ${e.name||"anonymous"}]`;if(Array.isArray(e))return `[Array(${e.length})]`;if(e instanceof Error)return `[Error: ${e.message}]`;if("object"===t){const t=Object.keys(e);return t.length<=3?JSON.stringify(e):`{${t.slice(0,3).join(", ")}...}`}return String(e)},e}static decorateMethod(e,t,n,o={}){const{logLevel:i="trace",logArgs:s=true,logReturn:r=true,logTime:a=true}=o;return function(...o){const i=a?performance.now():0;ye.isDebugMode()&&(ye.trace(t,`→ ${n}()${s&&o.length>0?" with args:":""}`),s&&o.length>0&&o.forEach((e,n)=>{ye.trace(t,`  [${n}]:`,$e.formatArgument(e));}));try{const s=e.apply(this,o);if(s instanceof Promise)return s.then(e=>{if(ye.isDebugMode()){const o=a?(performance.now()-i).toFixed(2):0;ye.trace(t,`← ${n}() async completed${a?` in ${o}ms`:""}`),r&&void 0!==e&&ye.trace(t,"  Return:",$e.formatArgument(e));}return e}).catch(e=>{if(ye.isDebugMode()){const o=a?(performance.now()-i).toFixed(2):0;ye.error(t,`✗ ${n}() failed${a?` after ${o}ms`:""}:`,e);}throw e});if(ye.isDebugMode()){const e=a?(performance.now()-i).toFixed(2):0;ye.trace(t,`← ${n}()${a?` in ${e}ms`:""}`),r&&void 0!==s&&ye.trace(t,"  Return:",$e.formatArgument(s));}return s}catch(l){if(ye.isDebugMode()){const e=a?(performance.now()-i).toFixed(2):0;ye.error(t,`✗ ${n}() failed${a?` after ${e}ms`:""}:`,l);}throw l}}}static formatArgument(e){if(null===e)return "null";if(void 0===e)return "undefined";const t=typeof e;if("string"===t)return e.length>100?`"${e.substring(0,100)}..."`:`"${e}"`;if("number"===t||"boolean"===t)return e;if("function"===t)return `[Function: ${e.name||"anonymous"}]`;if(Array.isArray(e))return `[Array(${e.length})]`;if(e instanceof Error)return `[Error: ${e.message}]`;if("object"===t){const t=Object.keys(e);if(t.length<=3)try{return JSON.stringify(e)}catch{return `{${t.slice(0,3).join(", ")}...}`}return `{${t.slice(0,3).join(", ")}...}`}return String(e)}static createModuleLogger(e){return {trace:(...t)=>ye.trace(e,...t),debug:(...t)=>ye.debug(e,...t),info:(...t)=>ye.info(e,...t),success:(...t)=>ye.success(e,...t),warn:(...t)=>ye.warn(e,...t),error:(...t)=>ye.error(e,...t),group:t=>ye.group(e,t),groupEnd:()=>ye.groupEnd(),table:t=>ye.table(e,t),time:t=>ye.time(`${e}-${t}`),timeEnd:t=>ye.timeEnd(`${e}-${t}`)}}}const _e=new class{constructor(){this.enabled=true;}measure(e,t){if(!this.enabled)return t();const n=performance.now();let o,i;try{o=t();}catch(r){i=r;}const s=performance.now()-n;if(ye.debug("计时",`${e}: ${s.toFixed(2)}ms`),i)throw i;return o}async measureAsync(e,t){if(!this.enabled)return await t();const n=performance.now();let o,i;try{o=await t();}catch(r){i=r;}const s=performance.now()-n;if(ye.debug("计时",`${e}: ${s.toFixed(2)}ms`),i)throw i;return o}destroy(){}};function Me(e){const t=Math.floor(e/60),n=Math.floor(e%60);return `${t.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`}function Pe(){const e=location.hostname;if(e.includes("youtube.com")){return {videoId:new URLSearchParams(window.location.search).get("v"),platform:"youtube",bvid:null,cid:null,aid:null}}if(e.includes("bilibili.com")){let e=null,t=null,n=null;e=function(e=window.location.href){const t=e.match(te);if(t)return t[1];const n=e.match(ne);return n?n[0]:null}();try{const o=unsafeWindow.__INITIAL_STATE__;o&&o.videoData&&(e=e||o.videoData.bvid,t=o.videoData.cid||o.videoData.pages?.[0]?.cid,n=o.videoData.aid);}catch(o){}return {bvid:e,cid:t,aid:n,videoId:e,platform:"bilibili"}}let t=null;const n=document.querySelector('meta[property="og:url"]');if(n){const e=n.content.split("/").filter(e=>e);t=e[e.length-1];}return {bvid:null,cid:null,aid:null,videoId:t,platform:"unknown"}}function Ne(){const e=location.hostname;let t="";if(e.includes("youtube.com")){const e=document.querySelector("h1.ytd-video-primary-info-renderer yt-formatted-string")||document.querySelector("h1 yt-formatted-string.style-scope.ytd-watch-metadata")||document.querySelector("#title h1")||document.querySelector("h1.title");if(e&&(t=e.textContent.trim()),!t){const e=document.querySelector('meta[property="og:title"]')||document.querySelector('meta[name="title"]');e&&(t=e.content);}return t||(t=document.title.replace(" - YouTube","").trim()),t||"未知视频"}if(e.includes("bilibili.com")){try{const e=unsafeWindow.__INITIAL_STATE__;e&&e.videoData&&e.videoData.title&&(t=e.videoData.title);}catch(o){}if(!t){const e=document.querySelector(le);e&&(t=e.textContent.trim());}return t||(t=document.title.replace(/_哔哩哔哩.*$/,"").replace(/_bilibili.*$/i,"").trim()),t||"未知视频"}const n=document.querySelector('meta[property="og:title"]');if(n&&(t=n.content),!t){const e=document.querySelector("h1");e&&(t=e.textContent.trim());}return t||(t=document.title),t||"未知视频"}function Le(){const e=location.hostname;if(e.includes("youtube.com")){const e=document.querySelector("#owner #channel-name a")||document.querySelector("#upload-info #channel-name a")||document.querySelector("ytd-channel-name a")||document.querySelector(".ytd-channel-name a")||document.querySelector("#owner-name a");if(e)return e.textContent.trim();const t=document.querySelector('link[itemprop="name"]');return t?t.content:"未知"}if(e.includes("bilibili.com")){try{const e=unsafeWindow.__INITIAL_STATE__;if(e&&e.videoData&&e.videoData.owner)return e.videoData.owner.name}catch(n){}const e=document.querySelector(".up-name")||document.querySelector(".up-info__name")||document.querySelector(".up-detail-name");return e?e.textContent.trim():"未知"}const t=document.querySelector('meta[name="author"]')||document.querySelector('meta[property="article:author"]');return t?t.content:"未知"}function qe(){return window.location.href.split("?")[0]}function Be(e){return new Promise(t=>setTimeout(t,e))}function De(e,t,n="操作超时"){return Promise.race([e,new Promise((e,o)=>setTimeout(()=>o(new Error(n)),t))])}function ze(e,t){let n;return function(...o){clearTimeout(n),n=setTimeout(()=>{clearTimeout(n),e(...o);},t);}}const Re=new class{constructor(){this.capturedSubtitleUrl=null,this.log=$e.createModuleLogger("SubtitleService"),this.setupInterceptor();}setupInterceptor(){const e=unsafeWindow.XMLHttpRequest.prototype.open,t=unsafeWindow.XMLHttpRequest.prototype.send;unsafeWindow.XMLHttpRequest.prototype.open=function(t,n){return this._url=n,e.apply(this,arguments)},unsafeWindow.XMLHttpRequest.prototype.send=function(){return this._url&&this._url.includes("aisubtitle.hdslb.com")&&(Re.log.trace("拦截到字幕请求:",this._url),Re.capturedSubtitleUrl=this._url,xe.subtitle.capturedUrl=this._url,setTimeout(()=>{Re.downloadCapturedSubtitle();},o)),t.apply(this,arguments)};}async downloadCapturedSubtitle(){this.capturedSubtitleUrl?(this.log.debug("开始下载字幕,URL:",this.capturedSubtitleUrl),await _e.measureAsync("字幕下载",async()=>{const e=Pe();xe.setVideoInfo(e);const t=xe.startRequest();if(t.success){xe.setBallStatus(v),fe.emit(E,e);try{const t=await this._fetchSubtitle(this.capturedSubtitleUrl,e),n=function(e){if(!Array.isArray(e))return {valid:!1,error:"字幕数据格式错误:非数组类型",details:{actualType:typeof e,value:e}};if(0===e.length)return {valid:!1,error:"字幕数据为空",details:{length:0}};const t=e[0],n=[];return void 0!==t.from&&null!==t.from||n.push("from"),void 0!==t.to&&null!==t.to||n.push("to"),t.content||n.push("content"),n.length>0?{valid:!1,error:`字幕数据格式不完整,缺少字段: ${n.join(", ")}`,details:{missingFields:n,firstItem:t,itemKeys:Object.keys(t||{})}}:{valid:!0,error:null,details:null}}(t);if(!n.valid)throw this.log.success(`字幕捕获成功,共 ${t.length} 条`),this.log.trace("字幕数据示例:",t.slice(0,3)),n.details&&ye.error("SubtitleService","验证详情:",n.details),new Error(n.error);xe.setSubtitleData(t),xe.setBallStatus(x);}catch(n){this.log.error("字幕下载失败:",n),xe.setBallStatus(S),fe.emit(I,n.message);}finally{xe.finishRequest();}}else if("已有缓存"===t.reason){const e=xe.getSubtitleData();e&&(xe.setBallStatus(x),fe.emit(k,e,xe.getVideoKey()));}})):this.log.debug("没有捕获到字幕URL");}_fetchSubtitle(e,t){return new Promise((n,o)=>{GM_xmlhttpRequest({method:"GET",url:e,headers:{Accept:"application/json, text/plain, */*","Accept-Encoding":"gzip, deflate, br","Accept-Language":"zh-CN,zh;q=0.9,en;q=0.8","Cache-Control":"no-cache",Origin:"https://www.bilibili.com",Referer:`https://www.bilibili.com/video/${t.bvid}/`,"Sec-Fetch-Dest":"empty","Sec-Fetch-Mode":"cors","Sec-Fetch-Site":"cross-site","User-Agent":navigator.userAgent},anonymous:false,onload:e=>{const i=Pe();if(i.bvid===t.bvid&&i.cid===t.cid)if(200===e.status)if(e.responseText.trim().startsWith("<!DOCTYPE")||e.responseText.trim().startsWith("<html"))o(new Error("服务器返回HTML而非JSON,可能被重定向"));else try{const t=JSON.parse(e.responseText);t.body&&t.body.length>0?n(t.body):o(new Error("字幕内容为空"));}catch(s){o(new Error("解析字幕数据失败"));}else o(new Error(`请求失败: ${e.status}`));else o(new Error("视频已切换"));},onerror:()=>{o(new Error("网络请求失败"));}});})}async checkSubtitleButton(){let n=0;return new Promise(o=>{const i=setInterval(()=>{n++;document.querySelector(re)?(clearInterval(i),this.tryActivateSubtitle(),o(true)):n>=t&&(clearInterval(i),xe.setBallStatus(w),o(false));},e);})}async tryActivateSubtitle(){await Be(n),this.capturedSubtitleUrl?this.downloadCapturedSubtitle():this.triggerSubtitleSelection();}async triggerSubtitleSelection(){const e=document.querySelector(re);if(!e)return void xe.setBallStatus(w);e.click(),await Be(i);let t=document.querySelector('.bpx-player-ctrl-subtitle-language-item[data-lan="ai-zh"]');if(t||(t=document.querySelector('.bpx-player-ctrl-subtitle-language-item[data-lan*="zh"]')),!t){const e=document.querySelectorAll(".bpx-player-ctrl-subtitle-language-item");for(let n of e){const e=n.querySelector(".bpx-player-ctrl-subtitle-language-item-text");if(e&&e.textContent.includes("中文")){t=n;break}}}if(t){t.click(),await Be(s);const e=document.querySelector(ae);e&&e.click(),await Be(n),this.capturedSubtitleUrl?this.downloadCapturedSubtitle():xe.setBallStatus(S);}else {const t=document.querySelector(".bpx-player-ctrl-subtitle-language-item");if(t){t.click(),await Be(s);const e=document.querySelector(ae);e&&e.click(),await Be(n),this.capturedSubtitleUrl?this.downloadCapturedSubtitle():xe.setBallStatus(S);}else e.click(),xe.setBallStatus(w);}}downloadSubtitleFile(){const e=xe.getSubtitleData();if(!e||0===e.length)throw new Error("没有字幕数据可下载");const t=xe.getVideoInfo(),n=Ne();!function(e,t,n="text/plain;charset=utf-8"){const o=new Blob([e],{type:n}),i=URL.createObjectURL(o),s=document.createElement("a");s.href=i,s.download=t,document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(i);}(e.map(e=>e.content).join("\n"),`${n}_${t.bvid}_字幕.txt`);}reset(){this.capturedSubtitleUrl=null,xe.subtitle.capturedUrl=null;}};class Oe{constructor(e){this.serviceName=e,this.defaultTimeout=3e4,this.maxRetries=3;}async request(e){const{url:t,method:n="GET",headers:o={},data:i=null,timeout:s=this.defaultTimeout,retry:r=true,retryCount:a=0}=e;return new Promise((l,c)=>{GM_xmlhttpRequest({method:n,url:t,headers:o,data:i?JSON.stringify(i):void 0,timeout:s,onload:t=>{this.handleResponse(t,l,c,e,r,a);},onerror:t=>{this.handleError(t,c,e,r,a);},ontimeout:()=>{this.handleTimeout(c,e,r,a);}});})}handleResponse(e,t,n,o,i,s){const{parseJson:r=true,validateStatus:a=e=>e>=200&&e<300}=o;if(!a(e.status)){const r=new Error(`HTTP ${e.status}: ${e.statusText}`);return r.status=e.status,r.response=e,i&&this.shouldRetry(e.status,s)?this.retryRequest(o,s+1).then(t).catch(n):void n(r)}try{t(r?JSON.parse(e.responseText):e.responseText);}catch(l){ye.error(this.serviceName,"Response parsing failed:",l),n(new Error("Failed to parse response"));}}handleError(e,t,n,o,i){if(ye.error(this.serviceName,"Request failed:",e),o&&i<this.maxRetries)return this.retryRequest(n,i+1).catch(t);t(e);}handleTimeout(e,t,n,o){if(ye.error(this.serviceName,"Request timeout"),n&&o<this.maxRetries)return this.retryRequest(t,o+1).catch(e);e(new Error("Request timeout"));}shouldRetry(e,t){return t<this.maxRetries&&(e>=500||0===e)}async retryRequest(e,t){const n=Math.min(1e3*Math.pow(2,t),1e4);return ye.info(this.serviceName,`Retrying request (attempt ${t+1}/${this.maxRetries}) after ${n}ms`),await new Promise(e=>setTimeout(e,n)),this.request({...e,retryCount:t})}async fetch(e,t={}){const{timeout:n=this.defaultTimeout,...o}=t,i=new AbortController,s=setTimeout(()=>i.abort(),n);try{const t=await fetch(e,{...o,signal:i.signal});return clearTimeout(s),t}catch(r){if(clearTimeout(s),"AbortError"===r.name)throw new Error("Request timeout");throw r}}async withTimeout(e,t,n){return De(e,t||this.defaultTimeout,n)}}const Ve=new class extends Oe{constructor(){super("YouTubeSubtitleService"),this.capturedSubtitleUrl=null,this.isVideoPage=false,this.initializeService();}initializeService(){this.checkIsVideoPage()?(ye.info("YouTubeSubtitleService","检测到YouTube播放页面,启动字幕拦截"),this.setupInterceptor()):ye.debug("YouTubeSubtitleService","非YouTube播放页面,跳过字幕拦截"),this.observePageChanges();}checkIsVideoPage(){return this.isVideoPage=location.hostname.includes("youtube.com")&&"/watch"===location.pathname,this.isVideoPage}observePageChanges(){let e=location.href;document.addEventListener("yt-navigate-finish",()=>{const t=location.href;if(t!==e){e=t;const n=this.isVideoPage,o=this.checkIsVideoPage();!n&&o?ye.info("YouTubeSubtitleService","进入YouTube播放页面,启动字幕拦截"):n&&!o&&(ye.info("YouTubeSubtitleService","离开YouTube播放页面"),this.capturedSubtitleUrl=null,xe.subtitle.capturedUrl=null,xe.subtitle.data=null);}});const t=()=>{if(!document.body)return void("loading"===document.readyState&&document.addEventListener("DOMContentLoaded",()=>{t();}));const n=new MutationObserver(()=>{location.href!==e&&(e=location.href,this.checkIsVideoPage());});try{n.observe(document.body,{childList:!0,subtree:!0});}catch(o){ye.warn("YouTubeSubtitleService","无法设置MutationObserver:",o);}};t();}setupInterceptor(){const e=unsafeWindow.XMLHttpRequest.prototype.open,t=unsafeWindow.XMLHttpRequest.prototype.send,n=unsafeWindow.fetch;unsafeWindow.XMLHttpRequest.prototype.open=function(t,n){return this._url=n,this._method=t,e.apply(this,arguments)},unsafeWindow.XMLHttpRequest.prototype.send=function(){const e=this._url;return this._method,e&&e.includes("/api/timedtext")&&Ve.isVideoPage&&(ye.info("YouTubeSubtitleService","拦截到YouTube字幕请求:",e),this.addEventListener("load",function(){if(this.responseText)try{const t=JSON.parse(this.responseText);t.events&&(ye.info("YouTubeSubtitleService","成功捕获字幕数据"),Ve.handleSubtitleResponse(e,t));}catch(t){ye.debug("YouTubeSubtitleService","非JSON响应");}})),t.apply(this,arguments)},unsafeWindow.fetch=async function(e,t){if("string"==typeof e&&e.includes("/api/timedtext")&&Ve.isVideoPage){ye.info("YouTubeSubtitleService","拦截到YouTube字幕fetch请求:",e);const t=await n.apply(this,arguments),i=t.clone();try{const t=await i.json();t.events&&(ye.info("YouTubeSubtitleService","成功捕获字幕数据(fetch)"),Ve.handleSubtitleResponse(e,t));}catch(o){ye.debug("YouTubeSubtitleService","非JSON响应(fetch)");}return t}return n.apply(this,arguments)};}async handleSubtitleResponse(e,t){this.capturedSubtitleUrl=e,xe.subtitle.capturedUrl=e,await this.processSubtitleData(t);}async handleSubtitleRequest(e){this.capturedSubtitleUrl=e,xe.subtitle.capturedUrl=e,setTimeout(()=>{this.downloadCapturedSubtitle();},o);}async processSubtitleData(e){await _e.measureAsync("YouTube字幕处理",async()=>{try{const t=this.getVideoInfo();xe.setVideoInfo(t);const n=xe.startRequest();if(!n.success)return void("已有缓存"===n.reason&&this.useCachedData());xe.setBallStatus("loading"),fe.emit(E,t);const o=this.parseYouTubeSubtitle(e);if(0===o.length)throw new Error("解析出的字幕为空");xe.setSubtitleData(o),xe.setBallStatus("active"),fe.emit(k,o,xe.getVideoKey()),ye.success("YouTubeSubtitleService",`字幕处理成功,共 ${o.length} 条`);}catch(t){ye.error("YouTubeSubtitleService","字幕处理失败:",t),xe.setBallStatus("error"),fe.emit(I,t.message);}finally{xe.finishRequest();}});}async downloadCapturedSubtitle(){this.capturedSubtitleUrl&&await _e.measureAsync("YouTube字幕下载",async()=>{try{const e=this.getVideoInfo();xe.setVideoInfo(e);const t=xe.startRequest();if(!t.success)return void("已有缓存"===t.reason&&this.useCachedData());xe.setBallStatus("loading"),fe.emit(E,e);const n=await this.fetchSubtitle(this.capturedSubtitleUrl),o=this.parseYouTubeSubtitle(n);xe.setSubtitleData(o),xe.setBallStatus("active"),fe.emit(k,o,xe.getVideoKey()),ye.success("YouTubeSubtitleService",`字幕获取成功,共 ${o.length} 条`);}catch(e){ye.error("YouTubeSubtitleService","字幕获取失败:",e),xe.setBallStatus("error"),fe.emit(I,e.message);}finally{xe.finishRequest();}});}getVideoInfo(){const e=new URLSearchParams(window.location.search).get("v"),t=document.querySelector("video");return {platform:"youtube",videoId:e,bvid:`yt_${e}`,cid:e,aid:e,title:document.querySelector("h1.title yt-formatted-string")?.textContent||document.querySelector("h1 yt-formatted-string")?.textContent||document.title.replace(" - YouTube",""),url:window.location.href,duration:t?.duration||0}}async fetchSubtitle(e){return new Promise((t,n)=>{GM_xmlhttpRequest({method:"GET",url:e,headers:{Accept:"application/json, text/plain, */*","Accept-Language":"zh-CN,zh;q=0.9,en;q=0.8","User-Agent":navigator.userAgent},onload:e=>{if(200===e.status)try{const n=JSON.parse(e.responseText);t(n);}catch(o){n(new Error("解析字幕数据失败"));}else n(new Error(`请求失败: ${e.status}`));},onerror:()=>{n(new Error("网络请求失败"));}});})}parseYouTubeSubtitle(e){const t=[];return e?"pb3"===e.wireMagic&&e.events?this.parseJSON3Format(e):e.events?this.parseEventsFormat(e.events):(ye.warn("YouTubeSubtitleService","未知的字幕格式"),t):(ye.warn("YouTubeSubtitleService","字幕数据为空"),t)}parseJSON3Format(e){const t=[],n=e.events||[];let o=null;return n.forEach(e=>{if((1!==e.id||e.segs)&&e.segs&&e.segs.length>0){const n=e.segs.map(e=>e.utf8||"").join("").replace(/\n/g," ").trim();if(!n||this.isSkippableText(n))return;1===e.aAppend&&o?(o.content+=" "+n,o.to=(e.tStartMs+(e.dDurationMs||0))/1e3):(o&&t.push(o),o={index:t.length,from:(e.tStartMs||0)/1e3,to:((e.tStartMs||0)+(e.dDurationMs||5e3))/1e3,content:n,raw:e});}}),o&&t.push(o),t.sort((e,t)=>e.from-t.from),t.forEach((e,t)=>{e.index=t;}),ye.debug("YouTubeSubtitleService",`解析JSON3格式,共 ${t.length} 条字幕`),t}parseEventsFormat(e){const t=[];return e.forEach((e,n)=>{if(!e.segs||0===e.segs.length)return;const o=e.segs.map(e=>e.utf8||e.text||"").join("").trim();if(!o||this.isSkippableText(o))return;const i=e.tStartMs||e.start||0,s=i+(e.dDurationMs||e.duration||5e3);t.push({index:n,from:i/1e3,to:s/1e3,content:o,raw:e});}),t.sort((e,t)=>e.from-t.from),t.forEach((e,t)=>{e.index=t;}),ye.debug("YouTubeSubtitleService",`解析Events格式,共 ${t.length} 条字幕`),t}isSkippableText(e){return [/^\[.*\]$/,/^\(.*\)$/,/^♪+$/,/^\.{3,}$/].some(t=>t.test(e))}useCachedData(){const e=xe.getSubtitleData();e&&(xe.setBallStatus("active"),fe.emit(k,e,xe.getVideoKey()),ye.info("YouTubeSubtitleService","使用缓存的字幕数据"));}async manualFetchSubtitle(){const e=new URLSearchParams(window.location.search).get("v");if(!e)throw new Error("无法获取视频ID");const t=["zh-Hans","zh-CN","zh","en","en-US"];for(const o of t)try{const t=`https://www.youtube.com/api/timedtext?v=${e}&lang=${o}&fmt=json3`,n=await this.fetchSubtitle(t);if(n&&n.events){const e=this.parseYouTubeSubtitle(n);if(e.length>0)return xe.setSubtitleData(e),xe.setBallStatus("active"),fe.emit(k,e,xe.getVideoKey()),ye.success("YouTubeSubtitleService",`成功获取${o}字幕`),e}}catch(n){ye.debug("YouTubeSubtitleService",`尝试获取${o}字幕失败`);}throw new Error("无法获取任何语言的字幕")}async checkSubtitleAvailability(){const e=document.querySelector(".ytp-subtitles-button");if(e){const t="true"===e.getAttribute("aria-disabled");if(e.getAttribute("aria-pressed"),!t)return ye.info("YouTubeSubtitleService","检测到字幕按钮可用"),true}return ye.info("YouTubeSubtitleService","未检测到可用字幕"),false}async autoEnableSubtitle(){const e=document.querySelector(".ytp-subtitles-button");if(e){"true"===e.getAttribute("aria-pressed")||(e.click(),ye.info("YouTubeSubtitleService","已自动启用字幕"),await Be(1e3),await this.selectChineseSubtitle());}}async selectChineseSubtitle(){const e=document.querySelector(".ytp-settings-button");if(e){e.click(),await Be(300);const t=Array.from(document.querySelectorAll(".ytp-menuitem")).find(e=>e.textContent.includes("字幕")||e.textContent.includes("Subtitle"));if(t){t.click(),await Be(300);const n=Array.from(document.querySelectorAll(".ytp-menuitem")).find(e=>e.textContent.includes("中文")||e.textContent.includes("Chinese")||e.textContent.includes("简体"));n&&(n.click(),ye.info("YouTubeSubtitleService","已选择中文字幕")),e.click();}}}reset(){this.capturedSubtitleUrl=null,xe.subtitle.capturedUrl=null,ye.info("YouTubeSubtitleService","字幕服务已重置");}};const je=new class{constructor(){this.currentPlatform=null,this.adapter=null,this.subtitleService=null;}init(){return this.detectPlatform(),this.setupAdapter(),this.setupSubtitleService(),ye.info("PlatformService",`当前平台: ${this.currentPlatform}`),this}detectPlatform(){const e=window.location.hostname;return e.includes("bilibili.com")?this.currentPlatform="bilibili":e.includes("youtube.com")||e.includes("youtu.be")?this.currentPlatform="youtube":this.currentPlatform="unknown",this.currentPlatform}setupAdapter(){switch(this.currentPlatform){case "bilibili":this.adapter=new Ce;break;case "youtube":this.adapter=new Ae;break;default:ye.warn("PlatformService","未知平台,使用默认适配器"),this.adapter=null;}}setupSubtitleService(){switch(this.currentPlatform){case "bilibili":this.subtitleService=Re;break;case "youtube":this.subtitleService=Ve;break;default:this.subtitleService=null;}}getPlatform(){return this.currentPlatform}getAdapter(){return this.adapter}getSubtitleService(){return this.subtitleService}isSupported(){return "unknown"!==this.currentPlatform&&null!==this.adapter}isVideoPage(){return !!this.adapter&&this.adapter.isVideoPage()}getVideoId(){return this.adapter?"bilibili"===this.currentPlatform?this.adapter.getBvid():"youtube"===this.currentPlatform?this.adapter.getVideoId():null:null}getVideoInfo(){const e=this.getVideoId(),t=document.querySelector("video");let n="",o=window.location.href,i="";return "bilibili"===this.currentPlatform?(n=document.querySelector("h1.video-title")?.textContent||document.querySelector(".video-info-title")?.textContent||document.title,i=document.querySelector(".up-name")?.textContent||document.querySelector(".username")?.textContent||""):"youtube"===this.currentPlatform&&(n=document.querySelector("h1.title yt-formatted-string")?.textContent||document.querySelector("#title h1")?.textContent||document.title.replace(" - YouTube",""),i=document.querySelector("#channel-name yt-formatted-string")?.textContent||document.querySelector(".ytd-channel-name a")?.textContent||""),{platform:this.currentPlatform,videoId:e,bvid:"bilibili"===this.currentPlatform?e:`${this.currentPlatform}_${e}`,cid:e,aid:e,title:n.trim(),creator:i.trim(),url:o,duration:t?.duration||0,currentTime:t?.currentTime||0}}getFeatures(){const e={subtitle:true,ai:true,notion:true,screenshot:true,notes:true,speed:true},t={bilibili:{...e,quality:true,widescreen:true,danmaku:true},youtube:{...e,sponsorblock:true,adblock:true,chapters:true},unknown:{subtitle:false,ai:false,notion:false,screenshot:true,notes:true,speed:true}};return t[this.currentPlatform]||t.unknown}isFeatureAvailable(e){return  true===this.getFeatures()[e]}getSelectors(){return {bilibili:{video:"video",subtitleButton:".bpx-player-ctrl-subtitle",settingsButton:".bpx-player-ctrl-setting",speedButton:".bpx-player-ctrl-playbackrate",fullscreenButton:".bpx-player-ctrl-full",progressBar:".bpx-player-progress-wrap",currentTime:".bpx-player-ctrl-time-current",duration:".bpx-player-ctrl-time-duration"},youtube:{video:"video.html5-main-video",subtitleButton:".ytp-subtitles-button",settingsButton:".ytp-settings-button",speedButton:".ytp-playback-speed-button",fullscreenButton:".ytp-fullscreen-button",progressBar:".ytp-progress-bar",currentTime:".ytp-time-current",duration:".ytp-time-duration"}}[this.currentPlatform]||{}}async platformInit(){"youtube"===this.currentPlatform?await this.initYouTube():"bilibili"===this.currentPlatform&&await this.initBilibili();}async initYouTube(){if(ye.info("PlatformService","初始化YouTube平台功能"),await this.waitForElement(".ytp-subtitles-button",5e3),this.subtitleService){await this.subtitleService.checkSubtitleAvailability()&&ye.info("PlatformService","YouTube视频有字幕可用");}}async initBilibili(){ye.info("PlatformService","初始化B站平台功能"),await this.waitForElement(".bpx-player-ctrl-subtitle",5e3);}async waitForElement(e,t=5e3){const n=Date.now();for(;Date.now()-n<t;){const t=document.querySelector(e);if(t)return t;await new Promise(e=>setTimeout(e,100));}return null}observePlatformChange(e){let t=window.location.href;new MutationObserver(()=>{const n=window.location.href;if(n!==t){t=n;const o=this.currentPlatform;this.init(),o!==this.currentPlatform&&(ye.info("PlatformService",`平台切换: ${o} -> ${this.currentPlatform}`),e(this.currentPlatform,o));}}).observe(document.body,{childList:true,subtree:true}),"youtube"===this.currentPlatform&&document.addEventListener("yt-navigate-finish",()=>{this.init(),e(this.currentPlatform);});}},Fe=function(){const e="undefined"!=typeof document&&document.createElement("link").relList;return e&&e.supports&&e.supports("modulepreload")?"modulepreload":"preload"}(),Ue={};const He=new class{constructor(){this.log=$e.createModuleLogger("NotesService"),this.blueDot=null,this.blueDotHideTimeout=null,this.savedSelectionText="",this.selectionTimeout=null;}init(){this.log.debug("初始化笔记服务...");try{this.createBlueDot(),this.initSelectionListener(),this.log.success("笔记服务初始化成功");}catch(e){this.log.error("初始化失败:",e);}}getAllNotes(){try{const e=localStorage.getItem(h);return e?JSON.parse(e):[]}catch(e){return this.log.error("读取笔记数据失败:",e),[]}}saveNotes(e){try{const n=JSON.stringify(e);new Blob([n]).size>g&&(this.log.warn("笔记存储空间过大,执行自动清理"),e=this.cleanupNotes(e));try{localStorage.setItem(h,JSON.stringify(e));}catch(t){this.log.error("存储配额超限,强制清理旧数据"),e=this.forceCleanupNotes(e),localStorage.setItem(h,JSON.stringify(e));}}catch(n){if(this.log.error("保存笔记数据失败:",n),"QuotaExceededError"===n.name){this.log.error("存储配额已满,清理旧数据后重试");const t=this.forceCleanupNotes(e);try{localStorage.setItem(h,JSON.stringify(t)),this.log.success("清理后保存成功");}catch(o){throw this.log.error("清理后仍然失败:",o),o}}throw n}}cleanupNotes(e){const t=e.filter(e=>"screenshot"===e.type),n=e.filter(e=>"screenshot"!==e.type),o=t.slice(0,m),i=n.slice(0,b);return this.log.debug(`清理笔记: 保留 ${o.length}/${t.length} 个截图, ${i.length}/${n.length} 条文本`),[...o,...i].sort((e,t)=>t.timestamp-e.timestamp)}forceCleanupNotes(e){const t=e.filter(e=>"screenshot"===e.type),n=e.filter(e=>"screenshot"!==e.type),o=t.slice(0,5),i=n.slice(0,50);return this.log.warn(`强制清理: 保留 ${o.length}/${t.length} 个截图, ${i.length}/${n.length} 条文本`),[...o,...i].sort((e,t)=>t.timestamp-e.timestamp)}addNote(e,t){try{let n;if("object"==typeof e){const t=e;n={id:Date.now()+Math.random().toString(36).substr(2,9),content:t.content.trim(),url:t.url||window.location.href,createdAt:Date.now(),type:t.type||"text",...t},n.timestamp||(n.timestamp=n.createdAt),this.log.info(`添加新笔记(${n.type}),内容: ${t.content}`);}else n={id:Date.now()+Math.random().toString(36).substr(2,9),content:e.trim(),url:t,timestamp:Date.now(),type:"text"},this.log.info(`添加新笔记,内容长度: ${e.length}`);let o=this.getAllNotes();if("screenshot"===n.type){const e=o.filter(e=>"screenshot"===e.type);if(e.length>=m){this.log.warn(`截图数量超过限制(${m}),删除最旧的截图`);const t=e[e.length-1];o=o.filter(e=>e.id!==t.id);}}return o.unshift(n),this.saveNotes(o),this.log.success(`笔记已保存,当前总数: ${o.length}`),n}catch(n){throw this.log.error("添加笔记失败:",n),("QuotaExceededError"===n.name||n.message?.includes("exceeded"))&&(this.log.error("存储空间不足,请清理部分笔记"),window.notification&&window.notification.error("存储空间不足,已自动清理部分旧笔记")),n}}addAISummary(e){try{const t={id:Date.now()+Math.random().toString(36).substr(2,9),type:"ai-summary",createdAt:Date.now(),timestamp:Date.now(),summary:e.summary||"",segments:e.segments||[],videoInfo:e.videoInfo||{},videoBvid:e.videoBvid||""};let n=this.getAllNotes();return n.unshift(t),this.saveNotes(n),this.log.success("AI总结已保存"),t}catch(t){throw this.log.error("添加AI总结失败:",t),t}}addScreenshotToSummary(e,t){try{const n=this.getAllNotes(),o=n.findIndex(t=>t.id===e);if(-1===o)throw new Error("笔记不存在");const i=n[o];if("ai-summary"!==i.type||!i.segments)throw new Error("笔记不是AI总结类型或没有时间戳段落");i.screenshots||(i.screenshots=[]);const s=t.videoTimestamp;let r=-1;for(let e=0;e<i.segments.length;e++){const t=i.segments[e];if(this.parseTimestamp(t.timestamp)>s){r=e;break}}return -1===r&&(r=i.segments.length),i.screenshots.push({...t,segmentIndex:r,addedAt:Date.now()}),this.saveNotes(n),this.log.success("截图已添加到总结笔记"),i}catch(n){throw this.log.error("添加截图到总结失败:",n),n}}parseTimestamp(e){const t=e.split(":").map(e=>parseInt(e,10));return 2===t.length?60*t[0]+t[1]:3===t.length?3600*t[0]+60*t[1]+t[2]:0}deleteNote(e){const t=this.getAllNotes().filter(t=>t.id!==e);this.saveNotes(t);}deleteNotes(e){const t=this.getAllNotes().filter(t=>!e.includes(t.id));this.saveNotes(t);}getGroupedNotes(){const e=this.getAllNotes(),t={};return e.forEach(e=>{const n=e.createdAt||e.timestamp,o=this.formatDate(n);t[o]||(t[o]=[]),t[o].push(e);}),Object.keys(t).sort((e,n)=>{const o=t[e][0].createdAt||t[e][0].timestamp;return (t[n][0].createdAt||t[n][0].timestamp)-o}).map(e=>({date:e,notes:t[e]}))}formatDate(e){const t=new Date(e),n=new Date,o=new Date(n);if(o.setDate(o.getDate()-1),t.toDateString()===n.toDateString())return "今天";if(t.toDateString()===o.toDateString())return "昨天";return `${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`}formatTime(e){const t=new Date(e);return `${String(t.getHours()).padStart(2,"0")}:${String(t.getMinutes()).padStart(2,"0")}`}createBlueDot(){if(this.log.debug("创建笔记保存点元素..."),this.blueDot)return this.log.debug("笔记保存点元素已存在"),this.blueDot;try{return this.blueDot=document.createElement("div"),this.blueDot.id="note-saver-blue-dot",this.blueDot.innerHTML='\n        <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="20" height="20">\n          <path d="M20.7 5.2c.4.4.4 1.1 0 1.6l-1 1-3.3-3.3 1-1c.4-.4 1.1-.4 1.6 0l1.7 1.7zm-3.3 2.3L6.7 18.2c-.2.2-.4.3-.7.3H3c-.6 0-1-.4-1-1v-3c0-.3.1-.5.3-.7L13 3.1l3.3 3.3z" stroke="#feebea" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="rgba(0,0,0,0.7)"/>\n        </svg>\n      ',this.blueDot.style.cssText="\n        position: absolute;\n        cursor: pointer;\n        z-index: 2147483647;\n        display: none;\n        transition: transform 0.2s, filter 0.2s;\n        pointer-events: auto;\n      ",this.blueDot.addEventListener("mouseenter",()=>{this.log.debug("鼠标进入保存点"),this.blueDot.style.transform="scale(1.15)",this.blueDot.style.filter="drop-shadow(0 2px 4px rgba(254, 235, 234, 0.5))";}),this.blueDot.addEventListener("mouseleave",()=>{this.log.debug("鼠标离开保存点"),this.blueDot.style.transform="scale(1)",this.blueDot.style.filter="none";}),this.blueDot.addEventListener("click",e=>this.handleBlueDotClick(e)),document.body.appendChild(this.blueDot),this.log.debug("笔记保存点元素已创建并添加到body"),this.blueDot}catch(e){return this.log.error("创建笔记保存点元素失败:",e),null}}showBlueDot(e,t){this.log.debug(`显示保存点在位置 (${e}, ${t})`);try{const n=this.createBlueDot();if(!n)return void this.log.error("无法获取保存点元素");n.style.left=`${e}px`,n.style.top=`${t}px`,n.style.display="block",this.blueDotHideTimeout&&clearTimeout(this.blueDotHideTimeout),this.blueDotHideTimeout=setTimeout(()=>{this.hideBlueDot(),this.savedSelectionText="";},f);}catch(n){ye.error("NotesService","✗ 显示保存点失败:",n);}}hideBlueDot(){try{this.blueDot&&(this.blueDot.style.display="none"),this.blueDotHideTimeout&&(clearTimeout(this.blueDotHideTimeout),this.blueDotHideTimeout=null);}catch(e){ye.error("NotesService","✗ 隐藏保存点失败:",e);}}handleBlueDotClick(e){ye.info("NotesService","保存点被点击 - 保存笔记");try{if(e&&(e.preventDefault(),e.stopPropagation()),this.savedSelectionText){this.addNote(this.savedSelectionText,window.location.href);this.savedSelectionText="";const e=window.getSelection();e.rangeCount>0&&e.removeAllRanges();}else ye.warn("NotesService","⚠ 没有保存的选中文本");this.hideBlueDot();}catch(t){ye.error("NotesService","✗ 处理保存点点击失败:",t);}}initSelectionListener(){ye.debug("NotesService","初始化文本选择监听器...");try{document.addEventListener("mouseup",e=>{const t=e.target.closest(".section-item"),n=e.target.closest(".time-btn, .segment-item, .ai-segments-section");if(t||n)return ye.debug("NotesService","点击的是时间戳段落,忽略文字选择"),void this.hideBlueDot();this.selectionTimeout&&clearTimeout(this.selectionTimeout);const o=e.clientX,i=e.clientY;this.selectionTimeout=setTimeout(()=>{try{const e=window.getSelection(),t=e.toString().trim();if(t&&e.rangeCount>0){ye.debug("NotesService",`检测到文本选择: "${t.substring(0,30)}${t.length>30?"...":""}"`),this.savedSelectionText=t;const e=o+window.scrollX+10,n=i+window.scrollY+10;this.showBlueDot(e,n);}else this.savedSelectionText="",this.hideBlueDot();}catch(e){ye.error("NotesService","✗ 处理文本选择失败:",e);}},100);}),document.addEventListener("mousedown",e=>{const t=e.target.closest(".section-item"),n=e.target.closest(".time-btn, .segment-item, .ai-segments-section");t||n?ye.debug("NotesService","mousedown 在段落元素上,忽略"):this.blueDot&&(e.target===this.blueDot||this.blueDot.contains(e.target))||(this.savedSelectionText="",this.hideBlueDot());}),ye.debug("NotesService","✓ 文本选择监听器已初始化");}catch(e){ye.error("NotesService","✗ 初始化文本选择监听器失败:",e);}}saveSubtitleNote(e){return this.addNote(e,window.location.href)}};const Ke=new class{constructor(){this.toastElement=null,this.init();}init(){this.toastElement=document.createElement("div"),this.toastElement.className="notion-toast";}showToast(e,t=c){this.toastElement.textContent=e,document.body.appendChild(this.toastElement),setTimeout(()=>this.toastElement.classList.add("show"),10),setTimeout(()=>{this.toastElement.classList.remove("show"),setTimeout(()=>{this.toastElement.parentNode&&document.body.removeChild(this.toastElement);},300);},t);}success(e){this.showToast(e);}warning(e){this.showToast(e);}error(e,t=false){this.showToast(e,3e3),t&&alert(e);}info(e){this.showToast(e);}handleError(e,t="",n=false,o=false){const i=e instanceof Error?e.message:String(e);console.error(`[Error] ${t}:`,e),n||this.error(i,o);}confirm(e){return window.confirm(e)}};const Ye=new class{async sendToNotion({videoInfo:e,aiSummary:t,subtitleData:n,isAuto:o=false}){const i=we.getNotionConfig(),s=we.getNotionContentOptions();if(!i.apiKey)throw new Error("请先配置 Notion API Key");if(s.videoInfo||s.summary||s.segments||s.subtitles){xe.notion.isSending=true,fe.emit(_);try{const r=e?.title||Ne()||"未知视频",a=e?.url||qe()||"",l=e?.creator||Le()||"",c=e?.bvid,d=[];if(s.segments&&t&&t.segments&&t.segments.length>0&&(d.push({object:"block",type:"heading_2",heading_2:{rich_text:[{type:"text",text:{content:"⏱️ 时间戳段落"}}]}}),t.segments.forEach(e=>{d.push({object:"block",type:"toggle",toggle:{rich_text:[{type:"text",text:{content:`[${e.timestamp}] ${e.title}`},annotations:{bold:!0}}],children:[{object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:e.summary}}]}}]}});})),s.summary&&t&&t.markdown){d.length>0&&d.push({object:"block",type:"divider",divider:{}}),d.push({object:"block",type:"heading_2",heading_2:{rich_text:[{type:"text",text:{content:"📊 视频总结"}}]}});const e=this._convertMarkdownToNotionBlocks(t.markdown);d.push(...e);}let u=i.databaseId||i.parentPageId;if(!u)throw new Error("请先配置目标位置(Page ID 或 Database ID)");const p=await this._getDatabaseSchema(i.apiKey,u),h=this._buildProperties(p,e,r,a,l,[],null);let g=xe.getNotionPageId(c);if(g?(await this._updatePage(i.apiKey,g,d),ye.info("[NotionService] ✓ 主页面更新成功")):(g=await this._createPage(i.apiKey,u,h,d),xe.setNotionPageId(c,g),ye.info("[NotionService] ✓ 主页面创建成功")),s.subtitles&&n&&n.length>0){const e=this._formatSubtitleContent(n),t=await this._createSubtitlePage(i.apiKey,g,`${r} - 完整字幕`,e),o=[];o.push({object:"block",type:"divider",divider:{}}),o.push({object:"block",type:"heading_2",heading_2:{rich_text:[{type:"text",text:{content:"📝 字幕内容"}}]}}),o.push({object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:"查看完整字幕: "}},{type:"mention",mention:{type:"page",page:{id:t}},annotations:{bold:!0,color:"blue"}},{type:"text",text:{content:` (共 ${n.length} 条)`},annotations:{italic:!0,color:"gray"}}]}}),await this.appendToPage(i.apiKey,g,o),ye.info("[NotionService] ✓ 字幕子页面创建成功");}xe.notion.isSending=!1,fe.emit(M),o?ye.info("NotionService",`自动发送完成: ${r}`):Ke.success("已成功发送到Notion");}catch(r){if(xe.notion.isSending=false,fe.emit(P,r.message),!o)throw r;ye.error("NotionService","自动发送失败:",r.message);}}else ye.warn("NotionService","没有选择任何要发送的内容");}async sendComplete(e,t,n){return this.sendToNotion({videoInfo:n,aiSummary:t,subtitleData:e,isAuto:false})}async sendAISummaryWithVideoInfo(e,t){const n=xe.getSubtitleData();return this.sendToNotion({videoInfo:t,aiSummary:e,subtitleData:n,isAuto:true})}async queryVideoPage(e,t,n){if(!e||!t||!n)return null;const o={filter:{property:"BV号",rich_text:{contains:n}},sorts:[{timestamp:"created_time",direction:"descending"}],page_size:1};return new Promise(n=>{GM_xmlhttpRequest({method:"POST",url:`${ee}/databases/${t}/query`,headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json","Notion-Version":Z},data:JSON.stringify(o),onload:e=>{if(200===e.status){const t=JSON.parse(e.responseText);t.results&&t.results.length>0?n(t.results[0].id):n(null);}else n(null);},onerror:()=>{n(null);}});})}async createDatabase(e,t){const n={parent:{type:"page_id",page_id:t},title:[{type:"text",text:{content:"📺 Bilibili 字幕收藏"}}],properties:{"标题":{title:{}},"BV号":{rich_text:{}},"创作者":{rich_text:{}},"视频链接":{url:{}},"收藏时间":{date:{}},"字幕条数":{number:{}},"状态":{select:{options:[{name:"未总结",color:"gray"},{name:"已总结",color:"green"}]}},"总结":{rich_text:{}}}};return new Promise((t,o)=>{GM_xmlhttpRequest({method:"POST",url:`${ee}/databases`,headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json","Notion-Version":Z},data:JSON.stringify(n),onload:e=>{if(200===e.status){const n=JSON.parse(e.responseText);t(n.id);}else {const t=this._parseNotionError(e);o(t);}},onerror:e=>{o(new Error("网络请求失败"));}});})}_getDatabaseSchema(e,t){return new Promise((n,o)=>{GM_xmlhttpRequest({method:"GET",url:`${ee}/databases/${t}`,headers:{Authorization:`Bearer ${e}`,"Notion-Version":Z},onload:e=>{if(200===e.status){const t=JSON.parse(e.responseText);n(t.properties);}else {const t=this._parseNotionError(e);o(t);}},onerror:()=>{o(new Error("获取数据库结构失败"));}});})}_createPage(e,t,n,o){const i={parent:{database_id:t},properties:n,children:o};return new Promise((t,n)=>{GM_xmlhttpRequest({method:"POST",url:`${ee}/pages`,headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json","Notion-Version":Z},data:JSON.stringify(i),onload:e=>{if(200===e.status){const n=JSON.parse(e.responseText);t(n.id);}else {const t=this._parseNotionError(e);n(t);}},onerror:()=>{n(new Error("创建页面失败"));}});})}_createChildPage(e,t,n,o){const i={parent:{page_id:t},properties:{title:{title:[{text:{content:n}}]}},children:o};return new Promise((t,o)=>{GM_xmlhttpRequest({method:"POST",url:`${ee}/pages`,headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json","Notion-Version":Z},data:JSON.stringify(i),onload:e=>{if(200===e.status){const o=JSON.parse(e.responseText);ye.info("[NotionService] ✓ 子页面创建成功:",n),t(o.id);}else {const t=this._parseNotionError(e);o(t);}},onerror:()=>{o(new Error("创建子页面失败"));}});})}async appendToPage(e,t,n){const o=95;if(n.length<=o)return this._appendBatch(e,t,n);ye.info(`[NotionService] 需要分批发送,总共 ${n.length} 个块`);for(let s=0;s<n.length;s+=o){const r=n.slice(s,Math.min(s+o,n.length));ye.info(`[NotionService] 发送第 ${Math.floor(s/o)+1} 批,包含 ${r.length} 个块`);try{await this._appendBatch(e,t,r),s+o<n.length&&await new Promise(e=>setTimeout(e,500));}catch(i){throw ye.error(`[NotionService] 第 ${Math.floor(s/o)+1} 批发送失败:`,i),i}}}async _appendBatch(e,t,n){return new Promise((o,i)=>{GM_xmlhttpRequest({method:"PATCH",url:`${ee}/blocks/${t}/children`,headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json","Notion-Version":Z},data:JSON.stringify({children:n}),onload:e=>{if(200===e.status)o();else {const t=this._parseNotionError(e);i(t);}},onerror:()=>{i(new Error("追加内容失败"));}});})}async getPageBlocks(e,t){return new Promise((n,o)=>{GM_xmlhttpRequest({method:"GET",url:`${ee}/blocks/${t}/children`,headers:{Authorization:`Bearer ${e}`,"Notion-Version":Z},onload:e=>{if(200===e.status){const t=JSON.parse(e.responseText);n(t.results||[]);}else {const t=this._parseNotionError(e);o(t);}},onerror:()=>{o(new Error("获取页面内容失败"));}});})}async sendAISummary(e){const t=we.getNotionConfig(),n=xe.getVideoInfo(),o=n?.bvid;if(!o)throw new Error("无效的视频信息");let i=xe.getNotionPageId(o);if(!i&&t.databaseId&&(i=await this.queryVideoPage(t.apiKey,t.databaseId,o),i&&xe.setNotionPageId(o,i)),!i)return void console.log("[NotionService] 未找到页面,AI总结将在下次发送字幕时自动包含");const s=this._buildAISummaryBlocks(e);console.log("[NotionService] 开始验证blocks结构...");for(let r=0;r<s.length;r++){const e=s[r];if(!e||"object"!=typeof e)throw console.error("[NotionService] Block at index",r,"is not an object:",e),new Error(`Block at index ${r} is not an object`);if(!e.object||"block"!==e.object)throw console.error("[NotionService] Block at index",r,"missing or invalid object property:",e),new Error(`Block at index ${r} missing object: 'block' property`);if(!e.type||"string"!=typeof e.type)throw console.error("[NotionService] Block at index",r,"missing or invalid type:",e),new Error(`Block at index ${r} missing valid type property`);if(!e[e.type])throw console.error("[NotionService] Block at index",r,"missing type-specific property for type",e.type,":",e),new Error(`Block at index ${r} is missing ${e.type} property`);const t=e[e.type];if(t&&void 0!==t.rich_text){if(!Array.isArray(t.rich_text))throw console.error("[NotionService] Block at index",r,"has invalid rich_text (not an array):",t.rich_text),new Error(`Block at index ${r} has invalid rich_text property`);t.rich_text.forEach((e,t)=>{if(!e||!e.type||!e.text||"string"!=typeof e.text.content)throw console.error("[NotionService] Block at index",r,"has invalid rich_text element at",t,":",e),new Error(`Block at index ${r} has invalid rich_text element at ${t}`)});}console.log("[NotionService] Block",r,"validated successfully:",e.type);}console.log("[NotionService] 所有blocks验证通过,准备发送",s.length,"个blocks到Notion"),await this.appendToPage(t.apiKey,i,s),console.log("[NotionService] AI总结已追加到Notion页面末尾");}_buildAISummaryBlocks(e){const t=[];try{if(t.push({object:"block",type:"divider",divider:{}}),t.push({object:"block",type:"heading_2",heading_2:{rich_text:[{type:"text",text:{content:"🤖 AI总结"}}]}}),e&&e.markdown){const n=String(e.markdown||"").split("\n").filter(e=>null!=e);let o=[],i=0;const s=10,r=1500;n.forEach((e,n)=>{const a=String(e||"").trim();!a&&o.length>0?(t.push({object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:o.join("\n")}}]}}),o=[],i=0):o.length>=s||i+a.length>r?(o.length>0&&t.push({object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:o.join("\n")}}]}}),o=a?[a]:[],i=a.length):a&&(o.push(a),i+=a.length);}),o.length>0&&t.push({object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:o.join("\n")}}]}});}if(e&&e.segments&&Array.isArray(e.segments)&&e.segments.length>0){t.push({object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:" "}}]}}),t.push({object:"block",type:"heading_3",heading_3:{rich_text:[{type:"text",text:{content:"⏱️ 时间戳段落"}}]}});const n=20;e.segments.slice(0,n).forEach(e=>{if(e&&"object"==typeof e){const n=String(e.timestamp||"00:00"),o=String(e.title||"未知标题"),i=String(e.summary||""),s=`${n} - ${o}${i?": "+i:""}`;s&&"string"==typeof s&&t.push({object:"block",type:"bulleted_list_item",bulleted_list_item:{rich_text:[{type:"text",text:{content:s}}]}});}}),e.segments.length>n&&t.push({object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:`... 还有 ${e.segments.length-n} 个时间戳段落未显示`},annotations:{italic:!0}}]}});}const n=t.filter(e=>e&&"object"==typeof e?e.type&&e.object?!!e[e.type]||(console.warn("[NotionService] Block缺少类型属性",e.type,":",e),!1):(console.warn("[NotionService] Block缺少必要属性:",e),!1):(console.warn("[NotionService] 发现无效的block:",e),!1));return console.log("[NotionService] 构建AI总结blocks完成,共",n.length,"个有效blocks"),n.forEach((e,t)=>{const n=e.type;["paragraph","heading_1","heading_2","heading_3","bulleted_list_item","numbered_list_item","quote","to_do","toggle","callout"].includes(n)&&e[n]&&e[n].rich_text&&(Array.isArray(e[n].rich_text)||(e[n].rich_text=[]),e[n].rich_text=e[n].rich_text.filter(e=>e&&"text"===e.type&&e.text&&"string"==typeof e.text.content),0===e[n].rich_text.length&&(e[n].rich_text=[{type:"text",text:{content:" "}}]));}),n}catch(n){return console.error("[NotionService] 构建AI总结blocks失败:",n),[{object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:"AI总结生成失败,请重试"}}]}}]}}_buildPageContent(e,t,n,o,i=null){const s=[{object:"block",type:"heading_2",heading_2:{rich_text:[{type:"text",text:{content:"📹 视频信息"}}]}},{object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:`视频标题:${t}`}}]}},{object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:`BV号:${e.bvid}`}}]}},{object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:`视频链接:${n}`}}]}},{object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:`字幕总数:${o.length} 条`}}]}},{object:"block",type:"divider",divider:{}}];if(i&&(i.markdown||i.segments)){const e=this._buildAISummaryBlocks(i);s.push(...e);}s.push({object:"block",type:"heading_2",heading_2:{rich_text:[{type:"text",text:{content:"📝 字幕内容"}}]}});const r=[];let a="";const l=d;for(let c of o){const e=`${c.content}\n`;a.length+e.length>l?(a&&r.push({type:"text",text:{content:a}}),a=e):a+=e;}if(a&&r.push({type:"text",text:{content:a}}),s.push({object:"block",type:"code",code:{rich_text:r,language:"plain text"}}),s.length>100){ye.warn("[NotionService] blocks数量超过100个,进行截断",s.length);const e=s.slice(0,95);return e.push({object:"block",type:"divider",divider:{}}),e.push({object:"block",type:"callout",callout:{rich_text:[{type:"text",text:{content:`⚠️ 内容被截断:原始内容包含 ${s.length} 个blocks,超过了Notion API的100个blocks限制。`}}],icon:{emoji:"⚠️"}}}),e}return s}_buildProperties(e,t,n,o,i,s,r=null){const a={},l=Object.keys(e).find(t=>"title"===e[t].type);return l&&(a[l]={title:[{text:{content:n}}]}),Object.keys(e).forEach(n=>{const l=e[n].type,c=n.toLowerCase().replace(/\s+/g,"");if(!c.includes("bv")||"rich_text"!==l&&"text"!==l||(a[n]={rich_text:[{type:"text",text:{content:t.bvid||""}}]}),!(c.includes("创作")||c.includes("作者")||c.includes("creator")||c.includes("up主"))||"rich_text"!==l&&"text"!==l||(a[n]={rich_text:[{type:"text",text:{content:i}}]}),c.includes("链接")&&"url"===l&&(a[n]={url:o}),"date"===l&&("日期"===c||c.includes("收藏")||c.includes("添加")||c.includes("创建"))&&(a[n]={date:{start:(new Date).toISOString()}}),(c.includes("条数")||c.includes("数量"))&&"number"===l&&(a[n]={number:s.length}),"状态"===c||"status"===c){const e=xe.getVideoKey(),t=e?xe.getAISummary(e):null;"select"===l||"status"===l?a[n]={[l]:{name:t?"已总结":"未总结"}}:"rich_text"===l&&(a[n]={rich_text:[{type:"text",text:{content:t?"已总结":"未总结"}}]});}if(("总结"===c||"summary"===c)&&null!==r&&"rich_text"===l){let e="";"string"==typeof r?e=r:r&&r.markdown&&(e=r.markdown),e&&(a[n]={rich_text:[{type:"text",text:{content:e.substring(0,u)}}]});}}),a}async sendAISummary(e){const t=we.getNotionConfig(),n=we.getNotionContentOptions();if(!t.apiKey)throw new Error("请先配置 Notion API Key");if(!e)throw new Error("没有总结数据可发送");xe.notion.isSending=true,fe.emit(_);try{const o=xe.getVideoInfo(),i=Ne(),s=qe(),r=Le(),a=o?.bvid;let l=xe.getNotionPageId(a);if(!l){const c=[];n.videoInfo&&(c.push({object:"block",type:"heading_2",heading_2:{rich_text:[{type:"text",text:{content:"📹 视频信息"}}]}}),c.push({object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:i||"未知视频"}}]}}));let d=t.databaseId||t.parentPageId;if(!d)throw new Error("请先配置目标位置(Page ID 或 Database ID)");const u=await this._getDatabaseSchema(t.apiKey,d),p=this._buildProperties(u,o,i,s,r,[],null);if(l=await this._createPage(t.apiKey,d,p,c),xe.setNotionPageId(a,l),ye.info("[NotionService] ✓ 主页面创建成功"),n.segments&&e.segments&&e.segments.length>0){ye.info("[NotionService] 添加时间戳段落...");const n=[];n.push({object:"block",type:"divider",divider:{}}),n.push({object:"block",type:"heading_2",heading_2:{rich_text:[{type:"text",text:{content:"⏱️ 时间戳段落"}}]}}),e.segments.forEach(e=>{n.push({object:"block",type:"toggle",toggle:{rich_text:[{type:"text",text:{content:`[${e.timestamp}] ${e.title}`},annotations:{bold:!0}}],children:[{object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:e.summary}}]}}]}});}),ye.info(`[NotionService] 准备发送 ${n.length} 个段落块`),await this.appendToPage(t.apiKey,l,n),ye.info("[NotionService] ✓ 时间戳段落已添加到主页面");}}if(n.summary&&e.markdown){ye.info("[NotionService] 添加 AI总结内容...");const n=[];n.push({object:"block",type:"divider",divider:{}}),n.push({object:"block",type:"heading_2",heading_2:{rich_text:[{type:"text",text:{content:"🤖 AI总结"}}]}});const o=this._convertMarkdownToNotionBlocks(e.markdown);n.push(...o),await this.appendToPage(t.apiKey,l,n),ye.info("[NotionService] ✓ AI总结已添加到主页面");}xe.notion.isSending=!1,fe.emit(M);}catch(o){throw xe.notion.isSending=false,fe.emit(P,o.message),o}}_convertMarkdownToNotionBlocks(e){const t=[],n=e.split("\n");ye.debug("[NotionService] 开始转换Markdown,共",n.length,"行");let o=[];const i=()=>{if(o.length>0){const e=o.join("\n");t.push({object:"block",type:"paragraph",paragraph:{rich_text:this._parseRichText(e)}}),o=[];}};for(let s=0;s<n.length;s++){const e=n[s].trim();if(!e)continue;if("---"===e||"***"===e||"___"===e){i(),t.push({object:"block",type:"divider",divider:{}});continue}const r=e.match(/^(#{1,3})\s+(.+)$/);if(r){i();const e=r[1].length,n=r[2].trim(),o=`heading_${e}`;t.push({object:"block",type:o,[o]:{rich_text:this._parseRichText(n)}});continue}if(e.match(/^[-*]\s+/)){i();const n=e.substring(2).trim();t.push({object:"block",type:"bulleted_list_item",bulleted_list_item:{rich_text:this._parseRichText(n)}});continue}const a=e.match(/^(\d+)\.\s+(.+)$/);if(a){i();const e=a[2].trim();t.push({object:"block",type:"numbered_list_item",numbered_list_item:{rich_text:this._parseRichText(e)}});continue}if(e.startsWith("> ")){i();const n=e.substring(2).trim();t.push({object:"block",type:"quote",quote:{rich_text:this._parseRichText(n)}});continue}if(e.startsWith("```")){i();const e=[];for(s++;s<n.length&&!n[s].trim().startsWith("```");)e.push(n[s]),s++;t.push({object:"block",type:"code",code:{rich_text:[{type:"text",text:{content:e.join("\n")}}],language:"plain text"}});continue}o.push(e);}return i(),ye.info("[NotionService] ✓ Markdown转换完成,生成",t.length,"个blocks(优化后)"),t}_parseRichText(e){const t=[];let n="",o=0;const i=e.split("\n");for(let s=0;s<i.length;s++){const e=i[s];for(o=0;o<e.length;){if("**"===e.substring(o,o+2)){n&&(t.push({type:"text",text:{content:n}}),n=""),o+=2;let i="";for(;o<e.length&&"**"!==e.substring(o,o+2);)i+=e[o],o++;i&&t.push({type:"text",text:{content:i},annotations:{bold:true}}),o+=2;continue}if("`"===e[o]&&"`"!==e[o+1]){n&&(t.push({type:"text",text:{content:n}}),n=""),o++;let i="";for(;o<e.length&&"`"!==e[o];)i+=e[o],o++;i&&t.push({type:"text",text:{content:i},annotations:{code:true}}),o++;continue}n+=e[o],o++;}n&&(t.push({type:"text",text:{content:n}}),n=""),s<i.length-1&&t.push({type:"text",text:{content:"\n"}});}return 0===t.length?[{type:"text",text:{content:e||""}}]:t}async sendAISummaryWithVideoInfo(e,t){const n=we.getNotionConfig(),o=we.getNotionContentOptions();if(!n.apiKey)throw new Error("请先配置 Notion API Key");if(!e)throw new Error("没有总结数据可发送");ye.info("NotionService",`后台发送AI总结,视频: ${t.bvid}`);try{const i=t.title||"未知视频",s=t.url||"",r=t.bvid;let a=xe.getNotionPageId(r);if(!a){const e=[];o.videoInfo&&(e.push({object:"block",type:"heading_2",heading_2:{rich_text:[{type:"text",text:{content:"📹 视频信息"}}]}}),e.push({object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:i}}]}}));let l=n.databaseId||n.parentPageId;if(!l)throw new Error("请先配置目标位置(Page ID 或 Database ID)");const c=await this._getDatabaseSchema(n.apiKey,l),d=this._buildProperties(c,t,i,s,"",[],null);a=await this._createPage(n.apiKey,l,d,e),xe.setNotionPageId(r,a),ye.info("NotionService","✓ 为后台任务创建主页面成功");}if(o.segments&&e.segments&&e.segments.length>0){const t=[];t.push({object:"block",type:"divider",divider:{}}),t.push({object:"block",type:"heading_2",heading_2:{rich_text:[{type:"text",text:{content:"⏱️ 时间戳段落"}}]}}),e.segments.forEach(e=>{t.push({object:"block",type:"toggle",toggle:{rich_text:[{type:"text",text:{content:`[${e.timestamp}] ${e.title}`},annotations:{bold:!0}}],children:[{object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:e.summary}}]}}]}});}),await this.appendToPage(n.apiKey,a,t),ye.info("NotionService","✓ 后台任务:时间戳段落已添加");}if(o.summary&&e.markdown){const t=[];t.push({object:"block",type:"divider",divider:{}}),t.push({object:"block",type:"heading_2",heading_2:{rich_text:[{type:"text",text:{content:"🤖 AI总结"}}]}});const o=this._convertMarkdownToNotionBlocks(e.markdown);t.push(...o),await this.appendToPage(n.apiKey,a,t),ye.info("NotionService","✓ 后台任务:AI总结已添加");}ye.success("NotionService",`后台任务完成:视频 ${t.bvid} 的AI总结已发送到Notion`);}catch(i){throw ye.error("NotionService",`后台任务失败:视频 ${t.bvid}`,i.message),i}}_formatSubtitleContent(e){return e.map(e=>`[${Me?Me(e.from):e.from}] ${e.content}`).join("\n\n")}async _createSubtitlePage(e,t,n,o){const i=[];for(let a=0;a<o.length;a+=2e3)i.push(o.slice(a,a+2e3));const s=[];i.forEach(e=>{s.push({object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:e}}]}});});const r={parent:{page_id:t},properties:{title:{title:[{type:"text",text:{content:n}}]}},children:s.slice(0,100)};return new Promise((t,n)=>{GM_xmlhttpRequest({method:"POST",url:`${ee}/pages`,headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json","Notion-Version":Z},data:JSON.stringify(r),onload:o=>{if(200===o.status){const n=JSON.parse(o.responseText);s.length>100?this.appendToPage(e,n.id,s.slice(100)).then(()=>{t(n.id);}):t(n.id);}else n(this._parseNotionError(o));},onerror:()=>{n(new Error("网络错误"));}});})}async _updatePage(e,t,n){const o=await this._getPageBlocks(e,t);for(const i of o)"child_page"!==i.type&&await this._deleteBlock(e,i.id);await this.appendToPage(e,t,n);}async _getPageBlocks(e,t){return new Promise((n,o)=>{GM_xmlhttpRequest({method:"GET",url:`${ee}/blocks/${t}/children`,headers:{Authorization:`Bearer ${e}`,"Notion-Version":Z},onload:e=>{if(200===e.status){const t=JSON.parse(e.responseText);n(t.results||[]);}else n([]);},onerror:()=>{n([]);}});})}async _deleteBlock(e,t){return new Promise(n=>{GM_xmlhttpRequest({method:"DELETE",url:`${ee}/blocks/${t}`,headers:{Authorization:`Bearer ${e}`,"Notion-Version":Z},onload:()=>{n();},onerror:()=>{n();}});})}_parseNotionError(e){try{const t=JSON.parse(e.responseText);return "object_not_found"===t.code||t.message?.includes("Could not find")?new Error("找不到指定的Notion页面或数据库,请检查:\n1. ID是否正确\n2. 是否已在Notion中授权该Integration"):new Error(t.message||"未知错误")}catch(t){return new Error(`请求失败: ${e.status}`)}}};const Ge=new class{constructor(){this.log=$e.createModuleLogger("AIService");}async fetchOpenRouterModels(e,t){const n=t.replace("/chat/completions","/models"),o=await fetch(n,{headers:{Authorization:`Bearer ${e}`}});if(!o.ok)throw new Error(`获取模型列表失败: ${o.status}`);return (await o.json()).data||[]}async summarize(e,t=false){return await _e.measureAsync("AI总结",async()=>{try{const n=we.getSelectedAIConfig();if(!n)throw new Error("未找到AI配置,请先在设置中添加配置");if(!n.apiKey||""===n.apiKey.trim())throw new Error('请先配置 AI API Key\n\n请点击右上角设置按钮,选择"AI配置",然后为所选的AI服务商配置API Key');if(!n.url||!n.url.startsWith("http"))throw new Error("API URL格式错误,请在设置中检查配置");if(!n.model||""===n.model.trim())throw new Error("未配置模型,请在设置中选择AI模型");const o=e.map(e=>e.content).join("\n"),i=e.map(e=>{const t=Math.floor(e.from/60),n=Math.floor(e.from%60);return `${`[${t.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}]`} ${e.content}`}).join("\n"),s={"Content-Type":"application/json",Authorization:`Bearer ${n.apiKey}`};n.isOpenRouter&&(s["HTTP-Referer"]=window.location.origin,s["X-Title"]="Bilibili Subtitle Extractor");const r=xe.getVideoInfo(),a=Ne(),l=qe(),c=r?.bvid;t&&c&&ve.clearVideoProcessed(c);const d={bvid:c,cid:r?.cid,aid:r?.aid,title:a,url:l},u=ve.createTask("ai_summary",d,async t=>await this._executeSummaryTask(e,n,s,o,i,t),t);if(!u)return this.log.info("视频已自动处理过,跳过自动任务"),null;fe.emit(T);const p=ve.activeTasks.get(u);if(!p)throw new Error("任务创建失败");for(;"pending"===p.status||"running"===p.status;)await new Promise(e=>setTimeout(e,100));if("completed"===p.status)return p.result;throw p.error?p.error:new Error("任务被取消")}catch(n){throw xe.cancelAISummary(),fe.emit(A,n.message),n}})}async _executeSummaryTask(e,t,n,o,i,s){try{const{videoInfo:e,signal:a}=s,l=(document.querySelector("video")?.duration||0)>300,c=[this._makeAIRequest(t,n,o,t.prompt1||this._getDefaultPrompt1(),"markdown"),this._makeAIRequest(t,n,i,t.prompt2||this._getDefaultPrompt2(),"json")];l&&c.push(this._makeAIRequest(t,n,i,this._getAdDetectionPrompt(),"ads"));const d=await Promise.race([Promise.all(c),new Promise((e,t)=>{a.addEventListener("abort",()=>t(new Error("Task aborted")));})]),u={markdown:d[0],segments:d[1],ads:l?d[2]:[]};if(!u.markdown||!u.segments)throw new Error("AI总结不完整:markdown或segments缺失");this.log.debug("Markdown总结长度:",u.markdown?.length||0),this.log.debug("段落数量:",u.segments?.length||0),this.log.trace("段落详情:",u.segments),0===u.segments.length&&this.log.warn("段落总结为空,请检查AI返回内容"),u.ads&&u.ads.length>0&&(this.log.info("检测到广告段落:",u.ads.length),this._applyAdSegments(u.ads)),xe.finishAISummary(u);try{const t=He.addAISummary({summary:u.markdown,segments:u.segments,videoInfo:e,videoBvid:e.bvid});this.log.success("任务完成,已保存到笔记,笔记ID:",t.id);}catch(r){this.log.error("保存AI总结到笔记失败:",r);}try{const t=we.getNotionConfig();we.getNotionAutoSendEnabled()&&t.apiKey&&e.bvid&&ve.createTask("notion_send_summary",e,async e=>{const t=xe.getSubtitleData();await Ye.sendToNotion({videoInfo:e.videoInfo,aiSummary:u,subtitleData:t,isAuto:!0});},!1);}catch(r){console.error("[AIService] 创建Notion任务失败:",r);}return u}catch(r){throw xe.cancelAISummary(),fe.emit(A,r.message),r}}async _makeAIRequest(e,t,n,o,i){const s={model:e.model,messages:[{role:"user",content:o+n}],stream:"markdown"===i};if("markdown"===i){const n=this._streamingRequest(e.url,t,s),o=await De(n,l,"Markdown总结超时,请稍后重试");return this._cleanMarkdownContent(o)}{const n=await fetch(e.url,{method:"POST",headers:t,body:JSON.stringify(s),signal:xe.ai.abortController?.signal});if(!n.ok){const e=await n.text();throw console.error(`[AIService] ${i} API错误响应:`,e),new Error(`${i}请求失败: ${n.status} ${n.statusText}`)}const o=await n.json(),r=o.choices?.[0]?.message?.content||"";if("json"===i)return this._parseJSONResponse(r);if("ads"===i)return this._parseAdResponse(r)}}_parseJSONResponse(e){this.log.trace("尝试解析JSON响应,原始内容长度:",e?.length||0);try{let n=e.trim();if(n.includes("```json")){const e=n.match(/```json\s*([\s\S]*?)```/);e&&(n=e[1].trim());}else if(n.includes("```")){const e=n.match(/```\s*([\s\S]*?)```/);e&&(n=e[1].trim());}const o=n.match(/\{[\s\S]*\}/);if(o){this.log.trace("找到JSON匹配:",o[0].substring(0,100)+"...");let e,n=o[0];n=n.replace(/\}\s*\{/g,"},{"),n=n.replace(/,\s*\]/g,"]"),n=n.replace(/,\s*\}/g,"}");try{e=JSON.parse(n);}catch(t){this.log.debug("修复后的JSON仍然解析失败,尝试原始内容"),e=JSON.parse(o[0]);}const i=Array.isArray(e.segments)?e.segments:Array.isArray(e)?e:[];if(i.length>0)return this.log.debug("成功解析段落数量:",i.length),i.map(e=>({timestamp:this._normalizeTimestamp(e.timestamp),title:e.title||"",summary:e.summary||""}));this.log.warn("JSON中没有找到segments数组,json内容:",e);}else this.log.warn("响应中没有找到JSON格式内容,原始内容前200字符:",n.substring(0,200));}catch(n){this.log.error("JSON解析失败:",n,"原始内容前200字符:",e?.substring(0,200));}return []}_cleanMarkdownContent(e){if(!e)return "";let t=e.trim();for(;t.includes("```markdown");){const e=t.match(/```markdown\s*([\s\S]*?)```/);if(!e)break;t=t.replace(/```markdown\s*[\s\S]*?```/,e[1].trim()),this.log.trace("从markdown代码块中提取内容");}if(t.startsWith("```")&&t.endsWith("```")){t=t.slice(3,-3).trim();const e=t.split("\n");e[0]&&!e[0].includes(" ")&&e[0].length<20&&(t=e.slice(1).join("\n").trim()),this.log.trace("从代码块中提取内容");}return t=t.replace(/```\s*\n?(#{1,3}\s+[\s\S]*?)```/g,"$1"),t}_normalizeTimestamp(e){if(!e)return "[00:00]";const t=e.replace(/[\[\]]/g,""),n=t.split(":");if(2===n.length){const[e,t]=n;return `[${e.padStart(2,"0")}:${t.padStart(2,"0")}]`}if(3===n.length){const[e,t,o]=n;return `[${(60*parseInt(e)+parseInt(t)).toString().padStart(2,"0")}:${o.padStart(2,"0")}]`}return `[${t}]`}async _streamingRequest(e,t,n){const o=await fetch(e,{method:"POST",headers:t,body:JSON.stringify(n),signal:xe.ai.abortController?.signal});if(!o.ok){const e=await o.text();throw console.error("[AIService] API错误响应:",e),new Error(`API请求失败: ${o.status} ${o.statusText}`)}const i=o.body.getReader(),s=new TextDecoder;let r="";try{for(;;){const{done:e,value:t}=await i.read();if(e)break;const n=s.decode(t).split("\n");for(const o of n)if(o.startsWith("data: ")){const e=o.slice(6);if("[DONE]"===e)continue;try{const t=JSON.parse(e),n=t.choices[0]?.delta?.content;n&&(r+=n,fe.emit($,r));}catch(a){}}}return r}finally{i.releaseLock();}}_getDefaultPrompt1(){return "请用中文总结以下视频字幕内容,使用Markdown格式输出。\n\n要求:\n1. 第一行使用 # 标题,简洁概括视频主题\n2. 第二部分使用 ## TL;DR 作为标题,提供2-3句话的核心摘要\n3. 第三部分使用 --- 分隔线\n4. 后续内容按主题使用 ### 三级标题分段\n5. 使用项目符号 - 列出要点\n6. 不要在总结中包含任何时间戳\n7. 直接输出Markdown内容,不要使用代码块包裹(不要使用```)\n\n字幕内容:\n"}_getDefaultPrompt2(){return '分析以下带时间戳的字幕,提取5-8个关键段落。\n\n重要:\n1. 你的回复必须是一个完整且有效的JSON\n2. 不要有任何其他文字、解释或markdown标记\n3. 直接以{开始,以}结束\n4. 数组元素之间必须用逗号分隔\n\nJSON格式要求(注意逗号):\n{\n  "segments": [\n    {"timestamp":"分钟:秒","title":"标题(10字内)","summary":"内容总结(30-50字)"},\n    {"timestamp":"分钟:秒","title":"标题(10字内)","summary":"内容总结(30-50字)"}\n  ]\n}\n\n正确示例(特别注意元素之间的逗号):\n{"segments":[{"timestamp":"00:15","title":"开场介绍","summary":"主持人介绍今天的主题和嘉宾背景"},{"timestamp":"02:30","title":"核心观点","summary":"讨论技术发展趋势和未来展望"}]}\n\n字幕内容:\n'}_getAdDetectionPrompt(){return '分析以下视频字幕,识别是否存在对特定产品或品牌的硬性广告推广介绍。\n\n判断标准:\n1. 明确提及产品名称、品牌或服务\n2. 包含推广性描述(如功能介绍、优惠信息、购买链接等)\n3. 明显的商业推广意图\n\n如果没有检测到广告,返回:\n{"hasAds":false,"segments":[]}\n\n如果检测到广告,返回广告时间段,格式为:\n{"hasAds":true,"segments":[{"start":"分钟:秒","end":"分钟:秒","product":"产品名称","description":"广告内容简述"}]}\n\n示例:\n{"hasAds":true,"segments":[{"start":"02:15","end":"03:45","product":"某品牌手机","description":"介绍手机功能和购买优惠"}]}\n\n重要:只返回JSON格式,不要有其他文字。\n\n字幕内容:\n'}_parseAdResponse(e){this.log.trace("解析广告检测响应,原始内容长度:",e?.length||0);try{let t=e.trim();if(t.includes("```json")){const e=t.match(/```json\s*([\s\S]*?)```/);e&&(t=e[1].trim());}else if(t.includes("```")){const e=t.match(/```\s*([\s\S]*?)```/);e&&(t=e[1].trim());}const n=t.match(/\{[\s\S]*\}/);if(n){const e=JSON.parse(n[0]);if(e.hasAds&&Array.isArray(e.segments))return this.log.info("检测到广告段落:",e.segments.length),e.segments.map(e=>({segment:[this._parseTimeToSeconds(e.start),this._parseTimeToSeconds(e.end)],category:"sponsor",product:e.product,description:e.description}))}}catch(t){this.log.warn("广告检测解析失败:",t);}return []}_parseTimeToSeconds(e){if(!e)return 0;const t=e.split(":").map(e=>parseInt(e));return 2===t.length?60*t[0]+t[1]:3===t.length?3600*t[0]+60*t[1]+t[2]:0}async _applyAdSegments(e){if(e&&0!==e.length)try{const t=(await function(e,t){let n=Promise.resolve();if(t&&t.length>0){document.getElementsByTagName("link");const e=document.querySelector("meta[property=csp-nonce]"),o=e?.nonce||e?.getAttribute("nonce");n=Promise.allSettled(t.map(e=>{if((e=function(e){return "/"+e}(e))in Ue)return;Ue[e]=!0;const t=e.endsWith(".css"),n=t?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${e}"]${n}`))return;const i=document.createElement("link");return i.rel=t?"stylesheet":Fe,t||(i.as="script"),i.crossOrigin="",i.href=e,o&&i.setAttribute("nonce",o),document.head.appendChild(i),t?new Promise((t,n)=>{i.addEventListener("load",t),i.addEventListener("error",()=>n(new Error(`Unable to preload CSS for ${e}`)));}):void 0}));}function o(e){const t=new Event("vite:preloadError",{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return n.then(t=>{for(const e of t||[])"rejected"===e.status&&o(e.reason);return e().catch(o)})}(async()=>{const{default:e}=await Promise.resolve().then(()=>rt);return {default:e}},void 0)).default;if(t.playerController){const n=[...t.playerController.segments||[],...e.map((e,t)=>({UUID:`ai-ad-${t}`,segment:e.segment,category:"sponsor",votes:100,videoDuration:0,description:`${e.product}: ${e.description}`}))];t.playerController.segments=n,t.playerController.renderProgressMarkers(),this.log.info("已将广告段落添加到进度条标记");}}catch(t){this.log.warn("添加广告段落到标记失败:",t);}}cancelCurrentSummary(){xe.cancelAISummary();}};const Je=new class{constructor(){this.cache=new Map,this.cacheTimeout=5e3;}get(e,t=false){const n=this.cache.get(e);if(!t&&n&&n.timestamp>Date.now()-this.cacheTimeout&&n.element&&document.contains(n.element))return n.element;const o=document.querySelector(e);return o&&this.cache.set(e,{element:o,timestamp:Date.now()}),o}getAll(e,t=false){const n=`all:${e}`,o=this.cache.get(n);if(!t&&o&&o.timestamp>Date.now()-this.cacheTimeout)return o.elements;const i=Array.from(document.querySelectorAll(e));return this.cache.set(n,{elements:i,timestamp:Date.now()}),i}clear(e){e?(this.cache.delete(e),this.cache.delete(`all:${e}`)):this.cache.clear();}clearExpired(){const e=Date.now();for(const[t,n]of this.cache.entries())n.timestamp<=e-this.cacheTimeout&&this.cache.delete(t);}preload(e){e.forEach(e=>{this.get(e,true);});}},We=1.5,Xe=200,Qe=1e3,Ze=10;const et=new class{constructor(){this.state={baseSpeed:1,isRightOptionPressed:false,isTempBoosted:false,lastKeyPressTime:{comma:0,period:0},lastOptionPressTime:0,optionDoubleClickTimer:null},this.observer=null;}init(){this.bindKeyboardEvents(),this.observeMediaElements(),this.applySpeedToExistingMedia();}getMediaElements(){return Je.getAll("video, audio",false)}applySpeed(e){this.getMediaElements().forEach(t=>{t.playbackRate=e,this.showSpeedIndicator(t,e);});const t=new CustomEvent("speed-changed",{detail:{speed:e,baseSpeed:this.state.baseSpeed}});document.dispatchEvent(t);}showSpeedIndicator(e,t){const n=e.parentElement?.querySelector(".speed-indicator");n&&n.remove();const o=document.createElement("div");if(o.className="speed-indicator",o.textContent=`${t.toFixed(2)}x`,o.style.cssText="\n      position: absolute;\n      top: 10px;\n      left: 10px;\n      background: rgba(0, 0, 0, 0.7);\n      color: white;\n      padding: 4px 8px;\n      border-radius: 4px;\n      font-size: 14px;\n      font-family: monospace;\n      z-index: 999999;\n      pointer-events: none;\n      transition: opacity 0.3s;\n    ",e.parentElement){"static"===window.getComputedStyle(e.parentElement).position&&(e.parentElement.style.position="relative"),e.parentElement.appendChild(o),setTimeout(()=>{o.style.opacity="0",setTimeout(()=>{o.remove();},300);},Qe);}}adjustBaseSpeed(e){this.state.baseSpeed=Math.max(.1,Math.min(Ze,this.state.baseSpeed+e)),this.applySpeed(this.calculateFinalSpeed());}setBaseSpeed(e){this.state.baseSpeed=Math.max(.1,Math.min(Ze,e)),this.applySpeed(this.calculateFinalSpeed());}applyTemporaryBoost(){this.state.isTempBoosted=true,this.applySpeed(this.calculateFinalSpeed());}removeTemporaryBoost(){this.state.isTempBoosted=false,this.applySpeed(this.calculateFinalSpeed());}applyPermanentBoost(){this.state.baseSpeed=Math.min(Ze,this.state.baseSpeed*We),this.applySpeed(this.calculateFinalSpeed());}resetToNormalSpeed(){this.state.baseSpeed=1,this.applySpeed(this.calculateFinalSpeed());}setToDoubleSpeed(){this.state.baseSpeed=2,this.applySpeed(this.calculateFinalSpeed());}detectDoubleClick(e){const t=Date.now(),n=this.state.lastKeyPressTime[e];return this.state.lastKeyPressTime[e]=t,t-n<Xe}calculateFinalSpeed(){let e=this.state.baseSpeed;return this.state.isTempBoosted&&(e*=We),Math.min(Ze,e)}bindKeyboardEvents(){document.addEventListener("keydown",e=>this.handleKeyDown(e),true),document.addEventListener("keyup",e=>this.handleKeyUp(e),true);}handleKeyDown(e){if("AltRight"!==e.code||2!==e.location);else if(!this.state.isRightOptionPressed){this.state.isRightOptionPressed=true;const e=Date.now();e-this.state.lastOptionPressTime<Xe?(this.applyPermanentBoost(),this.state.optionDoubleClickTimer&&(clearTimeout(this.state.optionDoubleClickTimer),this.state.optionDoubleClickTimer=null)):this.applyTemporaryBoost(),this.state.lastOptionPressTime=e;}}handleKeyUp(e){"AltRight"!==e.code||2!==e.location||this.state.isRightOptionPressed&&(this.state.isRightOptionPressed=false,this.state.optionDoubleClickTimer=setTimeout(()=>{!this.state.isRightOptionPressed&&this.state.isTempBoosted&&this.removeTemporaryBoost();},Xe));}observeMediaElements(){this.observer=new MutationObserver(()=>{this.getMediaElements().forEach(e=>{const t=this.calculateFinalSpeed();Math.abs(e.playbackRate-t)>.01&&(e.playbackRate=t);});}),this.observer.observe(document.body,{childList:true,subtree:true});}applySpeedToExistingMedia(){this.getMediaElements().forEach(e=>{e.playbackRate=this.state.baseSpeed;});}getCurrentSpeed(){return this.calculateFinalSpeed()}getState(){return {baseSpeed:this.state.baseSpeed,finalSpeed:this.calculateFinalSpeed(),isTempBoosted:this.state.isTempBoosted}}destroy(){ye.debug("SpeedControl","开始清理资源"),this.observer&&(this.observer.disconnect(),this.observer=null),ye.debug("SpeedControl","资源清理完成");}},tt="sponsorblock_settings";const nt=new class{constructor(){this.settings=this.loadSettings();}loadSettings(){const e=GM_getValue(tt,null);return e?JSON.parse(e):{...ce.DEFAULT_SETTINGS}}saveSettings(e){this.settings=e,GM_setValue(tt,JSON.stringify(e));}get(e){return this.settings[e]}set(e,t){this.settings[e]=t,this.saveSettings(this.settings);}getAll(){return {...this.settings}}setAll(e){this.saveSettings(e);}resetToDefaults(){this.saveSettings({...ce.DEFAULT_SETTINGS});}};class ot{constructor(){this.cache=new Map,this.pendingRequests=new Map,this.requestQueue=[],this.isProcessingQueue=false,this.maxConcurrentRequests=2,this.minRequestInterval=200,this.lastRequestTime=0;}async fetchSegments(e){const t=this.cache.get(e);if(t&&Date.now()-t.timestamp<ce.CACHE_EXPIRY)return t.data;if(this.pendingRequests.has(e))return this.pendingRequests.get(e);const n={bvid:e,resolve:null,reject:null,retryCount:0,lastError:null},o=new Promise((e,t)=>{n.resolve=e,n.reject=t;});return this.pendingRequests.set(e,o),this.requestQueue.push(n),this.processQueue(),o.finally(()=>{this.pendingRequests.delete(e);}),o}async processQueue(){if(!this.isProcessingQueue){for(this.isProcessingQueue=true;this.requestQueue.length>0;){if(this.getActiveRequestCount()>=this.maxConcurrentRequests){await this.waitForRequestSlot();continue}const e=this.requestQueue.shift();e&&this.executeRequest(e);}this.isProcessingQueue=false;}}async executeRequest(e){const t=Date.now()-this.lastRequestTime;t<this.minRequestInterval&&await new Promise(e=>setTimeout(e,this.minRequestInterval-t)),this.lastRequestTime=Date.now();try{const t=await this.makeRequestWithRetry(e.bvid,e.retryCount);e.resolve(t);}catch(n){if(e.lastError=n,e.retryCount++,this.shouldRetry(n,e.retryCount)){const t=this.calculateRetryDelay(e.retryCount,n);ye.debug("SponsorBlockAPI",`重试请求 ${e.bvid} (第${e.retryCount}次), 延迟${t}ms`),setTimeout(()=>{this.requestQueue.unshift(e),this.processQueue();},t);}else ye.warn("SponsorBlockAPI",`获取视频 ${e.bvid} 片段失败 (已重试${e.retryCount-1}次):`,n.message),e.reject(n);}}async makeRequestWithRetry(e,t=0){return new Promise((t,n)=>{GM_xmlhttpRequest({method:"GET",url:`${ce.API_URL}?videoID=${e}`,headers:{origin:"userscript-bilibili-sponsor-skip","x-ext-version":"1.0.0"},timeout:15e3,onload:o=>{try{if(404===o.status){const n=[];this.cache.set(e,{data:n,timestamp:Date.now()}),t(n);}else if(200===o.status){const n=JSON.parse(o.responseText);this.cache.set(e,{data:n,timestamp:Date.now()}),t(n);}else 400===o.status?n(new Error("参数错误 (400)")):429===o.status?n(new Error("请求频繁 (429)")):n(new Error(`HTTP ${o.status}`));}catch(i){n(new Error(`解析响应失败: ${i.message}`));}},onerror:e=>{n(new Error(`网络错误: ${e.message||"Unknown error"}`));},ontimeout:()=>{n(new Error("Timeout"));}});})}shouldRetry(e,t){return !(t>=3)&&(e.message.includes("429")?t<5:!!(e.message.includes("Timeout")||e.message.includes("网络错误")||e.message.includes("HTTP 5")))}calculateRetryDelay(e,t){let n=1e3;t.message.includes("429")&&(n=5e3);const o=n*Math.pow(2,e-1),i=1e3*Math.random();return Math.min(o+i,3e4)}getActiveRequestCount(){return this.pendingRequests.size}async waitForRequestSlot(){return new Promise(e=>{const t=()=>{this.getActiveRequestCount()<this.maxConcurrentRequests?e():setTimeout(t,100);};t();})}hasSegments(e){const t=this.cache.get(e);return t&&Date.now()-t.timestamp<ce.CACHE_EXPIRY?t.data.length>0:null}clearCache(){this.cache.clear();}}class it{constructor(e,t){this.api=e,this.config=t,this.video=null,this.segments=[],this.currentBVID=null,this.lastSkipTime=0,this.checkInterval=null,this.rafId=null,this.currentPrompt=null,this.promptedSegments=new Set,this.ignoredSegments=new Set,this.progressBar=null,this.markerContainer=null,this.playerObserver=null;}async init(){if(location.pathname.includes("/video/")&&(this.currentBVID=location.pathname.match(/video\/(BV\w+)/)?.[1],this.currentBVID)){await this.waitForVideo();try{this.segments=await this.api.fetchSegments(this.currentBVID),this.segments.length>0&&this.renderProgressMarkers();}catch(e){console.error("[SponsorBlock] 获取片段失败:",e),this.segments=[];}this.startMonitoring(),this.setupPlayerObserver();}}setupPlayerObserver(){const e=document.querySelector(".bpx-player-video-wrap")||document.querySelector(".bpx-player-container");e&&(this.playerObserver=new MutationObserver(()=>{this.segments.length>0&&!document.querySelector("#sponsorblock-preview-bar")&&this.renderProgressMarkers();}),this.playerObserver.observe(e,{childList:true,subtree:true}));}async waitForVideo(){return new Promise(e=>{const t=()=>{this.video=document.querySelector(ie),this.video?e():setTimeout(t,500);};t();})}renderProgressMarkers(){if(!this.config.get("showProgressMarkers"))return;const e=(t=0)=>{const n=document.querySelector(".bpx-player-progress-schedule");n?(this.progressBar=n,document.querySelectorAll("#sponsorblock-preview-bar").forEach(e=>e.remove()),this.markerContainer=document.createElement("ul"),this.markerContainer.id="sponsorblock-preview-bar",n.prepend(this.markerContainer),this.video.duration&&this.video.duration>0?this.createSegmentMarkers():this.video.addEventListener("loadedmetadata",()=>{this.createSegmentMarkers();},{once:true})):t<10&&setTimeout(()=>e(t+1),1e3);};e();}createSegmentMarkers(){if(!this.markerContainer||!this.video.duration||this.video.duration<=0)return;this.markerContainer.innerHTML="";const e=this.video.duration;[...this.segments].sort((e,t)=>t.segment[1]-t.segment[0]-(e.segment[1]-e.segment[0])).forEach((t,n)=>{const o=Math.min(e,t.segment[0]),i=Math.min(e,t.segment[1]),s=o/e*100,r=100*(1-i/e),a=document.createElement("li");a.className="sponsorblock-segment",a.dataset.segmentIndex=n.toString();const l=ce.CATEGORIES[t.category]||{name:t.category,color:"#999"};a.style.position="absolute",a.style.left=`${s}%`,a.style.right=`${r}%`,a.style.backgroundColor=l.color;const c=i-o;a.title=`${l.name}\n${o.toFixed(1)}s - ${i.toFixed(1)}s (${c.toFixed(1)}s)`,a.addEventListener("click",e=>{e.stopPropagation(),this.showSegmentDetails(t);}),this.markerContainer.appendChild(a);});}showSegmentDetails(e){const t=document.querySelector(".segment-details-popup");t&&(t.remove(),document.querySelector(".segment-details-overlay")?.remove());const n=ce.CATEGORIES[e.category]||{name:e.category,color:"#999"},o=e.segment[1]-e.segment[0],i=Me(e.segment[0]),s=Me(e.segment[1]),r=document.createElement("div");r.className="segment-details-overlay",r.onclick=()=>this.closeSegmentDetails();const a=document.createElement("div");a.className="segment-details-popup",a.onclick=e=>e.stopPropagation(),a.innerHTML=`\n      <div class="segment-details-header">\n        <div class="segment-details-title">\n          <div style="width: 16px; height: 16px; background: ${n.color}; border-radius: 3px;"></div>\n          <span>${n.name}</span>\n        </div>\n        <button class="segment-details-close">×</button>\n      </div>\n      <div class="segment-details-content">\n        <div class="segment-details-row">\n          <span class="segment-details-label">开始时间</span>\n          <span class="segment-details-value">${i}</span>\n        </div>\n        <div class="segment-details-row">\n          <span class="segment-details-label">结束时间</span>\n          <span class="segment-details-value">${s}</span>\n        </div>\n        <div class="segment-details-row">\n          <span class="segment-details-label">时长</span>\n          <span class="segment-details-value">${o.toFixed(1)} 秒</span>\n        </div>\n        <div class="segment-details-row">\n          <span class="segment-details-label">投票数</span>\n          <span class="segment-details-value">${e.votes}</span>\n        </div>\n        <div class="segment-details-row">\n          <span class="segment-details-label">UUID</span>\n          <span class="segment-details-value" style="font-size: 11px; font-family: monospace;">${e.UUID.substring(0,20)}...</span>\n        </div>\n      </div>\n      <div class="segment-details-actions">\n        <button class="segment-details-btn segment-details-btn-secondary" data-action="close">\n          关闭\n        </button>\n        <button class="segment-details-btn segment-details-btn-primary" data-action="jump">\n          跳转到此片段\n        </button>\n      </div>\n    `,document.body.appendChild(r),document.body.appendChild(a),a.querySelector(".segment-details-close").onclick=()=>this.closeSegmentDetails(),a.querySelector('[data-action="close"]').onclick=()=>this.closeSegmentDetails(),a.querySelector('[data-action="jump"]').onclick=()=>{this.video&&(this.video.currentTime=e.segment[0]),this.closeSegmentDetails();};const l=e=>{"Escape"===e.key&&(this.closeSegmentDetails(),document.removeEventListener("keydown",l));};document.addEventListener("keydown",l);}closeSegmentDetails(){document.querySelector(".segment-details-popup")?.remove(),document.querySelector(".segment-details-overlay")?.remove();}startMonitoring(){if(!this.video)return;const e=()=>{this.checkAndSkip(),this.rafId=requestAnimationFrame(e);};this.rafId=requestAnimationFrame(e),ye.debug("SponsorBlock","开始监控(使用RAF)");}checkAndSkip(){if(!this.video||this.video.paused)return;const e=this.video.currentTime,t=this.config.get("skipCategories")||[];for(const n of this.segments)if(e>=n.segment[0]&&e<n.segment[1]){const e=`${n.UUID}`;if(this.ignoredSegments.has(e))continue;if(t.includes(n.category)){if(Date.now()-this.lastSkipTime<1e3)continue;const e=n.segment[1];this.video.currentTime=e,this.lastSkipTime=Date.now(),this.showSkipToast(n);break}this.promptedSegments.has(e)||(this.showSkipPrompt(n),this.promptedSegments.add(e));continue}}showSkipToast(e){const t=ce.CATEGORIES[e.category]||{name:e.category},n=document.createElement("div");n.className="skip-toast",n.textContent=`已跳过 ${t.name}`,n.addEventListener("click",e=>{e.stopPropagation(),e.preventDefault();}),n.addEventListener("mousedown",e=>{e.stopPropagation(),e.preventDefault();});(document.querySelector(".bpx-player-video-wrap")||document.querySelector(".bpx-player-container")||document.body).appendChild(n),setTimeout(()=>{n.classList.add("hiding"),setTimeout(()=>{n.remove();},300);},3e3);}showSkipPrompt(e){this.closePrompt();const t=ce.CATEGORIES[e.category]||{name:e.category,color:"#999"},n=document.createElement("div");n.className="skip-prompt";const o=e.segment[1]-e.segment[0],i=Me(e.segment[0]),s=Me(e.segment[1]);n.innerHTML=`\n      <div class="skip-prompt-header">\n        <div class="skip-prompt-icon">\n          <svg viewBox="0 0 24 24" fill="${t.color}">\n            <path d="M8 5v14l11-7z"/>\n          </svg>\n        </div>\n        <div class="skip-prompt-message">\n          跳过${t.name}?<br>\n          <small style="color: #999; font-size: 11px;">${i} - ${s}</small>\n        </div>\n        <button class="skip-prompt-close" title="关闭">×</button>\n      </div>\n      <div class="skip-prompt-buttons">\n        <button class="skip-prompt-btn skip-prompt-btn-secondary" data-action="ignore">\n          不跳过\n        </button>\n        <button class="skip-prompt-btn skip-prompt-btn-primary" data-action="skip">\n          跳过 (${o.toFixed(0)}秒)\n        </button>\n      </div>\n    `,n.addEventListener("click",e=>{e.stopPropagation();}),n.addEventListener("mousedown",e=>{e.stopPropagation();});(document.querySelector(".bpx-player-video-wrap")||document.querySelector(".bpx-player-container")||document.body).appendChild(n),this.currentPrompt=n;const r=n.querySelector('[data-action="skip"]'),a=n.querySelector('[data-action="ignore"]'),l=n.querySelector(".skip-prompt-close"),c=()=>{this.video.currentTime=e.segment[1],this.lastSkipTime=Date.now(),this.closePrompt();},d=()=>{this.closePrompt();};r.onclick=c,a.onclick=()=>{const t=`${e.UUID}`;this.ignoredSegments.add(t),this.closePrompt();},l.onclick=d;const u=e=>{"Enter"===e.key?(c(),document.removeEventListener("keydown",u)):"Escape"===e.key&&(d(),document.removeEventListener("keydown",u));};document.addEventListener("keydown",u);const p=setInterval(()=>{this.video&&this.video.currentTime>=e.segment[1]&&(this.closePrompt(),clearInterval(p));},500),h=setTimeout(()=>{this.currentPrompt===n&&this.closePrompt();},5e3);n._cleanup=()=>{clearInterval(p),clearTimeout(h),document.removeEventListener("keydown",u);};}closePrompt(){this.currentPrompt&&(this.currentPrompt._cleanup&&this.currentPrompt._cleanup(),this.currentPrompt.classList.add("hiding"),setTimeout(()=>{this.currentPrompt&&(this.currentPrompt.remove(),this.currentPrompt=null);},300));}destroy(){ye.debug("SponsorBlock","销毁播放器控制器"),this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.checkInterval&&(clearInterval(this.checkInterval),this.checkInterval=null),this.closePrompt(),this.closeSegmentDetails(),this.markerContainer&&(this.markerContainer.remove(),this.markerContainer=null),this.playerObserver&&(this.playerObserver.disconnect(),this.playerObserver=null),this.promptedSegments.clear(),this.ignoredSegments.clear(),this.segments=[],this.video=null;}}const st=new class{constructor(){this.api=new ot,this.playerController=null,this.currentURL=location.href;}async init(){location.pathname.includes("/video/")&&(this.playerController=new it(this.api,nt),await this.playerController.init()),this.setupURLMonitor();}setupURLMonitor(){window.addEventListener("popstate",()=>{this.handleURLChange();});const e=history.pushState,t=history.replaceState;history.pushState=(...t)=>{e.apply(history,t),this.handleURLChange();},history.replaceState=(...e)=>{t.apply(history,e),this.handleURLChange();};}handleURLChange(){const e=location.href;e!==this.currentURL&&(this.currentURL=e,this.playerController?.destroy(),this.playerController=null,location.pathname.includes("/video/")&&setTimeout(async()=>{this.playerController=new it(this.api,nt),await this.playerController.init();},1e3));}getAPI(){return this.api}},rt=Object.freeze(Object.defineProperty({__proto__:null,default:st,sponsorBlockService:st},Symbol.toStringTag,{value:"Module"}));const at=new class{constructor(){this.isProcessing=false;}async captureVideoFrame(){const e=document.querySelector("video");if(!e)throw new Error("未找到视频元素");if(e.readyState<2)throw new Error("视频尚未加载");try{const t=800;let n=e.videoWidth,o=e.videoHeight;if(n>t){const i=t/n;n=t,o=Math.floor(e.videoHeight*i);}const i=new OffscreenCanvas(n,o);i.getContext("2d").drawImage(e,0,0,n,o);const s=await i.convertToBlob({type:"image/jpeg",quality:.7}),r=e.currentTime,a=Me(r);return ye.debug("[Screenshot] 截图成功",{originalSize:`${e.videoWidth}x${e.videoHeight}`,compressedSize:`${n}x${o}`,timestamp:a,blobSize:`${(s.size/1024).toFixed(2)}KB`}),{blob:s,timestamp:r,timeString:a}}catch(t){throw ye.error("[Screenshot] 截图失败:",t),t}}async captureAndSave(e=false){if(this.isProcessing)Ke.warning("截图处理中,请稍候");else {this.isProcessing=true;try{const{blob:n,timestamp:o,timeString:i}=await this.captureVideoFrame(),s=await this.blobToBase64(n),r=Pe(),a=Ne()||"未知视频",l=r?.bvid||"",c=`[截图] ${i}`,d=He.addNote({content:c,type:"screenshot",videoTimestamp:o,timeString:i,imageData:s,videoTitle:a,videoBvid:l});Ke.success(`截图已保存 (${i})`);try{const e=He.getAllNotes().find(e=>"ai-summary"===e.type);e&&e.segments&&e.segments.length>0&&(He.addScreenshotToSummary(e.id,{imageData:s,timeString:i,videoTimestamp:o}),ye.debug("[Screenshot] 截图已添加到AI总结笔记"));}catch(t){ye.warn("[Screenshot] 添加截图到AI总结失败:",t);}return e&&we.isNotionConfigured()&&await this.sendToNotion(n,o,i,a),d}catch(t){throw ye.error("[Screenshot] 截图保存失败:",t),Ke.error(`截图失败: ${t.message}`),t}finally{this.isProcessing=false;}}}async sendToNotion(e,t,n,o){try{const i=we.getNotionConfig();if(!i||!i.apiKey||!i.parentPageId)throw new Error("Notion未配置");Ke.info("正在上传截图到Notion..."),ye.info("[Screenshot] 开始发送截图到Notion...");const s=await this.uploadImageToNotion(e,i);ye.debug("[Screenshot] 获得 file_upload_id:",s);const r=await this.getOrCreateNotionPage(o,i);ye.debug("[Screenshot] 目标页面ID:",r),await this.insertScreenshotAtTimestamp(r,s,t,n,i),ye.info("[Screenshot] ✓ 截图已成功发送到Notion"),Ke.success("截图已发送到Notion");}catch(i){ye.error("[Screenshot] 发送到Notion失败:",i),Ke.error(`Notion上传失败: ${i.message}`);}}async uploadImageToNotion(e,t){if(ye.debug("[Screenshot] 开始上传图片到Notion,文件大小:",e.size),e.size>5242880)throw new Error("截图文件超过5MB,Notion不支持");try{ye.debug("[Screenshot] Step 1: 创建文件上传对象...");const n=await this._createFileUpload(e,t);return ye.debug("[Screenshot] 获得 file_upload_id:",n.id),ye.debug("[Screenshot] Step 2: 上传文件内容..."),await this._sendFileContent(n.id,e,t),ye.debug("[Screenshot] 文件上传成功"),n.id}catch(n){throw ye.error("[Screenshot] Notion上传失败:",n),n}}async _createFileUpload(e,t){const n={filename:`screenshot_${Date.now()}.jpg`,content_type:e.type||"image/jpeg"};return new Promise((e,o)=>{GM_xmlhttpRequest({method:"POST",url:"https://api.notion.com/v1/file_uploads",headers:{Authorization:`Bearer ${t.apiKey}`,"Content-Type":"application/json","Notion-Version":"2022-06-28"},data:JSON.stringify(n),timeout:3e4,onload:t=>{if(ye.debug("[Screenshot] 创建上传对象响应:",t.status),200===t.status){const n=JSON.parse(t.responseText);ye.debug("[Screenshot] 上传对象详情:",n),e(n);}else ye.error("[Screenshot] 创建上传对象失败:",t.responseText),o(new Error(`创建上传对象失败: ${t.status}`));},onerror:e=>{ye.error("[Screenshot] 创建上传对象网络错误:",e),o(e);},ontimeout:()=>o(new Error("创建上传对象超时"))});})}async _sendFileContent(e,t,n){const o=`https://api.notion.com/v1/file_uploads/${e}/send`,i=new FormData;return i.append("file",t,`screenshot_${Date.now()}.jpg`),new Promise((e,t)=>{GM_xmlhttpRequest({method:"POST",url:o,headers:{Authorization:`Bearer ${n.apiKey}`,"Notion-Version":"2022-06-28"},data:i,timeout:6e4,onload:n=>{if(ye.debug("[Screenshot] 上传文件响应:",n.status),200===n.status){const t=JSON.parse(n.responseText);ye.debug("[Screenshot] 文件上传完成:",t),e(t);}else ye.error("[Screenshot] 上传文件失败:",n.responseText),t(new Error(`上传文件失败: ${n.status}`));},onerror:e=>{ye.error("[Screenshot] 上传文件网络错误:",e),t(e);},ontimeout:()=>t(new Error("上传文件超时"))});})}async getOrCreateNotionPage(e,t){const n=xe.getVideoInfo(),o=n?.bvid;if(!o)throw new Error("无效的视频信息");let i=xe.getNotionPageId(o);if(i)return ye.debug("[Screenshot] 使用缓存的Notion页面ID:",i),i;const s=t.databaseId||t.parentPageId;if(s)try{if(i=await Ye.queryVideoPage(t.apiKey,s,o),i)return ye.debug("[Screenshot] 从Notion数据库找到页面:",i),xe.setNotionPageId(o,i),i}catch(r){ye.error("[Screenshot] 查询Notion页面失败:",r);}throw new Error("请先发送字幕和AI总结到Notion,以创建视频页面")}async insertScreenshotAtTimestamp(e,t,n,o,i){ye.info("[Screenshot] ========== 开始智能插入截图 =========="),ye.info("[Screenshot] 截图时间戳:",o,"(",n,"秒)"),ye.debug("[Screenshot] 目标页面ID:",e),ye.debug("[Screenshot] 图片上传ID:",t);try{ye.debug("[Screenshot] Step 1: 获取页面blocks");const s=await Ye.getPageBlocks(i.apiKey,e);ye.info("[Screenshot] 页面共有",s.length,"个blocks"),ye.debug("[Screenshot] Step 2: 查找时间戳段落标题");let r=-1;for(let e=0;e<s.length;e++){const t=s[e],n=t.type,o=t[n]?.rich_text?.[0]?.text?.content||"";if(ye.debug(`[Screenshot]   Block ${e}: type=${n}, content="${o.substring(0,50)}..."`),("heading_2"===n||"heading_3"===n)&&o.includes("⏱️ 时间戳段落")){r=e,ye.info("[Screenshot] ✓ 找到时间戳段落标题,位置:",e);break}}if(-1===r)return ye.warn("[Screenshot] ✗ 未找到时间戳段落,追加到页面末尾"),await this.appendScreenshotToEnd(e,t,o,i);ye.debug("[Screenshot] Step 3: 解析每个段落的时间戳");let a=null,l=null,c=-1;for(let e=r+1;e<s.length;e++){const t=s[e],o=t.type;if("heading_2"===o){ye.debug(`[Screenshot]   Block ${e}: 遇到heading_2,段落区域结束`);break}if("toggle"===o){const o=t.toggle?.rich_text?.[0]?.text?.content||"";ye.debug(`[Screenshot]   Block ${e}: type=toggle, content="${o}"`);const i=o.match(/\[?(\d{1,2}):(\d{2})\]?/);if(i){const o=parseInt(i[1],10),s=60*o+parseInt(i[2],10);if(ye.info(`[Screenshot]   → 解析时间戳: ${i[0]} = ${s}秒`),ye.debug(`[Screenshot]   比较: blockTimestamp(${s}) vs screenshot(${n})`),s<=n&&s>c)a=t.id,l=i[0],c=s,ye.info(`[Screenshot]   ✓ 更新最佳匹配: ${i[0]} (block ${e}, id: ${t.id})`);else if(s>n){ye.debug("[Screenshot]   → 超过截图时间,停止搜索");break}}else ye.debug("[Screenshot]   → 未toggle但无法解析时间戳");}if("bulleted_list_item"===o){const o=t.bulleted_list_item?.rich_text?.[0]?.text?.content||"";ye.debug(`[Screenshot]   Block ${e}: type=bulleted_list_item, content="${o}"`);const i=o.match(/\[?(\d{1,2}):(\d{2})\]?/);if(i){const e=parseInt(i[1],10),o=60*e+parseInt(i[2],10);if(ye.info(`[Screenshot]   → 解析时间戳: ${i[0]} = ${o}秒`),o<=n&&o>c)a=t.id,l=i[0],c=o,ye.info(`[Screenshot]   ✓ 更新最佳匹配: ${i[0]} (list item)`);else if(o>n)break}}}ye.debug("[Screenshot] Step 4: 构建截图blocks");const d=[{object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:`📸 ${o}`},annotations:{color:"gray"}}]}},{object:"block",type:"image",image:{type:"file_upload",file_upload:{id:t}}}];a?(ye.info("[Screenshot] ========================================"),ye.info("[Screenshot] ✓ 找到最佳匹配段落:",l),ye.info("[Screenshot] ✓ 目标toggle ID:",a),ye.info("[Screenshot] ✓ 准备插入截图作为children"),ye.info("[Screenshot] ========================================"),await this.insertBlocksAsChildren(a,d,i),ye.info("[Screenshot] ✓✓✓ 截图插入成功!")):(ye.warn("[Screenshot] ✗ 未找到合适的时间戳段落"),ye.warn("[Screenshot] → 将截图追加到页面末尾"),await this.appendScreenshotToEnd(e,t,o,i));}catch(s){ye.error("[Screenshot] 智能插入失败,降级为追加到末尾:",s),await this.appendScreenshotToEnd(e,t,o,i);}}async insertBlocksAsChildren(e,t,n){ye.debug("[Screenshot] ========== insertBlocksAsChildren =========="),ye.debug("[Screenshot] 父级block ID:",e),ye.debug("[Screenshot] 待插入blocks数量:",t.length),ye.debug("[Screenshot] Blocks详情:",JSON.stringify(t,null,2));const o=`https://api.notion.com/v1/blocks/${e}/children`;return new Promise((e,i)=>{const s={children:t};ye.debug("[Screenshot] API请求URL:",o),ye.debug("[Screenshot] API请求payload:",JSON.stringify(s,null,2)),GM_xmlhttpRequest({method:"PATCH",url:o,headers:{Authorization:`Bearer ${n.apiKey}`,"Content-Type":"application/json","Notion-Version":"2022-06-28"},data:JSON.stringify(s),timeout:3e4,onload:t=>{ye.info("[Screenshot] API响应状态:",t.status),200===t.status?(ye.info("[Screenshot] ✓ 截图已成功插入为children"),ye.debug("[Screenshot] 响应内容:",t.responseText.substring(0,200)+"..."),e(JSON.parse(t.responseText))):(ye.error("[Screenshot] ✗ 插入失败,状态码:",t.status),ye.error("[Screenshot] 错误响应:",t.responseText),i(new Error(`插入失败: ${t.status}`)));},onerror:e=>{ye.error("[Screenshot] ✗ 网络请求错误:",e),i(e);},ontimeout:()=>{ye.error("[Screenshot] ✗ 请求超时"),i(new Error("请求超时"));}});})}async appendScreenshotToEnd(e,t,n,o){ye.debug("[Screenshot] 追加截图到页面末尾");const i=`https://api.notion.com/v1/blocks/${e}/children`,s={children:[{object:"block",type:"heading_3",heading_3:{rich_text:[{type:"text",text:{content:`📸 截图 - ${n}`}}]}},{object:"block",type:"image",image:{type:"file_upload",file_upload:{id:t}}}]};return new Promise((e,t)=>{GM_xmlhttpRequest({method:"PATCH",url:i,headers:{Authorization:`Bearer ${o.apiKey}`,"Content-Type":"application/json","Notion-Version":"2022-06-28"},data:JSON.stringify(s),timeout:3e4,onload:n=>{200===n.status?e(JSON.parse(n.responseText)):t(new Error(`追加截图失败: ${n.status}`));},onerror:e=>t(e),ontimeout:()=>t(new Error("请求超时"))});})}async blobToBase64(e){return new Promise((t,n)=>{const o=new FileReader;o.onloadend=()=>t(o.result),o.onerror=n,o.readAsDataURL(e);})}async downloadScreenshot(){try{const{blob:e,timeString:t}=await this.captureVideoFrame(),n=xe.getVideoInfo(),o=`${(n?.title||"video").replace(/[<>:"/\\|?*]/g,"_")}_${t.replace(/:/g,"-")}.png`,i=URL.createObjectURL(e),s=document.createElement("a");s.href=i,s.download=o,s.style.display="none",document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(i),Ke.success("截图已下载");}catch(e){ye.error("[Screenshot] 下载失败:",e),Ke.error(`下载失败: ${e.message}`);}}};class lt{constructor(e){this.sponsorAPI=e,this.observer=null,this.statsCache=new Map,this.pendingRequests=new Map,this.abortController=new AbortController,this.processQueue=new Set,this.isProcessing=false;}start(){setTimeout(()=>{this.initScrollHandler(),this.initObserver(),this.checkNewCards();},800);}initScrollHandler(){let e;window.addEventListener("scroll",()=>{clearTimeout(e),e=setTimeout(()=>this.checkNewCards(),200);},{signal:this.abortController.signal});}checkNewCards(){if("hidden"===document.visibilityState)return;document.querySelectorAll("\n      .bili-video-card:not([data-quality-checked]),\n      .video-page-card-small:not([data-quality-checked]),\n      .video-page-card:not([data-quality-checked]),\n      .up-main-video-card:not([data-quality-checked]),\n      .small-item:not([data-quality-checked])\n    ").forEach(e=>{e.dataset.qualityChecked||this.processQueue.add(e);}),this.processNextBatch();}async processNextBatch(){if(this.isProcessing||0===this.processQueue.size)return;this.isProcessing=true;const e=Array.from(this.processQueue).slice(0,5);try{await Promise.all(e.map(e=>this.processCard(e)));}catch(t){}e.forEach(e=>this.processQueue.delete(e)),this.isProcessing=false,this.processQueue.size>0&&setTimeout(()=>this.processNextBatch(),100);}async processCard(e){if("true"===e.dataset.qualityChecked)return;if(!document.body.contains(e))return;e.dataset.qualityChecked="processing";const t=e.querySelector('a[href*="/video/BV"]');if(!t)return void(e.dataset.qualityChecked="true");const n=this.extractBVID(t.href);if(!n)return void(e.dataset.qualityChecked="true");const o=this.findBadgeContainer(e);if(o)try{const[t,i]=await Promise.all([this.fetchVideoStats(n).catch(()=>null),this.sponsorAPI.fetchSegments(n).catch(()=>[])]);if(!document.body.contains(e))return;const s=o.querySelector(".bili-tags-container");s&&s.remove();const r=document.createElement("div");r.className="bili-tags-container";const a=[];if(nt.get("showQualityBadge")&&t&&this.isHighQuality(t)&&a.push(this.createQualityBadge(t)),nt.get("showAdBadge")&&i&&i.length>0){const e=this.createSegmentBadges(i);a.push(...e);}const l=a.length>=3;a.forEach(e=>{l&&e.dataset.emoji&&e.dataset.text&&(e.textContent=e.dataset.emoji,e.classList.add("emoji-only")),r.appendChild(e);}),r.children.length>0&&(o.firstChild?o.insertBefore(r,o.firstChild):o.appendChild(r));}catch(i){}finally{document.body.contains(e)&&(e.dataset.qualityChecked="true");}else e.dataset.qualityChecked="true";}findBadgeContainer(e){return e.classList.contains("up-main-video-card")||e.classList.contains("small-item")?e.querySelector(".cover-container, .cover, .pic-box")||e:e.classList.contains("video-page-card-small")?e.querySelector(".pic-box"):e.classList.contains("video-page-card")?e.querySelector(".pic"):e.querySelector(".bili-video-card__cover, .cover, .pic, .bili-video-card__info")||e.closest(".bili-video-card")?.querySelector(".bili-video-card__cover")}isHighQuality(e){return e?.view>=ce.MIN_VIEWS&&e.like/e.view>=ce.MIN_SCORE}isTopQuality(e){return e?.coin>=e?.like}createQualityBadge(e){const t=document.createElement("span");return t.className="bili-quality-tag",this.isTopQuality(e)?(t.style.background=ce.TOP_TAG_COLOR,t.textContent=ce.TOP_TAG_TEXT,t.dataset.emoji="🏆",t.dataset.text="顶级",t.title="顶级优质视频"):(t.style.background=ce.TAG_COLOR,t.textContent=ce.TAG_TEXT,t.dataset.emoji="🔥",t.dataset.text="精选",t.title="精选优质视频"),t}createSegmentBadges(e){const t={};e.forEach(e=>{t[e.category]=(t[e.category]||0)+1;});const n=[],o={sponsor:{icon:"⚠️",text:"广告",color:"linear-gradient(135deg, #FF8C00, #FF6347)"},selfpromo:{icon:"📢",text:"推广",color:"linear-gradient(135deg, #FFD700, #FFA500)"},interaction:{icon:"👆",text:"三连",color:"linear-gradient(135deg, #9C27B0, #E91E63)"},poi_highlight:{icon:"⭐",text:"高光",color:"linear-gradient(135deg, #FF1493, #FF69B4)"},intro:{icon:"▶️",text:"开场",color:"linear-gradient(135deg, #00CED1, #00BFFF)"},outro:{icon:"🎬",text:"结尾",color:"linear-gradient(135deg, #1E90FF, #4169E1)"},preview:{icon:"🔄",text:"回顾",color:"linear-gradient(135deg, #00A1D6, #0087B3)"},filler:{icon:"💬",text:"闲聊",color:"linear-gradient(135deg, #9370DB, #8A2BE2)"},music_offtopic:{icon:"🎵",text:"非音乐",color:"linear-gradient(135deg, #FF8C00, #FF7F50)"},exclusive_access:{icon:"🤝",text:"合作",color:"linear-gradient(135deg, #2E8B57, #3CB371)"},mute:{icon:"🔇",text:"静音",color:"linear-gradient(135deg, #DC143C, #C71585)"}};return Object.entries(t).forEach(([e,t])=>{const i=o[e]||{icon:"📍",text:e,color:"linear-gradient(135deg, #888, #666)"},s=document.createElement("span");s.className="bili-ad-tag",s.style.background=i.color,s.dataset.emoji=t>1?`${i.icon}×${t}`:i.icon,s.dataset.text=i.text,s.textContent=`${i.icon} ${i.text}`,t>1&&(s.textContent+=` (${t})`),s.title=`包含 ${t} 个${i.text}片段`,n.push(s);}),n}extractBVID(e){try{return new URL(e).pathname.match(/video\/(BV\w+)/)?.[1]}catch{return null}}async fetchVideoStats(e){if(this.statsCache.has(e))return this.statsCache.get(e);if(this.pendingRequests.has(e))return this.pendingRequests.get(e);const t=new Promise((t,n)=>{GM_xmlhttpRequest({method:"GET",url:`https://api.bilibili.com/x/web-interface/view?bvid=${e}`,timeout:1e4,onload:o=>{try{const i=JSON.parse(o.responseText);0===i?.code&&i?.data?.stat?(this.statsCache.set(e,i.data.stat),t(i.data.stat)):n(new Error("Invalid API response"));}catch(i){n(i);}},onerror:n,ontimeout:()=>n(new Error("Timeout"))});});return this.pendingRequests.set(e,t),t.finally(()=>{this.pendingRequests.delete(e);})}initObserver(){this.observer=new MutationObserver(e=>{let t=false;for(const n of e)if(n.addedNodes.length>0){t=true;break}t&&this.checkNewCards();}),this.observer.observe(document.body,{childList:true,subtree:true});}destroy(){this.observer?.disconnect(),this.abortController.abort(),this.processQueue.clear(),this.pendingRequests.clear(),this.statsCache.clear();}}let ct=null;class dt{constructor(){this.baseUrl="https://sponsor.ajay.app/api",this.deArrowUrl="https://dearrow.ajay.app/api",this.cache=new Map,this.pendingRequests=new Map,this.cacheExpiry=6e5;}async getSegments(e,t=["sponsor","selfpromo","interaction","intro","outro"]){if(!e)return ye.warn("DeArrowAPI","缺少视频ID"),[];const n=`segments_${e}_${t.join(",")}`,o=this.cache.get(n);if(o&&Date.now()-o.timestamp<this.cacheExpiry)return ye.debug("DeArrowAPI",`使用缓存的段落数据: ${e}`),o.data;if(this.pendingRequests.has(n))return this.pendingRequests.get(n);const i=this._fetchSegments(e,t);this.pendingRequests.set(n,i);try{const e=await i;return this.cache.set(n,{data:e,timestamp:Date.now()}),e}finally{this.pendingRequests.delete(n);}}async _fetchSegments(e,t){const n=t.map(e=>`category=${encodeURIComponent(e)}`).join("&"),o=`${this.baseUrl}/skipSegments?videoID=${encodeURIComponent(e)}&${n}&service=YouTube`;try{return await new Promise((t,n)=>{GM_xmlhttpRequest({method:"GET",url:o,headers:{"User-Agent":"Bilibili-Tools/1.0",Accept:"application/json"},timeout:1e4,onload:n=>{if(404===n.status)ye.debug("DeArrowAPI",`视频 ${e} 没有广告段落`),t([]);else if(200===n.status)try{const e=JSON.parse(n.responseText),o=this._normalizeSegments(e);ye.info("DeArrowAPI",`获取到 ${o.length} 个广告段落`),t(o);}catch(o){ye.error("DeArrowAPI","解析响应失败:",o),t([]);}else ye.warn("DeArrowAPI",`HTTP ${n.status}: ${n.statusText}`),t([]);},onerror:e=>{ye.error("DeArrowAPI","请求失败:",e),t([]);},ontimeout:()=>{ye.warn("DeArrowAPI","请求超时"),t([]);}});})}catch(i){return ye.error("DeArrowAPI","获取段落失败:",i),[]}}async getBrandingData(e){if(!e)return null;const t=`branding_${e}`,n=this.cache.get(t);if(n&&Date.now()-n.timestamp<this.cacheExpiry)return n.data;const o=`${this.deArrowUrl}/branding?videoID=${e}`;try{return await new Promise(e=>{GM_xmlhttpRequest({method:"GET",url:o,headers:{"User-Agent":"Bilibili-Tools/1.0",Accept:"application/json"},timeout:5e3,onload:n=>{if(200===n.status)try{const o=JSON.parse(n.responseText),i={title:o.titles?.[0]?.title,thumbnail:o.thumbnails?.[0]?.timestamp,randomTime:o.randomTime,videoDuration:o.videoDuration};this.cache.set(t,{data:i,timestamp:Date.now()}),e(i);}catch(o){e(null);}else e(null);},onerror:()=>e(null),ontimeout:()=>e(null)});})}catch(i){return ye.error("DeArrowAPI","获取品牌数据失败:",i),null}}async submitSegment(e){const{videoId:t,startTime:n,endTime:o,category:i}=e;if(!t||void 0===n||void 0===o)return ye.error("DeArrowAPI","提交段落缺少必要参数"),false;const s=`${this.baseUrl}/skipSegments`,r=this._getUserId(),a=JSON.stringify({videoID:t,userID:r,segments:[{segment:[n,o],category:i||"sponsor"}]});try{return await new Promise(e=>{GM_xmlhttpRequest({method:"POST",url:s,headers:{"Content-Type":"application/json","User-Agent":"Bilibili-Tools/1.0"},data:a,timeout:1e4,onload:n=>{200===n.status||201===n.status?(ye.info("DeArrowAPI","成功提交广告段落"),this._clearVideoCache(t),e(!0)):(ye.error("DeArrowAPI",`提交失败: HTTP ${n.status}`),e(!1));},onerror:t=>{ye.error("DeArrowAPI","提交失败:",t),e(!1);},ontimeout:()=>{ye.error("DeArrowAPI","提交超时"),e(!1);}});})}catch(l){return ye.error("DeArrowAPI","提交段落异常:",l),false}}async voteOnSegment(e,t=1){const n=`${this.baseUrl}/api/voteOnSponsorTime`,o=this._getUserId();try{return await new Promise(i=>{GM_xmlhttpRequest({method:"POST",url:n,headers:{"Content-Type":"application/json","User-Agent":"Bilibili-Tools/1.0"},data:JSON.stringify({UUID:e,userID:o,type:t}),timeout:5e3,onload:e=>{200===e.status?(ye.info("DeArrowAPI","投票成功: "+(1===t?"支持":"反对")),i(!0)):i(!1);},onerror:()=>i(!1),ontimeout:()=>i(!1)});})}catch(i){return ye.error("DeArrowAPI","投票失败:",i),false}}_normalizeSegments(e){return Array.isArray(e)?e.map(e=>({UUID:e.UUID,segment:e.segment||[e.startTime,e.endTime],start:e.segment?e.segment[0]:e.startTime,end:e.segment?e.segment[1]:e.endTime,category:e.category,actionType:e.actionType||"skip",description:e.description||"",votes:e.votes||0,locked:e.locked||0,userID:e.userID,videoDuration:e.videoDuration})):[]}_getUserId(){let e=localStorage.getItem("dearrow_user_id");return e||(e="user_"+Math.random().toString(36).substr(2,9)+Date.now().toString(36),localStorage.setItem("dearrow_user_id",e)),e}_clearVideoCache(e){for(const[t]of this.cache)t.includes(e)&&this.cache.delete(t);}clearCache(){this.cache.clear(),ye.info("DeArrowAPI","已清除所有缓存");}}class ut{constructor(e){this.config=e,this.adapter=null,this.deArrowAPI=new dt,this.segments=[],this.nativeAdMarkers=[],this.skipHistory=new Set,this.ignoredSegments=new Set,this.isMonitoring=false,this.monitoringInterval=null,this.lastSkipTime=0,this.currentVideoId=null,this.segmentsCache=new Map,this.cacheTimeout=18e5,this.lastNativeAdCheck=0,this.nativeAdCheckInterval=5e3,this.options={autoSkip:false!==e.get("autoSkip"),skipCategories:e.get("skipCategories")||["sponsor","selfpromo"],showNotifications:false!==e.get("showNotifications"),showProgressMarkers:false!==e.get("showProgressMarkers"),detectNativeAds:false!==e.get("detectNativeAds"),skipDelay:e.get("skipDelay")||0,muteInsteadOfSkip:e.get("muteInsteadOfSkip")||false};}async init(){let e=location.hostname;try{window.top&&window.top!==window&&(e=window.top.location.hostname);}catch(i){ye.debug("UniversalAdSkipService","无法访问 top window,使用当前 hostname");}new URLSearchParams(window.location.search);const t=document.referrer;ye.debug("UniversalAdSkipService",`检测平台 - hostname: ${e}, referrer: ${t}`);const n=e.includes("youtube.com")||t.includes("youtube.com")||null!==document.querySelector("ytd-app"),o=e.includes("bilibili.com")||t.includes("bilibili.com")||null!==document.querySelector(".bili-header")||null!==document.querySelector(".bpx-player-container");if(n)this.adapter=new Ae,ye.debug("UniversalAdSkipService","创建YouTube适配器");else {if(!o)return void ye.debug("UniversalAdSkipService","无法检测到支持的网站,跳过初始化");this.adapter=new Ce,ye.debug("UniversalAdSkipService","创建Bilibili适配器");}this.adapter.isVideoPage()?(this.currentVideoId=this.adapter.getVideoId(),this.currentVideoId?(ye.info("UniversalAdSkipService",`初始化: ${this.adapter.platform} - ${this.currentVideoId}`),await this.adapter.waitForVideo(),await this.loadSegments(),this.startMonitoring(),this.setupUrlMonitor(),this.cacheCleanupInterval||(this.cacheCleanupInterval=setInterval(()=>{this.cleanupCache();},6e5))):ye.warn("UniversalAdSkipService","无法获取视频ID")):ye.debug("UniversalAdSkipService","不是视频页面,跳过初始化");}async loadSegments(){const e=`${this.adapter?.platform}_${this.currentVideoId}`,t=this.segmentsCache.get(e),n=Date.now();if(t&&n-t.timestamp<this.cacheTimeout)return ye.debug("UniversalAdSkipService",`使用缓存的广告段落 (${t.segments.length} 个)`),this.segments=[...t.segments],void(this.options.showProgressMarkers&&this.segments.length>0&&await this.showProgressMarkers());if(this.segments=[],ye.debug("UniversalAdSkipService",`获取广告段落 - 平台: ${this.adapter?.platform}, 视频ID: ${this.currentVideoId}`),"youtube"===this.adapter?.platform){try{const e=await this.deArrowAPI.getSegments(this.currentVideoId,this.options.skipCategories);this.segments=this.segments.concat(e),ye.info("UniversalAdSkipService",`加载了 ${e.length} 个社区广告段落`);}catch(i){ye.error("UniversalAdSkipService","加载社区段落失败:",i);}this.options.detectNativeAds&&this.detectNativeAds();}else if("bilibili"===this.adapter?.platform)try{const e=await this.fetchBilibiliSegments(this.currentVideoId);this.segments=this.segments.concat(e),ye.info("UniversalAdSkipService",`加载了 ${e.length} 个Bilibili广告段落`);}catch(i){ye.error("UniversalAdSkipService","加载Bilibili段落失败:",i);}const o=this.segments.filter(e=>!e.isNative);this.segmentsCache.set(e,{segments:o,timestamp:n}),this.options.showProgressMarkers&&this.segments.length>0&&await this.showProgressMarkers();}async fetchBilibiliSegments(e){const t=(this.options.skipCategories||["sponsor","intro","outro","selfpromo"]).map(e=>`category=${encodeURIComponent(e)}`).join("&");return new Promise(n=>{GM_xmlhttpRequest({method:"GET",url:`https://sponsor.ajay.app/api/skipSegments?videoID=${encodeURIComponent(e)}&${t}`,headers:{origin:"userscript-bilibili-sponsor-skip","x-ext-version":"1.0.0"},timeout:1e4,onload:e=>{if(200===e.status)try{const t=JSON.parse(e.responseText);n(t);}catch(t){n([]);}else n([]);},onerror:()=>n([]),ontimeout:()=>n([])});})}detectNativeAds(){if("youtube"!==this.adapter.platform)return;const e=Date.now();if(e-this.lastNativeAdCheck<this.nativeAdCheckInterval)return;this.lastNativeAdCheck=e;const t=this.adapter.detectNativeAdMarkers();if(JSON.stringify(t)!==JSON.stringify(this.nativeAdMarkers)&&(this.nativeAdMarkers=t,t.length>0&&ye.info("UniversalAdSkipService",`检测到 ${t.length} 个原生广告标记`),this.segments=this.segments.filter(e=>!e.isNative),t.forEach((e,t)=>{this.segments.push({UUID:`native-ad-${t}`,segment:[e.start,e.end],start:e.start,end:e.end,category:"sponsor",actionType:"skip",description:"原生广告",isNative:true});}),this.options.showProgressMarkers&&this.showProgressMarkers()),!this.adObserverSetup){this.adObserverSetup=true;let e=null;this.adapter.observeAdChanges(t=>{clearTimeout(e),e=setTimeout(()=>{JSON.stringify(t)!==JSON.stringify(this.nativeAdMarkers)&&(this.nativeAdMarkers=t,(t.length>0||this.segments.some(e=>e.isNative))&&ye.debug("UniversalAdSkipService",`广告标记变化: ${t.length} 个`),this.segments=this.segments.filter(e=>!e.isNative),t.forEach((e,t)=>{this.segments.push({UUID:`native-ad-${t}`,segment:[e.start,e.end],start:e.start,end:e.end,category:"sponsor",actionType:"skip",description:"原生广告",isNative:true});}),this.options.showProgressMarkers&&this.showProgressMarkers());},500);});}}async showProgressMarkers(){await this.adapter.waitForProgressBar();const e="youtube"===this.adapter.platform?{containerId:"universal-ad-markers",className:"universal-ad-marker",color:"#ff0000",opacity:.6}:{containerId:"sponsorblock-preview-bar",className:"sponsorblock-segment",opacity:.7};this.adapter.addProgressMarkers(this.segments,e);}startMonitoring(){if(this.isMonitoring)return;this.isMonitoring=true;const e=()=>{this.isMonitoring&&(this.checkAndSkipAds(),requestAnimationFrame(e));};requestAnimationFrame(e),ye.info("UniversalAdSkipService","开始监控广告");}stopMonitoring(){this.isMonitoring=false,ye.info("UniversalAdSkipService","停止监控");}checkAndSkipAds(){if(!this.adapter.isPlaying())return;const e=this.adapter.getCurrentTime();for(const t of this.segments){const n=t.start||t.segment[0],o=t.end||t.segment[1];if(e>=n&&e<o){const e=t.UUID||`${n}-${o}`;if(this.skipHistory.has(e)||this.ignoredSegments.has(e))continue;if(this.options.autoSkip&&this.shouldAutoSkip(t)){if(Date.now()-this.lastSkipTime<1e3)continue;this.options.muteInsteadOfSkip||"mute"===t.actionType?this.muteSegment(t):this.skipSegment(t);}else this.showSkipPrompt(t);break}}}shouldAutoSkip(e){return this.options.skipCategories.includes(e.category)}skipSegment(e){const t=e.end||e.segment[1],n=e.UUID||`${e.start}-${t}`;setTimeout(()=>{if(this.adapter.seekTo(t),this.lastSkipTime=Date.now(),this.skipHistory.add(n),this.options.showNotifications){const t=this.getCategoryName(e.category);this.adapter.showNotification(`已跳过 ${t}`,{type:"success",duration:2e3});}ye.info("UniversalAdSkipService",`跳过广告: ${n}`);},1e3*this.options.skipDelay);}muteSegment(e){const t=this.adapter.video;if(!t)return;const n=t.volume;t.volume=0;const o=e.UUID||`${e.start}-${e.end}`;if(this.skipHistory.add(o),this.options.showNotifications){const t=this.getCategoryName(e.category);this.adapter.showNotification(`已静音 ${t}`,{type:"info",duration:2e3});}const i=setInterval(()=>{this.adapter.getCurrentTime()>=(e.end||e.segment[1])&&(t.volume=n,clearInterval(i),ye.info("UniversalAdSkipService",`恢复音量: ${o}`));},100);}showSkipPrompt(e){const t=e.UUID||`${e.start}-${e.end}`;if(this.skipHistory.has(t))return;const n=document.createElement("div");n.className="universal-skip-prompt";const o=this.getCategoryName(e.category),i=(e.end||e.segment[1])-(e.start||e.segment[0]);n.innerHTML=`\n      <div class="skip-prompt-content">\n        <div class="skip-prompt-message">\n          跳过 ${o}?\n          <span class="skip-prompt-duration">(${i.toFixed(0)}秒)</span>\n        </div>\n        <div class="skip-prompt-buttons">\n          <button class="skip-btn skip-btn-ignore">不跳过</button>\n          <button class="skip-btn skip-btn-confirm">跳过</button>\n        </div>\n      </div>\n    `,this.addPromptStyles();const s=this.adapter.getPlayerContainer();s&&s.appendChild(n),this.skipHistory.add(t);const r=n.querySelector(".skip-btn-confirm"),a=n.querySelector(".skip-btn-ignore");r.onclick=()=>{this.skipSegment(e),n.remove();},a.onclick=()=>{this.ignoredSegments.add(t),n.remove();},setTimeout(()=>{n.style.animation="fadeOut 0.3s ease",setTimeout(()=>n.remove(),300);},5e3);}addPromptStyles(){if(document.querySelector("#universal-skip-styles"))return;const e=document.createElement("style");e.id="universal-skip-styles",e.textContent="\n      .universal-skip-prompt {\n        position: absolute;\n        top: 70px;\n        right: 12px;\n        background: rgba(0, 0, 0, 0.9);\n        color: white;\n        padding: 12px 16px;\n        border-radius: 4px;\n        font-size: 14px;\n        z-index: 10000;\n        animation: slideIn 0.3s ease;\n      }\n      \n      .skip-prompt-message {\n        margin-bottom: 10px;\n      }\n      \n      .skip-prompt-duration {\n        color: #999;\n        font-size: 12px;\n        margin-left: 4px;\n      }\n      \n      .skip-prompt-buttons {\n        display: flex;\n        gap: 8px;\n      }\n      \n      .skip-btn {\n        padding: 4px 12px;\n        border: none;\n        border-radius: 2px;\n        cursor: pointer;\n        font-size: 13px;\n        transition: opacity 0.2s;\n      }\n      \n      .skip-btn:hover {\n        opacity: 0.8;\n      }\n      \n      .skip-btn-confirm {\n        background: #ff0000;\n        color: white;\n      }\n      \n      .skip-btn-ignore {\n        background: #666;\n        color: white;\n      }\n      \n      @keyframes slideIn {\n        from { transform: translateX(20px); opacity: 0; }\n        to { transform: translateX(0); opacity: 1; }\n      }\n      \n      @keyframes fadeOut {\n        from { opacity: 1; }\n        to { opacity: 0; }\n      }\n    ",document.head.appendChild(e);}getCategoryName(e){return {sponsor:"赞助商",selfpromo:"自我推广",interaction:"互动提醒",intro:"开场",outro:"片尾",preview:"预告",filler:"填充内容",music_offtopic:"非音乐部分"}[e]||e||"广告"}setupUrlMonitor(){let e=location.href;const t=()=>{location.href!==e&&(e=location.href,this.handleUrlChange());};new MutationObserver(t).observe(document.body,{childList:true,subtree:true}),window.addEventListener("popstate",t),"youtube"===this.adapter.platform&&document.addEventListener("yt-navigate-finish",t);}async handleUrlChange(){const e=this.adapter?.getVideoId();if(e!==this.currentVideoId){if(!e||!this.adapter?.isVideoPage())return ye.debug("UniversalAdSkipService","不是视频页面,停止服务"),void this.destroy();ye.info("UniversalAdSkipService",`视频变化: ${this.currentVideoId} -> ${e}`),this.stopMonitoring(),this.currentVideoId=e,this.segments=[],this.nativeAdMarkers=[],this.skipHistory.clear(),this.ignoredSegments.clear(),this.adObserverSetup=false,await this.adapter.waitForVideo(),await this.loadSegments(),this.startMonitoring();}else ye.debug("UniversalAdSkipService","同一视频,跳过重新初始化");}updateConfig(e){this.options={...this.options,...e},ye.info("UniversalAdSkipService","配置已更新",this.options),this.currentVideoId&&this.loadSegments();}cleanupCache(){const e=Date.now();let t=0;for(const[n,o]of this.segmentsCache.entries())e-o.timestamp>this.cacheTimeout&&(this.segmentsCache.delete(n),t++);t>0&&ye.debug("UniversalAdSkipService",`清理了 ${t} 个过期缓存`);}destroy(){this.stopMonitoring(),this.segments=[],this.nativeAdMarkers=[],this.skipHistory.clear(),this.ignoredSegments.clear(),this.currentVideoId=null,this.adObserverSetup=false,this.cacheCleanupInterval&&(clearInterval(this.cacheCleanupInterval),this.cacheCleanupInterval=null),this.segmentsCache.clear(),this.adapter&&(this.adapter.destroy(),this.adapter=null),ye.info("UniversalAdSkipService","已清理资源");}}const pt=new class{constructor(){this.deArrowAPI=new dt,this.processedElements=new WeakSet,this.processingQueue=new Set,this.observer=null,this.tagCache=new Map,this.concurrentLimit=3,this.requestDelay=100,this.lastRequestTime=0,this.pendingScans=[],this.cacheTTL=18e5,this.scanTimer=null;}async init(){location.hostname.includes("youtube.com")&&(ye.info("YouTubeVideoTagger","初始化YouTube视频标签服务"),await this.scanAndTagVideos(),this.setupObserver(),this.setupNavigationListener());}async scanAndTagVideos(){this.scanTimer&&clearTimeout(this.scanTimer),this.scanTimer=setTimeout(async()=>{await this.performScan();},200);}async performScan(){try{const e=this.findVideoItems().filter(e=>!this.processedElements.has(e.element)&&!this.processingQueue.has(e.videoId));if(0===e.length)return;ye.debug("YouTubeVideoTagger",`发现 ${e.length} 个新视频需要处理`),await this.processVideoConcurrently(e);}catch(e){ye.error("YouTubeVideoTagger","扫描视频时出错:",e);}}async processVideoConcurrently(e){const t=[...e],n=[];for(;t.length>0||n.length>0;){for(;n.length<this.concurrentLimit&&t.length>0;){const e=t.shift();this.processedElements.add(e.element),this.processingQueue.add(e.videoId);const o=this.processVideoItemWithRateLimit(e).finally(()=>{this.processingQueue.delete(e.videoId);const t=n.indexOf(o);t>-1&&n.splice(t,1);});n.push(o);}n.length>0&&await Promise.race(n);}}async processVideoItemWithRateLimit(e){const t=Date.now()-this.lastRequestTime,n=Math.max(0,this.requestDelay-t);n>0&&await new Promise(e=>setTimeout(e,n)),this.lastRequestTime=Date.now();try{await this.processVideoItem(e);}catch(o){ye.error("YouTubeVideoTagger",`处理视频 ${e.videoId} 失败:`,o);}}findVideoItems(){const e=new Map,t=["ytd-video-renderer","ytd-rich-item-renderer","ytd-grid-video-renderer","ytd-compact-video-renderer","ytd-video-preview","ytd-playlist-video-renderer","ytd-reel-item-renderer","yt-lockup-view-model",".yt-lockup-view-model__content-image:has(yt-thumbnail-view-model)",'ytd-thumbnail:has(a[href*="/watch"])','div:has(> a#thumbnail[href*="/watch"])'].join(",");return document.querySelectorAll(t).forEach(t=>{if(t.querySelector(".youtube-ad-tag"))return;const n=this.extractVideoInfo(t);n&&n.videoId&&(!e.has(n.videoId)||n.thumbnailContainer&&!e.get(n.videoId).thumbnailContainer)&&e.set(n.videoId,{element:t,...n});}),Array.from(e.values())}extractVideoInfo(e){let t=null,n="",o="",i=null;const s=e.querySelectorAll('a[href*="/watch?v="]');if(s.length>0){const e=s[0].href.match(/[?&]v=([^&]+)/);e&&(t=e[1]);}if(!t){const n=e.querySelector("[data-video-id]");n&&(t=n.dataset.videoId);}if(!t&&e.closest('a[href*="/watch?v="]')){const n=e.closest('a[href*="/watch?v="]').href.match(/[?&]v=([^&]+)/);n&&(t=n[1]);}if(!t)return null;const r=e.querySelector("#video-title")||e.querySelector("h3")||e.querySelector("[title]");r&&(n=r.textContent||r.title||"");const a=e.querySelector("ytd-thumbnail-overlay-time-status-renderer")||e.querySelector(".ytd-thumbnail-overlay-time-status-renderer")||e.querySelector("span.style-scope.ytd-thumbnail-overlay-time-status-renderer")||e.querySelector(".yt-badge-shape__text")||e.querySelector("yt-thumbnail-overlay-badge-view-model .yt-badge-shape__text");return a&&(o=a.textContent?.trim()||""),i=e.querySelector("yt-thumbnail-view-model")||e.querySelector(".ytThumbnailViewModelImage")||e.querySelector("ytd-thumbnail")||e.querySelector("#thumbnail")||e.querySelector(".yt-core-image")||e.querySelector("img")?.closest("div"),{videoId:t,title:n.trim(),duration:o,thumbnailContainer:i}}async processVideoItem(e){const{videoId:t,title:n,thumbnailContainer:o,element:i}=e;if(!i.querySelector(".youtube-ad-tag"))try{let e=!1,s=[];const r=Date.now();if(this.tagCache.has(t)){const n=this.tagCache.get(t);r-n.timestamp<this.cacheTTL?(e=n.hasAds,s=n.segments,ye.debug("YouTubeVideoTagger",`使用缓存数据: ${t}`)):this.tagCache.delete(t);}this.tagCache.has(t)||(s=await this.deArrowAPI.getSegments(t),e=s&&s.length>0,this.tagCache.set(t,{hasAds:e,segments:s,timestamp:r})),e&&(this.addAdTag(i,o,s),ye.debug("YouTubeVideoTagger",`为视频 ${n||t} 添加广告标签`));}catch(s){ye.error("YouTubeVideoTagger",`处理视频 ${t} 时出错:`,s);}}addAdTag(e,t,n){if(t||(t=e),t.querySelector(".youtube-ad-tag"))return;const o=n.length,i=[...new Set(n.map(e=>e.category))],s=this.getMainCategory(i),r=document.createElement("div");r.className="youtube-ad-tag-wrapper",r.style.cssText="\n      position: absolute;\n      top: 6px;\n      left: 6px;\n      display: flex;\n      flex-direction: column;\n      gap: 4px;\n      align-items: flex-start;\n      z-index: 1000;\n      pointer-events: none;\n    ";const a=document.createElement("div");a.className="youtube-ad-tag";const l=this.createTagContent(s,o);a.appendChild(l),a.title=`包含 ${o} 个广告段落: ${i.join(", ")}`;const{background:c,color:d}=this.getTagStyle(s);if(a.style.cssText=`\n      display: inline-flex;\n      align-items: center;\n      gap: 2px;\n      background: ${c};\n      color: ${d};\n      padding: 3px 7px 3px 5px;\n      border-radius: 4px;\n      font-size: 12px;\n      font-weight: 600;\n      font-family: "PingFang SC", "Helvetica Neue", "Microsoft YaHei", sans-serif;\n      line-height: 18px;\n      box-shadow: 0 2px 8px rgba(0,0,0,0.15), inset 0 1px 0 rgba(255,255,255,0.15);\n      backdrop-filter: blur(10px) saturate(180%);\n      -webkit-backdrop-filter: blur(10px) saturate(180%);\n      white-space: nowrap;\n      transform-origin: left top;\n      transform: scale(0.9);\n      opacity: 0.95;\n      animation: slideInScale 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;\n      transition: all 0.2s ease;\n      cursor: default;\n    `,i.length>1){const e=document.createElement("div");e.className="youtube-ad-extra-tag",e.textContent="+"+(i.length-1),e.style.cssText="\n        display: inline-flex;\n        align-items: center;\n        justify-content: center;\n        background: rgba(0, 0, 0, 0.5);\n        color: #ffffff;\n        padding: 2px 5px;\n        border-radius: 3px;\n        font-size: 10px;\n        font-weight: bold;\n        margin-left: 3px;\n        backdrop-filter: blur(5px);\n      ",a.appendChild(e);}const u=document.createElement("style");u.textContent="\n      @keyframes slideInScale {\n        from {\n          opacity: 0;\n          transform: scale(0.7) translateY(-5px);\n        }\n        to {\n          opacity: 0.95;\n          transform: scale(0.9) translateY(0);\n        }\n      }\n      \n      @keyframes shimmer {\n        0% {\n          background-position: -200% center;\n        }\n        100% {\n          background-position: 200% center;\n        }\n      }\n      \n      .youtube-ad-tag:hover {\n        transform: scale(0.95) !important;\n        opacity: 1 !important;\n        box-shadow: 0 3px 12px rgba(0,0,0,0.2) !important;\n        transition: all 0.2s ease;\n      }\n      \n      .youtube-ad-tag-wrapper:hover .youtube-ad-tag {\n        pointer-events: auto;\n      }\n      \n      /* B站风格的高亮效果 */\n      .youtube-ad-tag::before {\n        content: '';\n        position: absolute;\n        top: 0;\n        left: 0;\n        right: 0;\n        bottom: 0;\n        background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);\n        background-size: 200% 100%;\n        border-radius: inherit;\n        opacity: 0;\n        transition: opacity 0.3s;\n      }\n      \n      .youtube-ad-tag:hover::before {\n        opacity: 1;\n        animation: shimmer 1s ease-in-out;\n      }\n    ",document.querySelector("#youtube-ad-tag-styles")||(u.id="youtube-ad-tag-styles",document.head.appendChild(u));"static"===window.getComputedStyle(t).position&&(t.style.position="relative"),r.appendChild(a),t.appendChild(r);}getMainCategory(e){const t=["sponsor","selfpromo","intro","outro","interaction","preview","filler"];for(const n of t)if(e.includes(n))return n;return e[0]||"ad"}createTagContent(e,t){const n={sponsor:{emoji:"💰",text:"赞助"},selfpromo:{emoji:"📢",text:"推广"},intro:{emoji:"🎬",text:"片头"},outro:{emoji:"🎭",text:"片尾"},interaction:{emoji:"👍",text:"互动"},preview:{emoji:"👀",text:"预览"},filler:{emoji:"⏸️",text:"填充"},ad:{emoji:"📺",text:"广告"}},o=n[e]||n.ad,i=document.createElement("span");i.style.display="inline-flex",i.style.alignItems="center",i.style.gap="3px";const s=document.createElement("span");s.textContent=o.emoji,s.style.fontSize="14px",s.style.lineHeight="1",s.style.display="inline-block",s.style.verticalAlign="middle",s.style.filter="drop-shadow(0 1px 1px rgba(0,0,0,0.2))";const r=document.createElement("span");if(r.textContent=o.text,r.style.fontSize="12px",r.style.letterSpacing="0.3px",r.style.verticalAlign="middle",i.appendChild(s),i.appendChild(r),t>1){const e=document.createElement("span");e.textContent=`×${t}`,e.style.marginLeft="2px",e.style.opacity="0.9",e.style.fontSize="11px",i.appendChild(e);}return i}getTagStyle(e){const t={sponsor:{background:"linear-gradient(135deg, #00c851 0%, #00a040 100%)",color:"#ffffff"},selfpromo:{background:"linear-gradient(135deg, #ffc107 0%, #ff9800 100%)",color:"#ffffff"},intro:{background:"linear-gradient(135deg, #00bcd4 0%, #0097a7 100%)",color:"#ffffff"},outro:{background:"linear-gradient(135deg, #ff9800 0%, #f57c00 100%)",color:"#ffffff"},interaction:{background:"linear-gradient(135deg, #e91e63 0%, #c2185b 100%)",color:"#ffffff"},preview:{background:"linear-gradient(135deg, #9c27b0 0%, #7b1fa2 100%)",color:"#ffffff"},filler:{background:"linear-gradient(135deg, #795548 0%, #5d4037 100%)",color:"#ffffff"},ad:{background:"linear-gradient(135deg, #f44336 0%, #d32f2f 100%)",color:"#ffffff"}};return t[e]||t.ad}getTagText(e,t){const n={sponsor:"赞助",selfpromo:"推广",intro:"片头",outro:"片尾",interaction:"互动",preview:"预览",filler:"填充",ad:"广告"}[e]||"广告";return t>1?`${n} ×${t}`:n}getTagColor(e){const t={sponsor:"#00d400",selfpromo:"#ffcc00",intro:"#00bcd4",outro:"#ff9100",interaction:"#ff00ff",preview:"#7c4dff",filler:"#795548",ad:"#f44336"};return t[e]||t.ad}setupObserver(){const e={childList:true,subtree:true,attributes:true,attributeFilter:["href","src"]},t=e=>{for(const t of e){if("childList"===t.type&&t.addedNodes.length>0)for(const e of t.addedNodes)if(e.nodeType===Node.ELEMENT_NODE&&this.isVideoRelatedElement(e))return  true;if("attributes"===t.type){const e=t.target;if(e.nodeType===Node.ELEMENT_NODE&&"A"===e.tagName&&e.href&&e.href.includes("/watch"))return  true}if("childList"===t.type&&t.removedNodes.length>10)return  true}return  false};this.observer=new MutationObserver(e=>{t(e)&&this.scanAndTagVideos();});const n=[document.querySelector("ytd-app"),document.querySelector("#content"),document.querySelector("#contents"),document.body].filter(Boolean);n.length>0&&(this.observer.observe(n[0],e),ye.debug("YouTubeVideoTagger",`DOM观察器已启动,观察 ${n[0].tagName}`)),this.startPeriodicCheck();}isVideoRelatedElement(e){if(!e.matches)return  false;const t=["ytd-video-renderer","ytd-rich-item-renderer","ytd-grid-video-renderer","ytd-compact-video-renderer","ytd-playlist-video-renderer","ytd-thumbnail",'[href*="/watch"]'];return !!t.some(t=>e.matches(t))||!(!e.querySelector||!e.querySelector(t.join(",")))}startPeriodicCheck(){setInterval(()=>{0===this.processingQueue.size&&this.scanAndTagVideos();},1e4),setInterval(()=>{this.cleanupCache();},3e5);}cleanupCache(){const e=Date.now();let t=0;for(const[n,o]of this.tagCache.entries())e-o.timestamp>this.cacheTTL&&(this.tagCache.delete(n),t++);t>0&&ye.debug("YouTubeVideoTagger",`清理了 ${t} 个过期缓存项`);}setupNavigationListener(){window.addEventListener("yt-navigate-finish",()=>{ye.debug("YouTubeVideoTagger","页面导航完成,重新扫描视频"),setTimeout(()=>this.scanAndTagVideos(),500);});let e=location.href;setInterval(()=>{const t=location.href;t!==e&&(e=t,ye.debug("YouTubeVideoTagger","URL变化,触发扫描"),this.scanAndTagVideos());},1e3),window.addEventListener("popstate",()=>{this.scanAndTagVideos();}),document.addEventListener("yt-action",e=>{!e.detail||"yt-append-continuation-items-action"!==e.detail.actionName&&"ytd-update-grid-items-action"!==e.detail.actionName||this.scanAndTagVideos();});}destroy(){this.observer&&(this.observer.disconnect(),this.observer=null),this.scanTimer&&(clearTimeout(this.scanTimer),this.scanTimer=null),document.querySelectorAll(".youtube-ad-tag").forEach(e=>e.remove()),this.processingQueue.clear(),this.tagCache.clear(),ye.info("YouTubeVideoTagger","服务已销毁");}};const ht=new class{constructor(){this.markedConfigured=false;}renderSubtitlePanel(e){const t=xe.getVideoKey(),n=t?xe.getAISummary(t):null;let o=`\n      <div class="subtitle-header">\n        <div class="subtitle-header-left">\n        </div>\n        <div class="subtitle-header-right">\n          <div class="subtitle-search-container">\n            <input type="text" class="search-input" placeholder="搜索..." id="subtitle-search-input">\n            <div class="search-controls" id="search-controls" style="display: none;">\n              <span class="search-counter" id="search-counter">0/0</span>\n              <button class="search-nav-btn search-prev" id="search-prev" title="上一个">\n                <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">\n                  <path d="M9 7.5L6 4.5L3 7.5" stroke-linecap="round" stroke-linejoin="round"/>\n                </svg>\n              </button>\n              <button class="search-nav-btn search-next" id="search-next" title="下一个">\n                <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">\n                  <path d="M3 4.5L6 7.5L9 4.5" stroke-linecap="round" stroke-linejoin="round"/>\n                </svg>\n              </button>\n            </div>\n          </div>\n          <div class="subtitle-header-actions">\n            <span class="ai-icon" title="AI配置">\n              ${ue}\n            </span>\n            <span class="download-icon" title="下载字幕">\n              ${pe}\n            </span>\n            <span class="notion-icon ${xe.notion.isSending?"loading":""}" title="Notion">\n              ${he}\n            </span>\n            <span class="subtitle-close">×</span>\n          </div>\n        </div>\n      </div>\n      <div class="subtitle-tabs">\n        <div class="subtitle-tab active" data-tab="summary">视频总结</div>\n        <div class="subtitle-tab" data-tab="subtitles">字幕列表</div>\n      </div>\n      <div class="subtitle-content">\n        <div class="subtitle-panel" id="summary-panel" style="display: block;">\n          ${this.renderAISummaryPanel(n)}\n        </div>\n        <div class="subtitle-panel" id="subtitles-panel" style="display: none;">\n          <div class="subtitle-list-container" id="subtitle-list-container">\n            <div class="original-subtitles-section">\n              <div class="segments-header">字幕列表</div>\n    `;return e.forEach((e,t)=>{const n=Me(e.from);o+=`\n        <div class="subtitle-item" data-index="${t}" data-from="${e.from}" data-to="${e.to}">\n          <span class="subtitle-time">${n}</span>\n          <span class="subtitle-text">${e.content}</span>\n          <button class="save-subtitle-note-btn" data-content="${this.escapeHtml(e.content)}" title="保存为笔记">保存</button>\n        </div>\n      `;}),o+='\n              </div>\n            </div>\n            <button class="subtitle-follow-btn" id="subtitle-follow-btn" style="display: none;" title="回到当前播放位置">\n              恢复滚动\n            </button>\n          </div>\n        </div>\n      </div>\n    ',o}escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}_escapeHtml(e){const t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};return e.replace(/[&<>"']/g,e=>t[e])}parseMarkdown(e){if(!e)return "";let t=e.trim();if(t=t.replace(/```[a-zA-Z0-9]*\s*```/g,""),t=t.replace(/```\s*\n\s*```/g,""),"undefined"!=typeof marked&&"function"==typeof marked.parse)try{"function"==typeof marked.use&&marked.use({renderer:{link:(e,t,n)=>`<a href="${e}" target="_blank" rel="noopener noreferrer" ${t?`title="${t}"`:""}>${n}</a>`}}),this.markedConfigured||"function"!=typeof marked.setOptions||(marked.setOptions({breaks:!0,gfm:!0}),this.markedConfigured=!0);let e=marked.parse(t);return e=e.replace(/<pre><code[^>]*>\s*<\/code><\/pre>/g,""),e}catch(o){ye.warn("UIRenderer","Marked解析失败:",o);}let n=t;return n=n.replace(/```([a-zA-Z0-9]*)?\n([\s\S]*?)```/g,(e,t,n)=>{if(!n.trim())return "";return `<pre><code${t?` class="language-${t}"`:""}>${this._escapeHtml(n)}</code></pre>`}),n=n.replace(/^### (.*$)/gim,"<h3>$1</h3>"),n=n.replace(/^## (.*$)/gim,"<h2>$1</h2>"),n=n.replace(/^# (.*$)/gim,"<h1>$1</h1>"),n=n.replace(/\*\*(.*?)\*\*/g,"<strong>$1</strong>"),n=n.replace(/(?<!\*)\*(?!\*)([^*]+?)(?<!\*)\*(?!\*)/g,"<em>$1</em>"),n=n.replace(/`([^`]+)`/g,"<code>$1</code>"),n=n.replace(/^\* (.*$)/gim,"<li>$1</li>"),n=n.replace(/^- (.*$)/gim,"<li>$1</li>"),n=n.replace(/^\d+\. (.*$)/gim,"<li>$1</li>"),n=n.replace(/(<li>.*<\/li>(?:\s*<li>.*<\/li>)*)/g,"<ul>$1</ul>"),n=n.replace(/<ul>((?:<li>.*<\/li>\s*)+)<\/ul>/g,(e,n)=>t.split("\n").some(e=>/^\d+\.\s+/.test(e))?`<ol>${n}</ol>`:e),n=n.replace(/\n/g,"<br>"),n}normalizeMarkdown(e){if(!e)return "";let t=String(e).replace(/\r\n/g,"\n").replace(/\r/g,"\n").trim();const n=t.match(/^```[a-zA-Z0-9+_-]*\s*\n([\s\S]*?)\s*```$/);n&&(t=n[1]);const o=t.split("\n"),i=o.filter(e=>e.trim().length>0);if(i.length>0){const e=Math.min(...i.map(e=>e.match(/^ */)[0].length));e>0&&(t=o.map(t=>t.startsWith(" ".repeat(e))?t.slice(e):t).join("\n"));}return t.trim()}renderAISummaryPanel(e=null,t=false){let n="";if(e){let t="",o=[];if("object"==typeof e&&e.markdown)t=this.parseMarkdown(e.markdown),o=e.segments||[];else if("string"==typeof e){t=this.parseAISummary(e).mainSummary;}n='<div class="summary-panel-container">',o&&o.length>0&&(n+=`\n          <div class="ai-segments-in-summary">\n            <div class="segments-header">AI时间戳段落</div>\n            ${this.renderAISegments(o)}\n            <div class="segments-divider"></div>\n          </div>\n        `),n+=t.trim()?`\n        <div class="ai-summary-main">\n          <div class="summary-content">${t}</div>\n        </div>\n      `:'<div class="ai-summary-empty">暂无总结内容</div>',n+="</div>";}else n='\n        <div class="ai-summary-empty">\n          <p>点击上方AI图标生成视频总结</p>\n        </div>\n      ';return n}parseAISummary(e){let t="",n=[];const o=e.match(/\[总结\]([\s\S]*?)(?=\[段落\]|$)/i),i=e.match(/\[段落\]([\s\S]*)/i);if(o&&o[1]){const e=o[1].trim();t=this.parseMarkdown(e);}if(i&&i[1]){const e=i[1],t=/\[(\d{1,2}:\d{2}(?::\d{2})?)\]\s*([^\n]+?)(?:\n\s+([^\[]+?))?(?=\[|$)/g;let o;for(;null!==(o=t.exec(e));){const[e,t,i,s]=o;let r=t;const a=t.split(":");if(2===a.length)r=`[${a[0].padStart(2,"0")}:${a[1].padStart(2,"0")}]`;else if(3===a.length){r=`[${(60*parseInt(a[0])+parseInt(a[1])).toString().padStart(2,"0")}:${a[2].padStart(2,"0")}]`;}const l=i.trim(),c=s?s.trim():"";n.push({title:l,time:r,content:c});}}if(!t&&0===n.length){const o=e.split("\n");let i=[],s=false;for(const e of o){const t=e.trim();if(t.match(/^\[\d{1,2}:\d{2}(?::\d{2})?\]/)){s=true;const e=t.match(/^\[(\d{1,2}:\d{2}(?::\d{2})?)\]\s*(.*)/);if(e){const[t,o,i]=e;let s=o;const r=o.split(":");if(2===r.length)s=`[${r[0].padStart(2,"0")}:${r[1].padStart(2,"0")}]`;else if(3===r.length){s=`[${(60*parseInt(r[0])+parseInt(r[1])).toString().padStart(2,"0")}:${r[2].padStart(2,"0")}]`;}n.push({title:i.trim(),time:s,content:""});}}else s?n.length>0&&e.startsWith("  ")&&(n[n.length-1].content+=(n[n.length-1].content?" ":"")+t):i.push(e);}i.length>0&&(t=this.parseMarkdown(i.join("\n").trim()));}if(!t&&0===n.length){const o=e.split(/##\s*要点/i);if(o.length>=2){const e=o[0].replace(/##\s*总结/i,"").trim();t=this.parseMarkdown(e);const i=o[1].matchAll(/###\s*\[?([^\]\n]+)\]?[\s\S]*?-\s*时间[::]\s*\[(\d{1,2}):(\d{2})\][\s\S]*?-\s*内容[::]\s*([^\n]+)/gi);for(const t of i){const[e,o,i,s,r]=t;n.push({title:o.trim(),time:`[${i.padStart(2,"0")}:${s.padStart(2,"0")}]`,content:r.trim()});}}}return t||0!==n.length||(t=this.parseMarkdown(e)),{mainSummary:t,keyPoints:n}}renderAISummarySection(e=null,t=false){const n=document.createElement("div");return n.className="ai-summary-section",n.innerHTML=this.renderAISummaryPanel(e,t),n}updateAISummary(e,t){if(e||(e=document.getElementById("subtitle-container")),!e)return;const n=e.querySelector("#summary-panel");n&&(n.innerHTML=this.renderAISummaryPanel(t,false));}renderAISegments(e){return e.map((e,t)=>{const n=(e.timestamp||"[00:00]").replace(/[\[\]]/g,"");return `\n        <div class="section-item" data-time="${e.timestamp||"[00:00]"}" data-index="${t}">\n          <button class="time-btn">${n}</button>\n          <div class="item-content">\n            ${e.summary?`<span class="item-title">${e.title||""}</span>\n               <span class="item-desc">${e.summary}</span>`:`<span class="item-single">${e.title||""}</span>`}\n          </div>\n        </div>\n      `}).join("")}createNotification(e){const t=document.createElement("div");return t.className="notion-toast show",t.textContent=e,document.body.appendChild(t),setTimeout(()=>{t.remove();},3e3),t}createNotionConfigModal(){const e=document.createElement("div");return e.id="notion-config-modal",e.className="config-modal",e.innerHTML='\n      <div class="config-modal-content">\n        <div class="config-modal-header">\n          <span>Notion 集成配置</span>\n        </div>\n        <div class="config-modal-body">\n          <div class="config-field">\n            <label>1️⃣ Notion API Key</label>\n            <input type="password" id="notion-api-key" placeholder="输入你的 Integration Token">\n            <div class="config-help">\n              访问 <a href="https://www.notion.so/my-integrations" target="_blank">Notion Integrations</a> 创建 Integration 并复制 Token\n            </div>\n          </div>\n          <div class="config-field">\n            <label>2️⃣ 目标位置(二选一)</label>\n            <input type="text" id="notion-parent-page-id" placeholder="Page ID 或 Database ID">\n            <div class="config-help">\n              <strong>方式A - 使用已有数据库:</strong><br>\n              从数据库 URL 中获取:<code>notion.so/<strong>abc123...</strong>?v=...</code><br>\n              脚本会直接向该数据库添加记录\n            </div>\n            <div class="config-help" style="margin-top: 8px;">\n              <strong>方式B - 自动创建数据库:</strong><br>\n              从页面 URL 中获取:<code>notion.so/My-Page-<strong>abc123...</strong></code><br>\n              首次使用会在此页面下创建数据库\n            </div>\n            <div class="config-help" style="margin-top: 8px; color: #f59e0b;">\n              ⚠️ 重要:需要在「Share」中邀请你的 Integration\n            </div>\n          </div>\n        <div class="config-field">\n          <label>\n            <input type="checkbox" id="notion-auto-send-enabled">\n            自动发送(获取字幕后自动发送到Notion)\n          </label>\n        </div>\n\n        <div class="config-field">\n          <label style="font-weight: 600; margin-bottom: 10px; display: block;">📋 自动添加内容选项</label>\n          <div style="display: flex; flex-direction: column; gap: 8px;">\n            <label style="display: flex; align-items: center; gap: 8px; font-weight: normal;">\n              <input type="checkbox" id="notion-content-video-info" checked>\n              <span>📹 视频信息</span>\n            </label>\n            <label style="display: flex; align-items: center; gap: 8px; font-weight: normal;">\n              <input type="checkbox" id="notion-content-summary" checked>\n              <span>📊 视频总结</span>\n            </label>\n            <label style="display: flex; align-items: center; gap: 8px; font-weight: normal;">\n              <input type="checkbox" id="notion-content-segments" checked>\n              <span>⏱️ 时间戳段落(含截图)</span>\n            </label>\n            <label style="display: flex; align-items: center; gap: 8px; font-weight: normal;">\n              <input type="checkbox" id="notion-content-subtitles" checked>\n              <span>📝 字幕内容</span>\n            </label>\n          </div>\n          <div class="config-help" style="margin-top: 10px;">\n            选择要自动添加到Notion的内容。未勾选的内容不会被发送。\n          </div>\n        </div>\n\n          <div id="notion-status-message"></div>\n        </div>\n        <div class="config-footer">\n          <button class="config-btn config-btn-secondary" id="notion-cancel-btn">取消</button>\n          <button class="config-btn config-btn-primary" id="notion-save-btn">保存配置</button>\n        </div>\n      </div>\n    ',e}createAIConfigModal(){const e=document.createElement("div");return e.id="ai-config-modal",e.className="config-modal",e.innerHTML='\n      <div class="config-modal-content">\n        <div class="config-modal-header">\n          <span>AI 配置管理</span>\n        </div>\n        <div class="config-modal-body">\n          <div style="margin-bottom: 20px; padding: 15px; background: linear-gradient(135deg, rgba(255, 107, 107, 0.15), rgba(255, 77, 77, 0.15)); border-radius: 10px; border-left: 4px solid #ff6b6b;">\n            <div style="font-size: 14px; color: #fff; font-weight: 600; margin-bottom: 8px;">⚠️ 首次使用必读</div>\n            <div style="font-size: 12px; color: rgba(255, 255, 255, 0.9); line-height: 1.6; margin-bottom: 8px;">\n              • 使用AI总结功能前,需要先配置API Key<br>\n              • 选择一个AI服务商,点击查看其配置,填写API Key后保存<br>\n              • 推荐使用 <strong>OpenRouter</strong>、<strong>DeepSeek</strong> 或 <strong>硅基流动</strong>(提供免费额度)\n            </div>\n            <div style="font-size: 11px; color: rgba(255, 255, 255, 0.6); margin-top: 8px;">\n              💡 提示:点击配置卡片可查看详情和获取API Key的教程链接\n            </div>\n          </div>\n          <div class="ai-config-list" id="ai-config-list"></div>\n          <div style="margin-bottom: 15px; text-align: center; display: flex; gap: 10px; justify-content: center;">\n            <button class="config-btn config-btn-primary" id="ai-start-summary-btn" style="padding: 8px 20px; font-size: 14px; font-weight: 600;">🚀 开始总结</button>\n            <button class="config-btn config-btn-secondary" id="ai-new-config-btn" style="padding: 8px 16px; font-size: 13px;">新建配置</button>\n          </div>\n          <div class="ai-config-form hidden">\n          <div class="config-field">\n            <label>配置名称</label>\n            <input type="text" id="ai-config-name" placeholder="例如:OpenAI GPT-4">\n          </div>\n          <div class="config-field">\n            <label>API URL</label>\n            <input type="text" id="ai-config-url" placeholder="https://api.openai.com/v1/chat/completions">\n          </div>\n          <div class="config-field">\n            <label>API Key <span id="api-key-help-link" style="font-size: 11px; margin-left: 8px;"></span></label>\n            <input type="password" id="ai-config-apikey" placeholder="sk-...">\n          </div>\n          <div class="config-field">\n            <label>模型</label>\n            <div class="model-field-with-button">\n              <input type="text" id="ai-config-model" placeholder="手动输入或点击获取模型">\n              <button class="fetch-models-btn" id="fetch-models-btn">获取模型</button>\n            </div>\n            <div class="model-select-wrapper" id="model-select-wrapper" style="display:none;">\n              <input type="text" id="model-search-input" class="model-search-input" placeholder="🔍 搜索模型...">\n              <select id="model-select" size="8"></select>\n            </div>\n          </div>\n          <div class="config-field">\n            <label>\n              <input type="checkbox" id="ai-config-is-openrouter">\n              使用OpenRouter (支持获取模型列表)\n            </label>\n          </div>\n          <div class="config-field">\n            <label>总结提示词 (Markdown总结)</label>\n            <textarea id="ai-config-prompt1" placeholder="请用中文总结以下视频字幕内容,使用Markdown格式输出..."></textarea>\n          </div>\n          <div class="config-field">\n            <label>段落提示词 (JSON时间段落)</label>\n            <textarea id="ai-config-prompt2" placeholder="请根据以下带时间戳的视频字幕内容,提取关键时间点段落..."></textarea>\n          </div>\n          <div class="config-field">\n            <label>\n              <input type="checkbox" id="ai-auto-summary-enabled">\n              自动总结(获取字幕后自动触发AI总结)\n            </label>\n          </div>\n          </div>\n        </div>\n        <div class="config-footer">\n          <button class="config-btn config-btn-danger" id="ai-delete-current-btn" style="display:none;">删除此配置</button>\n          <div style="flex: 1;"></div>\n          <button class="config-btn config-btn-secondary" id="ai-cancel-btn">取消</button>\n          <button class="config-btn config-btn-primary" id="ai-save-new-btn">添加新配置</button>\n          <button class="config-btn config-btn-primary" id="ai-update-btn" style="display:none;">更新配置</button>\n        </div>\n      </div>\n    ',e}renderAIConfigList(e){const t=we.getAIConfigs(),n=we.getSelectedAIConfigId();e.innerHTML=t.map(e=>{const t=e.apiKey&&""!==e.apiKey.trim(),o=t?"✅":"⚠️",i=t?"已配置":"未配置",s=t?"#4ade80":"#fbbf24";return `\n        <div class="ai-config-item ${e.id===n?"selected":""}" data-id="${e.id}">\n          <div class="ai-config-item-name">\n            ${e.name}\n            <span style="font-size: 11px; color: ${s}; margin-left: 8px;" title="API Key ${i}">\n              ${o} ${i}\n            </span>\n          </div>\n          <div class="ai-config-item-actions">\n            <button class="ai-config-btn-small config-btn-primary ai-edit-btn" data-id="${e.id}">查看</button>\n          </div>\n        </div>\n      `}).join("");}showNotionStatus(e,t=false){const n=document.getElementById("notion-status-message");n&&(n.className="config-status "+(t?"error":"success"),n.textContent=e);}renderShortcutConfigModal(){if(ye.debug("UIRenderer","renderShortcutConfigModal 开始"),!Ee)return console.error("[UIRenderer] shortcutManager 未定义"),null;let e;try{e=Ee.getAllShortcuts(),ye.debug("UIRenderer","获取到快捷键:",e);}catch(t){return console.error("[UIRenderer] 获取快捷键失败:",t),null}return e&&"object"==typeof e?`\n      <div class="config-modal" id="shortcut-config-modal">\n        <div class="config-modal-content">\n          <div class="config-modal-header">\n            <span class="config-modal-title">快捷键设置</span>\n            <button class="config-modal-close">×</button>\n          </div>\n          <div class="config-modal-body">\n            <div style="margin-bottom: 20px; padding: 15px; background: rgba(254, 235, 234, 0.1); border-radius: 10px; border-left: 4px solid #feebea;">\n              <div style="font-size: 13px; color: #fff; font-weight: 600; margin-bottom: 4px;">使用说明</div>\n              <div style="font-size: 12px; color: rgba(255, 255, 255, 0.7); line-height: 1.5;">\n                点击输入框后按下想要的组合键即可设置。特殊组合键:<br/>\n                • 截图:连按两下 / 键<br/>\n                • 双击操作:快速连续按两次同一个键\n              </div>\n            </div>\n            \n            <div style="display: flex; flex-direction: column; gap: 12px;">\n              ${Object.entries(e).map(([e,t])=>`\n                <div class="shortcut-item" data-key="${e}">\n                  <div class="shortcut-description">${t.description}</div>\n                  <div class="shortcut-controls">\n                    <input \n                      type="text" \n                      class="shortcut-input" \n                      value="${Ee.formatShortcut(t)}" \n                      readonly \n                      placeholder="点击设置"\n                      data-key="${e}"\n                    >\n                    <button class="shortcut-mode-btn shortcut-hold-btn ${t.holdMode?"active":""}" \n                            data-key="${e}" \n                            data-mode="hold"\n                            title="长按此键触发">\n                      长按\n                    </button>\n                    <button class="shortcut-mode-btn shortcut-double-btn ${t.doubleClickMode?"active":""}" \n                            data-key="${e}" \n                            data-mode="double"\n                            title="双击此键触发">\n                      双击\n                    </button>\n                    <button class="shortcut-reset-btn" data-key="${e}">重置</button>\n                  </div>\n                </div>\n              `).join("")}\n            </div>\n            \n            <div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid rgba(255, 255, 255, 0.1);">\n              <button class="config-btn config-btn-secondary" id="reset-all-shortcuts">\n                重置所有快捷键\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    `:(console.error("[UIRenderer] 快捷键配置无效:",e),null)}};const gt=new class{constructor(){this.stack=[],this.escHandler=null,this.init();}init(){this.escHandler=e=>{if("Escape"===e.key&&this.stack.length>0){const e=this.stack[this.stack.length-1];e&&"function"==typeof e.hide&&e.hide();}},document.addEventListener("keydown",this.escHandler);}push(e){if(!e||"function"!=typeof e.hide)return void ye.warn("ModalManager","模态框实例必须有hide方法");-1===this.stack.indexOf(e)&&this.stack.push(e);}pop(e){const t=this.stack.indexOf(e);t>-1&&this.stack.splice(t,1);}closeAll(){for(;this.stack.length>0;){const e=this.stack.pop();e&&"function"==typeof e.hide&&e.hide();}}getStackSize(){return this.stack.length}isInStack(e){return this.stack.includes(e)}destroy(){this.escHandler&&(document.removeEventListener("keydown",this.escHandler),this.escHandler=null),this.stack=[];}};const mt=new class{constructor(){this.panel=null,this.isPanelVisible=false,this.filters={showText:true,showScreenshot:true};}createPanel(){return this.panel||(this.panel=document.createElement("div"),this.panel.id="notes-panel",this.panel.className="notes-panel",document.body.appendChild(this.panel)),this.panel}showPanel(){const e=this.createPanel();this.renderPanel(),e.classList.add("show"),this.isPanelVisible=true,gt.push(this);}hidePanel(){this.panel&&this.panel.classList.remove("show"),this.isPanelVisible=false,gt.pop(this);}hide(){this.hidePanel();}togglePanel(){this.isPanelVisible?this.hidePanel():this.showPanel();}renderPanel(){const e=this.createPanel(),t=this.getFilteredGroupedNotes(),n=He.getAllNotes(),o=n.filter(e=>"screenshot"!==e.type&&"ai-summary"!==e.type).length,i=n.filter(e=>"ai-summary"===e.type).length,s=n.filter(e=>"screenshot"===e.type).length,r=o+i,a=`\n      <div class="notes-panel-content">\n        <div class="notes-panel-header">\n          <h2>我的笔记</h2>\n          <button class="notes-panel-close">×</button>\n        </div>\n        <div class="notes-filters">\n          <label class="filter-checkbox">\n            <input type="checkbox" id="filter-text-notes" ${this.filters.showText?"checked":""}>\n            <span>文字笔记 (${r})</span>\n          </label>\n          <label class="filter-checkbox">\n            <input type="checkbox" id="filter-screenshot-notes" ${this.filters.showScreenshot?"checked":""}>\n            <span>截图笔记 (${s})</span>\n          </label>\n        </div>\n        <div class="notes-panel-body">\n          ${0===t.length?this.renderEmptyState():t.map(e=>this.renderGroup(e)).join("")}\n        </div>\n      </div>\n    `;e.innerHTML=a,this.bindPanelEvents();}getFilteredGroupedNotes(){const e=He.getAllNotes().filter(e=>"screenshot"===e.type?this.filters.showScreenshot:(e.type,this.filters.showText)),t={};return e.forEach(e=>{const n=e.createdAt||e.timestamp,o=He.formatDate(n);t[o]||(t[o]=[]),t[o].push(e);}),Object.keys(t).sort((e,n)=>{const o=t[e][0].createdAt||t[e][0].timestamp;return (t[n][0].createdAt||t[n][0].timestamp)-o}).map(e=>({date:e,notes:t[e].sort((e,t)=>{if("ai-summary"===e.type&&"ai-summary"!==t.type)return  -1;if("ai-summary"!==e.type&&"ai-summary"===t.type)return 1;const n=e.createdAt||e.timestamp;return (t.createdAt||t.timestamp)-n})}))}renderEmptyState(){if(He.getAllNotes().length>0)return '\n        <div class="notes-empty-state">\n          <div class="notes-empty-icon">🔍</div>\n          <div>没有符合筛选条件的笔记</div>\n          <div class="notes-empty-hint">请调整上方的筛选条件</div>\n        </div>\n      ';{let t="截图";try{const e=window.shortcutManager;if(e){const n=e.getAllShortcuts();n.takeScreenshot&&(t=e.formatShortcut(n.takeScreenshot));}}catch(e){}return `\n        <div class="notes-empty-state">\n          <div class="notes-empty-icon">📝</div>\n          <div>还没有保存任何笔记</div>\n          <div class="notes-empty-hint">选中文字后点击粉色钢笔即可保存<br>或使用 ${t} 保存截图</div>\n        </div>\n      `}}renderGroup(e){return `\n      <div class="note-group">\n        <div class="note-group-header">\n          <div class="note-group-title">\n            ${e.date} (${e.notes.length}条)\n          </div>\n          <div class="note-group-actions">\n            <button class="note-group-copy-btn" data-date="${e.date}">\n              批量复制\n            </button>\n            <button class="note-group-delete-btn" data-date="${e.date}">\n              批量删除\n            </button>\n          </div>\n        </div>\n        <div class="note-group-items">\n          ${e.notes.map(e=>this.renderNote(e)).join("")}\n        </div>\n      </div>\n    `}renderNote(e){if("ai-summary"===e.type)return this.renderAISummaryNote(e);const t=e.content.length>200?e.content.substring(0,200)+"...":e.content,n="screenshot"===e.type&&e.imageData?`\n        <div class="note-screenshot">\n          <img src="${e.imageData}" alt="视频截图" style="max-width: 100%; border-radius: 4px; margin-top: 8px;">\n        </div>\n        <div class="note-content">${this.escapeHtml(t)}</div>\n      `:`<div class="note-content">${this.escapeHtml(t)}</div>`;let o="",i="";return "screenshot"===e.type?(o=e.timeString||He.formatTime(e.createdAt||e.timestamp),e.videoTitle&&"未知视频"!==e.videoTitle?i=` · ${this.escapeHtml(e.videoTitle)}`:e.videoBvid&&(i=` · ${this.escapeHtml(e.videoBvid)}`)):(o=He.formatTime(e.timestamp),e.videoTitle&&(i=` · ${this.escapeHtml(e.videoTitle)}`)),`\n      <div class="note-item ${"screenshot"===e.type?"note-item-screenshot":""}" data-note-id="${e.id}">\n        ${n}\n        <div class="note-footer">\n          <div class="note-time">\n            ${"screenshot"===e.type?"📸 ":""}${o}${i}\n          </div>\n          <div class="note-actions">\n            <button class="note-copy-btn" data-note-id="${e.id}">复制</button>\n            <button class="note-delete-btn" data-note-id="${e.id}">删除</button>\n          </div>\n        </div>\n      </div>\n    `}renderAISummaryNote(e){const t=e.videoInfo?.title||e.videoBvid||"未知视频",n=`\n      <div class="ai-summary-section">\n        <div class="ai-summary-title">📹 视频信息</div>\n        <div class="ai-summary-content">\n          ${this.escapeHtml(t)}\n        </div>\n      </div>\n    `,o=e.summary?`\n      <div class="ai-summary-section">\n        <div class="ai-summary-title">📊 视频总结</div>\n        <div class="ai-summary-content markdown-content">\n          ${this.escapeHtml(e.summary).replace(/\n/g,"<br>")}\n        </div>\n      </div>\n    `:"",i=e.segments&&e.segments.length>0?`\n      <div class="ai-summary-section">\n        <div class="ai-summary-title">⏱️ 时间戳段落</div>\n        <div class="ai-summary-segments">\n          ${e.segments.map((t,n)=>{const o=(e.screenshots||[]).filter(e=>e.segmentIndex===n);return `\n              <div class="segment-item">\n                <div class="segment-header">\n                  <span class="segment-time">[${t.timestamp}]</span>\n                  <span class="segment-title">${this.escapeHtml(t.title)}</span>\n                </div>\n                <div class="segment-summary">${this.escapeHtml(t.summary)}</div>\n                ${o.map(e=>`\n                  <div class="segment-screenshot">\n                    <img src="${e.imageData}" alt="截图" style="max-width: 100%; border-radius: 4px; margin-top: 8px;">\n                    <div class="screenshot-time">📸 ${e.timeString}</div>\n                  </div>\n                `).join("")}\n              </div>\n            `}).join("")}\n        </div>\n      </div>\n    `:"";return `\n      <div class="note-item note-item-ai-summary" data-note-id="${e.id}">\n        <div class="ai-summary-content-wrapper">\n          ${n}\n          ${o}\n          ${i}\n        </div>\n        <div class="note-footer">\n          <div class="note-time">\n            🤖 AI总结 · ${He.formatTime(e.createdAt||e.timestamp)}\n          </div>\n          <div class="note-actions">\n            <button class="note-copy-btn" data-note-id="${e.id}">复制</button>\n            <button class="note-delete-btn" data-note-id="${e.id}">删除</button>\n          </div>\n        </div>\n      </div>\n    `}escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}async copyToClipboard(e){try{if(navigator.clipboard&&navigator.clipboard.writeText)await navigator.clipboard.writeText(e);else {const t=document.createElement("textarea");t.value=e,t.style.position="fixed",t.style.opacity="0",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t);}}catch(t){console.error("复制失败:",t);}}bindPanelEvents(){const e=this.panel.querySelector(".notes-panel-close");e&&e.addEventListener("click",()=>this.hidePanel());const t=this.panel.querySelector("#filter-text-notes"),n=this.panel.querySelector("#filter-screenshot-notes");t&&t.addEventListener("change",e=>{this.filters.showText=e.target.checked,this.renderPanel();}),n&&n.addEventListener("change",e=>{this.filters.showScreenshot=e.target.checked,this.renderPanel();});const o=this.panel.querySelector(".notes-panel-body");o&&o.addEventListener("click",async e=>{const t=e.target.closest(".note-copy-btn");if(t){const e=t.getAttribute("data-note-id"),n=He.getAllNotes().find(t=>t.id===e);if(n){await this.copyToClipboard(n.content);const e=t.textContent;t.textContent="✓",setTimeout(()=>{t.textContent=e;},1e3);}return}const n=e.target.closest(".note-delete-btn");if(n){const e=n.getAttribute("data-note-id");return He.deleteNote(e),void this.renderPanel()}const o=e.target.closest(".note-group-copy-btn");if(o){const e=o.getAttribute("data-date"),t=He.getGroupedNotes().find(t=>t.date===e);if(t){const e=t.notes.map(e=>e.content).join("\n\n");await this.copyToClipboard(e);const n=o.textContent;o.textContent="✓",setTimeout(()=>{o.textContent=n;},1e3);}return}const i=e.target.closest(".note-group-delete-btn");if(i){const e=i.getAttribute("data-date"),t=He.getGroupedNotes().find(t=>t.date===e);if(t&&confirm(`确定要删除 ${e} 的 ${t.notes.length} 条笔记吗?`)){const e=t.notes.map(e=>e.id);He.deleteNotes(e),this.renderPanel();}return}});}addSaveButton(e){if(e.querySelector(".save-subtitle-note-btn"))return;const t=e.querySelector(".subtitle-text")?.textContent;if(!t)return;const n=document.createElement("button");n.className="save-subtitle-note-btn",n.textContent="保存",n.title="保存此字幕为笔记",n.addEventListener("click",e=>{e.stopPropagation(),He.saveSubtitleNote(t),n.textContent="✓",setTimeout(()=>{n.textContent="保存";},1e3);});const o=e.querySelector(".subtitle-time");o&&o.appendChild(n);}addSaveButtonsToSubtitles(e){e.querySelectorAll(".subtitle-item").forEach(e=>this.addSaveButton(e));}};const bt=new class{constructor(){this.index=new Map,this.items=[],this.minWordLength=1;}buildIndex(e){this.items=e,this.index.clear(),e.forEach((e,t)=>{this.tokenize(e.content).forEach(e=>{this.index.has(e)||this.index.set(e,[]);const n=this.index.get(e);n.includes(t)||n.push(t);});}),ye.debug("SearchIndex",`索引构建完成: ${this.index.size} 个词, ${e.length} 项数据`);}tokenize(e){if(!e)return [];const t=[],n=e.toLowerCase();for(let o=0;o<n.length;o++){n[o].trim()&&t.push(n[o]);for(let e=2;e<=Math.min(4,n.length-o);e++){const i=n.substring(o,o+e);i.trim().length===e&&t.push(i);}}return t}search(e){if(!e||!e.trim())return [];const t=e.toLowerCase().trim();if(this.index.has(t))return this.index.get(t);const n=this.tokenize(t);if(0===n.length)return [];let o=this.index.get(n[0])||[];for(let i=1;i<n.length&&o.length>0;i++){const e=this.index.get(n[i])||[];o=o.filter(t=>e.includes(t));}return o}incrementalSearch(e,t,n){if(e.startsWith(t)&&n.length>0){const t=e.toLowerCase();return n.filter(e=>{const n=this.items[e];return n&&n.content.toLowerCase().includes(t)})}return this.search(e)}clear(){this.index.clear(),this.items=[];}getStats(){return {indexSize:this.index.size,itemsCount:this.items.length,avgIndicesPerWord:this.index.size>0?(Array.from(this.index.values()).reduce((e,t)=>e+t.length,0)/this.index.size).toFixed(2):0}}};const ft=new class{constructor(){this.container=null,this.video=null,this.isFollowing=true,this.scrollTimer=null,this.followInterval=null,this.userScrollTimeout=null,this.lastScrollTime=0,this.lastHighlightedItem=null,this.config={followIntervalMs:200,userScrollDetectMs:300,scrollBehavior:"smooth",scrollPosition:"start",highlightClass:"current"},this.callbacks={onFollowStatusChange:null,onSubtitleHighlight:null};}init(e,t={}){e?(this.container=e,this.video=document.querySelector("video"),this.video?(this.config={...this.config,...t},this.setupScrollListener(),this.startAutoFollow()):console.warn("SubtitleScrollManager: 未找到视频元素")):console.warn("SubtitleScrollManager: 容器不存在");}setupScrollListener(){this.container&&this.container.addEventListener("scroll",()=>{this.handleUserScroll();},{passive:true});}handleUserScroll(){Date.now()-this.lastScrollTime<50||(this.userScrollTimeout&&clearTimeout(this.userScrollTimeout),this.isFollowing,this.userScrollTimeout=setTimeout(()=>{this.isFollowing&&(this.isFollowing=false,this.triggerFollowStatusChange(false));},this.config.userScrollDetectMs));}startAutoFollow(){this.stopAutoFollow(),this.isFollowing=true,this.triggerFollowStatusChange(true),this.updateScroll(),this.followInterval=setInterval(()=>{this.isFollowing&&this.updateScroll();},this.config.followIntervalMs);}stopAutoFollow(){this.followInterval&&(clearInterval(this.followInterval),this.followInterval=null),this.isFollowing=false,this.triggerFollowStatusChange(false);}resumeAutoFollow(){this.startAutoFollow(),this.scrollToCurrentSubtitle(true);}updateScroll(){if(!this.video||!this.container)return;const e=this.video.currentTime,t=this.container.querySelectorAll(".subtitle-item");if(0===t.length)return;let n=null;for(let o=0;o<t.length;o++){const i=t[o],s=parseFloat(i.dataset.startTime||0),r=parseFloat(i.dataset.endTime||0);if(e>=s&&e<=r){n=i;break}if(o<t.length-1){const r=parseFloat(t[o+1].dataset.startTime||0);if(e>=s&&e<r){n=i;break}}}if(!n){let o=1/0;for(const i of t){const t=parseFloat(i.dataset.startTime||0),s=Math.abs(e-t);s<o&&(o=s,n=i);}}n&&n!==this.lastHighlightedItem&&(this.updateHighlight(n),this.isFollowing&&this.scrollToElement(n));}updateHighlight(e){this.lastHighlightedItem&&this.lastHighlightedItem.classList.remove(this.config.highlightClass),e&&(e.classList.add(this.config.highlightClass),this.lastHighlightedItem=e,this.callbacks.onSubtitleHighlight&&this.callbacks.onSubtitleHighlight(e));}scrollToElement(e,t=false){if(!e||!this.container)return;const n=this.container.getBoundingClientRect(),o=e.getBoundingClientRect();let i;if(t||"center"===this.config.scrollPosition){const e=n.height/2,t=o.height/2;i=o.top-n.top+this.container.scrollTop-e+t;}else "start"===this.config.scrollPosition?(i=e.offsetTop-20,i=Math.max(0,i)):i=e.offsetTop-n.height+o.height;this.lastScrollTime=Date.now(),this.container.scrollTo({top:i,behavior:this.isFollowing?this.config.scrollBehavior:"auto"});}scrollToCurrentSubtitle(e=false){if(!this.video||!this.container)return;const t=this.video.currentTime,n=this.container.querySelectorAll(".subtitle-item");for(const o of n){const n=parseFloat(o.dataset.startTime||0),i=parseFloat(o.dataset.endTime||0);if(t>=n&&t<=i){this.updateHighlight(o),this.scrollToElement(o,e);break}}}triggerFollowStatusChange(e){this.callbacks.onFollowStatusChange&&this.callbacks.onFollowStatusChange(e);}on(e,t){this.callbacks.hasOwnProperty(e)&&(this.callbacks[e]=t);}updateConfig(e){this.config={...this.config,...e};}isAutoFollowing(){return this.isFollowing}destroy(){this.stopAutoFollow(),this.userScrollTimeout&&(clearTimeout(this.userScrollTimeout),this.userScrollTimeout=null),this.lastHighlightedItem&&(this.lastHighlightedItem.classList.remove(this.config.highlightClass),this.lastHighlightedItem=null),this.container=null,this.video=null,this.callbacks={onFollowStatusChange:null,onSubtitleHighlight:null};}};const yt=new class{constructor(){this.isDragging=false,this.dragStartX=0,this.dragStartY=0,this.translateX=0,this.translateY=0,this.isResizing=false,this.resizeStartX=0,this.resizeStartY=0,this.resizeStartWidth=0,this.resizeStartHeight=0,this.searchMatches=[],this.currentMatchIndex=-1,this.searchTerm="",this.lastSearchTerm="",this.searchIndexBuilt=false,this.subtitleDataCache=null,this.currentHighlightedIndex=-1,this.debouncedSearch=null,this.throttledHighlight=null,this.aiConfigModalProxy={hide:()=>this.hideAIConfigModal()},this.notionConfigModalProxy={hide:()=>this.hideNotionConfigModal()},this.shortcutConfigModalProxy={hide:()=>this.hideShortcutConfigModal()};}initializeSearchIndex(e){e&&e.length>0&&(bt.buildIndex(e),this.searchIndexBuilt=true);}bindSubtitlePanelEvents(e){this.restoreContainerState(e),this.bindDragEvents(e),this.bindResizeEvents(e),this.observeContainerResize(e);const t=e.querySelector(".subtitle-close");t&&t.addEventListener("click",()=>{xe.setPanelVisible(false),e.classList.remove("show"),ft.destroy();});const n=e.querySelectorAll(".subtitle-tab"),o=e.querySelectorAll(".subtitle-panel");n.forEach(e=>{e.addEventListener("click",()=>{const t=e.getAttribute("data-tab");n.forEach(e=>e.classList.remove("active")),e.classList.add("active"),o.forEach(e=>{if(e.id===`${t}-panel`){if(e.style.display="block","subtitles"===t){const t=e.querySelector("#subtitle-list-container");t&&!ft.container&&this.initSubtitleScroll(t);}}else e.style.display="none";});});});const i=e.querySelector(".ai-icon");i&&i.addEventListener("click",async t=>{if(t.stopPropagation(),xe.ai.isSummarizing)return void Ke.warning("AI总结正在生成中,请稍候...");const n=xe.getSubtitleData();if(!n||0===n.length)return void Ke.error("没有可用的字幕数据");if(!we.getSelectedAIConfig())return void Ke.warning("请先在油猴菜单中AI配置中选择或配置一个AI服务");const o=xe.getVideoKey();if(xe.getAISummary(o)){if(!confirm('已存在AI总结,是否重新生成?\n\n点击"确定"重新生成\n点击"取消"查看现有总结')){const t=e?.querySelector('.subtitle-tab[data-tab="summary"]');return void(t&&t.click())}o&&sessionStorage.removeItem(`ai-summary-${o}`),xe.ai.currentSummary=null;}try{await Ge.summarize(n,!0);const t=e?.querySelector('.subtitle-tab[data-tab="summary"]');t&&t.click();}catch(i){Ke.handleError(i,"AI总结");}});const s=e.querySelector("#progress-switch");s&&s.addEventListener("click",()=>{s.classList.toggle("on");s.classList.contains("on")?this.addProgressBarMarkers(e):this.removeProgressBarMarkers();});const r=e.querySelector(".download-icon");r&&r.addEventListener("click",e=>{e.stopPropagation();try{Re.downloadSubtitleFile(),Ke.success("字幕文件已下载");}catch(t){Ke.handleError(t,"下载字幕");}});const a=e.querySelector(".notion-icon");a&&a.addEventListener("click",async e=>{if(e.stopPropagation(),xe.ai.isSummarizing)return void Ke.warning("AI总结正在生成中,请稍后再发送到Notion");const t=xe.getSubtitleData();if(t&&0!==t.length)try{const e=xe.getVideoInfo(),n=xe.getVideoKey(),o=n?xe.getAISummary(n):null;await Ye.sendComplete(t,o,e);}catch(n){Ke.handleError(n,"Notion发送");}else Ke.error("没有字幕数据可发送");});const l=e.querySelector("#subtitle-list-container");l&&this.initSubtitleScroll(l);const c=e.querySelector("#subtitle-search-input");c&&(this.debouncedSearch||(this.debouncedSearch=ze((e,t)=>{this.handleSearch(e,t);},300)),c.addEventListener("input",t=>{this.debouncedSearch(e,t.target.value);}),c.addEventListener("keydown",t=>{"Enter"===t.key&&(t.preventDefault(),this.navigateSearch(e,1));}));const d=e.querySelector("#search-prev"),u=e.querySelector("#search-next");d&&d.addEventListener("click",()=>{this.navigateSearch(e,-1);}),u&&u.addEventListener("click",()=>{this.navigateSearch(e,1);}),e.addEventListener("click",t=>{const n=t.target.closest(".section-item");if(n){t.stopPropagation();const e=window.getSelection();e&&e.removeAllRanges(),ye.info("EventHandlers","段落元素被点击");const o=n.getAttribute("data-time");if(ye.info("EventHandlers","时间戳字符串:",o),o){let e=0;const t=o.match(/\[?(\d{1,2}):(\d{2})(?::(\d{2}))?\]?/);if(t){const[i,s,r,a]=t;e=a?3600*parseInt(s)+60*parseInt(r)+parseInt(a):60*parseInt(s)+parseInt(r),ye.info("EventHandlers","解析后的秒数:",e);const l=document.querySelector(ie);if(l){l.currentTime=e;const t=o.replace(/[\[\]]/g,"");Ke.info(`跳转到 ${t}`),n.classList.add("clicked"),setTimeout(()=>{n.classList.remove("clicked");},300);}}}return}const o=t.target.closest(".save-subtitle-note-btn");if(o){t.stopPropagation();const e=o.getAttribute("data-content");return void(e&&(He.saveSubtitleNote(e),o.textContent="✓",setTimeout(()=>{o.textContent="保存";},1e3)))}const i=t.target.closest(".subtitle-item");if(i){const t=document.querySelector(ie);if(t){const n=parseFloat(i.dataset.from);e.querySelectorAll(".subtitle-item").forEach(e=>{e.classList.remove("current");}),i.classList.add("current"),t.currentTime=n;}}}),this.syncSubtitleHighlight(e);}setupDragging(e){const t=e.querySelector(".subtitle-header");t&&(t.addEventListener("mousedown",t=>{t.target.closest(".subtitle-close")||t.target.closest(".ai-icon")||t.target.closest(".download-icon")||t.target.closest(".notion-icon")||t.target.closest(".subtitle-search-container")||(this.isDragging=true,this.dragStartX=t.clientX,this.dragStartY=t.clientY,e.style.willChange="transform",t.preventDefault());}),document.addEventListener("mousemove",t=>{this.isDragging&&requestAnimationFrame(()=>{const n=t.clientX-this.dragStartX,o=t.clientY-this.dragStartY;this.translateX+=n,this.translateY+=o,this.dragStartX=t.clientX,this.dragStartY=t.clientY,e.style.transform=`translate(${this.translateX}px, ${this.translateY}px)`;});}),document.addEventListener("mouseup",()=>{this.isDragging&&(this.isDragging=false,e.style.willChange="auto",this.savePanelPosition(e));}));}setupResize(e){const t=e.querySelector(".subtitle-resize-handle");t&&(t.addEventListener("mousedown",t=>{this.isResizing=true,this.resizeStartX=t.clientX,this.resizeStartY=t.clientY,this.resizeStartWidth=e.offsetWidth,this.resizeStartHeight=e.offsetHeight,t.preventDefault(),t.stopPropagation();}),document.addEventListener("mousemove",t=>{this.isResizing&&requestAnimationFrame(()=>{const n=t.clientX-this.resizeStartX,o=t.clientY-this.resizeStartY,i=this.resizeStartWidth+n,s=this.resizeStartHeight+o,r=Math.max(300,Math.min(800,i)),a=.9*window.innerHeight,l=Math.max(400,Math.min(a,s));e.style.width=`${r}px`,e.style.maxHeight=`${l}px`;});}),document.addEventListener("mouseup",()=>{this.isResizing&&(this.isResizing=false,this.savePanelDimensions(e));}));}savePanelPosition(e){try{localStorage.setItem("subtitle_panel_position",JSON.stringify({translateX:this.translateX,translateY:this.translateY}));}catch(t){console.error("保存面板位置失败:",t);}}savePanelDimensions(e){try{localStorage.setItem("subtitle_panel_dimensions",JSON.stringify({width:e.offsetWidth,height:e.offsetHeight}));}catch(t){console.error("保存面板尺寸失败:",t);}}loadPanelDimensions(e){try{const t=localStorage.getItem("subtitle_panel_dimensions");if(t){const{width:n,height:o}=JSON.parse(t);e.style.width=`${n}px`,e.style.maxHeight=`${o}px`;}const n=localStorage.getItem("subtitle_panel_position");if(n){const{translateX:t,translateY:o}=JSON.parse(n);this.translateX=t,this.translateY=o,e.style.transform=`translate(${t}px, ${o}px)`;}}catch(t){console.error("加载面板设置失败:",t);}}syncSubtitleHighlight(e){const t=Je.get(ie);if(!t)return;const n=Array.from(e.querySelectorAll(".subtitle-item"));this.subtitleDataCache=n.map(e=>({element:e,from:parseFloat(e.dataset.from),to:parseFloat(e.dataset.to)})),this.throttledHighlight||(this.throttledHighlight=function(e){let t=null;return function(...n){null===t&&(t=requestAnimationFrame(()=>{e.apply(this,n),t=null;}));}}(e=>{this.updateSubtitleHighlight(e);})),t.addEventListener("timeupdate",()=>{this.throttledHighlight(t.currentTime);});}updateSubtitleHighlight(e){if(!this.subtitleDataCache||0===this.subtitleDataCache.length)return;const t=function(e,t){if(!e||0===e.length)return  -1;if(e.length<50){for(let n=0;n<e.length;n++)if(t>=e[n].from&&t<=e[n].to)return n;return  -1}let n=0,o=e.length-1;for(;n<=o;){const i=Math.floor((n+o)/2),s=e[i];if(t>=s.from&&t<=s.to)return i;t<s.from?o=i-1:n=i+1;}return  -1}(this.subtitleDataCache,e);t!==this.currentHighlightedIndex&&(this.currentHighlightedIndex>=0&&this.currentHighlightedIndex<this.subtitleDataCache.length&&this.subtitleDataCache[this.currentHighlightedIndex].element.classList.remove("current"),t>=0&&this.subtitleDataCache[t].element.classList.add("current"),this.currentHighlightedIndex=t);}handleSearch(e,t){if(this.searchTerm=t.trim(),this.clearSearchHighlights(e),!this.searchTerm)return this.updateSearchCounter(0,0),void(this.lastSearchTerm="");if(!this.searchIndexBuilt){const e=xe.getSubtitleData();e&&(bt.buildIndex(e),this.searchIndexBuilt=true);}this.searchMatches=[],this.highlightSearchInContainer(e),this.updateSearchCounter(this.searchMatches.length>0?1:0,this.searchMatches.length),this.searchMatches.length>0&&(this.currentMatchIndex=0,this.scrollToMatch(this.searchMatches[0])),this.lastSearchTerm=this.searchTerm;}highlightSearchInContainer(e){const t=e.querySelector(".subtitle-content");if(!t)return;const n=t.querySelector(".ai-summary-section");if(n){const e=n.querySelector(".ai-summary-content");e&&this.highlightInElement(e,this.searchTerm);}t.querySelectorAll(".subtitle-item").forEach(e=>{const t=e.querySelector(".subtitle-text");t&&this.highlightInElement(t,this.searchTerm);});}highlightInElement(e,t){const n=e.textContent,o=new RegExp(`(${this.escapeRegex(t)})`,"gi");if(n.match(o)){let t=n.replace(o,e=>`<mark class="search-highlight" data-search-match>${e}</mark>`);e.innerHTML=t;e.querySelectorAll("mark[data-search-match]").forEach(e=>{this.searchMatches.push(e);});}}escapeRegex(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}clearSearchHighlights(e){e.querySelectorAll("mark[data-search-match]").forEach(e=>{const t=e.textContent,n=document.createTextNode(t);e.parentNode.replaceChild(n,e);}),this.searchMatches=[],this.currentMatchIndex=-1;}navigateSearch(e,t){if(0===this.searchMatches.length)return;this.currentMatchIndex>=0&&this.currentMatchIndex<this.searchMatches.length&&(this.searchMatches[this.currentMatchIndex].classList.remove("search-highlight-current"),this.searchMatches[this.currentMatchIndex].classList.add("search-highlight")),this.currentMatchIndex+=t,this.currentMatchIndex>=this.searchMatches.length?this.currentMatchIndex=0:this.currentMatchIndex<0&&(this.currentMatchIndex=this.searchMatches.length-1);const n=this.searchMatches[this.currentMatchIndex];n.classList.remove("search-highlight"),n.classList.add("search-highlight-current"),this.scrollToMatch(n),this.updateSearchCounter(this.currentMatchIndex+1,this.searchMatches.length);}scrollToMatch(e){e.classList.add("search-highlight-current"),e.scrollIntoView({behavior:"smooth",block:"center"});}updateSearchCounter(e,t){const n=document.getElementById("search-counter");n&&(n.textContent=`${e}/${t}`);const o=document.getElementById("search-controls");o&&(o.style.display=t>0?"flex":"none");const i=document.getElementById("search-prev"),s=document.getElementById("search-next");i&&(i.disabled=0===t),s&&(s.disabled=0===t);}showAIConfigModal(){const e=document.getElementById("ai-config-modal");if(!e)return;const t=document.getElementById("ai-config-list");t&&ht.renderAIConfigList(t),this.clearAIConfigForm();const n=e.querySelector(".ai-config-form");n&&n.classList.add("hidden"),document.getElementById("ai-auto-summary-enabled").checked=we.getAIAutoSummaryEnabled(),e.classList.add("show"),gt.push(this.aiConfigModalProxy);}hideAIConfigModal(){const e=document.getElementById("ai-config-modal");if(!e)return;const t=document.getElementById("ai-auto-summary-enabled").checked;we.setAIAutoSummaryEnabled(t),e.classList.remove("show"),this.clearAIConfigForm(),gt.pop(this.aiConfigModalProxy);}clearAIConfigForm(){const e=document.getElementById("ai-config-name"),t=document.getElementById("ai-config-url"),n=document.getElementById("ai-config-apikey"),o=document.getElementById("ai-config-model"),i=document.getElementById("ai-config-prompt1"),s=document.getElementById("ai-config-prompt2"),r=document.getElementById("ai-config-is-openrouter"),a=document.getElementById("ai-save-new-btn"),l=document.getElementById("ai-update-btn"),c=document.getElementById("model-select-wrapper"),d=document.getElementById("api-key-help-link");e&&(e.value=""),t&&(t.value="https://openrouter.ai/api/v1/chat/completions"),n&&(n.value=""),o&&(o.value="alibaba/tongyi-deepresearch-30b-a3b:free"),i&&(i.value=""),s&&(s.value=""),r&&(r.checked=true),a&&(a.style.display=""),l&&(l.style.display="none"),c&&(c.style.display="none"),d&&(d.innerHTML="");}showNotionConfigModal(){const e=document.getElementById("notion-config-modal");if(!e)return;const t=we.getNotionConfig(),n=we.getNotionContentOptions();document.getElementById("notion-api-key").value=t.apiKey,document.getElementById("notion-parent-page-id").value=t.parentPageId,document.getElementById("notion-auto-send-enabled").checked=we.getNotionAutoSendEnabled(),document.getElementById("notion-content-video-info").checked=n.videoInfo,document.getElementById("notion-content-summary").checked=n.summary,document.getElementById("notion-content-segments").checked=n.segments,document.getElementById("notion-content-subtitles").checked=n.subtitles;const o=document.getElementById("notion-status-message");o&&(o.innerHTML=""),e.classList.add("show"),gt.push(this.notionConfigModalProxy);}hideNotionConfigModal(){const e=document.getElementById("notion-config-modal");e&&e.classList.remove("show"),gt.pop(this.notionConfigModalProxy);}showShortcutConfigModal(){try{ye.debug("EventHandlers","显示快捷键配置模态框");const e=document.getElementById("shortcut-config-modal");if(e)return e.classList.add("show"),void gt.push(this.shortcutConfigModalProxy);const t=ht.renderShortcutConfigModal();if(!t)return console.error("[EventHandlers] 无法生成快捷键配置模态框HTML"),void Ke.error("无法打开快捷键设置");const n=document.createElement("div");n.innerHTML=t;const o=n.firstElementChild;if(!o)return console.error("[EventHandlers] 无法创建模态框元素"),void Ke.error("无法创建快捷键设置界面");document.body.appendChild(o),requestAnimationFrame(()=>{o.classList.add("show"),gt.push(this.shortcutConfigModalProxy);}),this.bindShortcutConfigModalEvents(o),ye.debug("EventHandlers","快捷键配置模态框已显示");}catch(e){console.error("[EventHandlers] 显示快捷键配置模态框失败:",e),Ke.error("打开快捷键设置失败: "+e.message);}}hideShortcutConfigModal(){const e=document.getElementById("shortcut-config-modal");e&&(e.classList.remove("show"),gt.pop(this.shortcutConfigModalProxy),setTimeout(()=>{e&&e.parentNode&&e.parentNode.removeChild(e);},300));}bindShortcutConfigModalEvents(e){const t=e.querySelector(".config-modal-close");t&&t.addEventListener("click",()=>{this.hideShortcutConfigModal();}),e.addEventListener("click",t=>{t.target===e&&this.hideShortcutConfigModal();});e.querySelectorAll(".shortcut-input").forEach(e=>{e.addEventListener("click",t=>{t.preventDefault(),this.startShortcutCapture(e);});});e.querySelectorAll(".shortcut-reset-btn").forEach(t=>{t.addEventListener("click",()=>{const n=t.dataset.key,o=Ee.constructor.DEFAULT_SHORTCUTS||{};if(o[n]){Ee.updateShortcut(n,o[n]);const t=e.querySelector(`.shortcut-input[data-key="${n}"]`);t&&(t.value=Ee.formatShortcut(o[n])),Ke.success("已重置到默认值");}});});const n=e.querySelector("#reset-all-shortcuts");n&&n.addEventListener("click",()=>{confirm("确定要重置所有快捷键到默认值吗?")&&(Ee.resetToDefaults(),Ke.success("快捷键已重置到默认值"),this.hideShortcutConfigModal(),this.showShortcutConfigModal());});e.querySelectorAll(".shortcut-mode-btn").forEach(e=>{e.addEventListener("click",t=>{t.preventDefault();const n=e.dataset.key,o=e.dataset.mode,i=Ee.getAllShortcuts()[n];if(!i)return;const s=e.closest(".shortcut-item"),r=s.querySelector(".shortcut-hold-btn"),a=s.querySelector(".shortcut-double-btn");if("hold"===o){const t=e.classList.contains("active");r.classList.toggle("active"),a.classList.remove("active");const o={...i,holdMode:!t,doubleClickMode:false};Ee.updateShortcut(n,o);}else if("double"===o){const t=e.classList.contains("active");a.classList.toggle("active"),r.classList.remove("active");const o={...i,holdMode:false,doubleClickMode:!t};Ee.updateShortcut(n,o);}});});}startShortcutCapture(e){const t=e.dataset.key,n=Ee.getAllShortcuts()[t];e.classList.add("recording");const o=e.closest(".shortcut-item"),i=o?.querySelector(".shortcut-hold-btn"),s=o?.querySelector(".shortcut-double-btn"),r=i?.classList.contains("active"),a=s?.classList.contains("active");e.value=r||a?"按下任意键...":"按下快捷键...";let l=null,c="";const d=o=>{if(o.preventDefault(),o.stopPropagation(),"Escape"===o.key)return e.classList.remove("recording"),e.value=Ee.formatShortcut(n),void document.removeEventListener("keydown",d);if(r||a){const i={key:o.code||o.key,meta:false,ctrl:false,alt:false,shift:false,holdMode:r,doubleClickMode:a},s=Ee.updateShortcut(t,i);return s.success?(e.value=Ee.formatShortcut(i),Ke.success("快捷键已更新")):(Ke.error(s.error),e.value=Ee.formatShortcut(n)),e.classList.remove("recording"),void document.removeEventListener("keydown",d)}if(["Meta","Control","Alt","Shift"].includes(o.key))return void(e.value="继续按下字符键...");if(!(o.ctrlKey||o.metaKey||o.altKey||o.shiftKey)&&"takeScreenshot"===t&&"Slash"===o.code){if("Slash"===c&&l){clearTimeout(l);const n={key:"Slash",meta:false,ctrl:false,alt:false,shift:false,doubleClick:true};Ee.updateShortcut(t,n).success&&(e.value=Ee.formatShortcut(n),Ke.success("快捷键已更新")),e.classList.remove("recording"),document.removeEventListener("keydown",d);}else c="Slash",l=setTimeout(()=>{l=null,c="";},300);return}const i={key:o.code||o.key,meta:o.metaKey,ctrl:o.ctrlKey,alt:o.altKey,shift:o.shiftKey,doubleClick:false},s=Ee.checkConflict(t,i);if(s)Ke.warning(`与"${s}"冲突,请重新设置`),e.value=Ee.formatShortcut(n);else {const o=Ee.updateShortcut(t,i);o.success?(e.value=Ee.formatShortcut(i),Ke.success("快捷键已更新")):(Ke.error(o.error),e.value=Ee.formatShortcut(n));}e.classList.remove("recording"),document.removeEventListener("keydown",d);};document.addEventListener("keydown",d);}bindAIConfigModalEvents(e){e.addEventListener("click",t=>{t.target===e&&this.hideAIConfigModal();});const t=document.getElementById("ai-config-list");t&&t.addEventListener("click",n=>{const o=n.target.closest(".ai-config-item"),i=n.target.closest(".ai-edit-btn");if(i){const t=i.dataset.id,n=e.querySelector(".ai-config-form");n&&n.classList.remove("hidden"),this.loadConfigToForm(t);}else if(o&&!i){const n=o.dataset.id;we.setSelectedAIConfigId(n),ht.renderAIConfigList(t);const i=we.getAIConfigs().find(e=>e.id===n);Ke.success(`已选择配置: ${i.name}`);const s=e.querySelector(".ai-config-form");s&&s.classList.remove("hidden"),this.loadConfigToForm(n);}});const n=document.getElementById("ai-start-summary-btn");n&&n.addEventListener("click",async()=>{const e=xe.getSubtitleData();if(!e||0===e.length)return void Ke.error("没有可用的字幕数据");if(we.getSelectedAIConfig()){this.hideAIConfigModal();try{await Ge.summarize(e,!0);const t=document.getElementById("subtitle-container"),n=t?.querySelector('.subtitle-tab[data-tab="summary"]');n&&n.click();}catch(t){Ke.handleError(t,"AI总结");}}else Ke.warning("请先选择或配置一个AI服务");}),document.getElementById("ai-new-config-btn").addEventListener("click",()=>{this.clearAIConfigForm();const t=e.querySelector(".ai-config-form");t&&(t.classList.remove("hidden"),setTimeout(()=>{t.scrollIntoView({behavior:"smooth",block:"nearest"});},100)),Ke.info("请填写新配置信息");}),document.getElementById("ai-save-new-btn").addEventListener("click",()=>{this.saveNewAIConfig();}),document.getElementById("ai-update-btn").addEventListener("click",()=>{this.updateAIConfig();}),document.getElementById("ai-cancel-btn").addEventListener("click",()=>{this.hideAIConfigModal();}),document.getElementById("ai-delete-current-btn").addEventListener("click",()=>{const e=document.getElementById("ai-delete-current-btn"),t=e?.dataset.deleteId;if(t&&Ke.confirm("确定要删除这个配置吗?")){const n=we.deleteAIConfig(t);if(n.success){Ke.success("配置已删除");const t=document.getElementById("ai-config-list");t&&ht.renderAIConfigList(t);const n=document.querySelector(".ai-config-form");n&&n.classList.add("hidden"),e.style.display="none";}else Ke.error(n.error);}}),document.getElementById("fetch-models-btn").addEventListener("click",async()=>{await this.fetchModels();});}loadConfigToForm(e){const t=we.getAIConfigs().find(t=>t.id===e);if(!t)return;const n=document.getElementById("ai-config-name"),o=document.getElementById("ai-config-url"),i=document.getElementById("ai-config-apikey"),s=document.getElementById("ai-config-model"),r=document.getElementById("ai-config-prompt1"),a=document.getElementById("ai-config-prompt2"),l=document.getElementById("ai-config-is-openrouter"),c=document.getElementById("ai-save-new-btn"),d=document.getElementById("ai-update-btn"),u=document.getElementById("model-select-wrapper");n&&(n.value=t.name),o&&(o.value=t.url),i&&(i.value=t.apiKey),s&&(s.value=t.model),r&&(r.value=t.prompt1||""),a&&(a.value=t.prompt2||""),l&&(l.checked=t.isOpenRouter||false);const p=document.getElementById("api-key-help-link");p&&z[t.id]?p.innerHTML=`<a href="${z[t.id]}" target="_blank" style="color: #60a5fa; text-decoration: none;">📖 如何获取API Key?</a>`:p&&(p.innerHTML=""),c&&(c.style.display="none"),d&&(d.style.display="",d.dataset.editId=e),u&&(u.style.display="none");const h=document.getElementById("ai-delete-current-btn");h&&("openrouter"===e||"openai"===e||"siliconflow"===e||"deepseek"===e||"moonshot"===e||"zhipu"===e||"yi"===e||"dashscope"===e||"gemini"===e?h.style.display="none":(h.style.display="",h.dataset.deleteId=e)),setTimeout(()=>{const e=document.querySelector(".ai-config-form");e&&e.scrollIntoView({behavior:"smooth",block:"nearest"});},100);}editAIConfig(e){this.loadConfigToForm(e);}saveNewAIConfig(){const e={name:document.getElementById("ai-config-name").value.trim(),url:document.getElementById("ai-config-url").value.trim(),apiKey:document.getElementById("ai-config-apikey").value.trim(),model:document.getElementById("ai-config-model").value.trim(),prompt1:document.getElementById("ai-config-prompt1").value,prompt2:document.getElementById("ai-config-prompt2").value,isOpenRouter:document.getElementById("ai-config-is-openrouter").checked},t=we.addAIConfig(e);if(t.success){Ke.success(`配置"${e.name}"已添加`);const t=document.getElementById("ai-config-list");t&&ht.renderAIConfigList(t),this.clearAIConfigForm();}else Ke.error(t.error);}updateAIConfig(){const e=document.getElementById("ai-update-btn").dataset.editId;if(!e)return;const t={name:document.getElementById("ai-config-name").value.trim(),url:document.getElementById("ai-config-url").value.trim(),apiKey:document.getElementById("ai-config-apikey").value.trim(),model:document.getElementById("ai-config-model").value.trim(),prompt1:document.getElementById("ai-config-prompt1").value,prompt2:document.getElementById("ai-config-prompt2").value,isOpenRouter:document.getElementById("ai-config-is-openrouter").checked},n=we.updateAIConfig(e,t);if(n.success){Ke.success(`配置"${t.name}"已更新`);const e=document.getElementById("ai-config-list");e&&ht.renderAIConfigList(e),this.clearAIConfigForm();}else Ke.error(n.error);}async fetchModels(){const e=document.getElementById("ai-config-apikey").value.trim(),t=document.getElementById("ai-config-url").value.trim(),n=document.getElementById("ai-config-is-openrouter").checked;if(!e)return void Ke.error("请先填写 API Key");if(!n)return void Ke.error("仅OpenRouter支持获取模型列表");const o=document.getElementById("fetch-models-btn");o.disabled=true,o.textContent="获取中...";try{const n=await Ge.fetchOpenRouterModels(e,t),o=document.getElementById("model-select-wrapper"),i=document.getElementById("model-select"),s=document.getElementById("model-search-input");if(!i)return void Ke.error("模型选择器未找到");this.allModels=n,i.innerHTML="",n.forEach(e=>{const t=document.createElement("option");t.value=e.id,t.textContent=`${e.name||e.id} (${e.context_length||"N/A"} tokens)`,t.title=e.id,i.appendChild(t);}),o&&(o.style.display="block"),i.onchange=()=>{document.getElementById("ai-config-model").value=i.value;},i.ondblclick=()=>{document.getElementById("ai-config-model").value=i.value,Ke.success("已选择模型");},s&&(s.value="",s.oninput=e=>{this.filterModels(e.target.value);},s.onkeydown=e=>{"Enter"===e.key&&i.options.length>0&&(i.selectedIndex=0,document.getElementById("ai-config-model").value=i.options[0].value,Ke.success("已选择: "+i.options[0].text));}),Ke.success(`已获取 ${n.length} 个模型`);}catch(i){Ke.error(`获取模型列表失败: ${i.message}`);}finally{o.disabled=false,o.textContent="获取模型";}}filterModels(e){if(!this.allModels)return;const t=document.getElementById("model-select");if(!t)return;const n=e.toLowerCase().trim();if(!n)return t.innerHTML="",void this.allModels.forEach(e=>{const n=document.createElement("option");n.value=e.id,n.textContent=`${e.name||e.id} (${e.context_length||"N/A"} tokens)`,n.title=e.id,t.appendChild(n);});const o=this.allModels.filter(e=>{const t=(e.id||"").toLowerCase(),o=(e.name||"").toLowerCase();return t.includes(n)||o.includes(n)});t.innerHTML="",o.forEach(e=>{const n=document.createElement("option");n.value=e.id,n.textContent=`${e.name||e.id} (${e.context_length||"N/A"} tokens)`,n.title=e.id,t.appendChild(n);});const i=document.getElementById("model-search-input");i&&(i.placeholder=o.length>0?`找到 ${o.length} 个模型`:"未找到匹配的模型");}addProgressBarMarkers(e){const t=e.querySelectorAll(".section-item[data-time]"),n=document.querySelector("video"),o=document.querySelector(".bpx-player-progress-wrap");if(!n||!o)return;const i=n.duration;if(!i)return;let s=o.querySelector(".ai-points-container");s||(s=document.createElement("div"),s.className="ai-points-container",o.appendChild(s)),s.innerHTML="",t.forEach(e=>{const t=e.getAttribute("data-time");if(!t)return;const o=t.match(/\[(\d{1,2}):(\d{2})\]/);if(!o)return;const r=60*parseInt(o[1])+parseInt(o[2]),a=r/i*100,l=document.createElement("span");l.className="bpx-player-progress-point bpx-player-progress-point-aipoint",l.style.cssText=`left: ${a}%;`,l.setAttribute("data-time",r),l.addEventListener("click",()=>{n.currentTime=r;}),s.appendChild(l);}),this._addProgressBarStyles();}removeProgressBarMarkers(){const e=document.querySelector(".ai-points-container");e&&e.remove();}_addProgressBarStyles(){if(document.querySelector("#ai-progress-styles"))return;const e=document.createElement("style");e.id="ai-progress-styles",e.textContent="\n      .ai-points-container {\n        position: absolute;\n        top: 0;\n        left: 0;\n        width: 100%;\n        height: 100%;\n        pointer-events: none;\n        z-index: 5;\n      }\n      \n      .bpx-player-progress-point-aipoint {\n        position: absolute;\n        top: 50%;\n        transform: translate(-50%, -50%);\n        width: 8px;\n        height: 8px;\n        background: #ff69b4;\n        border: 2px solid rgba(255, 255, 255, 0.9);\n        border-radius: 50%;\n        opacity: 0.9;\n        pointer-events: auto;\n        cursor: pointer;\n        transition: all 0.2s;\n        box-shadow: 0 0 4px rgba(255, 105, 180, 0.6);\n      }\n      \n      .bpx-player-progress-point-aipoint:hover {\n        opacity: 1;\n        transform: translate(-50%, -50%) scale(1.5);\n        box-shadow: 0 0 8px rgba(255, 105, 180, 0.9);\n      }\n    ",document.head.appendChild(e);}initSubtitleScroll(e){if(!e)return void ye.warn("EventHandlers","字幕容器不存在,无法初始化滚动");const t=document.querySelector("#subtitle-follow-btn");ft.init(e,{followIntervalMs:200,userScrollDetectMs:300,scrollBehavior:"smooth",scrollPosition:"center",highlightClass:"current"}),ft.on("onFollowStatusChange",e=>{t&&(t.style.display=e?"none":"block"),ye.debug("字幕滚动",`跟随状态改变: ${e}`);}),t&&t.addEventListener("click",()=>{ye.debug("字幕滚动","点击恢复滚动"),ft.resumeAutoFollow();}),ye.info("EventHandlers","字幕滚动管理器初始化完成");}bindNotionConfigModalEvents(e){e.addEventListener("click",t=>{t.target===e&&this.hideNotionConfigModal();}),document.getElementById("notion-save-btn").addEventListener("click",()=>{const e=document.getElementById("notion-api-key").value.trim(),t=document.getElementById("notion-parent-page-id").value.trim(),n=document.getElementById("notion-auto-send-enabled").checked,o={videoInfo:document.getElementById("notion-content-video-info").checked,summary:document.getElementById("notion-content-summary").checked,segments:document.getElementById("notion-content-segments").checked,subtitles:document.getElementById("notion-content-subtitles").checked};if(!e)return void ht.showNotionStatus("请输入 API Key",true);if(!t)return void ht.showNotionStatus("请输入目标位置(Page ID 或 Database ID)",true);const i=we.saveNotionConfig({apiKey:e,parentPageId:t});i.success?(we.setNotionAutoSendEnabled(n),we.saveNotionContentOptions(o),ht.showNotionStatus("配置已保存"),setTimeout(()=>{this.hideNotionConfigModal();},1500)):ht.showNotionStatus(i.error,true);}),document.getElementById("notion-cancel-btn").addEventListener("click",()=>{this.hideNotionConfigModal();});}restoreContainerState(e){const t=localStorage.getItem("subtitle-container-state");if(t)try{const n=JSON.parse(t);n.width&&(e.style.width=n.width),n.height&&(e.style.height=n.height),n.top&&(e.style.top=n.top),n.left&&(e.style.left=n.left);}catch(n){ye.warn("EventHandlers","恢复容器状态失败:",n);}}parsePositionValue(e,t){return e?e.endsWith("px")?parseInt(e):e.endsWith("%")?parseInt(e)/100*t:parseInt(e)||0:0}resetContainerPosition(e){localStorage.removeItem("subtitle-container-state"),e.style.width="500px",e.style.height="600px",e.style.top="10%",e.style.left="100%",e.style.marginLeft="10px",Ke.success("字幕面板位置已重置");}saveContainerState(e){const t={width:e.style.width||e.offsetWidth+"px",height:e.style.height||e.offsetHeight+"px",top:e.style.top||"10%",left:e.style.left||"100%"};localStorage.setItem("subtitle-container-state",JSON.stringify(t));}bindDragEvents(e){const t=e.querySelector(".subtitle-header");if(!t)return;let n=false,o=0,i=0,s=0,r=0;const a=a=>{if(a.target.closest("button")||a.target.closest("input")||a.target.closest(".subtitle-search-container")||a.target.closest(".ai-icon")||a.target.closest(".notion-icon")||a.target.closest(".subtitle-close"))return;n=true,o=a.clientX,i=a.clientY;const l=e.getBoundingClientRect(),c=document.querySelector(".bpx-player-primary-area"),d=c?.getBoundingClientRect()||{left:0,top:0};s=l.left-d.left,r=l.top-d.top,t.style.cursor="grabbing",a.preventDefault();},l=t=>{if(!n)return;const a=t.clientX-o,l=t.clientY-i,c=s+a,d=r+l;e.style.left=c+"px",e.style.top=d+"px",e.style.marginLeft="0";},c=()=>{n&&(n=false,t.style.cursor="move",this.saveContainerState(e));};t.addEventListener("mousedown",a),document.addEventListener("mousemove",l),document.addEventListener("mouseup",c),e._dragCleanup=()=>{t.removeEventListener("mousedown",a),document.removeEventListener("mousemove",l),document.removeEventListener("mouseup",c);};}bindResizeEvents(e){let t=false,n="",o=0,i=0,s=0,r=0,a=0,l=0;const c=(e,t)=>{const n=e.clientX-t.left,o=e.clientY-t.top,i=t.width,s=t.height;let r="";return o<8?r+="n":o>s-8&&(r+="s"),n<8?r+="w":n>i-8&&(r+="e"),r},d=n=>{if(t)return;const o=e.getBoundingClientRect(),i=c(n,o);e.className=e.className.replace(/\bresize-\w+\b/g,""),i&&e.classList.add(`resize-${i}`);},u=d=>{const u=e.getBoundingClientRect();n=c(d,u),n&&(d.target.closest(".subtitle-header")||(t=true,o=d.clientX,i=d.clientY,s=e.offsetWidth,r=e.offsetHeight,a=e.offsetLeft,l=e.offsetTop,d.preventDefault(),d.stopPropagation()));},p=c=>{if(!t)return;const d=c.clientX-o,u=c.clientY-i;let p=s,h=r,g=a,m=l;if(n.includes("e")&&(p=Math.max(400,Math.min(800,s+d))),n.includes("w")){const e=s-d;p=Math.max(400,Math.min(800,e)),g=a+(s-p);}if(n.includes("s")&&(h=Math.max(400,Math.min(.9*window.innerHeight,r+u))),n.includes("n")){const e=r-u;h=Math.max(400,Math.min(.9*window.innerHeight,e)),m=l+(r-h);}e.style.width=p+"px",e.style.height=h+"px",e.style.left=g+"px",e.style.top=m+"px";},h=()=>{t&&(t=false,n="",this.saveContainerState(e));};e.addEventListener("mousemove",d),e.addEventListener("mousedown",u),document.addEventListener("mousemove",p),document.addEventListener("mouseup",h),e._resizeCleanup=()=>{e.removeEventListener("mousemove",d),e.removeEventListener("mousedown",u),document.removeEventListener("mousemove",p),document.removeEventListener("mouseup",h);};}observeContainerResize(e){const t=new ResizeObserver(ze(()=>{this.saveContainerState(e);},500));t.observe(e),e._resizeObserver=t;}};const vt=new class{constructor(){this.modal=null;}createModal(){return this.modal||(this.modal=document.createElement("div"),this.modal.id="help-modal",this.modal.className="config-modal",document.body.appendChild(this.modal)),this.modal}show(){const e=this.createModal();this.renderModal(),e.classList.add("show"),gt.push(this);}hide(){this.modal&&this.modal.classList.remove("show"),gt.pop(this);}renderModal(){this.modal.innerHTML='\n      <div class="config-modal-content">\n        <div class="config-modal-header">\n          <span>使用帮助</span>\n        </div>\n        <div class="config-modal-body">\n          <div style="margin-bottom: 20px;">\n            <h3 style="color: #fff; margin-bottom: 10px; font-size: 16px;">功能特性</h3>\n            <ul style="line-height: 1.8; color: #e5e7eb;">\n              <li><strong>字幕提取</strong> - 自动检测并提取B站AI字幕和人工字幕</li>\n              <li><strong>AI智能总结</strong> - 支持OpenAI、OpenRouter等多种AI服务</li>\n              <li><strong>Notion集成</strong> - 一键发送字幕和总结到Notion数据库</li>\n              <li><strong>笔记保存</strong> - 选中任意文字显示粉色钢笔图标保存笔记</li>\n              <li><strong>播放速度控制</strong> - 键盘快捷键控制速度</li>\n            </ul>\n          </div>\n\n          <div style="margin-bottom: 20px;">\n            <h3 style="color: #fff; margin-bottom: 10px; font-size: 16px;">快捷键</h3>\n            <table style="width: 100%; border-collapse: collapse; font-size: 13px;">\n              <thead>\n                <tr style="background: rgba(255, 255, 255, 0.1); border-bottom: 1px solid rgba(254, 235, 234, 0.2);">\n                  <th style="padding: 8px; text-align: left; font-weight: 600; color: #fff;">功能</th>\n                  <th style="padding: 8px; text-align: left; font-weight: 600; color: #fff;">快捷键</th>\n                  <th style="padding: 8px; text-align: left; font-weight: 600; color: #fff;">说明</th>\n                </tr>\n              </thead>\n              <tbody>\n                <tr style="border-bottom: 1px solid rgba(254, 235, 234, 0.1);">\n                  <td style="padding: 8px; color: #e5e7eb;">切换字幕面板</td>\n                  <td style="padding: 8px;"><code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px; color: #feebea;">Cmd/Ctrl + B</code></td>\n                  <td style="padding: 8px; color: rgba(255, 255, 255, 0.7);">显示/隐藏字幕面板</td>\n                </tr>\n                <tr style="border-bottom: 1px solid rgba(254, 235, 234, 0.1);">\n                  <td style="padding: 8px; color: #e5e7eb;">视频截图</td>\n                  <td style="padding: 8px;"><code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px; color: #feebea;">Cmd/Ctrl + S</code></td>\n                  <td style="padding: 8px; color: rgba(255, 255, 255, 0.7);">截图并保存到笔记</td>\n                </tr>\n                <tr style="border-bottom: 1px solid rgba(254, 235, 234, 0.1);">\n                  <td style="padding: 8px; color: #e5e7eb;">增加速度</td>\n                  <td style="padding: 8px;"><code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px; color: #feebea;">.</code></td>\n                  <td style="padding: 8px; color: rgba(255, 255, 255, 0.7);">每次增加0.1x</td>\n                </tr>\n                <tr style="border-bottom: 1px solid rgba(254, 235, 234, 0.1);">\n                  <td style="padding: 8px; color: #e5e7eb;">减少速度</td>\n                  <td style="padding: 8px;"><code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px; color: #feebea;">,</code></td>\n                  <td style="padding: 8px; color: rgba(255, 255, 255, 0.7);">每次减少0.1x</td>\n                </tr>\n                <tr style="border-bottom: 1px solid rgba(254, 235, 234, 0.1);">\n                  <td style="padding: 8px; color: #e5e7eb;">2倍速</td>\n                  <td style="padding: 8px;"><code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px; color: #feebea;">.. (双击)</code></td>\n                  <td style="padding: 8px; color: rgba(255, 255, 255, 0.7);">直接设为2倍速</td>\n                </tr>\n                <tr style="border-bottom: 1px solid rgba(254, 235, 234, 0.1);">\n                  <td style="padding: 8px; color: #e5e7eb;">重置速度</td>\n                  <td style="padding: 8px;"><code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px; color: #feebea;">,, (双击)</code></td>\n                  <td style="padding: 8px; color: rgba(255, 255, 255, 0.7);">重置为1倍速</td>\n                </tr>\n                <tr style="border-bottom: 1px solid rgba(254, 235, 234, 0.1);">\n                  <td style="padding: 8px; color: #e5e7eb;">临时加速</td>\n                  <td style="padding: 8px;"><code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px; color: #feebea;">右Option键</code></td>\n                  <td style="padding: 8px; color: rgba(255, 255, 255, 0.7);">按住时1.5x加速</td>\n                </tr>\n              </tbody>\n            </table>\n          </div>\n\n          <div style="margin-bottom: 20px;">\n            <h3 style="color: #fff; margin-bottom: 10px; font-size: 16px;">使用说明</h3>\n            <div style="line-height: 1.8; color: #e5e7eb;">\n              <p style="margin: 8px 0;"><strong>字幕提取:</strong>打开B站视频,等待几秒,字幕面板自动出现在右侧</p>\n              <p style="margin: 8px 0;"><strong>AI总结:</strong>配置AI服务(菜单 → AI配置),点击魔法棒图标 ✨</p>\n              <p style="margin: 8px 0;"><strong>笔记保存:</strong>选中任意文字,点击粉色钢笔图标</p>\n              <p style="margin: 8px 0;"><strong>视频截图:</strong>按 Ctrl/Cmd + S 截取当前帧,自动保存到笔记,B站视频可同步到Notion</p>\n              <p style="margin: 8px 0;"><strong>速度控制:</strong>使用 , 和 . 键调整速度</p>\n            </div>\n          </div>\n\n          <div style="padding: 15px; background: rgba(254, 235, 234, 0.1); border-radius: 10px; border-left: 4px solid #feebea;">\n            <div style="font-size: 13px; color: #fff; font-weight: 600; margin-bottom: 4px;">提示</div>\n            <div style="font-size: 12px; color: rgba(255, 255, 255, 0.7); line-height: 1.5;">\n              • AI配置支持多个提供商,可自由切换<br>\n              • 笔记保存在本地,按日期自动分组<br>\n              • SponsorBlock支持自动跳过广告和赞助片段\n            </div>\n          </div>\n        </div>\n        <div class="config-footer">\n          <button class="config-btn config-btn-primary" id="help-close-btn">知道了</button>\n        </div>\n      </div>\n    ',this.bindEvents();}bindEvents(){this.modal.addEventListener("click",e=>{e.target===this.modal&&this.hide();});const e=document.getElementById("help-close-btn");e&&e.addEventListener("click",()=>this.hide());}};const xt=new class{constructor(){this.modal=null;}createModal(){return this.modal||(this.modal=document.createElement("div"),this.modal.id="sponsorblock-modal",this.modal.className="config-modal",document.body.appendChild(this.modal)),this.modal}show(){const e=this.createModal();this.renderModal(),e.classList.add("show"),gt.push(this);}hide(){this.modal&&this.modal.classList.remove("show"),gt.pop(this);}renderModal(){const e=nt.getAll();this.modal.innerHTML=`\n      <div class="config-modal-content">\n        <div class="config-modal-header">\n          <span>SponsorBlock 设置</span>\n        </div>\n        <div class="config-modal-body">\n          <div style="margin-bottom: 20px; padding: 15px; background: rgba(254, 235, 234, 0.1); border-radius: 10px; border-left: 4px solid #feebea;">\n            <div style="font-size: 13px; color: #fff; font-weight: 600; margin-bottom: 4px;">使用说明</div>\n            <div style="font-size: 12px; color: rgba(255, 255, 255, 0.7); line-height: 1.5;">\n              <strong>勾选的类别</strong> → 自动跳过<br>\n              <strong>未勾选的类别</strong> → 显示手动提示(5秒后自动消失)<br>\n              在进度条上会显示彩色标记,点击可查看详情\n            </div>\n          </div>\n\n          <div class="sponsor-settings-section">\n            <h3>片段类别(勾选=自动跳过,未勾选=手动提示)</h3>\n            <div class="sponsor-checkbox-group">\n              ${Object.entries(ce.CATEGORIES).map(([t,n])=>`\n                <div class="sponsor-checkbox-item">\n                  <input type="checkbox" \n                         id="category-${t}" \n                         value="${t}"\n                         ${e.skipCategories.includes(t)?"checked":""}>\n                  <label for="category-${t}">\n                    <span class="category-color-dot" style="background: ${n.color}"></span>\n                    <span>${n.name}</span>\n                  </label>\n                </div>\n              `).join("")}\n            </div>\n          </div>\n\n          <div class="sponsor-settings-section">\n            <h3>显示选项</h3>\n            <div class="sponsor-switch-item">\n              <span>显示片段标签(视频卡片)</span>\n              <label class="sponsor-switch">\n                <input type="checkbox" id="showAdBadge" \n                       ${e.showAdBadge?"checked":""}>\n                <span class="sponsor-switch-slider"></span>\n              </label>\n            </div>\n            <div class="sponsor-switch-item">\n              <span>显示优质视频标签</span>\n              <label class="sponsor-switch">\n                <input type="checkbox" id="showQualityBadge" \n                       ${e.showQualityBadge?"checked":""}>\n                <span class="sponsor-switch-slider"></span>\n              </label>\n            </div>\n            <div class="sponsor-switch-item">\n              <span>进度条显示片段标记</span>\n              <label class="sponsor-switch">\n                <input type="checkbox" id="showProgressMarkers" \n                       ${e.showProgressMarkers?"checked":""}>\n                <span class="sponsor-switch-slider"></span>\n              </label>\n            </div>\n          </div>\n        </div>\n        <div class="config-footer">\n          <button class="config-btn config-btn-secondary" id="sponsorblock-cancel-btn">取消</button>\n          <button class="config-btn config-btn-primary" id="sponsorblock-save-btn">保存</button>\n        </div>\n      </div>\n    `,this.bindEvents();}bindEvents(){this.modal.addEventListener("click",e=>{e.target===this.modal&&this.hide();});const e=document.getElementById("sponsorblock-save-btn");e&&e.addEventListener("click",()=>this.saveSettings());const t=document.getElementById("sponsorblock-cancel-btn");t&&t.addEventListener("click",()=>this.hide());}saveSettings(){const e={skipCategories:Array.from(this.modal.querySelectorAll('.sponsor-checkbox-item input[type="checkbox"]:checked')).map(e=>e.value),showAdBadge:this.modal.querySelector("#showAdBadge").checked,showQualityBadge:this.modal.querySelector("#showQualityBadge").checked,showProgressMarkers:this.modal.querySelector("#showProgressMarkers").checked};nt.setAll(e),this.hide(),Ke.info("设置已保存!\n\n✅ 勾选的类别 → 自动跳过\n⏸️ 未勾选的类别 → 手动提示(5秒)\n\n页面将刷新以应用新设置。"),setTimeout(()=>{location.reload();},2e3);}};const wt=new class{constructor(){this.resources={eventBusSubscriptions:new Map,domListeners:new Map,mutationObservers:new Set,intervals:new Map,timeouts:new Set,rafIds:new Set,audioContexts:new Set,customCleanups:new Set},this.isDestroyed=false,this.maxIntervalDuration=3e5,this.startAutoCleanup();}trackEventBusSubscription(e,t,n="default"){this.resources.eventBusSubscriptions.has(n)||this.resources.eventBusSubscriptions.set(n,[]),this.resources.eventBusSubscriptions.get(n).push({event:e,unsubscribe:t});}trackDOMListener(e,t,n,o){const i=Symbol("listener");return this.resources.domListeners.set(i,{element:e,event:t,handler:n,options:o}),e.addEventListener(t,n,o),i}removeDOMListener(e){const t=this.resources.domListeners.get(e);t&&(t.element.removeEventListener(t.event,t.handler,t.options),this.resources.domListeners.delete(e));}trackMutationObserver(e){return this.resources.mutationObservers.add(e),e}trackInterval(e,t,n=this.maxIntervalDuration){const o=Date.now(),i=setInterval(()=>{if(Date.now()-o>n)return ye.warn("ResourceManager",`定时器${i}运行超过${n}ms,自动清理`),void this.clearTrackedInterval(i);e();},t);return this.resources.intervals.set(i,{startTime:o,maxDuration:n,delay:t}),i}clearTrackedInterval(e){clearInterval(e),this.resources.intervals.delete(e);}trackTimeout(e,t){const n=setTimeout(()=>{e(),this.resources.timeouts.delete(n);},t);return this.resources.timeouts.add(n),n}clearTrackedTimeout(e){clearTimeout(e),this.resources.timeouts.delete(e);}trackRAF(e){const t=requestAnimationFrame(e);return this.resources.rafIds.add(t),t}cancelTrackedRAF(e){cancelAnimationFrame(e),this.resources.rafIds.delete(e);}trackAudioContext(e){return this.resources.audioContexts.add(e),e}addCleanup(e){this.resources.customCleanups.add(e);}startAutoCleanup(){this.autoCleanupInterval=setInterval(()=>{this.cleanupExpiredIntervals();},3e4);}cleanupExpiredIntervals(){const e=Date.now(),t=[];this.resources.intervals.forEach((n,o)=>{e-n.startTime>n.maxDuration&&(ye.warn("ResourceManager",`清理超时interval: ${o}, 运行了${((e-n.startTime)/1e3).toFixed(1)}秒`),clearInterval(o),t.push(o));}),t.forEach(e=>this.resources.intervals.delete(e)),t.length>0&&ye.debug("ResourceManager",`已清理${t.length}个超时定时器`);}getStats(){return {intervals:this.resources.intervals.size,timeouts:this.resources.timeouts.size,rafIds:this.resources.rafIds.size,audioContexts:this.resources.audioContexts.size,domListeners:this.resources.domListeners.size,mutationObservers:this.resources.mutationObservers.size,eventBusSubscriptions:Array.from(this.resources.eventBusSubscriptions.values()).reduce((e,t)=>e+t.length,0)}}cleanupModule(e){const t=this.resources.eventBusSubscriptions.get(e);t&&(t.forEach(({unsubscribe:t})=>{try{t();}catch(n){console.error(`[ResourceManager] 清理模块 "${e}" 订阅失败:`,n);}}),this.resources.eventBusSubscriptions.delete(e));}cleanup(){this.isDestroyed?ye.warn("ResourceManager","已经销毁,跳过清理"):(ye.debug("ResourceManager","开始清理资源..."),this.resources.eventBusSubscriptions.forEach((e,t)=>{e.forEach(({event:e,unsubscribe:n})=>{try{n();}catch(o){console.error(`[ResourceManager] 清理 EventBus 订阅失败 (${t}.${e}):`,o);}});}),this.resources.eventBusSubscriptions.clear(),this.resources.domListeners.forEach(({element:e,event:t,handler:n,options:o})=>{try{e.removeEventListener(t,n,o);}catch(i){console.error("[ResourceManager] 清理 DOM 监听器失败:",i);}}),this.resources.domListeners.clear(),this.resources.mutationObservers.forEach(e=>{try{e.disconnect();}catch(t){console.error("[ResourceManager] 清理 MutationObserver 失败:",t);}}),this.resources.mutationObservers.clear(),this.resources.intervals.forEach((e,t)=>{try{clearInterval(t);}catch(n){console.error("[ResourceManager] 清理 interval 失败:",n);}}),this.resources.intervals.clear(),this.autoCleanupInterval&&(clearInterval(this.autoCleanupInterval),this.autoCleanupInterval=null),this.resources.timeouts.forEach(e=>{try{clearTimeout(e);}catch(t){console.error("[ResourceManager] 清理 timeout 失败:",t);}}),this.resources.timeouts.clear(),this.resources.rafIds.forEach(e=>{try{cancelAnimationFrame(e);}catch(t){console.error("[ResourceManager] 清理 RAF 失败:",t);}}),this.resources.rafIds.clear(),this.resources.audioContexts.forEach(e=>{try{"closed"!==e.state&&e.close();}catch(t){console.error("[ResourceManager] 关闭 AudioContext 失败:",t);}}),this.resources.audioContexts.clear(),this.resources.customCleanups.forEach(e=>{try{e();}catch(t){console.error("[ResourceManager] 执行自定义清理失败:",t);}}),this.resources.customCleanups.clear(),this.isDestroyed=true,ye.debug("ResourceManager","资源清理完成"));}getStats(){return {eventBusSubscriptions:Array.from(this.resources.eventBusSubscriptions.entries()).reduce((e,[t,n])=>(e[t]=n.length,e),{}),domListeners:this.resources.domListeners.size,mutationObservers:this.resources.mutationObservers.size,intervals:this.resources.intervals.size,timeouts:this.resources.timeouts.size,rafIds:this.resources.rafIds.size,audioContexts:this.resources.audioContexts.size,customCleanups:this.resources.customCleanups.size}}},St=location.hostname.endsWith("bilibili.com"),kt=location.hostname.includes("youtube.com")||location.hostname.includes("youtu.be");const It=new class{constructor(){this.initialized=false,this.ball=null,this.container=null,this.videoQualityService=null,this.universalAdSkipService=null,this.isBilibili=St,this.isYouTube=kt;}setupErrorHandler(){const e=window.onerror;window.onerror=(t,n,o,i,s)=>{const r=String(t||""),a=String(n||"");return a&&(a.includes("extension://")||a.includes("content.js"))?(ye.debug("Main","忽略来自其他扩展的错误:",r),true):a.includes("nc-loader")||r.includes("addIceCandidate")?(ye.debug("Main","忽略第三方组件错误"),true):r.includes("Extension context invalidated")?(ye.debug("Main","忽略扩展上下文失效错误"),true):!!e&&e(t,n,o,i,s)},window.addEventListener("unhandledrejection",e=>{const t=e.reason,n=t?String(t.message||t):"";return n.includes("Extension context invalidated")?(e.preventDefault(),void ye.debug("Main","忽略Promise中的扩展上下文失效错误")):n.includes("addIceCandidate")||n.includes("nc-loader")?(e.preventDefault(),void ye.debug("Main","忽略Promise中的第三方组件错误")):void 0}),ye.info("Main","全局错误处理器已设置");}async init(){if(!this.initialized){if(this.setupErrorHandler(),je.init(),je.getSubtitleService(),function(){const e=document.createElement("style");e.textContent=de,document.head.appendChild(e);}(),await this.waitForPageReady(),this.isBilibili&&we.fixExistingConfigPrompts(),He.init(),et.init(),this.isBilibili){try{await st.init();}catch(t){ye.warn("Main","SponsorBlock 初始化失败:",t.message);}this.videoQualityService=(e=st.getAPI(),ct||(ct=new lt(e)),ct),this.videoQualityService.start();}var e;if(this.isBilibili||this.isYouTube)try{const e=this.isYouTube?{get:e=>({autoSkip:"false"!==localStorage.getItem("youtube_auto_skip"),skipCategories:JSON.parse(localStorage.getItem("youtube_skip_categories")||'["sponsor", "selfpromo"]'),showNotifications:"false"!==localStorage.getItem("youtube_show_notifications"),showProgressMarkers:"false"!==localStorage.getItem("youtube_show_markers"),detectNativeAds:"false"!==localStorage.getItem("youtube_detect_native"),skipDelay:parseInt(localStorage.getItem("youtube_skip_delay")||"0"),muteInsteadOfSkip:"true"===localStorage.getItem("youtube_mute_instead")}[e]),set:(e,t)=>{localStorage.setItem(`youtube_${e}`,JSON.stringify(t));}}:{get:e=>({autoSkip:"false"!==localStorage.getItem("bilibili_auto_skip"),skipCategories:JSON.parse(localStorage.getItem("bilibili_skip_categories")||'["sponsor", "selfpromo"]'),showNotifications:"false"!==localStorage.getItem("bilibili_show_notifications"),showProgressMarkers:"false"!==localStorage.getItem("bilibili_show_markers"),detectNativeAds:"false"!==localStorage.getItem("bilibili_detect_native"),skipDelay:parseInt(localStorage.getItem("bilibili_skip_delay")||"0"),muteInsteadOfSkip:"true"===localStorage.getItem("bilibili_mute_instead")}[e]),set:(e,t)=>{localStorage.setItem(`bilibili_${e}`,JSON.stringify(t));}};this.universalAdSkipService=new ut(e),await this.universalAdSkipService.init(),ye.info("Main","通用广告跳过服务已初始化");}catch(t){ye.warn("Main","通用广告跳过服务初始化失败:",t.message);}if(this.isYouTube)try{await pt.init(),ye.info("Main","YouTube视频标签服务已初始化");}catch(t){ye.warn("Main","YouTube视频标签服务初始化失败:",t.message);}if(this.isBilibili||this.isYouTube&&"/watch"===location.pathname?this.createUI():this.createBasicUI(),this.bindEvents(),(this.isBilibili||this.isYouTube&&"/watch"===location.pathname)&&this.setupAutomation(),this.registerMenuCommands(),this.registerShortcuts(),this.isBilibili)Re.checkSubtitleButton(),this.observeVideoChange();else if(this.isYouTube&&"/watch"===location.pathname){const e=je.getSubtitleService();e&&setTimeout(async()=>{await e.checkSubtitleAvailability();},2e3),this.observeVideoChange();}this.initialized=true;}}registerShortcuts(){Ee.register("toggleSubtitlePanel",()=>{xe.togglePanel();}),Ee.register("toggleNotesPanel",()=>{mt.togglePanel();}),Ee.register("takeScreenshot",async()=>{try{const e=xe.getVideoInfo(),t=e?.bvid,n=we.getNotionConfig().apiKey&&t&&xe.getNotionPageId(t);if(await at.captureAndSave(n)){Ke.success(n?"截图已保存到笔记和Notion":"截图已保存到笔记");const e=document.querySelector(".notes-panel");e&&"none"!==e.style.display&&window.notesPanel?.render();}}catch(e){console.error("[Main] 截图失败:",e),Ke.error("截图失败: "+e.message);}}),Ee.register("speedIncrease",()=>{et.adjustBaseSpeed(.1);}),Ee.register("speedDecrease",()=>{et.adjustBaseSpeed(-0.1);}),Ee.register("speedReset",()=>{et.resetToNormalSpeed();}),Ee.register("speedDouble",()=>{et.setToDoubleSpeed();}),Ee.startListening();}registerMenuCommands(){"undefined"!=typeof GM_registerMenuCommand&&(this.isBilibili&&(GM_registerMenuCommand("AI配置",()=>{yt.showAIConfigModal();}),GM_registerMenuCommand("Notion配置",()=>{yt.showNotionConfigModal();})),GM_registerMenuCommand("笔记管理",()=>{mt.togglePanel();}),GM_registerMenuCommand("⌨️ 快捷键设置",()=>{if(ye.debug("Main","快捷键设置菜单被点击"),ye.debug("Main","eventHandlers 是否存在:",!!yt),ye.debug("Main","showShortcutConfigModal 是否存在:",!!yt?.showShortcutConfigModal),!yt||!yt.showShortcutConfigModal)return console.error("[Main] eventHandlers 或其方法未正确加载"),void Ke.error("快捷键设置功能未正确加载");yt.showShortcutConfigModal();}),this.isBilibili&&(GM_registerMenuCommand("🔄 重置字幕面板位置",()=>{const e=document.getElementById("subtitle-container");e?yt.resetContainerPosition(e):Ke.warning("字幕面板未初始化,请先加载视频");}),GM_registerMenuCommand("SponsorBlock 设置",()=>{xt.show();})),GM_registerMenuCommand("使用帮助",()=>{vt.show();}),this.isYouTube&&GM_registerMenuCommand("🚫 YouTube广告设置",()=>{this.showYouTubeAdSettings();}),GM_registerMenuCommand(`🔧 调试模式 (${ye.isDebugMode()?"开启":"关闭"})`,()=>{const e=ye.toggleDebugMode();Ke.info("调试模式已"+(e?"开启":"关闭")),e&&Ke.info("调试模式已开启,控制台将输出详细日志");}));}async waitForPageReady(){return this.isBilibili?new Promise(t=>{const n=setInterval(()=>{document.querySelector(se)&&(clearInterval(n),t());},e);}):new Promise(e=>{"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>e(),{once:true}):e();})}createUI(){let e;if(this.ball=document.createElement("div"),this.ball.id="subtitle-ball",this.ball.title="字幕提取器",this.isBilibili)e=document.querySelector(se),e&&("relative"!==e.style.position&&"absolute"!==e.style.position&&(e.style.position="relative"),e.appendChild(this.ball));else if(this.isYouTube&&(e=document.querySelector("#movie_player")||document.querySelector(".html5-video-player"),e)){this.ball.style.right="10px",this.ball.style.top="10px",this.ball.style.transform="none",this.ball.style.zIndex="9999",e.appendChild(this.ball);const t=()=>{document.fullscreenElement||document.webkitFullscreenElement?this.ball.style.top="60px":this.ball.style.top="10px";};document.addEventListener("fullscreenchange",t),document.addEventListener("webkitfullscreenchange",t);}this.ball&&this.ball.addEventListener("click",()=>{if(this.ball.classList.contains("active")||this.ball.classList.contains("ai-summarizing"))this.container&&(this.container.classList.toggle("show"),fe.emit(N,this.container.classList.contains("show")));else if(this.ball.classList.contains("loading"))ye.debug("App","字幕正在加载中...");else {je.getSubtitleService()&&(this.isYouTube?Ve.manualFetchSubtitle().catch(e=>{ye.error("App","YouTube字幕获取失败:",e),Ke.error("获取字幕失败: "+e.message);}):this.isBilibili&&Re.toggleSubtitle());}}),this.createEmbeddedContainer();const t=ht.createNotionConfigModal();document.body.appendChild(t),yt.bindNotionConfigModalEvents(t);const n=ht.createAIConfigModal();document.body.appendChild(n),yt.bindAIConfigModalEvents(n);}createEmbeddedContainer(){let e;this.container=document.createElement("div"),this.container.id="subtitle-container",this.isBilibili?(e=document.querySelector(se),e&&("relative"!==e.style.position&&"absolute"!==e.style.position&&(e.style.position="relative"),e.appendChild(this.container))):this.isYouTube?setTimeout(()=>{if(e=document.querySelector("#secondary-inner")||document.querySelector("#secondary")||document.querySelector("#related")||document.querySelector("#columns"),e){const t=e.firstElementChild;t?e.insertBefore(this.container,t):e.appendChild(this.container),this.container.style.position="relative",this.container.style.left="auto",this.container.style.top="auto",this.container.style.marginBottom="16px",this.container.style.width="100%",this.container.style.maxWidth="400px",this.container.style.height="500px",this.container.classList.add("show");}else document.body.appendChild(this.container);},1e3):document.body.appendChild(this.container);}createBasicUI(){const e=document.createElement("div");e.id="universal-control-button",e.innerHTML="🎬",e.title="视频工具",e.style.cssText="\n      position: fixed;\n      bottom: 20px;\n      right: 20px;\n      width: 50px;\n      height: 50px;\n      border-radius: 50%;\n      background: linear-gradient(135deg, #feebea 0%, #ffdbdb 100%);\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      font-size: 24px;\n      cursor: pointer;\n      box-shadow: 0 4px 12px rgba(254, 235, 234, 0.5);\n      z-index: 999999;\n      transition: all 0.3s ease;\n    ",e.addEventListener("mouseenter",()=>{e.style.transform="scale(1.1)";}),e.addEventListener("mouseleave",()=>{e.style.transform="scale(1)";}),e.addEventListener("click",()=>{this.showQuickMenu();}),document.body.appendChild(e),ye.info("Main","基础UI已创建 - 适用于所有网站");}showQuickMenu(){const e=document.getElementById("universal-quick-menu");if(e)return void e.remove();const t=document.createElement("div");t.id="universal-quick-menu",t.style.cssText="\n      position: fixed;\n      bottom: 80px;\n      right: 20px;\n      background: white;\n      border-radius: 8px;\n      padding: 10px;\n      box-shadow: 0 4px 20px rgba(0,0,0,0.15);\n      z-index: 999998;\n      min-width: 150px;\n    ";const n=[{icon:"📝",text:"笔记管理",action:()=>mt.togglePanel()}];document.querySelectorAll("video").length>0&&n.push({icon:"⏩",text:"速度 +0.1",action:()=>et.adjustBaseSpeed(.1)},{icon:"⏪",text:"速度 -0.1",action:()=>et.adjustBaseSpeed(-0.1)},{icon:"⏯",text:"重置速度",action:()=>et.resetToNormalSpeed()},{icon:"📸",text:"截图",action:async()=>{try{if(await at.captureAndSave(!1)){Ke.success("截图已保存到笔记");const e=document.querySelector(".notes-panel");e&&"none"!==e.style.display&&window.notesPanel?.render();}}catch(e){Ke.error("截图失败: "+e.message);}}}),n.push({icon:"⚙️",text:"快捷键设置",action:()=>{yt.showShortcutConfigModal(),t.remove();}}),n.forEach(e=>{const n=document.createElement("div");n.style.cssText="\n        padding: 8px 12px;\n        cursor: pointer;\n        display: flex;\n        align-items: center;\n        gap: 8px;\n        transition: background 0.2s;\n        border-radius: 4px;\n      ",n.innerHTML=`<span>${e.icon}</span><span style="font-size: 14px;">${e.text}</span>`,n.addEventListener("mouseenter",()=>{n.style.background="#f0f0f0";}),n.addEventListener("mouseleave",()=>{n.style.background="transparent";}),n.addEventListener("click",()=>{e.action(),t.remove();}),t.appendChild(n);}),document.body.appendChild(t),setTimeout(()=>{document.addEventListener("click",function e(n){t.contains(n.target)||"universal-control-button"===n.target.id||(t.remove(),document.removeEventListener("click",e));});},0);}bindEvents(){fe.on(k,(e,t)=>{this.renderSubtitles(e),yt.initializeSearchIndex(e);}),fe.on(T,()=>{ye.debug("App","AI总结开始,小球进入AI总结状态"),this.ball&&(this.ball.classList.remove("loading","active","no-subtitle","error"),this.ball.classList.add("ai-summarizing"),this.ball.title="正在AI总结...");const e=this.container?.querySelector(".ai-icon");e&&e.classList.add("loading");}),fe.on($,e=>{this.container&&ht.updateAISummary(this.container,e);}),fe.on(C,(e,t)=>{ye.debug("App","AI总结完成,恢复小球正常状态"),Ke.success("AI总结完成"),this.container&&ht.updateAISummary(this.container,e),this.ball&&(this.ball.classList.remove("ai-summarizing","loading"),this.ball.classList.add("active"),this.ball.title="字幕提取器 - 点击查看字幕");const n=this.container?.querySelector(".ai-icon");n&&n.classList.remove("loading");}),fe.on(M,()=>{Ke.success("字幕已成功发送到 Notion");const e=this.container?.querySelector(".notion-icon");e&&e.classList.remove("loading");}),fe.on(I,e=>{Ke.handleError(e,"字幕获取");}),fe.on(A,e=>{ye.debug("App","AI总结失败,恢复小球正常状态"),Ke.handleError(e,"AI总结"),this.ball&&(this.ball.classList.remove("ai-summarizing","loading"),this.ball.classList.add("active"),this.ball.title="字幕提取器 - 点击查看字幕");const t=this.container?.querySelector(".ai-icon");t&&t.classList.remove("loading");}),fe.on(P,e=>{Ke.handleError(e,"Notion发送");}),fe.on(L,e=>{this.updateBallStatus(e);}),fe.on(N,e=>{this.container&&(e?this.container.classList.add("show"):this.container.classList.remove("show"));});}renderSubtitles(e){if(!this.container||!e)return;this.container.innerHTML=ht.renderSubtitlePanel(e);const t=xe.getVideoKey(),n=t?xe.getAISummary(t):null;n&&ht.updateAISummary(this.container,n),yt.bindSubtitlePanelEvents(this.container),ye.debug("App","字幕面板已渲染");}setupAutomation(){fe.on(k,async e=>{await Be(a);const t=we.getAIAutoSummaryEnabled(),n=we.getNotionAutoSendEnabled(),o=we.getSelectedAIConfig(),i=we.getNotionConfig(),s=xe.getVideoKey(),r=s?xe.getAISummary(s):null;if(t&&o&&o.apiKey&&!r)try{await Ge.summarize(e,!1);}catch(l){console.error("[App] 自动总结失败:",l);}else if(n&&i.apiKey)try{const t=xe.getVideoInfo();await Ye.sendToNotion({videoInfo:t,aiSummary:r,subtitleData:e,isAuto:!0}),ye.debug("App","字幕已自动发送到Notion");}catch(l){console.error("[App] 自动发送到Notion失败:",l);}}),fe.on(C,async e=>{ye.debug("App","AI总结完成,已由AIService处理Notion发送");});}updateBallStatus(e){if(this.ball)switch(this.ball.classList.remove("loading","active","no-subtitle","error","ai-summarizing"),e){case x:this.ball.classList.add("active"),this.ball.style.cursor="pointer",this.ball.title="字幕提取器 - 点击查看字幕";break;case w:this.ball.classList.add("no-subtitle"),this.ball.style.cursor="default",this.ball.title="该视频无字幕";break;case S:this.ball.classList.add("error"),this.ball.style.cursor="default",this.ball.title="字幕加载失败";break;case v:this.ball.classList.add("loading"),this.ball.style.cursor="default",this.ball.title="正在加载字幕...";}}observeVideoChange(){let e=location.href,t=location.href.match(/BV[1-9A-Za-z]{10}/)?.[0],n=null;const o=()=>{try{const e=unsafeWindow.__INITIAL_STATE__;return e?.videoData?.cid||e?.videoData?.pages?.[0]?.cid}catch(e){return null}};n=o();const i=()=>{const i=location.href,s=i.match(/BV[1-9A-Za-z]{10}/)?.[0],a=o();i===e||s===t&&a===n||(ye.debug("App","检测到视频切换:",{from:t,to:s}),e=i,t=s,n=a,xe.reset(),Re.reset(),fe.emit(q,{bvid:s,cid:a}),setTimeout(()=>{const e=Pe();xe.setVideoInfo(e),Re.checkSubtitleButton();},r));},s=history.pushState,a=history.replaceState;history.pushState=function(...e){s.apply(this,e),i();},history.replaceState=function(...e){a.apply(this,e),i();},window.addEventListener("popstate",i);const l=setInterval(i,1e3);this.urlChangeCleanup=()=>{history.pushState=s,history.replaceState=a,window.removeEventListener("popstate",i),clearInterval(l);},ye.debug("App","视频切换监听已启动(使用 History API 劫持)");}showYouTubeAdSettings(){const e=document.createElement("div");if(e.className="youtube-ad-settings-modal",e.innerHTML=`\n      <div class="settings-modal-overlay"></div>\n      <div class="settings-modal-content">\n        <div class="settings-modal-header">\n          <h3>YouTube广告跳过设置</h3>\n          <button class="settings-close-btn">×</button>\n        </div>\n        <div class="settings-modal-body">\n          <div class="setting-item">\n            <label>\n              <input type="checkbox" id="youtube-auto-skip" ${"false"!==localStorage.getItem("youtube_auto_skip")?"checked":""}>\n              自动跳过广告\n            </label>\n          </div>\n          <div class="setting-item">\n            <label>\n              <input type="checkbox" id="youtube-native-detect" ${"false"!==localStorage.getItem("youtube_detect_native")?"checked":""}>\n              检测原生广告标记\n            </label>\n          </div>\n          <div class="setting-item">\n            <label>\n              <input type="checkbox" id="youtube-show-notifications" ${"false"!==localStorage.getItem("youtube_show_notifications")?"checked":""}>\n              显示跳过提示\n            </label>\n          </div>\n          <div class="setting-item">\n            <label>\n              <input type="checkbox" id="youtube-show-markers" ${"false"!==localStorage.getItem("youtube_show_markers")?"checked":""}>\n              显示进度条标记\n            </label>\n          </div>\n          <div class="setting-item">\n            <label>\n              <input type="checkbox" id="youtube-mute-instead" ${"true"===localStorage.getItem("youtube_mute_instead")?"checked":""}>\n              静音而不是跳过\n            </label>\n          </div>\n          <div class="setting-item">\n            <label>\n              跳过延迟(秒):\n              <input type="number" id="youtube-skip-delay" min="0" max="10" value="${parseInt(localStorage.getItem("youtube_skip_delay")||"0")}">\n            </label>\n          </div>\n          <div class="setting-item">\n            <label>要跳过的类别:</label>\n            <div class="category-checkboxes">\n              <label><input type="checkbox" class="skip-category" value="sponsor" checked> 赞助商</label>\n              <label><input type="checkbox" class="skip-category" value="selfpromo"> 自我推广</label>\n              <label><input type="checkbox" class="skip-category" value="interaction"> 互动提醒</label>\n              <label><input type="checkbox" class="skip-category" value="intro"> 开场</label>\n              <label><input type="checkbox" class="skip-category" value="outro"> 片尾</label>\n            </div>\n          </div>\n        </div>\n        <div class="settings-modal-footer">\n          <button class="settings-save-btn">保存设置</button>\n          <button class="settings-cancel-btn">取消</button>\n        </div>\n      </div>\n    `,!document.querySelector("#youtube-ad-settings-styles")){const e=document.createElement("style");e.id="youtube-ad-settings-styles",e.textContent='\n        .youtube-ad-settings-modal {\n          position: fixed;\n          top: 0;\n          left: 0;\n          right: 0;\n          bottom: 0;\n          z-index: 999999;\n        }\n        .settings-modal-overlay {\n          position: absolute;\n          inset: 0;\n          background: rgba(0, 0, 0, 0.5);\n        }\n        .settings-modal-content {\n          position: absolute;\n          top: 50%;\n          left: 50%;\n          transform: translate(-50%, -50%);\n          background: white;\n          border-radius: 8px;\n          width: 500px;\n          max-height: 80vh;\n          overflow-y: auto;\n          box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n        }\n        .settings-modal-header {\n          padding: 20px;\n          border-bottom: 1px solid #e0e0e0;\n          display: flex;\n          justify-content: space-between;\n          align-items: center;\n        }\n        .settings-modal-header h3 {\n          margin: 0;\n          font-size: 18px;\n        }\n        .settings-close-btn {\n          background: none;\n          border: none;\n          font-size: 24px;\n          cursor: pointer;\n          padding: 0;\n          width: 30px;\n          height: 30px;\n          display: flex;\n          align-items: center;\n          justify-content: center;\n        }\n        .settings-modal-body {\n          padding: 20px;\n        }\n        .setting-item {\n          margin-bottom: 15px;\n        }\n        .setting-item label {\n          display: block;\n          cursor: pointer;\n          user-select: none;\n        }\n        .setting-item input[type="checkbox"] {\n          margin-right: 8px;\n        }\n        .setting-item input[type="number"] {\n          width: 60px;\n          padding: 4px;\n          border: 1px solid #ddd;\n          border-radius: 4px;\n        }\n        .category-checkboxes {\n          margin-top: 10px;\n          display: grid;\n          grid-template-columns: repeat(2, 1fr);\n          gap: 8px;\n        }\n        .category-checkboxes label {\n          display: flex;\n          align-items: center;\n        }\n        .settings-modal-footer {\n          padding: 15px 20px;\n          border-top: 1px solid #e0e0e0;\n          display: flex;\n          justify-content: flex-end;\n          gap: 10px;\n        }\n        .settings-modal-footer button {\n          padding: 8px 16px;\n          border-radius: 4px;\n          cursor: pointer;\n          font-size: 14px;\n        }\n        .settings-save-btn {\n          background: #ff0000;\n          color: white;\n          border: none;\n        }\n        .settings-save-btn:hover {\n          background: #cc0000;\n        }\n        .settings-cancel-btn {\n          background: #f0f0f0;\n          border: 1px solid #ddd;\n          color: #333;\n        }\n        .settings-cancel-btn:hover {\n          background: #e0e0e0;\n        }\n      ',document.head.appendChild(e);}document.body.appendChild(e);const t=JSON.parse(localStorage.getItem("youtube_skip_categories")||'["sponsor", "selfpromo"]');e.querySelectorAll(".skip-category").forEach(e=>{e.checked=t.includes(e.value);});const n=()=>e.remove();e.querySelector(".settings-modal-overlay").addEventListener("click",n),e.querySelector(".settings-close-btn").addEventListener("click",n),e.querySelector(".settings-cancel-btn").addEventListener("click",n),e.querySelector(".settings-save-btn").addEventListener("click",()=>{localStorage.setItem("youtube_auto_skip",e.querySelector("#youtube-auto-skip").checked),localStorage.setItem("youtube_detect_native",e.querySelector("#youtube-native-detect").checked),localStorage.setItem("youtube_show_notifications",e.querySelector("#youtube-show-notifications").checked),localStorage.setItem("youtube_show_markers",e.querySelector("#youtube-show-markers").checked),localStorage.setItem("youtube_mute_instead",e.querySelector("#youtube-mute-instead").checked),localStorage.setItem("youtube_skip_delay",e.querySelector("#youtube-skip-delay").value);const t=[];e.querySelectorAll(".skip-category:checked").forEach(e=>{t.push(e.value);}),localStorage.setItem("youtube_skip_categories",JSON.stringify(t)),this.universalAdSkipService&&this.universalAdSkipService.updateConfig({autoSkip:e.querySelector("#youtube-auto-skip").checked,detectNativeAds:e.querySelector("#youtube-native-detect").checked,showNotifications:e.querySelector("#youtube-show-notifications").checked,showProgressMarkers:e.querySelector("#youtube-show-markers").checked,muteInsteadOfSkip:e.querySelector("#youtube-mute-instead").checked,skipDelay:parseInt(e.querySelector("#youtube-skip-delay").value),skipCategories:t}),Ke.success("设置已保存"),n();});}cleanup(){ye.debug("App","开始清理应用资源"),this.urlChangeCleanup&&this.urlChangeCleanup(),this.videoQualityService&&this.videoQualityService.stop(),st.playerController&&st.playerController.destroy(),et.destroy(),searchIndex.clear(),_e.destroy(),wt.cleanup(),ye.debug("App","应用资源清理完成");}};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>It.init()):It.init();

})();