KIPSutian-autoplay

自動開啟查詢結果表格中每個詞目連結於 Modal iframe,依序播放音檔(自動偵測時長),主表格自動滾動高亮,**處理完畢後自動跳轉下一頁繼續播放(修正URL與啟動時機)**,可即時暫停/停止/點擊背景暫停/點擊表格列播放,並根據亮暗模式高亮按鈕。 **v4.19: 修正 ALL_TABLES_SELECTOR 的選擇器組合邏輯,確保支援多種容器。**

目前为 2025-04-05 提交的版本。查看 最新版本

// ==UserScript==
// @name         KIPSutian-autoplay
// @namespace    aiuanyu
// @version      4.19
// @description  自動開啟查詢結果表格中每個詞目連結於 Modal iframe,依序播放音檔(自動偵測時長),主表格自動滾動高亮,**處理完畢後自動跳轉下一頁繼續播放(修正URL與啟動時機)**,可即時暫停/停止/點擊背景暫停/點擊表格列播放,並根據亮暗模式高亮按鈕。 **v4.19: 修正 ALL_TABLES_SELECTOR 的選擇器組合邏輯,確保支援多種容器。**
// @author       Aiuanyu 愛灣語 + Gemini
// @match        http*://sutian.moe.edu.tw/und-hani/tshiau/*
// @match        http*://sutian.moe.edu.tw/und-hani/hunlui/*
// @match        http*://sutian.moe.edu.tw/und-hani/siannuntiau/*
// @match        http*://sutian.moe.edu.tw/und-hani/tsongpitueh/*
// @match        http*://sutian.moe.edu.tw/und-hani/huliok/*
// @match        http*://sutian.moe.edu.tw/zh-hant/tshiau/*
// @match        http*://sutian.moe.edu.tw/zh-hant/hunlui/*
// @match        http*://sutian.moe.edu.tw/zh-hant/siannuntiau/*
// @match        http*://sutian.moe.edu.tw/zh-hant/tsongpitueh/*
// @match        http*://sutian.moe.edu.tw/zh-hant/huliok/*
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @connect      sutian.moe.edu.tw
// @run-at       document-idle
// @license      GNU GPLv3
// ==/UserScript==

(function () {
  'use strict';

  // --- 配置 ---
  const MODAL_WIDTH = '80vw';
  const MODAL_HEIGHT = '70vh';
  const FALLBACK_DELAY_MS = 3000;
  const DELAY_BUFFER_MS = 500;
  const DELAY_BETWEEN_CLICKS_MS = 200;
  const DELAY_BETWEEN_IFRAMES_MS = 200;
  const HIGHLIGHT_CLASS = 'userscript-audio-playing';
  const ROW_HIGHLIGHT_CLASS_MAIN = 'userscript-row-highlight'; // 主頁面高亮 class
  const OVERLAY_ID = 'userscript-modal-overlay';
  const ROW_HIGHLIGHT_COLOR = 'rgba(0, 255, 0, 0.1)';
  const ROW_HIGHLIGHT_DURATION = 1500;
  const FONT_AWESOME_URL = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css';
  const FONT_AWESOME_INTEGRITY = 'sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==';
  const AUTOPLAY_PARAM = 'autoplay';
  const PAGINATION_PARAMS = ['iahbe', 'pitsoo']; // ** 可能需要根據實際情況調整分頁參數列表 **
  const AUTO_START_MAX_WAIT_MS = 10000; // 自動啟動時等待表格的最長時間
  const AUTO_START_CHECK_INTERVAL_MS = 500; // 自動啟動時檢查表格的間隔
  // ** 恢復完整的選擇器 **
  const TABLE_CONTAINER_SELECTOR = 'main.container-fluid div.mt-1.mb-5, main.container-fluid div.mt-1.mb-4, main.container-fluid div.mb-5';
  // ** 修正:正確組合 ALL_TABLES_SELECTOR **
  const ALL_TABLES_SELECTOR = TABLE_CONTAINER_SELECTOR.split(',')
    .map(s => `${s.trim()} > table`)
    .join(', ');
  const RELEVANT_ROW_MARKER_SELECTOR = 'td:first-of-type span.fw-normal';
  const WIDE_TABLE_SELECTOR = 'table.d-none.d-md-table';
  const NARROW_TABLE_SELECTOR = 'table.d-md-none';
  const RESIZE_DEBOUNCE_MS = 300; // ResizeObserver 的 debounce 延遲時間

  // --- 適應亮暗模式的高亮樣式 ---
  const HIGHLIGHT_STYLE = `
        /* 預設 (亮色模式) */
        .${HIGHLIGHT_CLASS} { /* iframe 內按鈕高亮 */
            background-color: #FFF352 !important;
            color: black !important;
            outline: 2px solid #FFB800 !important;
            box-shadow: 0 0 10px #FFF352;
            transition: background-color 0.2s ease-in-out, outline 0.2s ease-in-out, color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
        }
        /* 深色模式 */
        @media (prefers-color-scheme: dark) {
            .${HIGHLIGHT_CLASS} {
                background-color: #66b3ff !important;
                color: black !important;
                outline: 2px solid #87CEFA !important;
                box-shadow: 0 0 10px #66b3ff;
            }
        }
    `;
  // --- 配置結束 ---

  // --- 全局狀態變數 ---
  let isProcessing = false;
  let isPaused = false;
  let currentLinkIndex = 0;
  let totalLinks = 0;
  let currentSleepController = null;
  let currentIframe = null;
  let linksToProcess = []; // ** 注意:現在不儲存 tableRow **
  let rowHighlightTimeout = null;
  let resizeDebounceTimeout = null; // 用於 ResizeObserver 的 debounce

  // --- UI 元素引用 ---
  let startButton;
  let pauseButton;
  let stopButton;
  let statusDisplay;
  let overlayElement = null;

  // --- Helper 函數 ---

  // 可中斷的延遲函數
  function interruptibleSleep(ms) {
    if (currentSleepController) {
      currentSleepController.cancel('overridden');
    }
    let timeoutId;
    let rejectFn;
    let resolved = false;
    let rejected = false;
    const promise = new Promise((resolve, reject) => {
      rejectFn = reject;
      timeoutId = setTimeout(() => {
        if (!rejected) {
          resolved = true;
          currentSleepController = null;
          resolve();
        }
      }, ms);
    });
    const controller = {
      promise: promise,
      cancel: (reason = 'cancelled') => {
        if (!resolved && !rejected) {
          rejected = true;
          clearTimeout(timeoutId);
          currentSleepController = null;
          const error = new Error(reason);
          error.isCancellation = true;
          error.reason = reason;
          rejectFn(error);
        }
      }
    };
    currentSleepController = controller;
    return controller;
  }

  // 普通延遲函數
  function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  // 獲取音檔時長 (毫秒)
  function getAudioDuration(audioUrl) {
    console.log(`[自動播放] 嘗試獲取音檔時長: ${audioUrl}`);
    return new Promise((resolve) => {
      if (!audioUrl) {
        console.warn("[自動播放] 無效的音檔 URL,使用後備延遲。");
        resolve(FALLBACK_DELAY_MS);
        return;
      }
      const audio = new Audio();
      audio.preload = 'metadata';
      const timer = setTimeout(() => {
        console.warn(`[自動播放] 獲取音檔 ${audioUrl} 元數據超時 (5秒),使用後備延遲。`);
        cleanupAudio();
        resolve(FALLBACK_DELAY_MS);
      }, 5000);
      const cleanupAudio = () => {
        clearTimeout(timer);
        audio.removeEventListener('loadedmetadata', onLoadedMetadata);
        audio.removeEventListener('error', onError);
        audio.src = '';
      };
      const onLoadedMetadata = () => {
        if (audio.duration && isFinite(audio.duration)) {
          const durationMs = Math.ceil(audio.duration * 1000) + DELAY_BUFFER_MS;
          console.log(`[自動播放] 獲取到音檔時長: ${audio.duration.toFixed(2)}s, 使用延遲: ${durationMs}ms`);
          cleanupAudio();
          resolve(durationMs);
        } else {
          console.warn(`[自動播放] 無法從元數據獲取有效時長 (${audio.duration}),使用後備延遲。`);
          cleanupAudio();
          resolve(FALLBACK_DELAY_MS);
        }
      };
      const onError = (e) => {
        console.error(`[自動播放] 加載音檔 ${audioUrl} 元數據時出錯:`, e);
        cleanupAudio();
        resolve(FALLBACK_DELAY_MS); // 出錯時也使用後備延遲
      };
      audio.addEventListener('loadedmetadata', onLoadedMetadata);
      audio.addEventListener('error', onError);
      try {
        audio.src = audioUrl;
      } catch (e) {
        console.error(`[自動播放] 設置音檔 src 時發生錯誤 (${audioUrl}):`, e);
        cleanupAudio();
        resolve(FALLBACK_DELAY_MS);
      }
    });
  }

  // 在 Iframe 內部添加樣式
  function addStyleToIframe(iframeDoc, css) {
    try {
      const styleElement = iframeDoc.createElement('style');
      styleElement.textContent = css;
      iframeDoc.head.appendChild(styleElement);
      console.log("[自動播放] 已在 iframe 中添加高亮樣式。");
    } catch (e) {
      console.error("[自動播放] 無法在 iframe 中添加樣式:", e);
    }
  }

  // 背景遮罩點擊事件處理函數
  function handleOverlayClick(event) {
    if (event.target !== overlayElement) {
      console.log("[自動播放][偵錯] 點擊事件目標不是遮罩本身,忽略。", event.target);
      return;
    }
    console.log(`[自動播放][偵錯] handleOverlayClick 觸發。isProcessing: ${isProcessing}, isPaused: ${isPaused}, currentIframe: ${currentIframe ? currentIframe.id : 'null'}`);
    if (isProcessing && !isPaused) {
      console.log("[自動播放] 點擊背景遮罩,觸發暫停並關閉 Modal。");
      isPaused = true;
      pauseButton.textContent = '繼續';
      updateStatusDisplay();
      if (currentSleepController) {
        console.log("[自動播放][偵錯] 正在取消當前的 sleep...");
        currentSleepController.cancel('paused_overlay');
      } else {
        console.log("[自動播放][偵錯] 點擊遮罩時沒有正在進行的 sleep 可取消。");
      }
      closeModal();
    } else {
      console.log("[自動播放][偵錯] 點擊遮罩,但條件不滿足 (isProcessing 或 isPaused 狀態不對)。");
    }
  }

  // 顯示 Modal (Iframe + Overlay)
  function showModal(iframe) {
    overlayElement = document.getElementById(OVERLAY_ID);
    if (!overlayElement) {
      overlayElement = document.createElement('div');
      overlayElement.id = OVERLAY_ID;
      overlayElement.style.position = 'fixed';
      overlayElement.style.top = '0';
      overlayElement.style.left = '0';
      overlayElement.style.width = '100vw';
      overlayElement.style.height = '100vh';
      overlayElement.style.backgroundColor = 'rgba(0, 0, 0, 0.6)';
      overlayElement.style.zIndex = '9998';
      overlayElement.style.cursor = 'pointer';
      document.body.appendChild(overlayElement);
      console.log("[自動播放][偵錯] 已創建背景遮罩元素。");
    } else {
      console.log("[自動播放][偵錯] 背景遮罩元素已存在。");
    }
    overlayElement.removeEventListener('click', handleOverlayClick);
    console.log("[自動播放][偵錯] 已嘗試移除舊的遮罩點擊監聽器。");
    overlayElement.addEventListener('click', handleOverlayClick);
    console.log("[自動播放][偵錯] 已添加新的遮罩點擊監聽器。");

    iframe.style.position = 'fixed';
    iframe.style.width = MODAL_WIDTH;
    iframe.style.height = MODAL_HEIGHT;
    iframe.style.top = '50%';
    iframe.style.left = '50%';
    iframe.style.transform = 'translate(-50%, -50%)';
    iframe.style.border = '1px solid #ccc';
    iframe.style.borderRadius = '8px';
    iframe.style.boxShadow = '0 5px 20px rgba(0, 0, 0, 0.3)';
    iframe.style.backgroundColor = 'white';
    iframe.style.zIndex = '9999';
    iframe.style.opacity = '1';
    iframe.style.pointerEvents = 'auto';
    document.body.appendChild(iframe);
    currentIframe = iframe;
    console.log(`[自動播放] 已顯示 Modal iframe, id: ${currentIframe.id}`);
  }

  // 增強關閉 Modal (Iframe + Overlay) 的健壯性
  function closeModal() {
    console.log(`[自動播放][偵錯] closeModal 被調用。 currentIframe: ${currentIframe ? currentIframe.id : 'null'}, overlayElement: ${overlayElement ? 'exists' : 'null'}`);
    if (currentIframe && currentIframe.parentNode) {
      currentIframe.remove();
      console.log("[自動播放] 已移除 iframe");
    } else if (currentIframe) {
      console.log("[自動播放][偵錯] 嘗試移除 iframe 時,它已不在 DOM 中。");
    }
    currentIframe = null; // 清除 iframe 引用

    if (overlayElement) {
      overlayElement.removeEventListener('click', handleOverlayClick); // 嘗試移除監聽器
      if (overlayElement.parentNode) { // 檢查 overlayElement 是否仍在 DOM 中
        overlayElement.remove();
        console.log("[自動播放][偵錯] 已移除背景遮罩及其點擊監聽器。");
      } else {
        console.log("[自動播放][偵錯] 嘗試移除遮罩時,它已不在 DOM 中。");
      }
      overlayElement = null; // 清除遮罩引用
    } else {
      console.log("[自動播放][偵錯] 嘗試關閉 Modal 時,overlayElement 引用已為 null 或未找到元素。");
    }

    if (currentSleepController) {
      console.log("[自動播放] 關閉 Modal 時取消正在進行的 sleep");
      currentSleepController.cancel('modal_closed');
      currentSleepController = null;
    }
  }

  // 提取處理 iframe 內容的邏輯
  async function handleIframeContent(iframe, url, linkIndexInCurrentList) {
    let iframeDoc;
    try {
      await sleep(150); // 等待可能的初始化
      iframeDoc = iframe.contentWindow.document;
      addStyleToIframe(iframeDoc, HIGHLIGHT_STYLE);

      const audioButtons = iframeDoc.querySelectorAll('button.imtong-liua');
      console.log(`[自動播放] 在 iframe (${iframe.id}) 中找到 ${audioButtons.length} 個播放按鈕`);

      if (audioButtons.length > 0) {
        for (let i = 0; i < audioButtons.length; i++) {
          console.log(`[自動播放][偵錯] 進入音檔循環 ${i + 1}。 isProcessing: ${isProcessing}, isPaused: ${isPaused}`);
          if (!isProcessing) {
            console.log("[自動播放] 播放音檔前檢測到停止");
            break; // 跳出音檔循環
          }
          // ** 關鍵的暫停等待循環 **
          while (isPaused && isProcessing) {
            console.log(`[自動播放] 音檔循環 ${i + 1} 偵測到暫停,等待繼續...`);
            updateStatusDisplay();
            await sleep(500); // 使用普通 sleep,因為 interruptibleSleep 會被外部取消
            // 在等待後再次檢查 isProcessing,以防在暫停時被停止
            if (!isProcessing) {
              console.log("[自動播放] 在暫停等待期間檢測到停止");
              break; // 跳出 while 和 for 循環
            }
          }
          if (!isProcessing) { // 如果在暫停時停止,跳出 for 循環
            break;
          }
          // ** 再次檢查 isPaused,因為可能在 sleep(500) 期間狀態改變了 **
          if (isPaused) {
            console.log(`[自動播放][偵錯] sleep(500) 後仍然是暫停狀態,繼續等待。`);
            i--; // 回到同一個按鈕,以便下次循環重新檢查
            continue;
          }
          // --- 狀態檢查結束 ---

          const button = audioButtons[i];
          if (!button || !iframeDoc.body.contains(button)) {
            console.warn(`[自動播放] 按鈕 ${i + 1} 失效,跳過。`);
            continue;
          }
          console.log(`[自動播放] 準備播放 iframe 中的第 ${i + 1} 個音檔`);

          // --- data-src 解析 ---
          let actualDelayMs = FALLBACK_DELAY_MS;
          let audioSrc = null;
          let audioPath = null;
          const srcString = button.dataset.src;
          if (srcString) {
            try {
              const parsedData = JSON.parse(srcString.replace(/&quot;/g, '"'));
              if (Array.isArray(parsedData) && parsedData.length > 0 && typeof parsedData[0] === 'string') {
                audioPath = parsedData[0];
              }
            } catch (e) {
              if (typeof srcString === 'string' && srcString.trim().startsWith('/')) {
                audioPath = srcString.trim();
              }
            }
          }
          if (audioPath) {
            try {
              const base = iframe.contentWindow.location.href;
              audioSrc = new URL(audioPath, base).href;
            } catch (urlError) {
              audioSrc = null;
            }
          } else {
            audioSrc = null;
          }
          actualDelayMs = await getAudioDuration(audioSrc);
          // --- 解析結束 ---

          // --- **修改:決定 iframe 內部捲動目標** ---
          let scrollTargetElement = button; // 預設捲動到按鈕本身
          const flexContainer = button.closest('div.d-flex.flex-row.align-items-baseline');
          const fs6Container = button.closest('div.mb-0.fs-6');

          if (flexContainer) {
            // 情況 1: 嘗試尋找前面的 h1#main (使用者已更新)
            const mainHeading = iframeDoc.querySelector('h1#main'); // 使用 ID 選擇器
            if (mainHeading) {
              console.log("[自動播放][Iframe捲動] 找到 flex container,嘗試捲動到 h1#main");
              scrollTargetElement = mainHeading;
            } else {
              console.log("[自動播放][Iframe捲動] 找到 flex container,但未找到 h1#main,捲動到按鈕");
            }
          } else if (fs6Container) {
            // 情況 2: 嘗試尋找前面的 span.mb-0
            const precedingSpan = fs6Container.previousElementSibling;
            if (precedingSpan && precedingSpan.matches('span.mb-0')) {
              console.log("[自動播放][Iframe捲動] 找到 fs6 container,嘗試捲動到前面的 span.mb-0");
              scrollTargetElement = precedingSpan;
            } else {
              console.log("[自動播放][Iframe捲動] 找到 fs6 container,但未找到前面的 span.mb-0,捲動到按鈕");
            }
          } else {
            console.log("[自動播放][Iframe捲動] 未匹配特殊容器,捲動到按鈕");
          }

          if (scrollTargetElement && iframeDoc.body.contains(scrollTargetElement)) {
            scrollTargetElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
            console.log("[自動播放][Iframe捲動] 已執行捲動到目標:", scrollTargetElement);
          } else {
            console.warn("[自動播放][Iframe捲動] 捲動目標無效或不存在:", scrollTargetElement);
          }
          // --- 捲動邏輯結束 ---

          await sleep(300); // 等待捲動完成

          button.classList.add(HIGHLIGHT_CLASS);
          button.click();
          console.log(`[自動播放] 已點擊按鈕 ${i + 1},等待 ${actualDelayMs}ms`);

          // --- 可中斷 sleep ---
          try {
            const sleepController = interruptibleSleep(actualDelayMs);
            await sleepController.promise;
          } catch (error) {
            if (error.isCancellation) {
              console.log(`[自動播放] 等待音檔 ${i + 1} 被 '${error.reason}' 中斷。`);
              if (iframeDoc.body.contains(button)) {
                button.classList.remove(HIGHLIGHT_CLASS);
              }
              break; // 跳出音檔循環
            } else {
              console.error("[自動播放] interruptibleSleep 發生意外錯誤:", error);
              // 可以選擇繼續或終止
            }
          } finally {
            currentSleepController = null; // 清理控制器引用
          }
          // --- 中斷 sleep 結束 ---

          if (iframeDoc.body.contains(button) && button.classList.contains(HIGHLIGHT_CLASS)) {
            button.classList.remove(HIGHLIGHT_CLASS);
          }

          if (!isProcessing) { // 檢查 sleep 後是否被停止
            break;
          }

          // --- 按鈕間等待 (可中斷) ---
          if (i < audioButtons.length - 1) {
            console.log(`[自動播放] 播放下一個前等待 ${DELAY_BETWEEN_CLICKS_MS}ms`);
            try {
              const sleepController = interruptibleSleep(DELAY_BETWEEN_CLICKS_MS);
              await sleepController.promise;
            } catch (error) {
              if (error.isCancellation) {
                console.log(`[自動播放] 按鈕間等待被 '${error.reason}' 中斷。`);
                break; // 跳出音檔循環
              } else {
                throw error; // 重新拋出其他錯誤
              }
            } finally {
              currentSleepController = null;
            }
          }
          // --- 按鈕間等待結束 ---

          if (!isProcessing) { // 再次檢查狀態
            break;
          }
        } // --- for audioButtons loop end ---
      } else {
        console.log(`[自動播放] Iframe ${url} 中未找到播放按鈕`);
        await sleep(1000); // 讓使用者看一下空白內容
      }
    } catch (error) {
      console.error(`[自動播放] 處理 iframe 內容時出錯 (${url}):`, error);
    } finally {
      console.log(`[自動播放][偵錯] handleIframeContent finally 區塊。 isProcessing: ${isProcessing}, isPaused: ${isPaused}, currentIframe: ${currentIframe ? currentIframe.id : 'null'}`);
      // 清理 sleep controller
      if (currentSleepController) {
        console.warn("[自動播放][偵錯] handleIframeContent 結束時 currentSleepController 仍存在,強制清除。");
        currentSleepController.cancel('content_handled_exit');
        currentSleepController = null;
      }
      // **不再在這裡判斷 isPaused 或 isProcessing 來關閉 Modal**
    }
  }

  // 處理單一連結的入口函數
  async function processSingleLink(url, linkIndexInCurrentList) {
    console.log(`[自動播放] processSingleLink 開始: 列表索引 ${linkIndexInCurrentList} (第 ${linkIndexInCurrentList + 1} / ${totalLinks} 項) - ${url}. isProcessing: ${isProcessing}, isPaused: ${isPaused}`);
    const iframeId = `auto-play-iframe-${Date.now()}`;
    let iframe = document.createElement('iframe'); // 使用 let 允許重新賦值
    iframe.id = iframeId;

    return new Promise(async (resolve) => {
      if (!isProcessing) {
        console.log("[自動播放][偵錯] processSingleLink 開始時 isProcessing 為 false,直接返回。");
        resolve();
        return;
      }

      let isUsingExistingIframe = false;
      if (!currentIframe) {
        console.log("[自動播放][偵錯] currentIframe 為 null,顯示新 Modal。");
        showModal(iframe);
      } else {
        console.log("[自動播放][偵錯] currentIframe 已存在。");
        // 檢查 URL 是否匹配,決定是否重用或重新加載
        if (currentIframe.contentWindow && currentIframe.contentWindow.location.href === url) {
          console.log("[自動播放][偵錯] URL 匹配,繼續使用現有 iframe (按鈕暫停恢復)。");
          iframe = currentIframe; // ** 關鍵:使用現有的 iframe 引用 **
          isUsingExistingIframe = true;
        } else {
          console.warn("[自動播放][偵錯] currentIframe 存在但 URL 不匹配或無法訪問!強制關閉並重新打開。");
          closeModal();
          await sleep(50); // 短暫等待確保關閉完成
          if (!isProcessing) { resolve(); return; } // 再次檢查狀態
          showModal(iframe); // 顯示新的 iframe
        }
      }

      // 如果是使用現有 iframe (按鈕暫停恢復),onload 不會觸發,直接執行處理邏輯
      if (isUsingExistingIframe) {
        console.log("[自動播放][偵錯] 直接處理現有 iframe 的內容。");
        await handleIframeContent(iframe, url, linkIndexInCurrentList); // 處理內容
        resolve(); // ** 在這裡 resolve **
      } else {
        // 如果是新 iframe,設置 onload 和 onerror
        iframe.onload = async () => {
          console.log(`[自動播放] Iframe 載入完成: ${url}. isProcessing: ${isProcessing}, isPaused: ${isPaused}`);
          if (!isProcessing) {
            console.log("[自動播放] Iframe 載入時發現已停止,關閉 Modal");
            closeModal();
            resolve(); // ** 在這裡 resolve **
            return;
          }
          // 再次檢查 iframe 引用是否仍然是當前的
          if (currentIframe !== iframe) {
            console.warn(`[自動播放][偵錯] Iframe onload 觸發,但 currentIframe (${currentIframe ? currentIframe.id : 'null'}) 與當前 iframe (${iframe.id}) 不符!中止此 iframe 處理。`);
            resolve(); // ** 在這裡 resolve **
            return;
          }
          await handleIframeContent(iframe, url, linkIndexInCurrentList); // 處理內容
          resolve(); // ** 在這裡 resolve **
        };
        iframe.onerror = (error) => {
          console.error(`[自動播放] Iframe 載入失敗 (${url}):`, error);
          closeModal();
          resolve(); // ** 在這裡 resolve **
        };
        iframe.src = url; // 設置 src 開始加載
      }
      // ** 將 resolve 移到 onload/onerror 或 isUsingExistingIframe 處理完畢後 **
    });
  }

  // ** 修改:輔助函數,根據 URL 查找對應的主頁面元素 (td 或 table) **
  function findElementForLink(targetUrl) {
    if (!targetUrl) return null;

    console.log(`[自動播放][查找元素] 尋找 URL: ${targetUrl}`);
    const visibleTables = getVisibleTables(); // ** 使用了更新後的選擇器 **
    const linkSelector = getLinkSelector();
    let targetElement = null;

    for (const table of visibleTables) {
      const isWideTable = table.matches(WIDE_TABLE_SELECTOR);
      const isNarrowTable = table.matches(NARROW_TABLE_SELECTOR);
      const rows = table.querySelectorAll('tbody tr');
      // console.log(`[自動播放][查找元素] 檢查 ${isWideTable ? '寬' : isNarrowTable ? '窄' : '未知'} 表格...`);

      if (isWideTable) {
        for (const row of rows) {
          const firstTd = row.querySelector('td:first-of-type');
          if (firstTd && firstTd.querySelector('span.fw-normal')) {
            const linkElement = row.querySelector(linkSelector);
            if (linkElement) {
              try {
                const linkHref = new URL(linkElement.getAttribute('href'), window.location.origin).href;
                if (linkHref === targetUrl) {
                  // ** 修改:寬螢幕目標改為第一個 td **
                  targetElement = firstTd;
                  console.log("[自動播放][查找元素][寬] 找到對應 td:", targetElement);
                  break;
                }
              } catch (e) {
                console.error(`[自動播放][查找元素][寬] 處理連結 URL 時出錯:`, e, linkElement);
              }
            }
          }
        }
      } else if (isNarrowTable && rows.length >= 2) {
        const firstRowTd = rows[0].querySelector('td:first-of-type');
        const secondRowTd = rows[1].querySelector('td:first-of-type');
        if (firstRowTd && firstRowTd.querySelector('span.fw-normal') && secondRowTd) {
          const linkElement = secondRowTd.querySelector(linkSelector);
          if (linkElement) {
            try {
              const linkHref = new URL(linkElement.getAttribute('href'), window.location.origin).href;
              if (linkHref === targetUrl) {
                targetElement = table; // 窄螢幕目標是 table
                console.log("[自動播放][查找元素][窄] 找到對應 table:", targetElement);
                break;
              }
            } catch (e) {
              console.error(`[自動播放][查找元素][窄] 處理連結 URL 時出錯:`, e, linkElement);
            }
          }
        }
      }
      if (targetElement) break; // 找到就跳出外層循環
    }

    if (!targetElement) {
      console.warn(`[自動播放][查找元素] 未能找到 URL 對應的元素: ${targetUrl}`);
    }
    return targetElement;
  }


  // 循序處理連結列表 - 加入自動分頁邏輯
  async function processLinksSequentially() {
    console.log("[自動播放] processLinksSequentially 開始");
    while (currentLinkIndex < totalLinks && isProcessing) {
      // ** 關鍵的暫停等待循環 **
      while (isPaused && isProcessing) {
        console.log(`[自動播放] 主流程已暫停 (索引 ${currentLinkIndex}),等待繼續...`);
        updateStatusDisplay();
        await sleep(500); // 使用普通 sleep
        if (!isProcessing) { // 檢查在暫停等待期間是否被停止
          break;
        }
      }
      if (!isProcessing) { // 如果在暫停時停止,跳出主循環
        break;
      }

      updateStatusDisplay();
      const linkInfo = linksToProcess[currentLinkIndex]; // ** 注意:linkInfo 不再包含 tableRow **
      console.log(`[自動播放] 準備處理連結 ${currentLinkIndex + 1}/${totalLinks} (全局索引 ${linkInfo.originalIndex}) - URL: ${linkInfo.url}`);

      // --- **修改:動態查找、捲動和高亮主頁面元素** ---
      const targetElement = findElementForLink(linkInfo.url); // 返回 td 或 table

      if (targetElement) {
        // ** 修改:高亮目標改為 targetElement 的父元素 (tr 或 table 本身) **
        const highlightTarget = targetElement.closest('tr') || targetElement; // 如果是 td,找 tr;如果是 table,就是 table 本身
        console.log(`[自動播放][主頁捲動/高亮] 正在處理項目 ${linkInfo.originalIndex + 1} 對應的元素`, targetElement, `高亮目標:`, highlightTarget);

        if (rowHighlightTimeout) {
          clearTimeout(rowHighlightTimeout);
        }
        // 移除所有可能殘留的高亮
        document.querySelectorAll(`.${ROW_HIGHLIGHT_CLASS_MAIN}`).forEach(el => {
          el.classList.remove(ROW_HIGHLIGHT_CLASS_MAIN);
          el.style.backgroundColor = '';
          el.style.transition = '';
        });

        // ** 修改:捲動目標為找到的 targetElement (td 或 table) **
        targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); // 改回 center 試試
        await sleep(300); // 等待捲動基本完成

        // ** 修改:高亮目標為 highlightTarget **
        if (highlightTarget) {
          highlightTarget.classList.add(ROW_HIGHLIGHT_CLASS_MAIN);
          highlightTarget.style.backgroundColor = ROW_HIGHLIGHT_COLOR;
          highlightTarget.style.transition = 'background-color 0.5s ease-out';
          console.log(`[自動播放][主頁高亮] 已高亮項目 ${linkInfo.originalIndex + 1} 對應的元素`);

          const currentHighlightTarget = highlightTarget; // 捕獲當前高亮目標引用
          rowHighlightTimeout = setTimeout(() => {
            if (currentHighlightTarget && currentHighlightTarget.classList.contains(ROW_HIGHLIGHT_CLASS_MAIN)) { // 確保元素仍然存在且有高亮
              currentHighlightTarget.style.backgroundColor = '';
              // 等待背景色過渡完成後移除 class
              setTimeout(() => {
                if (currentHighlightTarget) {
                  currentHighlightTarget.classList.remove(ROW_HIGHLIGHT_CLASS_MAIN);
                }
              }, 500); // 匹配過渡時間
            }
            rowHighlightTimeout = null;
          }, ROW_HIGHLIGHT_DURATION);
        } else {
          console.warn(`[自動播放][主頁高亮] 未能確定項目 ${linkInfo.originalIndex + 1} 的高亮目標。`);
        }
      } else {
        console.warn(`[自動播放][主頁捲動] 未能找到項目 ${linkInfo.originalIndex + 1} (URL: ${linkInfo.url}) 對應的元素進行捲動和高亮。`);
      }
      // --- 捲動高亮邏輯結束 ---

      await sleep(200); // 等待滾動和高亮穩定
      if (!isProcessing || isPaused) { // 如果在等待時狀態改變
        continue; // 重新進入外層 while 檢查 isPaused
      }

      // ** 調用 processSingleLink **
      await processSingleLink(linkInfo.url, currentLinkIndex);
      if (!isProcessing) { // 檢查處理後是否被停止
        break;
      }

      // ** 處理完一個連結後的 Modal 關閉邏輯 **
      // 只有在非暫停狀態下才關閉 Modal
      if (!isPaused) {
        console.log(`[自動播放][偵錯] 連結 ${currentLinkIndex + 1} 處理完畢,非暫停狀態,關閉 Modal`);
        closeModal(); // 確保關閉當前 Modal
      } else {
        console.log(`[自動播放][偵錯] 連結 ${currentLinkIndex + 1} 處理完畢,但處於暫停狀態,保持 Modal 開啟`);
      }

      // 只有在沒有暫停的情況下才移動到下一個連結
      if (!isPaused) {
        console.log(`[自動播放][偵錯] 索引增加`);
        currentLinkIndex++;
      } else {
        console.log(`[自動播放][偵錯] 處於暫停狀態,索引保持不變`);
        // isPaused 仍然為 true,外層 while 循環會在下一次迭代時繼續等待
      }

      // 連結間的等待 (只有在非暫停且還有連結時執行)
      if (currentLinkIndex < totalLinks && isProcessing && !isPaused) {
        console.log(`[自動播放] 等待 ${DELAY_BETWEEN_IFRAMES_MS}ms 後處理下一個連結`);
        try {
          const sleepController = interruptibleSleep(DELAY_BETWEEN_IFRAMES_MS);
          await sleepController.promise;
        } catch (error) {
          if (error.isCancellation) {
            console.log(`[自動播放] 連結間等待被 '${error.reason}' 中斷。`);
          } else {
            throw error; // 重新拋出其他錯誤
          }
        } finally {
          currentSleepController = null;
        }
      }
      if (!isProcessing) { // 再次檢查狀態
        break;
      }
    } // --- while loop end ---

    console.log(`[自動播放][偵錯] processLinksSequentially 循環結束。 isProcessing: ${isProcessing}, isPaused: ${isPaused}`);
    if (rowHighlightTimeout) { clearTimeout(rowHighlightTimeout); } // 清除高亮
    document.querySelectorAll(`.${ROW_HIGHLIGHT_CLASS_MAIN}`).forEach(el => { el.classList.remove(ROW_HIGHLIGHT_CLASS_MAIN); el.style.backgroundColor = ''; el.style.transition = ''; });

    // --- **自動分頁邏輯** ---
    if (isProcessing && !isPaused) { // 只有在正常處理完畢時才嘗試翻頁
      console.log("[自動播放] 當前頁面處理完畢,檢查是否有下一頁...");
      const paginationNav = document.querySelector('nav[aria-label="頁碼"] ul.pagination');
      if (paginationNav) {
        const nextPageLink = paginationNav.querySelector('li:last-child > a');
        // ** 修改:包含 "下一頁" (來自使用者更新) **
        if (nextPageLink && (nextPageLink.textContent.includes('後一頁') || nextPageLink.textContent.includes('下一頁')) && !nextPageLink.closest('li.disabled')) {
          const nextPageHref = nextPageLink.getAttribute('href');
          if (nextPageHref && nextPageHref !== '#') {
            console.log(`[自動播放] 找到下一頁原始 href: ${nextPageHref}`);
            try {
              const currentParams = new URLSearchParams(window.location.search);
              const nextPageUrlTemp = new URL(nextPageHref, window.location.origin);
              const nextPageParams = nextPageUrlTemp.searchParams;
              const finalParams = new URLSearchParams(currentParams.toString());
              PAGINATION_PARAMS.forEach(param => {
                if (nextPageParams.has(param)) {
                  finalParams.set(param, nextPageParams.get(param));
                  console.log(`[自動播放][分頁] 更新參數 ${param}=${nextPageParams.get(param)}`);
                }
              });
              finalParams.set(AUTOPLAY_PARAM, 'true');
              const finalNextPageUrl = `${window.location.pathname}?${finalParams.toString()}`;

              console.log(`[自動播放] 組合完成,準備跳轉至: ${finalNextPageUrl}`);
              await sleep(1000);
              window.location.href = finalNextPageUrl;
              return; // 跳轉後結束
            } catch (e) {
              console.error("[自動播放] 處理下一頁 URL 時出錯:", e);
            }
          } else {
            console.log("[自動播放] 「後一頁」/「下一頁」連結無效或被禁用。");
          }
        } else {
          console.log("[自動播放] 未找到有效的「後一頁」或「下一頁」連結。");
        }
      } else {
        console.log("[自動播放] 未找到分頁導航元素。");
      }
      // 無下一頁或處理出錯,執行正常完成邏輯
      console.log("[自動播放] 所有連結處理完畢 (無下一頁)。");
      alert("所有連結攏處理完畢!");
      resetTriggerButton();
    } else if (!isProcessing) { // 如果是被停止的
      console.log("[自動播放] 處理流程被停止。");
      resetTriggerButton();
    } else { // 如果是暫停狀態結束
      console.log("[自動播放] 流程結束於暫停狀態。");
      // 維持 UI,等待使用者操作
    }
  }

  // --- 控制按鈕事件處理 ---

  // ** 輔助函數,獲取當前可見的表格元素列表 **
  function getVisibleTables() {
    // ** 恢復使用 ALL_TABLES_SELECTOR,移除除錯日誌 **
    const allTables = document.querySelectorAll(ALL_TABLES_SELECTOR);
    const visibleTables = Array.from(allTables).filter(table => {
      try {
        // 檢查 display 和 visibility,確保表格是真的可見
        const style = window.getComputedStyle(table);
        return style.display !== 'none' && style.visibility !== 'hidden';
      } catch (e) {
        console.error("[自動播放] 檢查表格可見性時出錯:", e, table);
        return false; // 出錯時視為不可見
      }
    });
    // console.log(`[自動播放] 找到 ${allTables.length} 個潛在表格,其中 ${visibleTables.length} 個當前可見。`);
    return visibleTables;
  }

  // startPlayback
  function startPlayback(startIndex = 0) {
    console.log(`[自動播放] startPlayback 調用。 startIndex: ${startIndex}, isProcessing: ${isProcessing}, isPaused: ${isPaused}`);
    if (!isProcessing) {
      // 獲取連結選擇器
      const linkSelector = getLinkSelector();
      console.log(`[自動播放] 使用連結選擇器: ${linkSelector}`);

      // ** 修改:區分表格類型來查找連結 **
      const visibleTables = getVisibleTables(); // ** 使用了更新後的選擇器 **
      if (visibleTables.length === 0) {
        alert("頁面上揣無目前顯示的結果表格!(已恢復完整選擇器)"); // ** 更新提示訊息 **
        return;
      }

      const allLinks = [];
      let globalRowIndex = 0; // 用於計算 originalIndex

      visibleTables.forEach(table => {
        const isWideTable = table.matches(WIDE_TABLE_SELECTOR);
        const isNarrowTable = table.matches(NARROW_TABLE_SELECTOR);

        if (isWideTable) {
          // console.log("[自動播放][偵錯][連結] 處理寬螢幕表格...");
          const rows = table.querySelectorAll('tbody tr');
          rows.forEach(row => {
            const firstTd = row.querySelector('td:first-of-type');
            // 寬螢幕:標記和連結在同一行
            if (firstTd && firstTd.querySelector('span.fw-normal')) {
              const linkElement = row.querySelector(linkSelector); // 在同一行找連結
              if (linkElement) {
                try {
                  allLinks.push({
                    url: new URL(linkElement.getAttribute('href'), window.location.origin).href,
                    anchorElement: linkElement,
                    // ** 移除 tableRow **
                    originalIndex: globalRowIndex
                  });
                  globalRowIndex++;
                } catch (e) {
                  console.error(`[自動播放][連結][寬] 處理連結 URL 時出錯:`, e, linkElement);
                }
              } else {
                // console.log(`[自動播放][偵錯][連結][寬] 在標記行 ${globalRowIndex + 1} 中未找到連結 ${linkSelector}`);
              }
            }
          });
        } else if (isNarrowTable) {
          // console.log("[自動播放][偵錯][連結] 處理窄螢幕表格...");
          const rows = table.querySelectorAll('tbody tr');
          // 窄螢幕:標記在第一行,連結在第二行
          if (rows.length >= 2) { // 至少需要兩行
            const firstRowTd = rows[0].querySelector('td:first-of-type');
            if (firstRowTd && firstRowTd.querySelector('span.fw-normal')) {
              // 在第二行的第一個 td 中找連結
              const secondRowTd = rows[1].querySelector('td:first-of-type');
              if (secondRowTd) {
                const linkElement = secondRowTd.querySelector(linkSelector);
                if (linkElement) {
                  try {
                    allLinks.push({
                      url: new URL(linkElement.getAttribute('href'), window.location.origin).href,
                      anchorElement: linkElement,
                      // ** 移除 tableRow, 高亮和捲動目標將動態查找 **
                      originalIndex: globalRowIndex
                    });
                    globalRowIndex++;
                  } catch (e) {
                    console.error(`[自動播放][連結][窄] 處理連結 URL 時出錯:`, e, linkElement);
                  }
                } else {
                  // console.log(`[自動播放][偵錯][連結][窄] 在第二行 td:first-of-type 中未找到連結 ${linkSelector}`);
                }
              } else {
                // console.log(`[自動播放][偵錯][連結][窄] 找不到第二行的第一個 td`);
              }
            } else {
              // console.log(`[自動播放][偵錯][連結][窄] 第一行未找到標記`);
            }
          } else {
            // console.log(`[自動播放][偵錯][連結][窄] 表格行數不足 (< 2)`);
          }
        } else {
          console.warn("[自動播放][連結] 發現未知類型的可見表格:", table);
        }
      });


      if (allLinks.length === 0) {
        alert("目前顯示的表格內底揣無詞目連結 (已區分表格結構,已恢復完整選擇器)!"); // ** 更新提示訊息 **
        return;
      }
      console.log(`[自動播放] 從 ${visibleTables.length} 個可見表格中根據結構找到 ${allLinks.length} 個連結。`);


      if (startIndex >= allLinks.length) {
        console.error(`[自動播放] 指定的開始索引 ${startIndex} 超出範圍 (${allLinks.length} 個連結)。`);
        return;
      }
      linksToProcess = allLinks.slice(startIndex);
      totalLinks = linksToProcess.length; // 總數是切片後的長度
      currentLinkIndex = 0; // 從切片後的列表的 0 開始
      isProcessing = true;
      isPaused = false;
      console.log(`[自動播放] 開始新的播放流程,從全局索引 ${startIndex} 開始,共 ${totalLinks} 項。`);
      startButton.style.display = 'none';
      pauseButton.style.display = 'inline-block';
      pauseButton.textContent = '暫停';
      stopButton.style.display = 'inline-block';
      statusDisplay.style.display = 'inline-block';
      updateStatusDisplay();
      processLinksSequentially(); // 開始處理
    } else if (isPaused) {
      isPaused = false;
      pauseButton.textContent = '暫停';
      updateStatusDisplay();
      console.log("[自動播放] 從暫停狀態繼續。");
      // 不需要重新調用 processLinksSequentially,它會在循環中自動繼續
    } else {
      console.warn("[自動播放][偵錯] 開始/繼續 按鈕被點擊,但 isProcessing 為 true 且 isPaused 為 false,不執行任何操作。");
    }
  }
  // pausePlayback
  function pausePlayback() {
    console.log(`[自動播放] 暫停/繼續 按鈕點擊。 isProcessing: ${isProcessing}, isPaused: ${isPaused}`);
    if (isProcessing) {
      if (!isPaused) {
        isPaused = true;
        pauseButton.textContent = '繼續';
        updateStatusDisplay();
        console.log("[自動播放] 執行暫停 (保持 Modal 開啟)。");
        if (currentSleepController) {
          currentSleepController.cancel('paused');
        }
      } else {
        // 從暫停狀態恢復,直接調用 startPlayback 處理恢復邏輯
        startPlayback();
      }
    } else {
      console.warn("[自動播放][偵錯] 暫停 按鈕被點擊,但 isProcessing 為 false,不執行任何操作。");
    }
  }
  // stopPlayback
  function stopPlayback() {
    console.log(`[自動播放] 停止 按鈕點擊。 isProcessing: ${isProcessing}, isPaused: ${isPaused}`);
    if (!isProcessing && !isPaused) {
      console.log("[自動播放][偵錯] 停止按鈕點擊,但腳本已停止,不執行操作。");
      return;
    }
    isProcessing = false;
    isPaused = false;
    if (currentSleepController) {
      currentSleepController.cancel('stopped');
    }
    console.log(`[自動播放][偵錯][停止前] currentIframe: ${currentIframe ? currentIframe.id : 'null'}, overlayElement: ${overlayElement ? 'exists' : 'null'}`);
    closeModal(); // 確保關閉 Modal
    resetTriggerButton();
    updateStatusDisplay();
  }
  // updateStatusDisplay
  function updateStatusDisplay() {
    if (statusDisplay) {
      if (isProcessing && linksToProcess.length > 0 && linksToProcess[currentLinkIndex]) {
        // 計算正確的全局進度 (基於 linksToProcess)
        const globalCurrentIndex = linksToProcess[currentLinkIndex].originalIndex; // 獲取當前項的原始全局索引 (相對於可見且相關的連結列表)
        // totalLinks 已經是當前批次的總數
        const currentBatchProgress = `(${currentLinkIndex + 1}/${totalLinks})`; // 這是當前批次的進度

        if (!isPaused) {
          statusDisplay.textContent = `處理中 ${currentBatchProgress}`; // 顯示批次進度
        } else {
          statusDisplay.textContent = `已暫停 ${currentBatchProgress}`; // 顯示批次進度
        }
      } else {
        statusDisplay.textContent = '';
      }
    }
  }
  // resetTriggerButton
  function resetTriggerButton() {
    console.log("[自動播放] 重置按鈕狀態。");
    isProcessing = false;
    isPaused = false;
    currentLinkIndex = 0;
    totalLinks = 0;
    linksToProcess = [];
    if (startButton && pauseButton && stopButton && statusDisplay) {
      startButton.disabled = false;
      startButton.style.display = 'inline-block';
      pauseButton.style.display = 'none';
      pauseButton.textContent = '暫停';
      stopButton.style.display = 'none';
      statusDisplay.style.display = 'none';
      statusDisplay.textContent = '';
    }
    if (rowHighlightTimeout) clearTimeout(rowHighlightTimeout);
    document.querySelectorAll(`.${ROW_HIGHLIGHT_CLASS_MAIN}`).forEach(el => { el.classList.remove(ROW_HIGHLIGHT_CLASS_MAIN); el.style.backgroundColor = ''; el.style.transition = ''; });
    closeModal(); // 確保關閉 Modal
  }
  // 表格列播放按鈕點擊處理
  async function handleRowPlayButtonClick(event) {
    const button = event.currentTarget;
    const rowIndex = parseInt(button.dataset.rowIndex, 10); // 這個 rowIndex 是全局索引 (相對於可見且相關的行)
    console.log(`[自動播放] 表格列播放按鈕點擊,全局列索引 (可見且相關): ${rowIndex}`);
    if (isNaN(rowIndex)) {
      console.error("[自動播放] 無法獲取有效的列索引。");
      return;
    }
    if (isProcessing && !isPaused) {
      console.log("[自動播放] 目前正在播放中,請先停止或等待完成才能從指定列開始。");
      alert("目前正在播放中,請先停止或等待完成才能從指定列開始。");
      return;
    }
    if (isProcessing && isPaused) {
      console.log("[自動播放] 偵測到處於暫停狀態,先停止當前流程...");
      stopPlayback();
      await sleep(100); // 等待停止完成
    }
    // 使用全局索引啟動 (相對於可見且相關的行)
    startPlayback(rowIndex);
  }
  // 確保 Font Awesome 加載
  function ensureFontAwesome() {
    const faLinkId = 'userscript-fontawesome-css';
    if (!document.getElementById(faLinkId)) {
      const link = document.createElement('link');
      link.id = faLinkId;
      link.rel = 'stylesheet';
      link.href = FONT_AWESOME_URL;
      link.integrity = FONT_AWESOME_INTEGRITY;
      link.crossOrigin = 'anonymous';
      link.referrerPolicy = 'no-referrer';
      document.head.appendChild(link);
      console.log('[自動播放] Font Awesome CSS 已注入。');
    }
  }

  // ** 輔助函數:注入或更新單個按鈕 **
  function injectOrUpdateButton(targetRow, targetTd, rowIndex) {
    const buttonClass = 'userscript-row-play-button';
    let button = targetRow.querySelector(`.${buttonClass}`); // 在目標行查找按鈕

    // ** 除錯:檢查 targetTd 是否有效 **
    if (!targetTd) {
      console.error(`[自動播放][按鈕注入] 錯誤:目標 td (行 ${rowIndex + 1}) 無效!`, targetRow);
      return;
    }

    if (button) {
      // 更新現有按鈕
      if (button.dataset.rowIndex !== String(rowIndex)) {
        console.log(`[自動播放][偵錯] 更新行 ${rowIndex + 1} 的按鈕索引。`);
        button.dataset.rowIndex = rowIndex;
        button.title = `從此列開始播放 (第 ${rowIndex + 1} 項)`;
      }
      // ** 除錯:確保按鈕在正確的 td 內 **
      if (button.parentElement !== targetTd) {
        console.warn(`[自動播放][按鈕注入] 按鈕 (行 ${rowIndex + 1}) 不在目標 td 內,正在移動...`);
        targetTd.insertBefore(button, targetTd.querySelector('span.fw-normal')?.nextSibling || targetTd.firstChild);
      }
    } else {
      // 添加新按鈕
      const playButtonBaseStyle = ` background-color: #28a745; color: white; border: none; border-radius: 4px; padding: 2px 6px; margin: 0 8px; cursor: pointer; font-size: 12px; line-height: 1; vertical-align: middle; transition: background-color 0.2s ease; `;
      button = document.createElement('button');
      button.className = buttonClass;
      button.style.cssText = playButtonBaseStyle;
      button.innerHTML = '<i class="fas fa-play"></i>';
      button.dataset.rowIndex = rowIndex;
      button.title = `從此列開始播放 (第 ${rowIndex + 1} 項)`;
      button.addEventListener('click', handleRowPlayButtonClick);

      // 注入按鈕到目標 td
      const numberSpan = targetTd.querySelector('span.fw-normal');
      if (numberSpan && numberSpan.nextSibling) {
        targetTd.insertBefore(button, numberSpan.nextSibling);
      } else if (numberSpan) {
        targetTd.appendChild(button);
      } else {
        targetTd.insertBefore(button, targetTd.firstChild);
      }
      // console.log(`[自動播放][按鈕注入] 已為行 ${rowIndex + 1} 添加新按鈕。`);
    }
  }

  // ** 輔助函數:從行中移除按鈕 **
  function removeButtonFromRow(row) {
    const button = row.querySelector('.userscript-row-play-button');
    if (button) {
      button.remove();
    }
  }


  // 注入表格列播放按鈕
  function injectRowPlayButtons() {
    const visibleTables = getVisibleTables(); // ** 使用了更新後的選擇器 **
    if (visibleTables.length === 0) {
      console.log("[自動播放][injectRowPlayButtons] 未找到任何當前可見的結果表格,無法注入列播放按鈕。");
      return;
    }
    // console.log(`[自動播放] 找到 ${visibleTables.length} 個當前可見的結果表格。`);

    // 添加懸停樣式
    const playButtonHoverStyle = `.userscript-row-play-button:hover { background-color: #218838 !important; }`;
    GM_addStyle(playButtonHoverStyle);

    // ** 修正:正確組合移除按鈕的選擇器 **
    const buttonClass = 'userscript-row-play-button';
    const containerSelectors = TABLE_CONTAINER_SELECTOR.split(',').map(s => s.trim());
    const removeSelectorParts = containerSelectors.map(sel => `${sel} > table .${buttonClass}`);
    const removeSelector = removeSelectorParts.join(', ');

    console.log(`[自動播放][injectRowPlayButtons] 準備移除舊按鈕,使用修正後的選擇器: "${removeSelector}"`);
    const buttonsToRemove = document.querySelectorAll(removeSelector);
    console.log(`[自動播放][injectRowPlayButtons] 找到 ${buttonsToRemove.length} 個待移除的舊按鈕。`);
    buttonsToRemove.forEach(btn => btn.remove());
    console.log(`[自動播放][injectRowPlayButtons] 已移除所有舊的行播放按鈕。`);

    let globalRowIndex = 0; // ** 維護一個基於可見且相關行的全局行索引 **

    visibleTables.forEach((table, tableIndex) => {
      const isWideTable = table.matches(WIDE_TABLE_SELECTOR);
      const isNarrowTable = table.matches(NARROW_TABLE_SELECTOR);
      const rows = table.querySelectorAll('tbody tr');
      const tableId = `可見表格 ${tableIndex + 1} (${isWideTable ? '寬' : isNarrowTable ? '窄' : '未知'})`; // 用於日誌
      // console.log(`[自動播放][按鈕注入] ${tableId} 找到 ${rows.length} 行。`); // 可能過於頻繁

      if (isWideTable) {
        rows.forEach((row, rowIndexInTable) => {
          const firstTd = row.querySelector('td:first-of-type');
          if (firstTd && firstTd.querySelector('span.fw-normal')) {
            // 寬螢幕:按鈕注入此行
            // console.log(`[自動播放][按鈕注入][寬] ${tableId} - 行 ${rowIndexInTable + 1}: 找到標記,注入按鈕 (全局索引 ${globalRowIndex})。`);
            injectOrUpdateButton(row, firstTd, globalRowIndex);
            globalRowIndex++;
          } else {
            // console.log(`[自動播放][按鈕注入][寬] ${tableId} - 行 ${rowIndexInTable + 1}: 未找到標記,跳過。`);
          }
        });
      } else if (isNarrowTable) {
        // console.log(`[自動播放][按鈕注入][窄] ${tableId}: 開始檢查...`);
        if (rows.length >= 1) { // 至少需要一行來放標記和按鈕
          const firstRow = rows[0];
          const firstRowTd = firstRow.querySelector('td:first-of-type');
          const hasMarker = firstRowTd && firstRowTd.querySelector('span.fw-normal');
          // console.log(`[自動播放][按鈕注入][窄] ${tableId} - 檢查第一行: ${firstRow ? '存在' : '不存在'}, 檢查第一行 td: ${firstRowTd ? '存在' : '不存在'}, 找到標記: ${hasMarker ? '是' : '否'}`);

          if (hasMarker) {
            // 窄螢幕:按鈕注入第一行 (有標記的行)
            // 但需要檢查第二行是否有連結,才算是有效的項目
            let isValidNarrowEntry = false;
            let linkFoundInSecondRow = false;
            if (rows.length >= 2) {
              const secondRow = rows[1];
              const secondRowTd = secondRow.querySelector('td:first-of-type');
              // console.log(`[自動播放][按鈕注入][窄] ${tableId} - 檢查第二行: ${secondRow ? '存在' : '不存在'}, 檢查第二行 td: ${secondRowTd ? '存在' : '不存在'}`);
              if (secondRowTd) {
                // ** 修改:在第二行的 td 中尋找連結 **
                const linkElement = secondRowTd.querySelector(getLinkSelector());
                linkFoundInSecondRow = !!linkElement; // 轉換為布林值
                // console.log(`[自動播放][按鈕注入][窄] ${tableId} - 在第二行 td 中查找連結 (${getLinkSelector()}): ${linkFoundInSecondRow ? '找到' : '未找到'}`);
                if (linkFoundInSecondRow) {
                  isValidNarrowEntry = true;
                }
              }
            } else {
              // console.log(`[自動播放][按鈕注入][窄] ${tableId}: 行數不足 (< 2),無法檢查第二行連結。`);
            }

            // console.log(`[自動播放][按鈕注入][窄] ${tableId} - isValidNarrowEntry: ${isValidNarrowEntry}`);
            if (isValidNarrowEntry) {
              // console.log(`[自動播放][按鈕注入][窄] ${tableId} - 條件滿足,注入按鈕到第一行 (全局索引 ${globalRowIndex})。`);
              injectOrUpdateButton(firstRow, firstRowTd, globalRowIndex);
              globalRowIndex++;
            } else {
              // console.log(`[自動播放][按鈕注入][窄] ${tableId} - 第一行有標記,但第二行無有效連結或不存在,不添加按鈕。`);
            }
          } else {
            // console.log(`[自動播放][按鈕注入][窄] ${tableId} - 第一行未找到標記,不處理此表格。`);
          }
        } else {
          // console.log(`[自動播放][按鈕注入][窄] ${tableId}: 行數為 0,跳過。`);
        }
      } else {
        // console.warn(`[自動播放][按鈕注入] ${tableId} 類型未知,跳過按鈕注入。`);
      }
    });
    console.log(`[自動播放][injectRowPlayButtons] 已處理 ${globalRowIndex} 個有效項目,注入或更新播放按鈕。`);
  }

  // 添加觸發按鈕
  function addTriggerButton() {
    if (document.getElementById('auto-play-controls-container')) return;
    const buttonContainer = document.createElement('div');
    buttonContainer.id = 'auto-play-controls-container';
    buttonContainer.style.position = 'fixed';
    buttonContainer.style.top = '10px';
    buttonContainer.style.left = '10px';
    buttonContainer.style.zIndex = '10001';
    buttonContainer.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
    buttonContainer.style.padding = '5px 10px';
    buttonContainer.style.borderRadius = '5px';
    buttonContainer.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
    const buttonStyle = `padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; margin-right: 5px; transition: background-color 0.2s ease;`;
    startButton = document.createElement('button');
    startButton.id = 'auto-play-start-button';
    startButton.textContent = '開始播放全部';
    startButton.style.cssText = buttonStyle;
    startButton.style.backgroundColor = '#28a745';
    startButton.style.color = 'white';
    startButton.addEventListener('click', () => startPlayback(0));
    buttonContainer.appendChild(startButton);
    pauseButton = document.createElement('button');
    pauseButton.id = 'auto-play-pause-button';
    pauseButton.textContent = '暫停';
    pauseButton.style.cssText = buttonStyle;
    pauseButton.style.backgroundColor = '#ffc107';
    pauseButton.style.color = 'black';
    pauseButton.style.display = 'none';
    pauseButton.addEventListener('click', pausePlayback);
    buttonContainer.appendChild(pauseButton);
    stopButton = document.createElement('button');
    stopButton.id = 'auto-play-stop-button';
    stopButton.textContent = '停止';
    stopButton.style.cssText = buttonStyle;
    stopButton.style.backgroundColor = '#dc3545';
    stopButton.style.color = 'white';
    stopButton.style.display = 'none';
    stopButton.addEventListener('click', stopPlayback);
    buttonContainer.appendChild(stopButton);
    statusDisplay = document.createElement('span');
    statusDisplay.id = 'auto-play-status';
    statusDisplay.style.display = 'none';
    statusDisplay.style.marginLeft = '10px';
    statusDisplay.style.fontSize = '14px';
    statusDisplay.style.verticalAlign = 'middle';
    buttonContainer.appendChild(statusDisplay);
    document.body.appendChild(buttonContainer);
    GM_addStyle(`#auto-play-controls-container button:disabled { opacity: 0.65; cursor: not-allowed; } #auto-play-start-button:hover:not(:disabled) { background-color: #218838 !important; } #auto-play-pause-button:hover:not(:disabled) { background-color: #e0a800 !important; } #auto-play-stop-button:hover:not(:disabled) { background-color: #c82333 !important; }`);
  }

  // 輔助函數,獲取當前應使用的連結選擇器
  function getLinkSelector() {
    const currentUrl = window.location.href;
    if (currentUrl.includes('/zh-hant/')) {
      return 'a[href^="/zh-hant/su/"]';
    } else {
      return 'a[href^="/und-hani/su/"]';
    }
  }

  // 初始化
  function initialize() {
    if (window.autoPlayerInitialized) return;
    window.autoPlayerInitialized = true;
    console.log("[自動播放] 初始化腳本 v4.19 ..."); // 更新版本號
    ensureFontAwesome();
    addTriggerButton();
    // 初始注入按鈕
    setTimeout(injectRowPlayButtons, 1000);

    // ** 修改:改用 ResizeObserver 監聽 RWD 變化 **
    try {
      const resizeObserver = new ResizeObserver(entries => {
        // 使用 debounce 避免短時間內重複觸發
        clearTimeout(resizeDebounceTimeout);
        resizeDebounceTimeout = setTimeout(() => {
          console.log("[自動播放][ResizeObserver] Debounced: 偵測到尺寸變化,重新注入按鈕並嘗試捲動...");
          injectRowPlayButtons(); // ** 使用了更新後的選擇器 **
          // ** 修改:調用新的查找函數來捲動 **
          const currentUrl = linksToProcess[currentLinkIndex]?.url;
          if (currentUrl) {
            const elementToScroll = findElementForLink(currentUrl); // ** 使用了更新後的選擇器 **
            if (elementToScroll) {
              console.log("[自動播放][ResizeObserver] 找到元素,執行捲動:", elementToScroll);
              elementToScroll.scrollIntoView({ behavior: 'smooth', block: 'center' }); // 改回 center
            } else {
              console.warn("[自動播放][ResizeObserver] 未找到元素進行捲動:", currentUrl);
            }
          } else {
            console.log("[自動播放][ResizeObserver] 沒有當前連結 URL,不執行捲動。");
          }
        }, RESIZE_DEBOUNCE_MS);
      });

      // 監聽 body 尺寸變化,這通常能反映視窗大小變化觸發的 RWD
      resizeObserver.observe(document.body);
      console.log("[自動播放] 已啟動 ResizeObserver 監聽 document.body 變化。");

    } catch (e) {
      console.error("[自動播放] 無法啟動 ResizeObserver:", e);
      // 作為後備,可以考慮監聽 window 的 resize 事件,但 ResizeObserver 通常更好
      // window.addEventListener('resize', () => { ... debounce logic ... injectRowPlayButtons(); ... find and scroll ... });
    }


    const urlParams = new URLSearchParams(window.location.search);
    if (urlParams.has(AUTOPLAY_PARAM)) {
      console.log(`[自動播放] 檢測到 URL 參數 "${AUTOPLAY_PARAM}",準備自動啟動...`);
      // 從 URL 中移除 autoplay 參數,避免刷新頁面時再次觸發
      const newUrl = new URL(window.location.href);
      newUrl.searchParams.delete(AUTOPLAY_PARAM);
      history.replaceState(null, '', newUrl.toString());

      let elapsedTime = 0;
      const waitForTableAndStart = () => {
        console.log("[自動播放][等待] 檢查可見表格和有效連結是否存在...");
        // ** 檢查邏輯以適應不同表格結構 **
        const linkSelector = getLinkSelector();
        const visibleTables = getVisibleTables(); // ** 使用了更新後的選擇器 **
        let linksExist = false;

        visibleTables.forEach(table => {
          if (linksExist) return; // 如果已找到,提前退出

          const isWideTable = table.matches(WIDE_TABLE_SELECTOR);
          const isNarrowTable = table.matches(NARROW_TABLE_SELECTOR);
          const rows = table.querySelectorAll('tbody tr');

          if (isWideTable) {
            linksExist = Array.from(rows).some(row => {
              const firstTd = row.querySelector('td:first-of-type');
              return firstTd && firstTd.querySelector('span.fw-normal') && row.querySelector(linkSelector);
            });
          } else if (isNarrowTable && rows.length >= 2) {
            const firstRowTd = rows[0].querySelector('td:first-of-type');
            const secondRowTd = rows[1].querySelector('td:first-of-type');
            // ** 修改:在第二行的 td 中尋找連結 **
            linksExist = firstRowTd && firstRowTd.querySelector('span.fw-normal') &&
              secondRowTd && secondRowTd.querySelector(linkSelector);
          }
        });


        if (linksExist) {
          console.log("[自動播放][等待] 可見表格和有效連結已找到,延遲後啟動播放...");
          // ** 修改:確保行內播放按鈕已注入完成後再啟動 **
          setTimeout(() => {
            console.log("[自動播放] 重新注入/更新行內播放按鈕以確保索引正確...");
            injectRowPlayButtons(); // ** 使用了更新後的選擇器 **
            setTimeout(() => {
              console.log("[自動播放] 自動啟動播放流程...");
              startPlayback(0);
            }, 300); // 短暫延遲後啟動
          }, 500); // 確保 inject 有足夠時間
        } else {
          elapsedTime += AUTO_START_CHECK_INTERVAL_MS;
          if (elapsedTime >= AUTO_START_MAX_WAIT_MS) {
            console.error("[自動播放][等待] 等待可見表格和有效連結超時。自動播放失敗。");
            alert("自動播放失敗:等待表格內容載入超時。");
          } else {
            console.log(`[自動播放][等待] 可見表格或有效連結未就緒,${AUTO_START_CHECK_INTERVAL_MS}ms 後重試...`);
            setTimeout(waitForTableAndStart, AUTO_START_CHECK_INTERVAL_MS);
          }
        }
      };
      setTimeout(waitForTableAndStart, 500); // 初始等待
    }
  }

  // --- 確保 DOM 加載完成後執行 ---
  if (document.readyState === 'complete' || document.readyState === 'interactive') {
    setTimeout(initialize, 0);
  } else {
    document.addEventListener('DOMContentLoaded', initialize);
  }

})();