cela-auto

中国干部网络学院自动学习脚本,支持浦东分院、企业分院、党校分院,模块化架构

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

Advertisement:

// ==UserScript==
// @name         cela-auto
// @version      4.4.0
// @description  中国干部网络学院自动学习脚本,支持浦东分院、企业分院、党校分院,模块化架构
// @author       Moker32
// @license      GPL-3.0-or-later
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @match        *://cela.e-celap.cn/*
// @match        *://pudong.e-celap.cn/*
// @match        *://pd.cela.cn/*
// @match        *://*.e-celap.cn/*
// @match        *://www.cela.gov.cn/*
// @match        *://cela.gwypx.com.cn/*
// @match        *://cela.cbead.cn/*
// @connect      cela.e-celap.cn
// @connect      pudong.e-celap.cn
// @connect      pd.cela.cn
// @connect      cela.gwypx.com.cn
// @connect      cela.cbead.cn
// @connect      www.cela.gov.cn
// @connect      zpyapi.shsets.com
// @run-at       document-idle
// @namespace https://github.com/Moker32/
// ==/UserScript==
(function(exports) {
    "use strict";
    const CONSTANTS = {
        EVENTS: {
            LOG: "log",
            STATUS_UPDATE: "statusUpdate",
            PROGRESS_UPDATE: "progressUpdate",
            STATISTICS_UPDATE: "statisticsUpdate",
            LEARNING_START: "learningStart",
            LEARNING_STOP: "learningStop",
            COURSE_START: "courseStart",
            COURSE_COMPLETE: "courseComplete",
            COURSE_SKIP: "courseSkip",
            COURSE_ERROR: "courseError",
            PROGRESS_REPORT: "progressReport",
            PROGRESS_SUCCESS: "progressSuccess",
            PROGRESS_ERROR: "progressError",
            ERROR: "error",
            FATAL_ERROR: "fatalError"
        },
        LEARNING_STATES: {
            IDLE: "idle",
            PREPARING: "preparing",
            LEARNING: "learning",
            COMPLETED: "completed",
            FAILED: "failed",
            SKIPPED: "skipped"
        },
        WAITING_FOR_USER: "WAITING_FOR_USER",
        SELECTORS: {
            PANEL: "#api-learner-panel",
            STATUS_LABEL: "#learner-status",
            PROGRESS_INNER: "#learner-progress-inner",
            TOGGLE_BTN: "#toggle-learning-btn",
            LOG_CONTAINER: "#api-learner-panel .log-container",
            STAT_TOTAL: "#stat-total",
            STAT_COMPLETED: "#stat-completed",
            STAT_LEARNED: "#stat-learned",
            STAT_FAILED: "#stat-failed",
            STAT_SKIPPED: "#stat-skipped",
            APP: "#app"
        },
        STORAGE_KEYS: {
            TOKEN: "token",
            AUTH_TOKEN: "authToken",
            ACCESS_TOKEN: "access_token",
            USER_ID: "userId",
            USER_ID_ALT: "user_id"
        },
        COURSE_SELECTORS: [ ".dsf-many-schedule-course-list-row", ".dsf_nc_pd_special_item", '[class*="course"]', "[data-course]", ".course-item", ".lesson-item", ".el-card", ".el-card__body", ".course-card", ".course-box", ".nc-course-item", ".study-item", ".learn-item", '[class*="item"]', '[class*="card"]', "[data-id]", ".pudong-course", ".pd-course", ".dsf-course", ".dsjy_card", ".item_content", ".class-item-desc" ],
        COOKIE_PATTERNS: {
            USER_ID: /userId=([^;]+)/,
            TOKEN: /token=([^;]+)/,
            P_PARAM: /_p=([^;]+)/
        },
        TIME_FORMATS: {
            DEFAULT_DURATION: 1800
        },
        UI_LIMITS: {
            MAX_LOG_ENTRIES: 50,
            LOG_FLUSH_DELAY: 100
        },
        ENVIRONMENTS: {
            PORTAL: {
                name: "中央门户",
                hostnames: [ "www.cela.gov.cn" ],
                pathPatterns: [ "/home" ],
                features: {
                    type: "traditional",
                    framework: "jquery",
                    courseContainer: "#courseCon",
                    branchNavigation: "#branchCon"
                },
                supported: false,
                reason: "门户网站仅用于信息展示,不支持自动学习"
            },
            PUDONG: {
                name: "浦东分院",
                hostnames: [ "pudong.e-celap.cn", "pd.cela.cn", "cela.e-celap.cn" ],
                pathPatterns: [ "/pc/nc/page", "/page:pd" ],
                features: {
                    type: "spa",
                    framework: "vue",
                    router: "hash",
                    apiBase: "auto",
                    ssoParam: "cela_sso_logged"
                },
                supported: true,
                variants: {
                    MAIN: "cela.e-celap.cn",
                    SUBDOMAIN: "pudong.e-celap.cn",
                    ALIAS: "pd.cela.cn"
                }
            },
            GWYPX: {
                name: "党校分院",
                hostnames: [ "cela.gwypx.com.cn" ],
                pathPatterns: [ "/pcPage/index", "/pcPage/commend/coursedetail" ],
                features: {
                    type: "spa",
                    framework: "vue",
                    router: "history",
                    apiBase: "auto",
                    siteMeta: "fosung"
                },
                supported: true,
                variants: {
                    MAIN: "cela.gwypx.com.cn"
                }
            },
            CBEAD: {
                name: "企业分院",
                hostnames: [ "cela.cbead.cn" ],
                pathPatterns: [ "/home", "/study", "/train-new" ],
                features: {
                    type: "spa",
                    framework: "vue",
                    router: "hash",
                    apiBase: "auto",
                    ssoParam: "cela_sso_logged"
                },
                supported: true,
                variants: {
                    MAIN: "cela.cbead.cn"
                }
            }
        },
        PAGE_CONFIG: {
            PUDONG: {
                PLAYER: {
                    path: "coursePlayer",
                    type: "player",
                    whitelist: true,
                    dom: [ "#coursePlayer", ".course-player", ".pd_course_pla" ],
                    actions: [ "report_progress" ]
                },
                COLUMN: {
                    path: [ "zgpdyxkc", "zgpdyxkczl", "specialcolumn", "specialColumn", "specialdetail", "specialDetail", "channelDetail", "pdchanel", "pdchanel/specialdetail", "pdchanel/pdzq" ],
                    type: "column",
                    whitelist: true,
                    actions: [ "scan_courses" ]
                },
                INDEX: {
                    path: "/pc/nc/pagehome/index",
                    type: "index",
                    whitelist: true,
                    actions: [ "scan_courses" ]
                },
                COURSE_LIST: {
                    path: "pagecourse/courseList",
                    type: "course_list",
                    whitelist: true,
                    actions: [ "scan_courses", "batch_learn" ]
                }
            },
            GWYPX: {
                PLAYER: {
                    path: "/pcPage/commend/coursedetail",
                    type: "player",
                    whitelist: true,
                    dom: [ "video.vjs-tech", ".prism-player", ".aliplayer-container" ],
                    actions: [ "report_progress" ]
                },
                CENTER: {
                    path: "/pcPage/personalCenter",
                    type: "personal_center",
                    whitelist: true,
                    actions: [ "batch_learn", "scan_courses" ]
                },
                INDEX: {
                    path: "/pcPage/index",
                    type: "index",
                    whitelist: false,
                    actions: [ "scan_courses" ]
                },
                SUBJECT_COLUMN_DETAIL: {
                    path: "/pcPage/subjectColumnDetail",
                    type: "subject_column_detail",
                    whitelist: true,
                    dom: [ ".course-list" ],
                    actions: [ "scan_courses", "batch_learn" ]
                },
                SUBJECT_DETAIL: {
                    path: "/pcPage/subjectDetail",
                    type: "subject_detail",
                    whitelist: true,
                    dom: [ ".course-list" ],
                    actions: [ "scan_courses", "batch_learn" ]
                },
                COMMEND_INDEX: {
                    path: "/pcPage/commendIndex",
                    type: "commend_index",
                    whitelist: true,
                    actions: [ "scan_courses", "batch_learn" ]
                }
            },
            CBEAD: {
                PLAYER: {
                    path: "study/course/detail",
                    type: "player",
                    whitelist: true,
                    dom: [ ".player-content", ".new-global-height", "video" ],
                    actions: [ "report_progress" ]
                },
                BRANCH_LIST: {
                    path: "branch-list-v",
                    type: "branch_list",
                    whitelist: true,
                    actions: [ "scan_courses" ]
                },
                COLUMN: {
                    path: "branch-list-v",
                    type: "column",
                    whitelist: true,
                    actions: [ "scan_courses" ]
                },
                TRAIN_NEW: {
                    path: "train-new/class-detail",
                    type: "train_new",
                    whitelist: true,
                    actions: [ "scan_courses" ]
                },
                HOME_V: {
                    path: "home-v",
                    type: "home_v",
                    whitelist: false,
                    actions: [ "scan_courses" ]
                },
                CENTER: {
                    path: "center/my/course",
                    type: "center",
                    whitelist: false,
                    actions: [ "scan_courses" ]
                }
            }
        },
        STATUS_DISPLAY: Object.freeze({
            completed: Object.freeze({
                icon: "✅",
                text: "已完成"
            }),
            in_progress: Object.freeze({
                icon: "📖",
                text: "学习中"
            }),
            not_started: Object.freeze({
                icon: "📝",
                text: "未开始"
            })
        })
    };
    const EventBus = {
        events: {},
        subscribe(event, listener) {
            if (!this.events[event]) {
                this.events[event] = [];
            }
            this.events[event].push(listener);
            return () => {
                const index = this.events[event].indexOf(listener);
                if (index > -1) {
                    this.events[event].splice(index, 1);
                }
            };
        },
        publish(event, data) {
            if (!this.events[event]) return;
            this.events[event].forEach(listener => {
                try {
                    listener(data);
                } catch (error) {
                    console.error(`EventBus error in ${event}:`, error);
                }
            });
        },
        once(event, listener) {
            const unsubscribe = this.subscribe(event, data => {
                unsubscribe();
                listener(data);
            });
            return unsubscribe;
        }
    };
    const Utils = {
        publishStats(stats) {
            EventBus.publish(CONSTANTS.EVENTS.STATISTICS_UPDATE, {
                total: stats.total,
                completed: stats.completed ?? stats.completedCourses,
                learned: stats.learned ?? stats.completedCourses,
                failed: stats.failed ?? stats.failedCourses,
                skipped: stats.skipped ?? stats.skippedCourses
            });
        },
        formatTime(seconds) {
            if (!seconds || seconds < 0) return "00:00:00";
            const hours = Math.floor(seconds / 3600);
            const minutes = Math.floor(seconds % 3600 / 60);
            const secs = seconds % 60;
            return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
        },
        parseTimeToSeconds(timeStr) {
            try {
                if (!timeStr) return 0;
                const parts = timeStr.split(":").map(part => parseInt(part, 10));
                if (parts.length === 3 && !parts.some(isNaN)) {
                    return parts[0] * 3600 + parts[1] * 60 + parts[2];
                }
                return 0;
            } catch {
                return 0;
            }
        },
        parseDuration(durationStr) {
            if (!durationStr || typeof durationStr !== "string") {
                return CONSTANTS.TIME_FORMATS.DEFAULT_DURATION;
            }
            return this.parseTimeToSeconds(durationStr) || CONSTANTS.TIME_FORMATS.DEFAULT_DURATION;
        }
    };
    var utils = Object.freeze({
        __proto__: null,
        Utils: Utils
    });
    const ServiceLocator = {
        services: {},
        register: function(name, service) {
            if (!name || typeof name !== "string") {
                throw new Error("ServiceLocator.register: name must be a non-empty string");
            }
            if (!service || typeof service !== "object") {
                throw new Error(`ServiceLocator.register: service must be an object (got: ${typeof service})`);
            }
            this.services[name] = service;
        },
        get: function(name) {
            return this.services[name] || null;
        },
        has: function(name) {
            return name in this.services;
        }
    };
    const ServiceNames = Object.freeze({
        API: "api",
        LEARNER: "learner",
        UI: "ui",
        CONFIG: "config"
    });
    const LearningState = {
        _failed: false,
        _reason: null,
        markFailed(reason) {
            this._failed = true;
            this._reason = reason;
            console.error(`[LearningState] 🚨 课程已标记为失败: ${reason}`);
        },
        reset() {
            this._failed = false;
            this._reason = null;
            console.log("[LearningState] 🔄 学习状态已重置");
        },
        isFailed() {
            return this._failed;
        },
        getFailureReason() {
            return this._reason;
        },
        getState() {
            return {
                failed: this._failed,
                reason: this._reason
            };
        }
    };
    const Settings = {
        defaultConfig: {
            LEARNING_STRATEGY: "default",
            SKIP_COMPLETED_COURSES: true,
            STUDY_RECORD_ENABLED: true,
            FALLBACK_MODE: false,
            DEBUG_MODE: false,
            HEARTBEAT_INTERVAL: 10,
            COMPLETION_THRESHOLD: 95,
            MAX_RETRY_ATTEMPTS: 10,
            RETRY_DELAY: 3e3,
            COURSE_COMPLETION_DELAY: 5,
            PUDONG_MODE: false,
            PUDONG_API_BASE: "",
            GWYPX_MODE: false,
            GWYPX_API_BASE: "",
            CBEAD_MODE: false,
            CBEAD_API_BASE: "",
            IS_PORTAL: false,
            UNSUPPORTED_BRANCH: "",
            SUPER_FAST_MODE: true,
            FAST_LEARNING_MODE: true,
            WARNING_BATCH_LEARNING: true
        },
        config: {},
        load() {
            this.config = {
                ...this.defaultConfig
            };
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: "✅ 使用固定配置:默认学习模式",
                type: "success"
            });
        },
        get(key) {
            return this.config[key];
        },
        set(key, value) {
            this.config[key] = value;
        }
    };
    const CONFIG = new Proxy(Settings.config, {
        get(target, prop) {
            return Settings.get(prop) ?? target[prop];
        },
        set(target, prop, value) {
            Settings.set(prop, value);
            target[prop] = value;
            return true;
        }
    });
    const RequestQueue = {
        queue: [],
        activeCount: 0,
        maxConcurrent: 2,
        requestGap: 1e3,
        add(fn) {
            return new Promise((resolve, reject) => {
                this.queue.push({
                    fn: fn,
                    resolve: resolve,
                    reject: reject
                });
                this.process();
            });
        },
        async process() {
            if (this.activeCount >= this.maxConcurrent || this.queue.length === 0) return;
            this.activeCount++;
            const {fn: fn, resolve: resolve, reject: reject} = this.queue.shift();
            try {
                const delay = this.requestGap + Math.random() * 500;
                if (delay > 0) await new Promise(r => setTimeout(r, delay));
                const result = await fn();
                resolve(result);
            } catch (e) {
                reject(e);
            } finally {
                this.activeCount--;
                setTimeout(() => this.process(), 100);
            }
        }
    };
    const SIGN_UP_PATTERNS = [ "我要报名", "立即报名", "立即加入", "报名学习", "加入学习", "是", "确定", "确认" ];
    function findSignUpButton() {
        const xpathConditions = SIGN_UP_PATTERNS.map(pattern => `contains(., "${pattern}")`).join(" or ");
        const xpath = `//button[${xpathConditions}] | //a[${xpathConditions}]`;
        const result = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
        if (result.snapshotLength > 0) {
            console.log(`[DOMHelper] ✅ 通过 XPath 找到报名按钮,共 ${result.snapshotLength} 个`);
            return result.snapshotItem(0);
        }
        const selectors = [ "button", "a", ".btn", '[class*="button"]', '[class*="btn"]', '[role="button"]', ".el-button", ".ant-btn" ];
        for (const selector of selectors) {
            const elements = document.querySelectorAll(selector);
            for (const btn of elements) {
                const text = btn.textContent?.trim() || "";
                if (SIGN_UP_PATTERNS.some(pattern => text.includes(pattern))) {
                    console.log(`[DOMHelper] ✅ 通过选择器 "${selector}" 找到报名按钮`);
                    console.log(`[DOMHelper] 按钮文本: "${text}"`);
                    console.log(`[DOMHelper] 按钮标签: ${btn.tagName}, class: ${btn.className}`);
                    return btn;
                }
            }
        }
        console.log(`[DOMHelper] ⚠️ 未找到报名按钮,页面可能已报名`);
        return null;
    }
    function getSignUpButtonText(button) {
        return button?.textContent?.trim() || "报名按钮";
    }
    const MASK_SELECTORS = [ "#D339registerMask", '[id*="registerMask"]', '[class*="register-mask"]', ".el-message-box__wrapper", ".v-modal" ];
    const MASK_BUTTON_SELECTORS = [ ".register-img", '[class*="register-img"]', ".vjs-big-play-button", ".el-message-box__btns .el-button--primary" ];
    function clickMaskButton() {
        let clickedCount = 0;
        const clickDetails = [];
        const messageBox = document.querySelector(".el-message-box");
        if (messageBox) {
            const dialogButtons = messageBox.querySelectorAll('button, .el-button, [role="button"]');
            for (const btn of dialogButtons) {
                if (btn.offsetParent !== null) {
                    const text = btn.textContent?.trim() || "";
                    if (text === "是" || text === "确定" || text === "确认") {
                        btn.click();
                        clickedCount++;
                        console.log(`[DOMHelper] 🎯 点击弹窗按钮: "${text}"`);
                    }
                }
            }
            return {
                clicked: clickedCount,
                buttons: clickDetails
            };
        }
        for (const selector of MASK_BUTTON_SELECTORS) {
            const elements = document.querySelectorAll(selector);
            for (const el of elements) {
                if (el.offsetParent !== null) {
                    el.click();
                    clickedCount++;
                    console.log(`[DOMHelper] 鼠标模拟点击选择器匹配按钮: ${selector}`);
                }
            }
        }
        const allButtons = document.querySelectorAll('button, .el-button, [role="button"]');
        for (const btn of allButtons) {
            if (btn.offsetParent !== null) {
                const text = btn.textContent?.trim() || "";
                if (SIGN_UP_PATTERNS.some(pattern => text === pattern || text.includes(pattern))) {
                    if (text === "否" || text === "取消") continue;
                    btn.click();
                    clickedCount++;
                    console.log(`[DOMHelper] 鼠标模拟点击文本匹配按钮: "${text}"`);
                }
            }
        }
        return {
            clicked: clickedCount,
            buttons: clickDetails
        };
    }
    function detectMask() {
        const masks = [];
        for (const selector of MASK_SELECTORS) {
            const elements = document.querySelectorAll(selector);
            for (const el of elements) {
                if (el.offsetParent !== null || el.style.display !== "none") {
                    masks.push({
                        selector: selector,
                        tagName: el.tagName,
                        id: el.id,
                        className: el.className
                    });
                }
            }
        }
        const elMessages = document.querySelectorAll(".el-message-box__message");
        for (const msg of elMessages) {
            if (msg.textContent.includes("是否学习") || msg.textContent.includes("未学习")) {
                masks.push({
                    selector: "text:是否学习",
                    tagName: "DIV"
                });
            }
        }
        return {
            exists: masks.length > 0,
            masks: masks
        };
    }
    function startMaskObserver() {
        let isClicking = false;
        let stopped = false;
        const observer = new MutationObserver(mutations => {
            if (isClicking || stopped) return;
            let shouldCheck = false;
            for (const mutation of mutations) {
                if (mutation.addedNodes.length > 0) {
                    shouldCheck = true;
                    break;
                }
            }
            if (shouldCheck) {
                isClicking = true;
                setTimeout(() => {
                    if (stopped) return;
                    const mask = detectMask();
                    if (mask.exists) {
                        clickMaskButton();
                    }
                    setTimeout(() => {
                        isClicking = false;
                    }, 1e3);
                }, 300);
            }
        });
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
        console.log("[DOMHelper] 🛡️ 全局遮罩监听器已启动");
        const cleanup = () => {
            if (!stopped) {
                stopped = true;
                observer.disconnect();
                console.log("[DOMHelper] 🛑 遮罩监听器已自动清理(页面卸载)");
            }
        };
        window.addEventListener("beforeunload", cleanup);
        window.addEventListener("pagehide", cleanup);
        return {
            stop() {
                if (!stopped) {
                    stopped = true;
                    observer.disconnect();
                    window.removeEventListener("beforeunload", cleanup);
                    window.removeEventListener("pagehide", cleanup);
                    console.log("[DOMHelper] 🛑 遮罩监听器已手动停止");
                }
            },
            isStopped() {
                return stopped;
            }
        };
    }
    var domHelper = Object.freeze({
        __proto__: null,
        clickMaskButton: clickMaskButton,
        detectMask: detectMask,
        findSignUpButton: findSignUpButton,
        getSignUpButtonText: getSignUpButtonText,
        startMaskObserver: startMaskObserver
    });
    function getUI() {
        return typeof window !== "undefined" && window.UI ? window.UI : null;
    }
    function detectEnvironment() {
        CONFIG.PUDONG_MODE = false;
        CONFIG.GWYPX_MODE = false;
        CONFIG.CBEAD_MODE = false;
        CONFIG.IS_PORTAL = false;
        CONFIG.UNSUPPORTED_BRANCH = "";
        const hostname = window.location.hostname;
        const pathname = window.location.pathname;
        const hash = window.location.hash;
        const result = {
            currentEnv: null,
            hostname: hostname,
            pathname: pathname,
            hash: hash,
            confidence: 0,
            features: {}
        };
        for (const [envKey, envConfig] of Object.entries(CONSTANTS.ENVIRONMENTS)) {
            if (envConfig.hostnames.some(host => hostname === host || hostname.endsWith("." + host))) {
                result.currentEnv = envKey;
                result.confidence = 50;
                console.log(`🔍 域名匹配: ${envKey} (${envConfig.name})`);
                break;
            }
        }
        if (result.currentEnv) {
            const envConfig = CONSTANTS.ENVIRONMENTS[result.currentEnv];
            if (envConfig.pathPatterns) {
                const fullPath = pathname + hash;
                const matchesPath = envConfig.pathPatterns.some(pattern => fullPath.includes(pattern));
                if (matchesPath) {
                    result.confidence += 30;
                    console.log(`✅ 路径验证通过 (+30分)`);
                }
            }
        }
        if (result.currentEnv) {
            const envConfig = CONSTANTS.ENVIRONMENTS[result.currentEnv];
            if (envConfig.features) {
                if (envConfig.features.framework === "vue") {
                    if (document.querySelector("#app") || window.Vue) {
                        result.confidence += 15;
                        result.features.framework = "vue";
                        console.log(`✅ Vue框架特征检测 (+15分)`);
                    }
                }
                if (envConfig.features.framework === "jquery") {
                    if (window.jQuery && !document.querySelector("#app")) {
                        result.confidence += 15;
                        result.features.framework = "jquery";
                        console.log(`✅ jQuery框架特征检测 (+15分)`);
                    }
                }
                if (envConfig.features.courseContainer) {
                    const container = document.querySelector(envConfig.features.courseContainer);
                    if (container) {
                        result.confidence += 5;
                        console.log(`✅ 容器特征检测 (${envConfig.features.courseContainer}, +5分)`);
                    }
                }
                if (envConfig.features.siteMeta) {
                    const meta = document.querySelector(`meta[name="site"][content="${envConfig.features.siteMeta}"]`);
                    if (meta) {
                        result.confidence += 20;
                        console.log(`✅ Site Meta特征检测 (${envConfig.features.siteMeta}, +20分)`);
                    }
                }
            }
        }
        const envConfig = result.currentEnv ? CONSTANTS.ENVIRONMENTS[result.currentEnv] : null;
        if (result.currentEnv === "PORTAL") {
            CONFIG.IS_PORTAL = true;
            console.log("🏠 检测到中国干部网络学院门户页面");
        } else if (result.currentEnv === "PUDONG") {
            CONFIG.PUDONG_MODE = true;
            CONFIG.PUDONG_API_BASE = `https://${hostname}`;
            console.log("🏢 检测到浦东分院环境");
        } else if (result.currentEnv === "GWYPX") {
            CONFIG.GWYPX_MODE = true;
            CONFIG.GWYPX_API_BASE = `https://${hostname}`;
            console.log("🏢 检测到党校分院环境");
        } else if (result.currentEnv === "CBEAD") {
            CONFIG.CBEAD_MODE = true;
            CONFIG.CBEAD_API_BASE = `https://${hostname}`;
            console.log("🏢 检测到企业分院环境");
        }
        console.log(`🌐 环境检测完成: ${result.currentEnv || "UNKNOWN"} (置信度: ${result.confidence}%)`);
        console.log(`   - 域名: ${hostname}`);
        console.log(`   - 路径: ${pathname}`);
        console.log(`   - 特征: ${JSON.stringify(result.features)}`);
        EventBus.publish("environment:detected", {
            ...result,
            pudongMode: CONFIG.PUDONG_MODE,
            gwypxMode: CONFIG.GWYPX_MODE,
            isPortal: CONFIG.IS_PORTAL,
            unsupportedBranch: CONFIG.UNSUPPORTED_BRANCH
        });
        setTimeout(() => {
            const UI = getUI();
            if (!UI || !UI.setIncompatible) return;
            if (CONFIG.UNSUPPORTED_BRANCH && envConfig) {
                UI.setIncompatible(`⚠️ 当前检测到【${envConfig.name}】,${envConfig.reason}`);
            } else if (CONFIG.IS_PORTAL && envConfig) {
                UI.setIncompatible(`🏠 ${envConfig.reason}`);
            } else if (!CONFIG.PUDONG_MODE && !CONFIG.IS_PORTAL && !CONFIG.CBEAD_MODE && !CONFIG.GWYPX_MODE) {
                UI.setIncompatible("🔍 当前域名未被识别为受支持的学习环境,脚本已停止加载。");
            }
        }, 100);
        return result;
    }
    function _matchesPath(urlPath, pattern) {
        return urlPath.toLowerCase().includes(pattern.toLowerCase());
    }
    function detectPageType(options) {
        const {url: url = window.location.href, pathPatterns: pathPatterns, pageTypes: pageTypes, domSelectors: domSelectors = null, domMatchType: domMatchType = "PLAYER"} = options;
        let urlPath = "";
        const hashIndex = url.indexOf("#");
        if (hashIndex !== -1) {
            urlPath = url.substring(hashIndex);
        } else {
            try {
                urlPath = new URL(url).pathname;
            } catch {
                urlPath = url;
            }
        }
        for (const [type, pattern] of Object.entries(pathPatterns)) {
            if (Array.isArray(pattern)) {
                for (const p of pattern) {
                    if (_matchesPath(urlPath, p)) {
                        return pageTypes[type];
                    }
                }
            } else if (_matchesPath(urlPath, pattern)) {
                return pageTypes[type];
            }
        }
        if (domSelectors && domSelectors.length > 0) {
            for (const selector of domSelectors) {
                if (document.querySelector(selector)) {
                    return pageTypes[domMatchType] || domMatchType.toLowerCase();
                }
            }
        }
        return pageTypes.UNKNOWN || "unknown";
    }
    function createPageDetector(config) {
        const {pathPatterns: pathPatterns, pageTypes: pageTypes, domSelectors: domSelectors, domMatchType: domMatchType} = config;
        return function identifyPage() {
            return detectPageType({
                url: window.location.href,
                pathPatterns: pathPatterns,
                pageTypes: pageTypes,
                domSelectors: domSelectors,
                domMatchType: domMatchType
            });
        };
    }
    const pudongPages = CONSTANTS.PAGE_CONFIG.PUDONG;
    const PUDONG_CONSTANTS = {
        PATH_PATTERNS: Object.fromEntries(Object.entries(pudongPages).map(([k, v]) => [ k, v.path ])),
        PAGE_TYPES: {
            ...Object.fromEntries(Object.entries(pudongPages).map(([k, v]) => [ k, v.type ])),
            UNKNOWN: "unknown"
        },
        PAGE_TYPE_WHITELIST: Object.keys(pudongPages).filter(k => pudongPages[k].whitelist),
        COMPLETION_THRESHOLD: 80,
        SKIP_COMPLETED_COURSES: true
    };
    const Compliance = {
        enforce(url, data) {
            if (!data) return data;
            if (url.includes("/pulseSaveRecord") || url.includes("pulseSaveRecord")) {
                return this._enforcePulseRecord(data);
            }
            return data;
        },
        _enforcePulseRecord(data) {
            let params;
            let isString = typeof data === "string";
            if (isString) {
                params = new URLSearchParams(data);
            } else if (data instanceof FormData) {
                return data;
            } else {
                params = new URLSearchParams(data);
            }
            params.set("pulseTime", "10");
            params.set("pulseRate", "1");
            if (params.has("progress")) {
                const progress = parseInt(params.get("progress"));
                if (progress > 98) {
                    console.warn("[Compliance] ⚠️ 拦截异常进度上报:", progress, "-> 强制修正为 98");
                    params.set("progress", "98");
                }
            }
            return isString ? params.toString() : Object.fromEntries(params);
        }
    };
    const ApiCore = {
        _cachedToken: null,
        _tokenExpiry: null,
        _TOKEN_CACHE_DURATION: 5 * 60 * 1e3,
        abortController: null,
        getBaseUrl() {
            const config = ServiceLocator.get(ServiceNames.CONFIG);
            if (config?.CBEAD_MODE) {
                return config?.CBEAD_API_BASE || `https://${window.location.hostname}`;
            }
            if (config?.PUDONG_MODE) {
                return config?.PUDONG_API_BASE || `https://${window.location.hostname}`;
            }
            return config?.PUDONG_API_BASE || config?.CBEAD_API_BASE || `https://${window.location.hostname}`;
        },
        _prepareHeaders(customHeaders = {}, data = null) {
            const token = this._extractToken();
            const headers = {
                Accept: "application/json, text/plain, */*",
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
                "X-Requested-With": "XMLHttpRequest",
                Referer: window.location.href,
                Origin: this.getBaseUrl(),
                Cookie: document.cookie,
                ...customHeaders
            };
            if (!(data instanceof FormData)) {
                const config = ServiceLocator.get(ServiceNames.CONFIG);
                if (config?.GWYPX_MODE && data) {
                    headers["Content-Type"] = "application/json";
                } else if (typeof data === "string" && data.includes("=") && !data.startsWith("{")) {
                    headers["Content-Type"] = "application/x-www-form-urlencoded";
                } else if (data) {
                    headers["Content-Type"] = "application/json";
                }
            }
            if (token) {
                headers["Authorization"] = `Bearer ${token}`;
                headers["X-Auth-Token"] = token;
            }
            return headers;
        },
        _handleResponse(response, resolve, _reject) {
            if (response.status === 401) {
                this._cachedToken = null;
                this._log("Token 可能已过期 (401),清除缓存", "warn");
            }
            const config = ServiceLocator.get(ServiceNames.CONFIG);
            if (config?.DEBUG_MODE) {
                this._log(`${response.status} ${response.responseText?.substring(0, 100)}...`);
            }
            try {
                if (response.responseText && response.responseText.trim()) {
                    return resolve(JSON.parse(response.responseText));
                }
                if (response.status >= 200 && response.status < 300) {
                    return resolve({
                        code: response.status,
                        success: true,
                        message: "Success"
                    });
                }
                resolve({
                    status: response.status,
                    message: "Empty response"
                });
            } catch {
                const html = response.responseText || "";
                if (html.trim().startsWith("<")) {
                    if (html.includes("login") || html.includes("登录")) {
                        this._log("登录已失效,请重新登录", "error");
                        alert("cela学习助手:登录已失效,请刷新页面重新登录!");
                        const learner = ServiceLocator.get(ServiceNames.LEARNER);
                        if (learner) learner.stop();
                        EventBus.publish(CONSTANTS.EVENTS.LEARNING_STOP);
                    } else if (html.includes("verification") || html.includes("验证码") || html.includes("人机")) {
                        this._log("触发人机验证,请手动完成验证", "error");
                        alert('cela学习助手:触发人机验证!请在页面上完成验证后点击"开始学习"继续。');
                        const learner = ServiceLocator.get(ServiceNames.LEARNER);
                        if (learner) learner.stop();
                        EventBus.publish(CONSTANTS.EVENTS.LEARNING_STOP);
                    }
                    return resolve({
                        error: "HTML response received",
                        status: response.status,
                        isHtml: true
                    });
                }
                resolve({
                    status: response.status,
                    message: html || "Empty response",
                    success: response.status >= 200 && response.status < 300
                });
            }
        },
        async request(options) {
            return RequestQueue.add(() => new Promise((resolve, reject) => {
                if (this.abortController && this.abortController.signal.aborted) {
                    return reject(new DOMException("Aborted", "AbortError"));
                }
                if (options.data) {
                    options.data = Compliance.enforce(options.url, options.data);
                }
                const headers = this._prepareHeaders(options.headers, options.data);
                const config = ServiceLocator.get(ServiceNames.CONFIG);
                if (config?.DEBUG_MODE) {
                    this._log(`${options.method || "GET"} ${options.url}`);
                }
                const req = GM_xmlhttpRequest({
                    method: options.method || "GET",
                    url: options.url,
                    headers: headers,
                    data: options.data,
                    timeout: options.timeout || 3e4,
                    onload: res => this._handleResponse(res, resolve, reject),
                    onerror: err => {
                        this._log(`请求失败: ${err.message}`, "error");
                        reject(err);
                    },
                    ontimeout: () => {
                        this._log("请求超时", "error");
                        reject(new Error("请求超时"));
                    }
                });
                if (this.abortController) {
                    this.abortController.signal.addEventListener("abort", () => {
                        if (req.abort) req.abort();
                        reject(new DOMException("Aborted", "AbortError"));
                    });
                }
            }));
        },
        async get(url, options = {}) {
            return this.request({
                ...options,
                method: "GET",
                url: url
            });
        },
        async post(url, data, options = {}) {
            return this.request({
                ...options,
                method: "POST",
                url: url,
                data: data
            });
        },
        _setTokenCache(token) {
            this._cachedToken = token;
            this._tokenExpiry = Date.now() + this._TOKEN_CACHE_DURATION;
        },
        _isTokenCacheValid() {
            if (!this._cachedToken || !this._tokenExpiry) {
                return false;
            }
            return Date.now() < this._tokenExpiry;
        },
        _extractToken() {
            if (this._isTokenCacheValid()) {
                return this._cachedToken;
            }
            const sources = [ () => localStorage.getItem(CONSTANTS.STORAGE_KEYS.TOKEN), () => localStorage.getItem(CONSTANTS.STORAGE_KEYS.AUTH_TOKEN), () => localStorage.getItem(CONSTANTS.STORAGE_KEYS.ACCESS_TOKEN), () => sessionStorage.getItem(CONSTANTS.STORAGE_KEYS.TOKEN), () => sessionStorage.getItem(CONSTANTS.STORAGE_KEYS.AUTH_TOKEN), () => document.querySelector('meta[name="csrf-token"]')?.getAttribute("content"), () => window.token, () => window.authToken, () => {
                const match = document.cookie.match(CONSTANTS.COOKIE_PATTERNS.TOKEN);
                return match ? match[1] : null;
            } ];
            for (const source of sources) {
                try {
                    const token = source();
                    if (token && token.length > 10) {
                        this._setTokenCache(token);
                        this._log(`找到认证token: ${token.substring(0, 20)}... (缓存5分钟)`, "debug");
                        return token;
                    }
                } catch {}
            }
            this._log("未找到认证token", "debug");
            return null;
        },
        _log(message, level = "info") {
            const ui = ServiceLocator.get(ServiceNames.UI);
            if (ui) {
                ui.log(message, level);
            } else {
                console.log(`[ApiCore] ${message}`);
            }
        },
        setAbortController(controller) {
            this.abortController = controller;
        },
        clearTokenCache() {
            this._cachedToken = null;
            this._tokenExpiry = null;
        }
    };
    const CourseAdapter = {
        normalize(raw, source = "api") {
            return {
                id: raw.id || raw.businessId || raw.courseId,
                courseId: raw.courseId || raw.id || raw.businessId,
                dsUnitId: raw.dsUnitId || raw.unitId || (raw.unitOrder && raw.order ? `unit_${raw.unitOrder}_${raw.order}` : "unit_default"),
                title: raw.name || raw.title || raw.courseName || "未命名课程",
                courseName: raw.name || raw.title || raw.courseName || "未命名课程",
                teacher: raw.teacher || "",
                durationStr: raw.duration || raw.durationStr || raw.timeLength || "00:30:00",
                period: raw.period || 0,
                status: raw.status || "not_started",
                source: source
            };
        }
    };
    function debugLog(...args) {}
    const PUDONG_API_CONFIG = {
        baseUrl: null,
        endpoints: {
            GET_PLAY_TREND: "/inc/nc/course/play/getPlayTrend",
            GET_COURSE_LIST: "/inc/nc/class/course/list",
            GET_COURSE_LIST_OLD: "/inc/nc/course/list",
            PULSE_SAVE_RECORD: "/inc/nc/course/play/pulseSaveRecord",
            PULSE_SAVE_RECORD_ALT: "/api/player/pulse",
            GET_COURSEWARE_LIST: "/inc/nc/course/play/getPlayTrend",
            GET_COURSEWARE_LIST_ALT: "/inc/nc/course/play/getPlayInfoById",
            GET_PLAY_BASE: "/inc/nc/course/play/getPlayBase",
            GET_PLAY_RECORD: "/inc/nc/course/play/getPlayRecord"
        },
        getBaseUrl() {
            const config = ServiceLocator.get(ServiceNames.CONFIG);
            this.baseUrl = config?.PUDONG_API_BASE || `https://${window.location.hostname}`;
            return this.baseUrl;
        },
        getUrl(endpoint) {
            const baseUrl = this.getBaseUrl();
            const endpointPath = this.endpoints[endpoint] || endpoint;
            return baseUrl + endpointPath;
        }
    };
    function _buildUrl(endpoint, params = {}) {
        let url = PUDONG_API_CONFIG.getUrl(endpoint);
        const queryString = new URLSearchParams({
            ...params,
            _t: Date.now()
        }).toString();
        return `${url}?${queryString}`;
    }
    const PudongApi = {
        ...ApiCore,
        isSuccessResponse(result) {
            return result && (result.success === true || result.code === 200 || result.code === 2e4 || result.state === 2e4 || result.status === "success" || result.status === "ok" || result.code >= 200 && result.code < 300 || result.result === "success" || result.success === 1);
        },
        _validateResponse(response, context = "getPlayInfo") {
            if (!response) {
                return {
                    valid: false,
                    error: `[${context}] API 响应为空`
                };
            }
            if (!this.isSuccessResponse(response)) {
                const errorCode = response.code || response.status || "unknown";
                const errorMsg = response.message || response.msg || response.error || "未知错误";
                return {
                    valid: false,
                    error: `[${context}] API 调用失败: code=${errorCode}, message=${errorMsg}`
                };
            }
            if (!response.data) {
                return {
                    valid: false,
                    error: `[${context}] 响应 data 字段为空`
                };
            }
            return {
                valid: true
            };
        },
        async getPlayInfo(courseId, coursewareId = null, courseDuration = null) {
            const {Utils: Utils} = await Promise.resolve().then(function() {
                return utils;
            });
            try {
                debugLog(`[getPlayInfo] 获取课程 ${courseId} 的播放信息`);
                const response = await this.get(_buildUrl("GET_PLAY_TREND", {
                    courseId: courseId
                }));
                const validation = this._validateResponse(response, "getPlayInfo");
                if (!validation.valid) {
                    debugLog(validation.error);
                    return null;
                }
                const data = response.data;
                if (data.playTree !== undefined && data.playTree !== null) {
                    if (data.playTree.children !== undefined && data.playTree.children !== null) {
                        if (!Array.isArray(data.playTree.children)) {
                            debugLog("[getPlayInfo] playTree.children 不是数组");
                        }
                    }
                }
                if (data.locationSite !== undefined && data.locationSite !== null) {
                    if (typeof data.locationSite !== "object") {
                        debugLog("[getPlayInfo] locationSite 不是对象");
                    }
                }
                let videoId = null;
                let duration = 0;
                let lastLearnedTime = 0;
                let foundCoursewareId = coursewareId;
                if (coursewareId && data.playTree?.children && Array.isArray(data.playTree.children)) {
                    const target = data.playTree.children.find(c => String(c.id) === String(coursewareId));
                    if (target) {
                        videoId = target.id;
                        foundCoursewareId = target.id;
                        duration = target.sumDurationLong || 0;
                        lastLearnedTime = target.lastWatchPoint ? Utils.parseTimeToSeconds(target.lastWatchPoint) : 0;
                        debugLog(`成功匹配到课件: ${target.title}`);
                    } else {
                        debugLog(`[getPlayInfo] 未在 playTree 中找到课件: ${coursewareId}`);
                    }
                } else if (coursewareId) {
                    debugLog(`[getPlayInfo] playTree.children 无效,跳过课件匹配: courseId=${courseId}, coursewareId=${coursewareId}`);
                }
                if (!videoId && data.locationSite && typeof data.locationSite === "object") {
                    videoId = data.locationSite.id;
                    foundCoursewareId = data.locationSite.id;
                    duration = data.locationSite.sumDurationLong || 0;
                    lastLearnedTime = data.locationSite.lastWatchPoint ? Utils.parseTimeToSeconds(data.locationSite.lastWatchPoint) : 0;
                }
                if (duration === 0 && courseDuration) {
                    duration = Utils.parseDuration(courseDuration);
                }
                if (duration === 0) {
                    duration = CONSTANTS.TIME_FORMATS.DEFAULT_DURATION;
                }
                if (!videoId) {
                    videoId = `mock_video_${courseId}`;
                    debugLog(`[getPlayInfo] 无法获取真实 videoId,使用模拟ID: courseId=${courseId}`);
                }
                return {
                    courseId: courseId,
                    coursewareId: foundCoursewareId,
                    videoId: videoId,
                    duration: duration,
                    lastLearnedTime: lastLearnedTime,
                    playURL: `https://zpyapi.shsets.com/player/get?videoId=${videoId}`,
                    dataSource: videoId.startsWith("mock_") ? "fallback" : "api"
                };
            } catch (error) {
                debugLog(`[getPlayInfo] 出错: ${error.message}`);
                return null;
            }
        },
        async reportProgress(playInfo, currentTime) {
            const {Utils: Utils} = await Promise.resolve().then(function() {
                return utils;
            });
            const watchPoint = Utils.formatTime(currentTime);
            const progress = Math.round(currentTime / playInfo.duration * 100);
            const payload = new URLSearchParams({
                courseId: playInfo.courseId,
                coursewareId: playInfo.coursewareId || playInfo.videoId,
                videoId: playInfo.videoId || "",
                watchPoint: watchPoint,
                currentTime: currentTime,
                duration: playInfo.duration,
                progress: progress,
                pulseTime: 10,
                pulseRate: 1,
                _t: Date.now()
            }).toString();
            try {
                return await this.post(_buildUrl("PULSE_SAVE_RECORD"), payload, {
                    headers: {
                        "Content-Type": "application/x-www-form-urlencoded"
                    }
                });
            } catch (error) {
                return await this.post(_buildUrl("PULSE_SAVE_RECORD_ALT"), payload, {
                    headers: {
                        "Content-Type": "application/x-www-form-urlencoded"
                    }
                });
            }
        },
        async reportProgressWithDelay(playInfo, currentTime) {
            const progressPercent = Math.round(currentTime / playInfo.duration * 100);
            if (progressPercent > 90) {
                await new Promise(resolve => setTimeout(resolve, 1e3 + Math.random() * 1e3));
            }
            return await this.reportProgress(playInfo, currentTime);
        },
        async checkCompletion(courseId, coursewareId = null) {
            try {
                const response = await this.get(_buildUrl("GET_PLAY_TREND", {
                    courseId: courseId
                }));
                const config = ServiceLocator.get(ServiceNames.CONFIG);
                if (response?.success && response?.data) {
                    const data = response.data;
                    if (data.playTree?.children && Array.isArray(data.playTree.children) && data.playTree.children.length > 0) {
                        const allVideos = data.playTree.children.filter(c => c.rTypeValue === "video");
                        if (allVideos.length > 1) {
                            const allDone = allVideos.every(v => parseInt(v.finishedRate || 0) >= (config?.COMPLETION_THRESHOLD || 80));
                            const minRate = Math.min(...allVideos.map(v => parseInt(v.finishedRate || 0)));
                            return {
                                isCompleted: allDone,
                                finishedRate: minRate,
                                method: "multi_chapter_all"
                            };
                        }
                        if (coursewareId) {
                            const target = allVideos.find(c => String(c.id) === String(coursewareId));
                            if (target) {
                                const rate = parseInt(target.finishedRate || 0);
                                return {
                                    isCompleted: rate >= (config?.COMPLETION_THRESHOLD || 80),
                                    finishedRate: rate,
                                    method: "playTree_match"
                                };
                            }
                        }
                    }
                    if (data.locationSite && data.locationSite.finishedRate !== undefined) {
                        const finishedRate = parseInt(data.locationSite.finishedRate);
                        return {
                            isCompleted: finishedRate >= (config?.COMPLETION_THRESHOLD || 80),
                            finishedRate: finishedRate,
                            method: "playTrend_total"
                        };
                    }
                }
                return {
                    isCompleted: false,
                    finishedRate: 0,
                    method: "default"
                };
            } catch (error) {
                debugLog(`完成度检查失败: ${error.message}`);
                return {
                    isCompleted: false,
                    finishedRate: 0,
                    method: "error"
                };
            }
        },
        async getCoursewareList(courseId) {
            try {
                debugLog(`正在获取课程包详细信息 (ID: ${courseId})...`);
                const endpoints = [ _buildUrl("GET_COURSEWARE_LIST", {
                    courseId: courseId
                }), _buildUrl("GET_COURSEWARE_LIST_ALT", {
                    id: courseId
                }) ];
                for (const endpoint of endpoints) {
                    try {
                        const response = await this.get(endpoint);
                        if (response && response.success && response.data) {
                            const data = response.data;
                            if (data.playTree && data.playTree.children && Array.isArray(data.playTree.children)) {
                                const videos = data.playTree.children.filter(c => c.rTypeValue === "video" || c.rTypeValue === "courseware");
                                if (videos.length > 0) {
                                    debugLog(`从 playTree 获取到 ${videos.length} 个课件`);
                                    return videos.map((v, index) => {
                                        const norm = CourseAdapter.normalize({
                                            id: courseId,
                                            courseId: courseId,
                                            dsUnitId: v.id,
                                            title: v.title || `${data.title || "课程"} - 视频${index + 1}`,
                                            duration: v.sumDurationLong || 0
                                        }, "pudong_api_tree");
                                        norm.finishedRate = parseInt(v.finishedRate || 0);
                                        return norm;
                                    });
                                }
                            }
                            if (data.coursewareIdList && Array.isArray(data.coursewareIdList) && data.coursewareIdList.length > 0) {
                                debugLog(`从 coursewareIdList 获取到 ${data.coursewareIdList.length} 个课件`);
                                return data.coursewareIdList.map((cw, index) => CourseAdapter.normalize({
                                    id: courseId,
                                    courseId: courseId,
                                    dsUnitId: cw.id || cw.coursewareId,
                                    title: cw.name || cw.title || `${data.title || "课程"} - 视频${index + 1}`,
                                    duration: cw.duration || 0
                                }, "pudong_api_list"));
                            }
                            const list = data.subList || data.courseList || data.lessons;
                            if (list && Array.isArray(list) && list.length > 0) {
                                debugLog(`从 API 子列表获取到 ${list.length} 个视频`);
                                return list.map(item => CourseAdapter.normalize(item, "pudong_api_sublist"));
                            }
                        }
                    } catch {
                        continue;
                    }
                }
                return [];
            } catch (error) {
                debugLog(`获取课件列表失败: ${error.message}`);
                return [];
            }
        },
        async getCourseList() {
            const currentUrl = window.location.href;
            if (!currentUrl.toLowerCase().includes("specialdetail") && !currentUrl.toLowerCase().includes("specialDetail") && !currentUrl.toLowerCase().includes("channeldetail") && !currentUrl.toLowerCase().includes("pdchanel")) {
                return [];
            }
            let channelId = null;
            try {
                const urlObj = new URL(currentUrl.replace("#", ""));
                channelId = urlObj.searchParams.get("id");
                if (!channelId) {
                    const hash = window.location.hash;
                    const match = hash.match(/[?&]id=([^&]+)/);
                    if (match) channelId = match[1];
                }
            } catch (e) {}
            if (!channelId) return [];
            return await this.getCourseListFromChannel(channelId);
        },
        async getCourseListFromChannel(channelId) {
            try {
                debugLog(`正在从频道获取课程列表 (ID: ${channelId})...`);
                const isClassCourseList = window.location.href.toLowerCase().includes("specialdetail");
                const apiEndpoints = isClassCourseList ? [ `/inc/nc/class/course/list?id=${channelId}&_t=${Date.now()}`, `/inc/nc/pack/getById?id=${channelId}&_t=${Date.now()}` ] : [ `/inc/nc/pack/getById?id=${channelId}&_t=${Date.now()}`, `/inc/nc/class/course/list?id=${channelId}&_t=${Date.now()}`, `/inc/nc/course/pd/getPackById?id=${channelId}&_t=${Date.now()}`, `/api/nc/channel/detail?id=${channelId}&_t=${Date.now()}`, `/inc/nc/course/getCourseList?channelId=${channelId}&_t=${Date.now()}` ];
                for (const endpoint of apiEndpoints) {
                    try {
                        debugLog(`尝试API端点: ${endpoint}`);
                        const response = await this.get(PUDONG_API_CONFIG.getBaseUrl() + endpoint);
                        if (response && response.success && response.data) {
                            const data = response.data;
                            const courseList = [];
                            if (Array.isArray(data)) {
                                for (const unit of data) {
                                    if (unit.subList && Array.isArray(unit.subList)) {
                                        for (const course of unit.subList) {
                                            courseList.push(CourseAdapter.normalize({
                                                courseId: course.businessId || course.subId,
                                                id: course.businessId || course.subId,
                                                dsUnitId: course.subId || course.unitId,
                                                courseName: course.name || course.title,
                                                durationStr: course.duration || "00:30:00",
                                                status: course.progress > 0 ? course.progress >= 100 ? "completed" : "in_progress" : "not_started",
                                                teacher: course.teacher,
                                                period: course.period,
                                                categoryText: course.categoryText
                                            }, "pudong_api_class_course"));
                                        }
                                    }
                                }
                            } else if (data.pdChannelUnitList) {
                                for (const unit of data.pdChannelUnitList) {
                                    if (unit.subList) {
                                        for (const course of unit.subList) {
                                            if (course.typeValue === "course") {
                                                courseList.push(CourseAdapter.normalize({
                                                    ...course,
                                                    unitOrder: unit.order
                                                }, "pudong_api_unit"));
                                            }
                                        }
                                    }
                                }
                            } else if (data.subList && Array.isArray(data.subList)) {
                                debugLog(`从 subList 获取到 ${data.subList.length} 个课程`);
                                data.subList.forEach((item, index) => {
                                    courseList.push(CourseAdapter.normalize({
                                        courseId: item.id || item.courseId || item.dsId,
                                        dsUnitId: item.dsUnitId || item.id,
                                        courseName: item.courseName || item.title || item.name || `课程${index + 1}`,
                                        durationStr: item.durationStr || item.duration || "00:30:00",
                                        status: item.status || "not_started"
                                    }, "pudong_api_sublist"));
                                });
                            } else if (data.courseList && Array.isArray(data.courseList)) {
                                debugLog(`从 courseList 获取到 ${data.courseList.length} 个课程`);
                                data.courseList.forEach((item, index) => {
                                    courseList.push(CourseAdapter.normalize({
                                        courseId: item.id || item.courseId || item.dsId,
                                        dsUnitId: item.dsUnitId || item.id,
                                        courseName: item.courseName || item.title || item.name || `课程${index + 1}`,
                                        durationStr: item.durationStr || item.duration || "00:30:00",
                                        status: item.status || "not_started"
                                    }, "pudong_api_courselist"));
                                });
                            }
                            if (courseList.length > 0) {
                                debugLog(`从API获取到 ${courseList.length} 门课程`);
                                return courseList;
                            }
                        }
                    } catch (error) {
                        debugLog(`API端点 ${endpoint} 失败: ${error.message}`);
                        continue;
                    }
                }
                debugLog("所有频道API端点都失败了");
                return [];
            } catch (error) {
                debugLog(`从频道获取课程列表失败: ${error.message}`);
                return [];
            }
        },
        async getMetaListData(pageNum = 1, pageSize = 1e3) {
            try {
                const query = encodeURIComponent(JSON.stringify({
                    searchValue: ""
                }));
                const url = PUDONG_API_CONFIG.getBaseUrl() + `/inc/meta/list/data?pageNum=${pageNum}&pageSize=${pageSize}&query=${query}&order=[]&filter=[]&namespace=nc.pagecourse&pageName=courseList&_t=${Date.now()}`;
                const response = await this.get(url);
                if (response && response.data && Array.isArray(response.data)) {
                    return response.data.map(item => ({
                        id: item._id,
                        courseId: item._id,
                        dsUnitId: item["nc_courses_page_listsource.ds_unit_id"] || item.ds_unit_id,
                        title: item["nc_courses_page_listsource._name"] || item["nc_courses_page_listsource.title"] || item._name || item.title,
                        duration: item["nc_courses_page_listsource.duration_long"] || item.duration_long || 0,
                        progress: 0,
                        link: `#/pc/nc/page/coursePlayer?id=${item._id}`
                    }));
                }
                return [];
            } catch (error) {
                debugLog(`获取课程列表失败: ${error.message}`);
                return [];
            }
        }
    };
    const PudongScanner = {
        async scanCourses() {
            console.log("[PudongScanner] scanCourses called, URL:", window.location.href.substring(0, 80));
            const currentUrl = window.location.href;
            if (currentUrl.toLowerCase().includes("channeldetail") || currentUrl.toLowerCase().includes("specialdetail") || currentUrl.toLowerCase().includes("specialDetail") || currentUrl.toLowerCase().includes("pdchanel")) {
                const apiCourses = await PudongApi.getCourseList();
                if (apiCourses && apiCourses.length > 0) return apiCourses;
            }
            if (currentUrl.toLowerCase().includes("pagecourse/courselist")) {
                const apiCourses = await PudongApi.getMetaListData();
                if (apiCourses && apiCourses.length > 0) return apiCourses;
            }
            return [];
        }
    };
    const PudongPlayerFlow = {
        async startPlayerFlow() {
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: "检测到课程播放页面,正在检索所有视频课件...",
                type: "info"
            });
            const courseId = this._extractCourseIdFromUrl();
            if (!courseId) {
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "无法从页面URL中提取课程ID",
                    type: "error"
                });
                return null;
            }
            const apiCourses = await PudongApi.getCoursewareList(courseId);
            if (apiCourses && apiCourses.length > 0) {
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: `成功获取到 ${apiCourses.length} 个视频课件`,
                    type: "success"
                });
                return apiCourses;
            }
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: "无法通过API获取视频列表,处理当前单一视频",
                type: "warn"
            });
            return [ {
                id: courseId,
                courseId: courseId,
                dsUnitId: courseId,
                title: document.title || `当前视频 ${courseId}`,
                courseName: document.title || `当前视频 ${courseId}`,
                durationStr: "00:30:00"
            } ];
        },
        _extractCourseIdFromUrl() {
            const urlParams = new URLSearchParams(window.location.search);
            let courseId = urlParams.get("id");
            if (!courseId) {
                const hash = window.location.hash;
                if (hash.includes("?")) {
                    const hashParams = new URLSearchParams(hash.split("?")[1]);
                    courseId = hashParams.get("id");
                }
                if (!courseId) {
                    const match = hash.match(/[?&]id=([^&]+)/);
                    if (match) {
                        courseId = match[1];
                    }
                }
            }
            return courseId;
        }
    };
    const PudongHandler = {
        PAGE_TYPES: PUDONG_CONSTANTS.PAGE_TYPES,
        identifyPage: createPageDetector({
            pathPatterns: PUDONG_CONSTANTS.PATH_PATTERNS,
            pageTypes: PUDONG_CONSTANTS.PAGE_TYPES,
            domSelectors: [ "#coursePlayer", ".course-player", ".pd_course_pla" ],
            domMatchType: "PLAYER"
        }),
        init() {
            if (!this.isPudongMode()) return;
            console.log("[PudongHandler] 浦东分院处理器已激活");
        },
        isPudongMode() {
            return CONFIG.PUDONG_MODE === true;
        },
        async scanCourses() {
            return await PudongScanner.scanCourses();
        },
        async startPlayerFlow() {
            return await PudongPlayerFlow.startPlayerFlow();
        }
    };
    const cbeadPages = CONSTANTS.PAGE_CONFIG.CBEAD;
    const CBEAD_CONSTANTS = {
        PATH_PATTERNS: Object.fromEntries(Object.entries(cbeadPages).map(([k, v]) => [ k, v.path ])),
        PAGE_TYPES: {
            ...Object.fromEntries(Object.entries(cbeadPages).map(([k, v]) => [ k, v.type ])),
            UNKNOWN: "unknown"
        },
        PAGE_TYPE_WHITELIST: Object.keys(cbeadPages).filter(k => cbeadPages[k].whitelist),
        TIMEOUT: {
            NAVIGATION: 3e3,
            PAGE_LOAD: 1e4,
            PLAYER_LOAD: 15e3,
            LEARNING_SESSION: 6e5,
            POLLING_INTERVAL: 500
        },
        TIMING: {
            NAVIGATION: 3e3,
            PLAYER_INIT_TIMEOUT: 1e4,
            PLAYER_CHECK_INTERVAL: 500,
            PROGRESS_REPORT_INTERVAL: 3e4,
            CHAPTER_CHECK_INTERVAL: 15e3,
            MUTE_VERIFY_DELAY: 1e3,
            NEXT_COURSE_DELAY: 2e3,
            CHAPTER_SWITCH_DELAY: 2e3,
            PROTECTION_TIMEOUT: 100
        },
        THRESHOLDS: {
            COMPLETED_PROGRESS: 100,
            SKIP_COURSE_PROGRESS: 100,
            CHAPTER_CHECK_MIN_PROGRESS: 80,
            VIDEO_START_THRESHOLD: 10,
            STORAGE_EXPIRY: 864e5
        },
        STORAGE_KEYS: {
            LEARNING_PROGRESS: "cbead_learning_progress"
        },
        SELECTORS: {
            START_BUTTON: '.start-study-btn, [class*="start-learn"]',
            PROGRESS_BAR: ".el-progress-bar",
            VIDEO_PLAYER: 'video, [class*="video-player"], [class*="player-container"]',
            LEARNING_COMPLETED: '[class*="completed"], [class*="finished"]'
        },
        BRANCH_LIST: {
            CONTAINER_SELECTOR: ".card-wrapper",
            ITEM_SELECTOR: ".card-item",
            CARD_SELECTOR: ".card-item",
            CARD_BOX_SELECTOR: ".card-box",
            STATUS_SELECTOR: ".status",
            TITLE_SELECTOR: ".title-row",
            PAGINATION_BOX_SELECTOR: ".e-pagination-box",
            PAGINATION_SELECTOR: ".zxy-pagination",
            PAGE_ITEM_SELECTOR: ".zxy-pagination-item:not(.zxy-pagination-prev):not(.zxy-pagination-next):not(.zxy-pagination-dots)",
            ACTIVE_PAGE_SELECTOR: ".zxy-pagination-item.active",
            NEXT_BTN_SELECTOR: ".e-pagination-box .zxy-pagination .zxy-pagination-next",
            PREV_BTN_SELECTOR: ".zxy-pagination-prev",
            DISABLED_PAGINATION_CLASS: "zxy-pagination-disabled",
            TAG_CONTAINER_SELECTOR: ".label-container .tag-list",
            TAG_BTN_SELECTOR: ".label-btn",
            CATEGORY_MENU_SELECTOR: ".menu-wrapper .catalog-menu",
            CATEGORY_ITEM_SELECTOR: ".catalog-menu-item",
            PAGE_LOAD_TIMEOUT: 5e3,
            PAGINATION_DELAY: 2e3,
            TAG_SWITCH_DELAY: 2e3,
            API_ENDPOINTS: {
                COURSE_LIST: "/api/v1/course-study/course-info/front/find-by-ids",
                MY_COURSE_LIST: "/api/v1/course-study/course-study-progress/personCourse-list",
                STUDY_PROGRESS: "/api/v1/course-study/course-study-progress/course-section-study-number"
            },
            VALIDATION: {
                PAGE_LOAD_TIMEOUT: 1e4,
                SKELETON_TIMEOUT: 5e3,
                CONTENT_TIMEOUT: 8e3,
                VUE_TIMEOUT: 6e3,
                MAX_RETRY_COUNT: 3
            }
        },
        HEARTBEAT: {
            INTERVAL: 1e4,
            ENABLED: true,
            MAX_RETRIES: 3,
            TIMEOUT: 5e3
        },
        SCANNER: {
            ELEMENT_WAIT: {
                MAX_ATTEMPTS: 20,
                DELAY: 500
            },
            DEEP_SCAN: {
                MAX_DEPTH_DEFAULT: 10,
                MAX_DEPTH_VUE: 15,
                MAX_ITEMS: 5e3
            },
            PAGINATION: {
                ITEMS_PER_PAGE: 12,
                MAX_PAGES: 100
            },
            PROGRESS: {
                IN_PROGRESS_DEFAULT: 50,
                COMPLETED: 100,
                NOT_STARTED: 0
            }
        }
    };
    const scannerCore = {
        categorizeAndSortCourses(courses) {
            if (!Array.isArray(courses)) {
                console.warn("[CbeadScanner] categorizeAndSortCourses 收到非数组参数:", typeof courses);
                return {
                    inProgress: [],
                    notStarted: [],
                    completed: [],
                    toLearn: []
                };
            }
            const categorized = {
                inProgress: [],
                notStarted: [],
                completed: [],
                toLearn: []
            };
            courses.forEach(course => {
                if (course.status === "in_progress") {
                    categorized.inProgress.push(course);
                    categorized.toLearn.push(course);
                } else if (course.status === "not_started") {
                    categorized.notStarted.push(course);
                    categorized.toLearn.push(course);
                } else if (course.status === "completed") {
                    categorized.completed.push(course);
                }
            });
            categorized.inProgress.sort((a, b) => b.progress - a.progress);
            return categorized;
        },
        getSortedLearningList(courses) {
            const categorized = this.categorizeAndSortCourses(courses);
            const sortedList = [ ...categorized.inProgress, ...categorized.notStarted ];
            return sortedList;
        }
    };
    const CBEAD_API_CONFIG = {
        baseUrl: null,
        endpoints: {
            SECTION_PROGRESS: "/api/v1/course-study/course-front/course-section-progress",
            COURSE_LIST: "/api/v1/course-study/course-info/front/find-by-ids",
            COURSE_PROGRESS: "/api/v1/course-study/course-front/course-info-progress",
            COURSE_SECTION: "/api/v1/course-study/course-front/course-info-section",
            CLASS_INFO: "/api/v1/training/student/class-info/front/find-by-ids",
            COURSE_POOL: "/api/v1/course-study/resource-pool/course/front",
            CHAPTER_ACTIVITY_ALL: "/api/v1/training/student/class-info/safe/chapter-activity-all",
            STUDY_RATE_BY_ACTIVITY: "/api/v1/training/class-activity/study-rate-by-activityId",
            COURSE_INFO: "/api/v1/course-study/course-front/info"
        },
        getBaseUrl() {
            this.baseUrl = this.baseUrl || `https://${window.location.hostname}`;
            return this.baseUrl;
        },
        getUrl(endpoint) {
            const baseUrl = this.getBaseUrl();
            const endpointPath = this.endpoints[endpoint] || endpoint;
            return baseUrl + endpointPath;
        }
    };
    const CbeadApi = {
        ...ApiCore,
        _extractToken() {
            const raw = ApiCore._extractToken.call(this);
            if (!raw) return null;
            try {
                const parsed = JSON.parse(raw);
                if (parsed.access_token && parsed.access_token.length > 10) {
                    return parsed.access_token;
                }
            } catch {}
            return raw;
        },
        _prepareHeaders(customHeaders = {}, data = null) {
            const headers = ApiCore._prepareHeaders.call(this, customHeaders, data);
            const token = this._extractToken();
            if (token) {
                headers["Authorization"] = `Bearer__${token}`;
            }
            headers["Version"] = "12.4.0";
            return headers;
        },
        async getCourseProgress(courseIds) {
            if (!courseIds || courseIds.length === 0) return [];
            const url = CBEAD_API_CONFIG.getUrl("COURSE_PROGRESS") + "?ids=" + courseIds.join(",") + "&_=" + Date.now();
            const response = await this.get(url);
            return Array.isArray(response) ? response : [];
        },
        async getCourseListByOrg(organizationId, pageNum = 1, pageSize = 20) {
            const homeConfigId = this._extractHomeConfigId();
            if (homeConfigId) {
                try {
                    const url = CBEAD_API_CONFIG.getUrl("COURSE_POOL") + `?page=${pageNum}&pageSize=${pageSize}&homeConfigId=${homeConfigId}&viewRule=0&publishClient=1&type=1&order=2&orderBy=0&_=${Date.now()}`;
                    const response = await this.get(url);
                    const items = response?.items || response?.data?.items || response?.data?.list || [];
                    if (items.length > 0) return items;
                } catch (e) {
                    console.warn("[CbeadApi] resource-pool API失败:", e.message);
                }
            } else {
                console.warn("[CbeadApi] 未找到 homeConfigId");
            }
            return [];
        },
        _extractHomeConfigId() {
            for (let i = 0; i < localStorage.length; i++) {
                const key = localStorage.key(i);
                if (key.includes("homeConfigId")) {
                    const match = key.match(/"homeConfigId":"([a-f0-9-]+)"/);
                    if (match) return match[1];
                }
            }
            return "";
        },
        async getClassInfo(classIds) {
            if (!classIds || classIds.length === 0) return [];
            const url = CBEAD_API_CONFIG.getUrl("CLASS_INFO");
            const response = await this.post(url, JSON.stringify({
                ids: classIds
            }));
            return response?.data || [];
        },
        async getChapterActivities(classId) {
            if (!classId) return [];
            const url = CBEAD_API_CONFIG.getUrl("CHAPTER_ACTIVITY_ALL") + "?classId=" + classId + "&_=" + Date.now();
            const response = await this.get(url);
            return Array.isArray(response) ? response : [];
        },
        async getStudyRateByActivity(activityIds, classId) {
            if (!activityIds || activityIds.length === 0 || !classId) return [];
            const url = CBEAD_API_CONFIG.getUrl("STUDY_RATE_BY_ACTIVITY");
            const data = "activityIds=" + activityIds.join(",") + "&classId=" + classId;
            const response = await this.post(url, data);
            return Array.isArray(response) ? response : [];
        },
        async getCourseInfo(courseId) {
            if (!courseId) return null;
            const url = CBEAD_API_CONFIG.getUrl("COURSE_INFO") + "/" + courseId + "?type=1&_=" + Date.now();
            const response = await this.get(url);
            return response || null;
        },
        async getSectionProgress(resourceIds, courseId) {
            if (!resourceIds || resourceIds.length === 0 || !courseId) return [];
            const url = CBEAD_API_CONFIG.getUrl("SECTION_PROGRESS");
            const data = "resourceIds=" + resourceIds.join(",") + "&courseId=" + courseId;
            const response = await this.post(url, data);
            return Array.isArray(response) ? response : [];
        }
    };
    const STATUS_TEXT_MAP = {
        "学习中": "in_progress",
        "已完成": "completed",
        "未开始": "not_started"
    };
    function resolveCourseStatus({progress: progress = 0, statusText: statusText, studyStatus: studyStatus} = {}) {
        let status = "not_started";
        if (studyStatus !== undefined) {
            if (studyStatus === 1 || studyStatus === "studying") {
                status = "in_progress";
            } else if (studyStatus === 2 || studyStatus === "completed") {
                status = "completed";
            } else if (studyStatus === 0 || studyStatus === "not_started") {
                status = "not_started";
            }
        } else if (statusText && STATUS_TEXT_MAP[statusText]) {
            status = STATUS_TEXT_MAP[statusText];
        } else if (progress >= 100) {
            status = "completed";
        } else if (progress > 0) {
            status = "in_progress";
        }
        return {
            status: status,
            isCompleted: progress >= 100
        };
    }
    const branchListMethods = {
        async scanCoursesFromBranchListPage(pageNum = 1) {
            const orgId = this._getOrgIdFromUrl();
            if (!orgId) return [];
            try {
                const apiCourses = await CbeadApi.getCourseListByOrg(orgId, pageNum);
                if (!apiCourses || apiCourses.length === 0) return [];
                const courseIds = apiCourses.map(c => c.id || c.courseId).filter(Boolean);
                const progressList = await CbeadApi.getCourseProgress(courseIds);
                const progressMap = {};
                if (Array.isArray(progressList)) {
                    progressList.forEach(p => {
                        progressMap[p.courseId] = p;
                    });
                }
                const courses = [];
                for (const item of apiCourses) {
                    const prog = progressMap[item.id || item.courseId] || {};
                    const merged = {
                        ...item,
                        ...prog
                    };
                    const course = this._buildCourseInfoFromApiData(merged);
                    if (course && course.id) {
                        course.source = "api";
                        courses.push(course);
                    }
                }
                if (courses.length > 0) {
                    console.log(`[CbeadScanner] 从API成功获取 ${courses.length} 门课程`);
                    return courses;
                }
            } catch (e) {
                console.warn("[CbeadScanner] API获取课程失败:", e.message);
            }
            return [];
        },
        _buildCourseInfoFromApiData(data) {
            const courseId = data.id || data.courseId || data.dsUnitId || null;
            if (!courseId) return null;
            const title = data.courseName || data.title || data.name || "未知课程";
            const studyLink = `#/study/course/detail/${courseId}`;
            let progress = 0;
            if (data.studyProgress !== undefined) {
                progress = data.studyProgress;
            } else if (data.progress !== undefined) {
                progress = data.progress;
            } else if (data.percentage !== undefined) {
                progress = data.percentage;
            }
            if (data.finishStatus === 2) {
                progress = 100;
            } else if (data.finishStatus === 1) {
                progress = progress || 1;
            } else if (data.finishStatus === 0) {
                progress = 0;
            }
            if (data.studyStatus === 1 || data.studyStatus === "studying") {
                progress = progress || 1;
            } else if (data.studyStatus === 2 || data.studyStatus === "completed") {
                progress = 100;
            } else if (data.studyStatus === 0 || data.studyStatus === "not_started") {
                progress = 0;
            }
            const {status: status} = resolveCourseStatus({
                progress: progress,
                studyStatus: data.finishStatus === 2 ? 2 : data.studyStatus
            });
            return {
                id: courseId,
                courseId: courseId,
                dsUnitId: courseId,
                title: title,
                courseName: title,
                link: studyLink,
                progress: progress,
                isCompleted: progress >= CBEAD_CONSTANTS.THRESHOLDS.COMPLETED_PROGRESS,
                status: status,
                element: null,
                source: "cbead_vue_data_scan",
                rawData: data
            };
        },
        _getOrgIdFromUrl() {
            const hash = window.location.hash || "";
            const match = hash.match(/branch-list-v\/([a-f0-9-]+)/i);
            return match ? match[1] : null;
        },
        async scanAllCoursesWithPagination() {
            const allCourses = [];
            let pageNum = 1;
            console.log(`[CbeadScanner] 开始翻页扫描课程...`);
            while (true) {
                const pageCourses = await this.scanCoursesFromBranchListPage(pageNum);
                if (!pageCourses || pageCourses.length === 0) {
                    console.log(`[CbeadScanner] 第 ${pageNum} 页无数据,停止扫描`);
                    break;
                }
                console.log(`[CbeadScanner] 第 ${pageNum} 页: 扫描到 ${pageCourses.length} 门课程`);
                allCourses.push(...pageCourses);
                pageNum++;
            }
            console.log(`[CbeadScanner] 翻页扫描完成,共 ${allCourses.length} 门课程`);
            return allCourses;
        }
    };
    const columnMethods = {
        async scanCoursesFromColumnPage() {
            const classId = this._extractClassIdFromUrl();
            if (!classId) return [];
            const apiCourses = await this._scanFromColumnPageApi(classId);
            if (apiCourses && apiCourses.length > 0) {
                console.log(`[CbeadScanner] 从API成功获取 ${apiCourses.length} 门课程`);
                return apiCourses;
            }
            return [];
        },
        _extractClassIdFromUrl() {
            const hash = window.location.hash || "";
            const match = hash.match(/(?:train-new\/class-detail|center\/my\/course)\/(?:[^/]*?)([a-f0-9-]{36})/i);
            return match ? match[1] : null;
        },
        async _scanFromColumnPageApi(classId) {
            const activities = await CbeadApi.getChapterActivities(classId);
            if (!activities || activities.length === 0) return [];
            const allActivityIds = [];
            activities.forEach(group => {
                if (group.classActivitys && Array.isArray(group.classActivitys)) {
                    group.classActivitys.forEach(act => {
                        if (act.id) allActivityIds.push(act.id);
                    });
                }
            });
            let rateMap = {};
            try {
                const rateList = await CbeadApi.getStudyRateByActivity(allActivityIds, classId);
                if (Array.isArray(rateList)) {
                    rateList.forEach(r => {
                        rateMap[r.activityId] = r;
                    });
                }
            } catch (e) {
                console.warn("[CbeadScanner] 获取完成率失败,使用默认值:", e.message);
            }
            const courses = [];
            activities.forEach(group => {
                if (!group.classActivitys || !Array.isArray(group.classActivitys)) return;
                group.classActivitys.forEach(act => {
                    const course = this._buildCourseFromChapterActivity(act, rateMap[act.id]);
                    if (course) courses.push(course);
                });
            });
            return courses;
        },
        _buildCourseFromChapterActivity(act, rate) {
            const courseId = act.businessId;
            if (!courseId) return null;
            const title = act.businessName || "未知课程";
            const studyLink = `#/study/course/detail/${courseId}`;
            const progressMeta = act.classStudentActivityProgress || {};
            let progress = 0;
            let status = "not_started";
            if (progressMeta.finishStatus === 2 || progressMeta.status === 2) {
                progress = 100;
                status = "completed";
            } else if (progressMeta.finishStatus === 1 || progressMeta.status === 1) {
                progress = rate && rate.completedRate != null ? Math.round(rate.completedRate * 100) : 1;
                status = "in_progress";
            } else {
                progress = 0;
                status = "not_started";
            }
            return {
                id: courseId,
                courseId: courseId,
                dsUnitId: courseId,
                title: title,
                courseName: title,
                link: studyLink,
                progress: progress,
                isCompleted: progress >= CBEAD_CONSTANTS.THRESHOLDS.COMPLETED_PROGRESS,
                status: status,
                element: null,
                source: "cbead_column_api",
                rawData: act
            };
        }
    };
    const CbeadScanner = {
        ...scannerCore,
        ...branchListMethods,
        ...columnMethods
    };
    const learningState = {
        failed: false,
        failureReason: null,
        markFailed(reason) {
            this.failed = true;
            this.failureReason = reason;
            console.error(`[CbeadProgressManager] 🚨 课程已标记为失败: ${reason}`);
        },
        reset() {
            this.failed = false;
            this.failureReason = null;
        },
        isFailed() {
            return this.failed;
        },
        getFailureReason() {
            return this.failureReason;
        }
    };
    const CbeadProgressManager = {
        getLearningState() {
            return learningState;
        },
        clearLearningProgress() {
            try {
                localStorage.removeItem(CBEAD_CONSTANTS.STORAGE_KEYS.LEARNING_PROGRESS);
                console.log("[CbeadProgressManager] 学习进度已清除");
            } catch (error) {
                console.error("[CbeadProgressManager] 清除学习进度失败:", error);
            }
        },
        saveLearningQueue(learningList, totalCourses, pageUrl) {
            try {
                const data = {
                    learningList: learningList.map(c => ({
                        id: c.id,
                        title: c.title,
                        link: c.link,
                        progress: c.progress,
                        status: c.status
                    })),
                    totalCourses: totalCourses,
                    currentIndex: 0,
                    pageUrl: pageUrl,
                    timestamp: Date.now()
                };
                localStorage.setItem(CBEAD_CONSTANTS.STORAGE_KEYS.LEARNING_PROGRESS, JSON.stringify(data));
                console.log(`[CbeadProgressManager] 学习队列已保存: ${learningList.length} 门课程`);
            } catch (error) {
                console.error("[CbeadProgressManager] 保存学习队列失败:", error);
            }
        },
        loadLearningQueue() {
            try {
                const data = localStorage.getItem(CBEAD_CONSTANTS.STORAGE_KEYS.LEARNING_PROGRESS);
                if (!data) {
                    console.log("[CbeadProgressManager] 未找到学习队列");
                    return null;
                }
                const parsed = JSON.parse(data);
                const QUEUE_TIMEOUT = 24 * 60 * 60 * 1e3;
                if (Date.now() - parsed.timestamp > QUEUE_TIMEOUT) {
                    console.log("[CbeadProgressManager] 学习队列已超时,清除");
                    this.clearLearningProgress();
                    return null;
                }
                console.log(`[CbeadProgressManager] 学习队列已加载: 当前索引 ${parsed.currentIndex}/${parsed.learningList.length}`);
                return parsed;
            } catch (error) {
                console.error("[CbeadProgressManager] 加载学习队列失败:", error);
                return null;
            }
        },
        updateCurrentIndex(newIndex) {
            try {
                const data = this.loadLearningQueue();
                if (data) {
                    data.currentIndex = newIndex;
                    data.timestamp = Date.now();
                    localStorage.setItem(CBEAD_CONSTANTS.STORAGE_KEYS.LEARNING_PROGRESS, JSON.stringify(data));
                    console.log(`[CbeadProgressManager] 当前索引已更新: ${newIndex}`);
                }
            } catch (error) {
                console.error("[CbeadProgressManager] 更新索引失败:", error);
            }
        },
        hasValidQueue(currentUrl) {
            const data = this.loadLearningQueue();
            if (!data) return false;
            if (!data.learningList || !Array.isArray(data.learningList)) {
                console.warn("[CbeadProgressManager] 队列数据结构无效: 缺少 learningList");
                return false;
            }
            if (typeof data.currentIndex !== "number") {
                console.warn("[CbeadProgressManager] 队列数据结构无效: 缺少 currentIndex");
                return false;
            }
            if (data.learningList.length === 0) {
                console.warn("[CbeadProgressManager] 学习队列为空");
                return false;
            }
            if (data.currentIndex < 0 || data.currentIndex >= data.learningList.length) {
                console.warn(`[CbeadProgressManager] currentIndex ${data.currentIndex} 超出范围 [0, ${data.learningList.length})`);
                return false;
            }
            if (currentUrl && data.pageUrl) {
                try {
                    const currentUrlObj = new URL(currentUrl, "https://dummy.com");
                    const savedUrlObj = new URL(data.pageUrl, "https://dummy.com");
                    if (currentUrlObj.origin !== savedUrlObj.origin) {
                        console.warn("[CbeadProgressManager] URL 域名不匹配");
                        return false;
                    }
                    const currentPath = currentUrlObj.pathname || "";
                    const savedPath = savedUrlObj.pathname || "";
                    if (currentPath !== savedPath) {
                        if (!savedPath.startsWith(currentPath) && !currentPath.startsWith(savedPath)) {
                            console.warn(`[CbeadProgressManager] URL 路径不匹配: ${currentPath} vs ${savedPath}`);
                            return false;
                        }
                    }
                } catch (e) {
                    console.warn("[CbeadProgressManager] URL 解析失败,跳过 URL 匹配检查");
                }
            }
            console.log("[CbeadProgressManager] 学习队列有效");
            return true;
        },
        async returnToList(returnUrl) {
            console.log("[CbeadProgressManager] 准备返回列表页...");
            this.clearLearningProgress();
            await new Promise(resolve => setTimeout(resolve, CBEAD_CONSTANTS.TIMING.NEXT_COURSE_DELAY));
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: `🔄 返回列表页,扫描后自动继续...`,
                type: "info"
            });
            if (returnUrl && !returnUrl.includes("study/course/detail")) {
                console.log(`[CbeadProgressManager] 🚀 返回列表页: ${returnUrl}`);
                window.location.href = returnUrl;
            } else {
                console.warn("[CbeadProgressManager] ⚠️ 没有有效的返回 URL,尝试使用浏览器后退");
                window.history.back();
            }
        },
        publishCompletionStats(totalCourses) {
            EventBus.publish(CONSTANTS.EVENTS.STATISTICS_UPDATE, {
                total: totalCourses,
                completed: totalCourses,
                learned: totalCourses,
                failed: 0,
                skipped: 0
            });
            EventBus.publish(CONSTANTS.EVENTS.STATUS_UPDATE, "学习完成");
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: `🎉 专题班全部课程学习完成!共 ${totalCourses} 门课程`,
                type: "success"
            });
        },
        normalizeCourseId(rawId) {
            if (!rawId) return null;
            const uuidPattern = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
            if (uuidPattern.test(rawId)) {
                return rawId;
            }
            if (/^\d+$/.test(rawId)) {
                return rawId;
            }
            const match = rawId.match(/[a-f0-9-]{36}/);
            return match ? match[0] : rawId;
        },
        extractPlayerParams() {
            const hash = window.location.hash;
            const newMatch = hash.match(/detail\/(?:[^/]*@@)?([a-f0-9-]{36})/i);
            if (newMatch) {
                console.log(`[CbeadProgressManager] ✅ 使用新 URL 格式提取参数`);
                return {
                    uuid: newMatch[1],
                    courseId: newMatch[1],
                    coursewareId: newMatch[1]
                };
            }
            const midMatch = hash.match(/detail\/(\d+)&([a-f0-9-]{36})/i);
            if (midMatch) {
                console.log(`[CbeadProgressManager] ✅ 使用中间 URL 格式提取参数`);
                return {
                    courseId: midMatch[1],
                    uuid: midMatch[2],
                    coursewareId: midMatch[2]
                };
            }
            const oldMatch = hash.match(/detail\/(\d+)&([a-f0-9-]+)&(\d+)\/(\d+)\/(\d+)/);
            if (oldMatch) {
                console.log(`[CbeadProgressManager] ⚠️ 使用旧 URL 格式提取参数(兼容模式)`);
                return {
                    courseId: oldMatch[1],
                    uuid: oldMatch[2],
                    sectionIndex: oldMatch[3],
                    totalSections: oldMatch[4],
                    currentIndex: oldMatch[5]
                };
            }
            console.warn(`[CbeadProgressManager] ⚠️ 无法从 URL 提取参数: ${hash}`);
            return null;
        }
    };
    class IntervalManager {
        constructor() {
            this.intervals = new Set;
            this.timeouts = new Set;
            this._unloadHandlerRegistered = false;
            this._boundUnloadHandler = null;
        }
        setInterval(callback, delay) {
            const id = setInterval(callback, delay);
            this.intervals.add(id);
            return id;
        }
        setTimeout(callback, delay) {
            const id = setTimeout(callback, delay);
            this.timeouts.add(id);
            return id;
        }
        clearAll() {
            this.intervals.forEach(id => {
                try {
                    clearInterval(id);
                } catch {}
            });
            this.intervals.clear();
            this.timeouts.forEach(id => {
                try {
                    clearTimeout(id);
                } catch {}
            });
            this.timeouts.clear();
        }
        getCounts() {
            return {
                intervals: this.intervals.size,
                timeouts: this.timeouts.size
            };
        }
        registerUnloadHandler() {
            if (this._unloadHandlerRegistered) return;
            this._boundUnloadHandler = () => this.clearAll();
            window.addEventListener("beforeunload", this._boundUnloadHandler);
            window.addEventListener("pagehide", this._boundUnloadHandler);
            window.addEventListener("unload", this._boundUnloadHandler);
            this._unloadHandlerRegistered = true;
        }
        unregisterUnloadHandler() {
            if (!this._unloadHandlerRegistered || !this._boundUnloadHandler) return;
            window.removeEventListener("beforeunload", this._boundUnloadHandler);
            window.removeEventListener("pagehide", this._boundUnloadHandler);
            window.removeEventListener("unload", this._boundUnloadHandler);
            this._unloadHandlerRegistered = false;
            this._boundUnloadHandler = null;
        }
    }
    const BasePlayer = {
        DEBUG: false,
        _debugLog(...args) {
            if (this.DEBUG) {
                console.log(...args);
            }
        },
        detectVideoPlayer() {
            const videoJsPlayer = document.querySelector("video.vjs-tech");
            if (videoJsPlayer) {
                const videoJsInstance = window.player || videoJsPlayer.player && videoJsPlayer.player;
                return {
                    type: "videojs",
                    element: videoJsPlayer,
                    player: videoJsInstance,
                    getCurrentTime: () => videoJsPlayer.currentTime,
                    setCurrentTime: time => {
                        videoJsPlayer.currentTime = time;
                    },
                    getDuration: () => videoJsPlayer.duration,
                    play: () => videoJsPlayer.play(),
                    pause: () => videoJsPlayer.pause(),
                    setMuted: muted => {
                        videoJsPlayer.muted = muted;
                        if (videoJsInstance && typeof videoJsInstance.muted === "function") {
                            try {
                                videoJsInstance.muted(muted);
                            } catch {}
                        }
                        if (muted) videoJsPlayer.volume = 0;
                    },
                    getMuted: () => videoJsPlayer.muted
                };
            }
            const genericVideo = document.querySelector("video");
            if (genericVideo) {
                return {
                    type: "generic",
                    element: genericVideo,
                    getCurrentTime: () => genericVideo.currentTime,
                    setCurrentTime: time => {
                        genericVideo.currentTime = time;
                    },
                    getDuration: () => genericVideo.duration,
                    play: () => genericVideo.play(),
                    pause: () => genericVideo.pause(),
                    setMuted: muted => {
                        genericVideo.muted = muted;
                    },
                    getMuted: () => genericVideo.muted
                };
            }
            return null;
        },
        parseTimeToSeconds(timeStr) {
            if (!timeStr) return 0;
            const match = timeStr.match(/(\d+):(\d+)/);
            if (!match) return 0;
            return parseInt(match[1]) * 60 + parseInt(match[2]);
        },
        createIntervalManager() {
            return new IntervalManager;
        },
        async waitForPlayerReady(maxWaitTime = 1e4, checkInterval = 500) {
            const startTime = Date.now();
            const manager = new IntervalManager;
            return new Promise(resolve => {
                manager.setInterval(() => {
                    try {
                        const elapsed = Date.now() - startTime;
                        const player = this.detectVideoPlayer();
                        if (player) {
                            const duration = player.getDuration();
                            if (duration && duration > 0 && isFinite(duration)) {
                                manager.clearAll();
                                resolve(player);
                                return;
                            }
                        }
                        if (elapsed >= maxWaitTime) {
                            manager.clearAll();
                            resolve(null);
                        }
                    } catch {
                        const elapsed = Date.now() - startTime;
                        if (elapsed >= maxWaitTime) {
                            manager.clearAll();
                            resolve(null);
                        }
                    }
                }, checkInterval);
            });
        },
        getServerProgress(currentChapterIndex = null) {
            try {
                const chapterProgress = this.extractChapterProgress(false);
                if (!chapterProgress) return null;
                if (currentChapterIndex !== null) {
                    const currentChapter = chapterProgress.chapters?.find(ch => ch.index === currentChapterIndex);
                    if (currentChapter) return currentChapter.progress;
                }
                if (chapterProgress.firstIncomplete) {
                    return chapterProgress.firstIncomplete.progress;
                }
                return null;
            } catch {
                return null;
            }
        },
        cleanupPlaybackResources(resources, _reason = "未知原因") {
            const {wakeLock: wakeLock, handleVisibilityChange: handleVisibilityChange, timerManager: timerManager} = resources;
            if (wakeLock) {
                try {
                    wakeLock.release();
                } catch {}
            }
            if (handleVisibilityChange) {
                try {
                    document.removeEventListener("visibilitychange", handleVisibilityChange);
                } catch {}
            }
            if (timerManager) {
                try {
                    timerManager.clearAll();
                } catch {}
            }
        }
    };
    const CbeadPlayer = {
        ...BasePlayer,
        _extractCourseIdFromUrl() {
            const hash = window.location.hash || "";
            const match = hash.match(/study\/course\/detail\/([a-f0-9-]{36})/i);
            return match ? match[1] : null;
        },
        async _extractChapterProgressFromApi(verbose = true) {
            const courseId = this._extractCourseIdFromUrl();
            if (!courseId) {
                if (verbose) console.warn("[CbeadPlayer] API: 未找到课程ID");
                return null;
            }
            let courseInfo;
            try {
                courseInfo = await CbeadApi.getCourseInfo(courseId);
            } catch (e) {
                if (verbose) console.warn("[CbeadPlayer] API: 获取课程信息失败:", e.message);
                return null;
            }
            if (!courseInfo || !courseInfo.courseChapters) {
                if (verbose) console.warn("[CbeadPlayer] API: 课程信息无章节数据");
                return null;
            }
            const sections = [];
            const resourceIds = [];
            courseInfo.courseChapters.forEach(chapter => {
                if (chapter.courseChapterSections) {
                    chapter.courseChapterSections.forEach(sec => {
                        sections.push({
                            chapterId: chapter.id,
                            chapterName: chapter.name,
                            sectionId: sec.id,
                            sectionName: sec.name,
                            resourceId: sec.resourceId || sec.attachmentId,
                            totalTime: sec.totalTime || 0,
                            required: sec.required !== 0,
                            sequence: sec.sequence || 0
                        });
                        if (sec.resourceId) resourceIds.push(sec.resourceId);
                    });
                }
            });
            if (sections.length === 0) return null;
            let progressList = [];
            try {
                progressList = await CbeadApi.getSectionProgress(resourceIds, courseId);
            } catch (e) {
                if (verbose) console.warn("[CbeadPlayer] API: 获取节进度失败:", e.message);
            }
            const progressMap = {};
            if (Array.isArray(progressList)) {
                progressList.forEach(p => {
                    if (p.resourceId) progressMap[p.resourceId] = p;
                });
            }
            const chapters = [];
            sections.forEach((sec, index) => {
                const prog = progressMap[sec.resourceId] || {};
                const completedRate = prog.completedRate != null ? prog.completedRate : 0;
                const finishStatus = prog.finishStatus != null ? prog.finishStatus : 0;
                let status = "not_started";
                let isCompleted = false;
                let progress = 0;
                if (finishStatus === 2 || completedRate >= 100) {
                    status = "completed";
                    isCompleted = true;
                    progress = 100;
                } else if (finishStatus === 1 || completedRate > 0) {
                    status = "in_progress";
                    progress = completedRate;
                } else {
                    status = "not_started";
                    progress = 0;
                }
                chapters.push({
                    index: index + 1,
                    title: sec.sectionName || sec.chapterName,
                    status: status,
                    statusText: status === "completed" ? "已完成" : status === "in_progress" ? "学习中" : "未开始",
                    progress: progress,
                    isCompleted: isCompleted,
                    element: null
                });
            });
            const stats = {
                completed: chapters.filter(ch => ch.status === "completed").length,
                inProgress: chapters.filter(ch => ch.status === "in_progress").length,
                notStarted: chapters.filter(ch => ch.status === "not_started").length
            };
            const firstIncomplete = chapters.find(ch => !ch.isCompleted);
            if (verbose) {
                console.log(`[CbeadPlayer] API: 找到 ${chapters.length} 个章节: ✅ ${stats.completed} 📖 ${stats.inProgress} 📝 ${stats.notStarted}`);
                if (firstIncomplete) {
                    const sd = CONSTANTS.STATUS_DISPLAY[firstIncomplete.status] || {
                        text: "未知"
                    };
                    console.log(`[CbeadPlayer] API: 💡 当前章节: ${firstIncomplete.index} - ${sd.text} (${firstIncomplete.progress}%)`);
                }
            }
            return {
                total: chapters.length,
                completed: stats.completed,
                inProgress: stats.inProgress,
                notStarted: stats.notStarted,
                chapters: chapters,
                firstIncomplete: firstIncomplete ?? null,
                allCompleted: firstIncomplete == null
            };
        },
        async extractChapterProgress(verbose = true) {
            return await this._extractChapterProgressFromApi(verbose);
        },
        async getServerProgress(currentChapterIndex = null) {
            try {
                const chapterProgress = await this.extractChapterProgress(false);
                if (!chapterProgress) return null;
                if (currentChapterIndex !== null) {
                    const currentChapter = chapterProgress.chapters?.find(ch => ch.index === currentChapterIndex);
                    if (currentChapter) return currentChapter.progress;
                }
                if (chapterProgress.firstIncomplete) {
                    return chapterProgress.firstIncomplete.progress;
                }
                return null;
            } catch {
                return null;
            }
        },
        async isCourseReallyCompleted() {
            const chapterProgress = await this.extractChapterProgress();
            if (!chapterProgress) {
                console.warn("[CbeadPlayer] 无法判断章节进度,假设未完成");
                return false;
            }
            if (chapterProgress.allCompleted) {
                console.log(`[CbeadPlayer] ✅ 所有章节已完成 (${chapterProgress.completed}/${chapterProgress.total})`);
                return true;
            }
            const first = chapterProgress.firstIncomplete;
            if (first) {
                console.log(`[CbeadPlayer] 📖 章节 ${first.index} 未完成 (${first.status}, ${first.progress}%)`);
            } else {
                return chapterProgress.completed >= chapterProgress.total;
            }
            return false;
        },
        clickChapter(chapterTitle) {
            try {
                const catalog = document.querySelector(".course-side-catalog");
                if (!catalog) {
                    console.warn("[CbeadPlayer] 未找到章节目录");
                    return false;
                }
                const chapterBoxes = catalog.querySelectorAll(".chapter-list-box");
                for (const box of chapterBoxes) {
                    const titleEl = box.querySelector(".chapter-item .text-overflow");
                    const title = titleEl?.textContent?.trim();
                    if (title === chapterTitle || title?.includes(chapterTitle)) {
                        box.click();
                        const playBtn = box.querySelector(".section-item");
                        if (playBtn) playBtn.click();
                        return true;
                    }
                }
                return false;
            } catch (error) {
                console.error(`[CbeadPlayer] 点击章节失败:`, error);
                return false;
            }
        }
    };
    const WORKER_CODE = `\n  let timer = null;\n  let config = null;\n\n  self.onmessage = async function(e) {\n    const { action, data } = e.data;\n\n    if (action === 'start') {\n      config = data;\n      console.log('[CbeadHeartbeatWorker] 🚀 Worker 启动,心跳间隔:', config?.interval || 10000, 'ms');\n\n      timer = setInterval(async () => {\n        try {\n          // 【TODO】后续补充企业分院 API 调用\n          // 目前仅发送心跳消息保活 Worker 框架\n          // 浦东分院 API (/inc/nc/course/play/pulseSaveRecord) 在企业分院无效\n          // 企业分院专用 API 待探索\n\n          // 示例调用(待实现):\n          // const result = await sendHeartbeat({\n          //   courseId: config.courseId,\n          //   chapterId: config.chapterId,\n          //   timestamp: Date.now()\n          // });\n\n          self.postMessage({\n            type: 'heartbeat',\n            timestamp: Date.now(),\n            config: config\n          });\n        } catch (err) {\n          self.postMessage({\n            type: 'error',\n            error: err.message,\n            timestamp: Date.now()\n          });\n        }\n      }, config?.interval || 10000);\n\n    } else if (action === 'stop') {\n      if (timer) {\n        clearInterval(timer);\n        timer = null;\n      }\n      config = null;\n      console.log('[CbeadHeartbeatWorker] 🛑 Worker 已停止');\n\n    } else if (action === 'ping') {\n      self.postMessage({\n        type: 'pong',\n        timestamp: Date.now(),\n        config: config\n      });\n    }\n  };\n`;
    const CbeadHeartbeatWorker = {
        createWorker() {
            const blob = new Blob([ WORKER_CODE ], {
                type: "application/javascript"
            });
            const workerUrl = URL.createObjectURL(blob);
            return new Worker(workerUrl);
        },
        start(courseInfo) {
            const worker = this.createWorker();
            worker.onmessage = e => {
                const {type: type, timestamp: timestamp, config: config} = e.data;
                switch (type) {
                  case "heartbeat":
                    console.log(`[CbeadHeartbeatWorker] 💓 心跳 ${timestamp} - 课程: ${config?.courseId || "N/A"}`);
                    break;

                  case "error":
                    console.error(`[CbeadHeartbeatWorker] ❌ 心跳错误: ${e.data.error}`);
                    break;

                  case "pong":
                    console.log(`[CbeadHeartbeatWorker] 🔵 Pong ${timestamp}`);
                    break;

                  default:
                    console.log(`[CbeadHeartbeatWorker] 📨 消息:`, e.data);
                }
            };
            worker.onerror = error => {
                console.error(`[CbeadHeartbeatWorker] 💥 Worker 错误:`, error);
            };
            worker.postMessage({
                action: "start",
                data: {
                    courseId: courseInfo.courseId,
                    chapterId: courseInfo.chapterId,
                    interval: courseInfo.interval || 1e4
                }
            });
            console.log(`[CbeadHeartbeatWorker] ✅ Worker 已启动`);
            return worker;
        },
        stop(worker) {
            if (worker) {
                worker.postMessage({
                    action: "stop"
                });
                worker.terminate();
                console.log(`[CbeadHeartbeatWorker] 🛑 Worker 已终止`);
            }
        },
        async ping(worker) {
            if (!worker) return false;
            return new Promise(resolve => {
                const timeout = setTimeout(() => {
                    resolve(false);
                }, 2e3);
                worker.onmessage = e => {
                    if (e.data.type === "pong") {
                        clearTimeout(timeout);
                        resolve(true);
                    }
                };
                worker.postMessage({
                    action: "ping"
                });
            });
        }
    };
    const CbeadPlayerFlow = {
        async learnWithRealPlayback(course) {
            console.log(`[CbeadPlayerFlow] ========== 开始真实播放学习 ==========`);
            console.log(`[CbeadPlayerFlow] 📚 课程名称: ${course.title}`);
            console.log(`[CbeadPlayerFlow] 🆔 课程ID: ${course.id || course.courseId || "N/A"}`);
            console.log(`[CbeadPlayerFlow] 🔗 当前URL: ${window.location.href}`);
            const learningState = CbeadProgressManager.getLearningState();
            learningState.reset();
            console.log(`[CbeadPlayerFlow] 🔄 学习状态已重置`);
            const timerManager = CbeadPlayer.createIntervalManager();
            timerManager.registerUnloadHandler();
            const cleanup = () => {
                timerManager.unregisterUnloadHandler();
                timerManager.clearAll();
            };
            let chapterName = course.title;
            let currentChapterIndex = null;
            try {
                let chapterProgress = null;
                for (let i = 0; i < 2; i++) {
                    chapterProgress = await CbeadPlayer.extractChapterProgress(false);
                    if (chapterProgress && chapterProgress.firstIncomplete) {
                        break;
                    }
                    if (i < 1) {
                        console.log(`[CbeadPlayerFlow] ⏳ 第${i + 1}次提取未完成,等待2秒后重试...`);
                        await new Promise(resolve => setTimeout(resolve, 2e3));
                    }
                }
                if (chapterProgress && chapterProgress.firstIncomplete) {
                    chapterName = chapterProgress.firstIncomplete.title;
                    currentChapterIndex = chapterProgress.firstIncomplete.index;
                    console.log(`[CbeadPlayerFlow] 📖 章节名称: ${chapterName}`);
                    console.log(`[CbeadPlayerFlow] 📌 章节索引: ${currentChapterIndex}`);
                    if (currentChapterIndex > 1) {
                        console.log(`[CbeadPlayerFlow] ⏭️ 已跳过 ${currentChapterIndex - 1} 个已完成章节`);
                        EventBus.publish(CONSTANTS.EVENTS.LOG, {
                            message: `⏭️ 已跳过 ${currentChapterIndex - 1} 个已完成章节,开始学习: ${chapterName}`,
                            type: "info"
                        });
                    }
                } else {
                    console.warn(`[CbeadPlayerFlow] ⚠️ 未能提取到章节信息,使用课程标题`);
                }
            } catch (error) {
                console.warn(`[CbeadPlayerFlow] 无法提取章节信息,使用课程名:`, error);
            }
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: `📖 开始学习: ${chapterName}`,
                type: "info"
            });
            console.log(`[CbeadPlayerFlow] 🔍 立即检查章节状态...`);
            const initialChapterProgress = await CbeadPlayer.extractChapterProgress(false);
            if (initialChapterProgress && currentChapterIndex !== null) {
                const targetChapter = initialChapterProgress.chapters.find(ch => ch.index === currentChapterIndex);
                if (targetChapter && targetChapter.status === "completed") {
                    console.log(`[CbeadPlayerFlow] ✅ 章节 ${currentChapterIndex} 已完成,跳过播放`);
                    EventBus.publish(CONSTANTS.EVENTS.LOG, {
                        message: `⏭️ 章节已完成,跳过: ${targetChapter.title}`,
                        type: "info"
                    });
                    return {
                        success: true,
                        method: "skip_completed",
                        duration: 0,
                        watched: 0,
                        chapterName: targetChapter.title,
                        chapterCompleted: true,
                        earlyTermination: true,
                        reason: "章节已是已完成状态",
                        savedPercent: 100
                    };
                }
            }
            try {
                console.log(`[CbeadPlayerFlow] ⏳ 步骤 1/7: 等待播放器初始化...`);
                let player = await CbeadPlayer.waitForPlayerReady();
                if (!player) {
                    console.error(`[CbeadPlayerFlow] ❌ 播放器初始化失败`);
                    throw new Error("播放器初始化超时,无法获取视频信息");
                }
                console.log(`[CbeadPlayerFlow] ✅ 步骤 1/7: 播放器初始化完成`);
                console.log(`[CbeadPlayerFlow] 📹 播放器类型: ${player.type}`);
                if (initialChapterProgress && currentChapterIndex !== null) {
                    const targetChapter = initialChapterProgress.chapters.find(ch => ch.index === currentChapterIndex);
                    if (targetChapter) {
                        console.log(`[CbeadPlayerFlow] 📖 目标章节: ${targetChapter.title}, 状态: ${targetChapter.status}`);
                        const targetServerProgress = targetChapter.progress || 0;
                        if (targetServerProgress > 80 || targetServerProgress < 5) {
                            console.log(`[CbeadPlayerFlow] 🔄 服务器进度 ${targetServerProgress}%,尝试切换到目标章节: ${targetChapter.title}`);
                            const clicked = CbeadPlayer.clickChapter(targetChapter.title);
                            if (clicked) {
                                console.log(`[CbeadPlayerFlow] ✅ 已点击切换到章节: ${targetChapter.title}`);
                                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                                    message: `🔄 切换到章节: ${targetChapter.title}`,
                                    type: "info"
                                });
                                await new Promise(resolve => setTimeout(resolve, CBEAD_CONSTANTS.TIMING.CHAPTER_SWITCH_DELAY));
                                const newPlayer = CbeadPlayer.detectVideoPlayer();
                                if (newPlayer) {
                                    player = newPlayer;
                                    console.log(`[CbeadPlayerFlow] 📹 切换后播放器已重新检测`);
                                } else {
                                    console.warn(`[CbeadPlayerFlow] ⚠️ 切换章节后播放器未就绪,等待重新初始化...`);
                                    const reloadedPlayer = await CbeadPlayer.waitForPlayerReady();
                                    if (reloadedPlayer) player = reloadedPlayer;
                                }
                            } else {
                                console.warn(`[CbeadPlayerFlow] ⚠️ 章节切换失败,可能已在正确章节`);
                            }
                        } else {
                            console.log(`[CbeadPlayerFlow] ✅ 服务器进度 ${targetServerProgress}%,似乎在正确的章节`);
                        }
                    }
                }
                console.log(`[CbeadPlayerFlow] ⏳ 步骤 2/7: 获取视频信息...`);
                const duration = player.getDuration();
                const currentTime = player.getCurrentTime();
                const durationMinutes = Math.round(duration / 60);
                const serverProgress = await CbeadPlayer.getServerProgress(currentChapterIndex);
                if (serverProgress !== null) {
                    EventBus.publish(CONSTANTS.EVENTS.PROGRESS_UPDATE, serverProgress);
                }
                console.log(`[CbeadPlayerFlow] ✅ 步骤 2/7: 视频信息获取完成`);
                console.log(`[CbeadPlayerFlow] ⏱️ 总时长: ${Math.round(duration)}秒 (${durationMinutes}分钟)`);
                console.log(`[CbeadPlayerFlow] ⏯️ 当前位置: ${Math.round(currentTime)}秒 (服务器进度: ${serverProgress !== null ? serverProgress + "%" : "N/A"})`);
                console.log(`[CbeadPlayerFlow] ⏳ 步骤 3/7: 设置静音...`);
                player.setMuted(true);
                console.log(`[CbeadPlayerFlow] 🔇 已调用 setMuted(true)`);
                const mutedAfter = player.getMuted();
                console.log(`[CbeadPlayerFlow] 🔍 静音状态验证: ${mutedAfter ? "成功" : "失败"}`);
                console.log(`[CbeadPlayerFlow] ✅ 步骤 3/7: 静音设置完成`);
                console.log(`[CbeadPlayerFlow] ⏳ 步骤 4/7: 设置播放位置...`);
                if (serverProgress > 0) {
                    const targetTime = serverProgress / 100 * duration;
                    console.log(`[CbeadPlayerFlow] 📍 策略: 恢复进度 (服务器 ${serverProgress}% → ${Math.round(targetTime)}s / ${Math.round(duration)}s)`);
                    player.setCurrentTime(targetTime);
                } else if (currentTime < CBEAD_CONSTANTS.THRESHOLDS.VIDEO_START_THRESHOLD) {
                    console.log(`[CbeadPlayerFlow] 📍 策略: 从头播放`);
                    player.setCurrentTime(0);
                } else {
                    console.log(`[CbeadPlayerFlow] 📍 策略: 断点续播 (当前位置: ${Math.round(currentTime)}s)`);
                }
                console.log(`[CbeadPlayerFlow] ✅ 步骤 4/7: 播放位置设置完成`);
                console.log(`[CbeadPlayerFlow] ⏳ 步骤 5/7: 启动播放器...`);
                const currentPlayer = CbeadPlayer.detectVideoPlayer();
                if (!currentPlayer) {
                    console.error(`[CbeadPlayerFlow] ❌ 播放器检测失败,可能已被销毁`);
                    throw new Error("播放器在启动前被销毁");
                }
                const {clickMaskButton: clickMaskButton} = await Promise.resolve().then(function() {
                    return domHelper;
                });
                clickMaskButton();
                const bigPlayButton = document.querySelector(".vjs-big-play-button");
                if (bigPlayButton) {
                    console.log(`[CbeadPlayerFlow] 🎯 发现大播放按钮遮罩,准备点击...`);
                    bigPlayButton.click();
                    console.log(`[CbeadPlayerFlow] ✅ 已点击大播放按钮`);
                    await new Promise(resolve => setTimeout(resolve, 800));
                }
                if (currentPlayer.element.paused) {
                    const playControlBtn = document.querySelector(".vjs-play-control");
                    if (playControlBtn) {
                        console.log(`[CbeadPlayerFlow] 🎯 发现播放控制按钮,准备点击...`);
                        playControlBtn.click();
                        await new Promise(resolve => setTimeout(resolve, 500));
                        console.log(`[CbeadPlayerFlow] ✅ 已点击播放控制按钮`);
                    }
                }
                if (!currentPlayer.element.paused) {
                    console.log(`[CbeadPlayerFlow] ✅ 播放器成功启动`);
                } else {
                    console.error(`[CbeadPlayerFlow] ❌ 播放器启动失败`);
                    EventBus.publish(CONSTANTS.EVENTS.LOG, {
                        message: "⚠️ 自动播放失败,请手动点击播放按钮",
                        type: "warn"
                    });
                }
                console.log(`[CbeadPlayerFlow] ✅ 步骤 5/7: 播放器启动完成`);
                console.log(`[CbeadPlayerFlow] ⏳ 步骤 6/7: 设置播放监听器...`);
                let notificationPermission = "default";
                if ("Notification" in window) {
                    try {
                        notificationPermission = await Notification.requestPermission();
                    } catch (err) {
                        console.warn(`[CbeadPlayerFlow] ⚠️ 请求通知权限失败:`, err);
                    }
                }
                let wakeLock = null;
                if ("wakeLock" in navigator) {
                    try {
                        wakeLock = await navigator.wakeLock.request("screen");
                        console.log(`[CbeadPlayerFlow] 📱 已启用屏幕常亮锁`);
                        EventBus.publish(CONSTANTS.EVENTS.LOG, {
                            message: "📱 已启用屏幕常亮,防止屏幕关闭",
                            type: "info"
                        });
                    } catch (err) {
                        console.warn(`[CbeadPlayerFlow] ⚠️ 无法启用屏幕常亮锁:`, err);
                    }
                }
                const handleVisibilityChange = () => {
                    if (document.hidden) {
                        console.warn(`[CbeadPlayerFlow] ⚠️ 页面已隐藏到后台!`);
                        if (CBEAD_CONSTANTS.HEARTBEAT.ENABLED) {
                            console.log(`[CbeadPlayerFlow] 📱 页面进入后台,启动 Worker 心跳`);
                            heartbeatWorker = CbeadHeartbeatWorker.start({
                                courseId: course.id || course.courseId,
                                chapterId: currentChapterIndex,
                                interval: CBEAD_CONSTANTS.HEARTBEAT.INTERVAL
                            });
                        }
                        EventBus.publish(CONSTANTS.EVENTS.LOG, {
                            message: "⚠️ 页面已隐藏到后台,已启动Worker心跳保活",
                            type: "warn"
                        });
                        if ("Notification" in window && notificationPermission === "granted") {
                            try {
                                new Notification("学习提醒", {
                                    body: "页面已隐藏,请返回前台继续学习",
                                    icon: "📺",
                                    requireInteraction: true
                                });
                            } catch (err) {
                                console.warn(`[CbeadPlayerFlow] 发送通知失败:`, err);
                            }
                        }
                    } else {
                        console.log(`[CbeadPlayerFlow] ✅ 页面已返回前台`);
                        if (heartbeatWorker) {
                            console.log(`[CbeadPlayerFlow] 📱 页面回到前台,停止 Worker 心跳`);
                            CbeadHeartbeatWorker.stop(heartbeatWorker);
                            heartbeatWorker = null;
                        }
                        EventBus.publish(CONSTANTS.EVENTS.LOG, {
                            message: "✅ 页面已返回前台,继续学习",
                            type: "info"
                        });
                    }
                };
                document.removeEventListener("visibilitychange", handleVisibilityChange);
                document.addEventListener("visibilitychange", handleVisibilityChange);
                console.log(`[CbeadPlayerFlow] ✅ 步骤 7/7: 监听器设置完成`);
                console.log(`[CbeadPlayerFlow] ========== 开始播放,等待完成 ==========`);
                let heartbeatWorker = null;
                return new Promise((resolve, reject) => {
                    let chapterCompletedDetected = false;
                    currentPlayer.element.addEventListener("ended", async () => {
                        console.log(`[CbeadPlayerFlow] ========== 播放完成 ==========`);
                        console.log(`[CbeadPlayerFlow] ✅ 视频播放完成!`);
                        if (heartbeatWorker) {
                            CbeadHeartbeatWorker.stop(heartbeatWorker);
                            heartbeatWorker = null;
                        }
                        if (wakeLock) {
                            wakeLock.release();
                            wakeLock = null;
                        }
                        document.removeEventListener("visibilitychange", handleVisibilityChange);
                        cleanup();
                        console.log(`[CbeadPlayerFlow] ⏳ 等待页面更新完成状态...`);
                        EventBus.publish(CONSTANTS.EVENTS.LOG, {
                            message: "⏳ 等待服务端记录学习进度...",
                            type: "info"
                        });
                        let serverConfirmed = false;
                        const POLL_INTERVAL = 5e3;
                        const MAX_WAIT = 12e4;
                        const startTime = Date.now();
                        await new Promise(resolveWait => {
                            const pollTimer = setInterval(async () => {
                                if (Date.now() - startTime > MAX_WAIT) {
                                    clearInterval(pollTimer);
                                    console.warn(`[CbeadPlayerFlow] ⚠️ 等待服务端确认超时 (${MAX_WAIT}ms)`);
                                    resolveWait();
                                    return;
                                }
                                const chapterProgress = await CbeadPlayer.extractChapterProgress(false);
                                if (chapterProgress && chapterProgress.allCompleted) {
                                    console.log(`[CbeadPlayerFlow] ✅ 所有章节已完成 (${chapterProgress.completed}/${chapterProgress.total})`);
                                    serverConfirmed = true;
                                    chapterCompletedDetected = true;
                                    clearInterval(pollTimer);
                                    resolveWait();
                                } else if (chapterProgress && currentChapterIndex !== null) {
                                    const currentChapter = chapterProgress.chapters.find(ch => ch.index === currentChapterIndex);
                                    if (currentChapter && currentChapter.status === "completed") {
                                        console.log(`[CbeadPlayerFlow] ✅ 当前章节已完成 (${currentChapter.title})`);
                                        serverConfirmed = true;
                                        chapterCompletedDetected = true;
                                        clearInterval(pollTimer);
                                        resolveWait();
                                    }
                                }
                            }, POLL_INTERVAL);
                        });
                        if (serverConfirmed) {
                            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                                message: "✅ 课程学习完成!",
                                type: "success"
                            });
                        } else {
                            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                                message: "⚠️ 视频已播完,但服务端进度尚未更新",
                                type: "warn"
                            });
                        }
                        const finalWatched = Math.round(duration) - Math.round(currentTime);
                        resolve({
                            success: true,
                            method: "real_playback",
                            duration: Math.round(duration),
                            watched: finalWatched,
                            chapterName: chapterName,
                            chapterCompleted: chapterCompletedDetected,
                            earlyTermination: chapterCompletedDetected,
                            reason: serverConfirmed ? "服务端确认完成" : "视频播放完成(服务端未确认)"
                        });
                    }, {
                        once: true
                    });
                    currentPlayer.element.addEventListener("error", error => {
                        console.error(`[CbeadPlayerFlow] 播放错误:`, error);
                        if (heartbeatWorker) {
                            CbeadHeartbeatWorker.stop(heartbeatWorker);
                            heartbeatWorker = null;
                        }
                        if (wakeLock) wakeLock.release();
                        document.removeEventListener("visibilitychange", handleVisibilityChange);
                        cleanup();
                        reject(new Error("视频播放出错"));
                    }, {
                        once: true
                    });
                    timerManager.setInterval(async () => {
                        if (learningState.isFailed()) {
                            console.warn(`[CbeadPlayerFlow] 🚨 检测到课程失败,立即停止播放`);
                            if (heartbeatWorker) {
                                CbeadHeartbeatWorker.stop(heartbeatWorker);
                                heartbeatWorker = null;
                            }
                            if (wakeLock) wakeLock.release();
                            currentPlayer.pause();
                            cleanup();
                            reject(new Error(`进度上报失败: ${learningState.getFailureReason()}`));
                            return;
                        }
                        const currentTime = currentPlayer.getCurrentTime();
                        const serverProgress = await CbeadPlayer.getServerProgress(currentChapterIndex);
                        console.log(`[CbeadPlayerFlow] 📊 已播放: ${Math.round(currentTime)}秒 / ${Math.round(duration)}秒 (服务器进度: ${serverProgress !== null ? serverProgress + "%" : "N/A"})`);
                        if (serverProgress !== null) {
                            EventBus.publish(CONSTANTS.EVENTS.PROGRESS_UPDATE, serverProgress);
                        }
                        if (currentPlayer.element.paused) {
                            console.warn(`[CbeadPlayerFlow] ⚠️ 检测到播放暂停,尝试恢复播放...`);
                            const playPromise = currentPlayer.play();
                            if (playPromise !== undefined) {
                                playPromise.then(() => {
                                    console.log(`[CbeadPlayerFlow] ✅ 播放已恢复`);
                                }).catch(error => {
                                    console.warn(`[CbeadPlayerFlow] ⚠️ 恢复播放失败: ${error.message}`);
                                    if (document.hidden && "Notification" in window && notificationPermission === "granted") {
                                        try {
                                            new Notification("学习暂停提醒", {
                                                body: "⚠️ 视频已暂停!请返回前台继续学习。",
                                                icon: "⚠️",
                                                requireInteraction: true
                                            });
                                        } catch (err) {
                                            console.warn(`[CbeadPlayerFlow] 发送通知失败:`, err);
                                        }
                                    }
                                });
                            }
                        }
                    }, CBEAD_CONSTANTS.TIMING.PROGRESS_REPORT_INTERVAL);
                    timerManager.setInterval(async () => {
                        if (learningState.isFailed()) {
                            console.warn(`[CbeadPlayerFlow] 🚨 检测到课程失败,立即停止播放`);
                            if (wakeLock) wakeLock.release();
                            currentPlayer.pause();
                            cleanup();
                            reject(new Error(`进度上报失败: ${learningState.getFailureReason()}`));
                            return;
                        }
                        const serverProgress = await CbeadPlayer.getServerProgress(currentChapterIndex);
                        if (serverProgress !== null && serverProgress < CBEAD_CONSTANTS.THRESHOLDS.CHAPTER_CHECK_MIN_PROGRESS) {
                            return;
                        }
                        console.log(`[CbeadPlayerFlow] 🔍 检查章节状态 (服务器进度: ${serverProgress}%)...`);
                        const chapterProgress = await CbeadPlayer.extractChapterProgress(false);
                        if (chapterProgress && chapterProgress.completed === chapterProgress.total) {
                            console.log(`[CbeadPlayerFlow] 🎉 检测到所有章节已完成!`);
                            chapterCompletedDetected = true;
                            if (heartbeatWorker) {
                                CbeadHeartbeatWorker.stop(heartbeatWorker);
                                heartbeatWorker = null;
                            }
                            if (wakeLock) wakeLock.release();
                            currentPlayer.pause();
                            currentPlayer.setCurrentTime(duration);
                            cleanup();
                            resolve({
                                success: true,
                                method: "real_playback",
                                duration: Math.round(duration),
                                watched: Math.round(currentTime),
                                chapterName: chapterName,
                                chapterCompleted: true,
                                earlyTermination: true,
                                reason: "所有章节已完成",
                                savedPercent: 100 - (serverProgress || 0)
                            });
                        }
                        if (chapterProgress && currentChapterIndex !== null) {
                            const currentChapter = chapterProgress.chapters.find(ch => ch.index === currentChapterIndex);
                            if (currentChapter && currentChapter.status === "completed") {
                                console.log(`[CbeadPlayerFlow] 🎉 检测到章节状态变化: 章节 ${currentChapterIndex} 已完成!`);
                                chapterCompletedDetected = true;
                                if (heartbeatWorker) {
                                    CbeadHeartbeatWorker.stop(heartbeatWorker);
                                    heartbeatWorker = null;
                                }
                                if (wakeLock) wakeLock.release();
                                currentPlayer.pause();
                                currentPlayer.setCurrentTime(duration);
                                cleanup();
                                resolve({
                                    success: true,
                                    method: "real_playback",
                                    duration: Math.round(duration),
                                    watched: Math.round(currentTime),
                                    chapterName: chapterName,
                                    chapterCompleted: true,
                                    earlyTermination: true,
                                    reason: "章节状态变更为已完成",
                                    savedPercent: 100 - (serverProgress || 0)
                                });
                            }
                        }
                    }, CBEAD_CONSTANTS.TIMING.CHAPTER_CHECK_INTERVAL);
                });
            } catch (error) {
                cleanup();
                console.error(`[CbeadPlayerFlow] 学习失败: ${course.title}`, error);
                throw error;
            }
        }
    };
    const CbeadHandler = {
        PAGE_TYPES: CBEAD_CONSTANTS.PAGE_TYPES,
        SELECTORS: {
            COURSE_ITEMS: [ ".activity-stage .list li", ".list-item", ".activity-list .list-item" ],
            ENTER_BTN: ".study-btn",
            PLAYER_CONTAINER: ".player-content",
            VIDEO_ELEMENT: "video",
            CHAPTER_CONTAINER: ".course-side-catalog",
            BRANCH_LIST: {
                CONTAINER: ".activity-main-area .vertical",
                ITEM: ".list-item",
                PAGINATION: ".e-pagination-box .zxy-pagination",
                NEXT_BTN: ".zxy-pagination-item-next",
                TAG_CONTAINER: ".label .tag-list",
                TAG_BTN: ".label-btn"
            }
        },
        identifyPage: createPageDetector({
            pathPatterns: CBEAD_CONSTANTS.PATH_PATTERNS,
            pageTypes: CBEAD_CONSTANTS.PAGE_TYPES,
            domSelectors: [ ".player-content", ".new-global-height", "video" ],
            domMatchType: "PLAYER"
        }),
        scanCoursesFromColumnPage() {
            return CbeadScanner.scanCoursesFromColumnPage();
        },
        getSortedLearningList(courses) {
            return CbeadScanner.getSortedLearningList(courses);
        },
        extractPlayerParams() {
            return CbeadProgressManager.extractPlayerParams();
        },
        async isCourseReallyCompleted() {
            return await CbeadPlayer.isCourseReallyCompleted();
        },
        extractPageCourseTitle() {
            const selectors = [ ".course-title", ".video-course-title", ".detail-title", ".study-detail-title", "h1.title", ".header-title", ".course-name", '[class*="title"]' ];
            for (const selector of selectors) {
                const el = document.querySelector(selector);
                if (el && el.textContent?.trim()) {
                    const text = el.textContent.trim();
                    if (text.length > 5 && text.length < 200 && !text.includes("中国干部网络学院")) {
                        return text;
                    }
                }
            }
            const pageText = document.body?.textContent || "";
            const courseNameMatch = pageText.match(/《([^》]+)》/);
            if (courseNameMatch && courseNameMatch[1]) {
                return courseNameMatch[1];
            }
            return null;
        },
        async learnWithRealPlayback(course) {
            return await CbeadPlayerFlow.learnWithRealPlayback(course);
        },
        returnToList(returnUrl) {
            return CbeadProgressManager.returnToList(returnUrl);
        },
        isCbeadMode() {
            return CONFIG.CBEAD_MODE === true;
        },
        init() {
            if (!this.isCbeadMode()) return;
            console.log(`[CbeadHandler] 检测到页面类型: ${this.identifyPage()}`);
        }
    };
    const gwypxPages = CONSTANTS.PAGE_CONFIG.GWYPX;
    const GWYPX_CONSTANTS = {
        PATH_PATTERNS: Object.fromEntries(Object.entries(gwypxPages).map(([k, v]) => [ k, v.path ])),
        PAGE_TYPES: {
            ...Object.fromEntries(Object.entries(gwypxPages).map(([k, v]) => [ k, v.type ])),
            UNKNOWN: "unknown"
        },
        PAGE_TYPE_WHITELIST: Object.keys(gwypxPages).filter(k => gwypxPages[k].whitelist)
    };
    const GwypxHandler = {
        PAGE_TYPES: GWYPX_CONSTANTS.PAGE_TYPES,
        identifyPage: createPageDetector({
            pathPatterns: GWYPX_CONSTANTS.PATH_PATTERNS,
            pageTypes: GWYPX_CONSTANTS.PAGE_TYPES,
            domSelectors: [ "video.vjs-tech", ".prism-player", ".aliplayer-container" ],
            domMatchType: "PLAYER"
        }),
        init() {
            if (!CONFIG.GWYPX_MODE) return;
            console.log("[GwypxHandler] 初始化党校分院处理器");
        }
    };
    const DEFAULT_MESSAGES = {
        default: "⚠️ 当前页面不支持自动学习。请进入课程播放页或列表页。"
    };
    function createPageValidator(options) {
        const {whitelist: whitelist, pageTypes: pageTypes, customMessages: customMessages = {}} = options;
        function validate(pageType) {
            const allowedTypes = whitelist.map(key => pageTypes[key]);
            if (allowedTypes.includes(pageType)) {
                return true;
            }
            const message = customMessages[pageType] || customMessages.default || DEFAULT_MESSAGES.default;
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: message,
                type: "warn"
            });
            EventBus.publish(CONSTANTS.EVENTS.STATUS_UPDATE, "页面不支持");
            return false;
        }
        return {
            validate: validate
        };
    }
    const SessionHelper = {
        get(key, def = null) {
            try {
                return sessionStorage.getItem(key) ?? def;
            } catch {
                return def;
            }
        },
        set(key, value) {
            try {
                sessionStorage.setItem(key, value);
            } catch {}
        },
        remove(key) {
            try {
                sessionStorage.removeItem(key);
            } catch {}
        }
    };
    const UI_CSS_CONTENT = '#api-learner-panel { all: initial !important; position: fixed !important; bottom: 20px !important; right: 20px !important; left: auto !important; top: auto !important; width: 400px !important; height: auto !important; min-height: 200px !important; margin: 0 !important; padding: 0 !important; transform: none !important; zoom: 1 !important; background: #ffffff !important; border: 1px solid #dddddd !important; border-radius: 8px !important; box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important; z-index: 2147483647 !important; font-family: -apple-system, "SF Pro Text", "PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif !important; font-size: 14px !important; color: #333333 !important; line-height: 1.6 !important; text-align: left !important; box-sizing: border-box !important; display: flex !important; flex-direction: column !important; overflow: hidden !important; } #api-learner-panel * { all: unset !important; box-sizing: border-box !important; font-family: inherit !important; background: transparent !important; margin: 0 !important; padding: 0 !important; border: none !important; } #api-learner-panel #learner-progress-inner .progress-shine { position: absolute !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; background: linear-gradient( 90deg, transparent 0%, rgba(255, 255, 255, 0.3) 50%, transparent 100% ) !important; animation: progress-shine 0.8s linear infinite !important; pointer-events: none !important; display: none !important; } #api-learner-panel *:before, #api-learner-panel *:after { content: none !important; display: none !important; } #api-learner-panel .header { display: block !important; background: #f7f7f7 !important; padding: 10px 15px !important; font-weight: 600 !important; border-bottom: 1px solid #dddddd !important; width: 100% !important; letter-spacing: 0.3px !important; } #api-learner-panel .content { display: block !important; padding: 15px !important; width: 100% !important; background: #ffffff !important; flex-grow: 1 !important; } #api-learner-panel .status-row { display: flex !important; align-items: center !important; gap: 8px !important; margin-bottom: 12px !important; font-weight: 600 !important; font-size: 15px !important; } #api-learner-panel #learner-status { flex: 1 !important; font-weight: 500 !important; letter-spacing: 0.2px !important; display: flex !important; align-items: center !important; gap: 4px !important; overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important; } #api-learner-panel #learner-status .stat-num { font-family: "SF Mono", "Monaco", "Menlo", "Consolas", monospace !important; color: #666666 !important; } #api-learner-panel #learner-status .course-title { font-weight: 400 !important; color: #333333 !important; max-width: 180px !important; overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important; } #api-learner-panel .status-indicator { width: 12px !important; height: 12px !important; border-radius: 50% !important; background: #9ca3af !important; position: relative !important; flex-shrink: 0 !important; } #api-learner-panel .status-indicator[data-state="idle"] { background: #9ca3af !important; } #api-learner-panel .status-indicator[data-state="running"] { background: #22c55e !important; animation: status-pulse 1.5s ease-in-out infinite !important; } #api-learner-panel .status-indicator[data-state="completed"] { background: transparent !important; width: 16px !important; height: 16px !important; border: 2px solid #22c55e !important; transform: rotate(45deg) !important; } #api-learner-panel .status-indicator[data-state="completed"]::before { content: \'\' !important; position: absolute !important; top: 8px !important; left: 4px !important; width: 4px !important; height: 6px !important; background: #22c55e !important; transform: rotate(-45deg) !important; } #api-learner-panel .status-indicator[data-state="completed"]::after { content: \'\' !important; position: absolute !important; top: 10px !important; left: 0 !important; width: 6px !important; height: 4px !important; background: #22c55e !important; transform: rotate(-45deg) !important; } #api-learner-panel .status-indicator[data-state="error"] { background: #ef4444 !important; animation: status-shake 0.5s ease-in-out !important; } @keyframes status-pulse { 0%, 100% { opacity: 1 !important; box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.6) !important; transform: scale(1) !important; } 50% { opacity: 0.3 !important; box-shadow: 0 0 0 8px rgba(34, 197, 94, 0) !important; transform: scale(0.7) !important; } } @keyframes status-shake { 0%, 100% { transform: translateX(0); } 20% { transform: translateX(-3px); } 40% { transform: translateX(3px); } 60% { transform: translateX(-2px); } 80% { transform: translateX(2px); } } #api-learner-panel .status { display: block !important; margin-bottom: 10px !important; font-weight: 600 !important; font-size: 15px !important; } #api-learner-panel .warning-box { display: block !important; margin-bottom: 10px !important; padding: 8px 12px !important; background: #fff3cd !important; border: 1px solid #ffc107 !important; border-radius: 4px !important; color: #856404 !important; font-size: 13px !important; line-height: 1.4 !important; } #api-learner-panel .statistics { display: flex !important; justify-content: space-between !important; margin-bottom: 10px !important; padding: 8px !important; background: #f9f9f9 !important; border-radius: 4px !important; font-size: 12px !important; width: 100% !important; } #api-learner-panel .stat-item { display: block !important; text-align: center !important; flex: 1 !important; font-weight: 500 !important; } #api-learner-panel .progress-bar { display: block !important; height: 8px !important; background: #f0f0f0 !important; border: none !important; border-radius: 4px !important; overflow: hidden !important; margin-bottom: 10px !important; width: 100% !important; } #api-learner-panel #learner-progress-inner { display: block !important; height: 100% !important; width: auto; background: #4caf50 !important; border-radius: 4px !important; transition: width 0.3s ease !important; } #api-learner-panel #learner-progress-inner[data-animate="true"] { background: repeating-linear-gradient( -45deg, #4caf50, #4caf50 8px, #66bb6a 8px, #66bb6a 16px ) !important; background-size: 22.63px 100% !important; background-attachment: fixed !important; animation: progress-stripe 0.5s linear infinite !important; } @keyframes progress-stripe { from { background-position: 0 0; } to { background-position: 22.63px 0; } } #api-learner-panel #learner-progress-inner[data-state="completed"] { background: #22c55e !important; transition: width 0.5s ease, background 0.3s ease !important; } #api-learner-panel #learner-progress-inner[data-state="idle"] { background: #9ca3af !important; width: 0% !important; } @keyframes progress-stripes { 0% { background-position: 0 0; } 100% { background-position: 22.63px 0; } } #api-learner-panel .log-container { display: block !important; height: 150px !important; overflow-y: auto !important; background: #fafafa !important; padding: 8px 10px !important; border: 1px solid #eeeeee !important; border-radius: 4px !important; font-size: 11px !important; line-height: 1.6 !important; font-family: "SF Mono", "Monaco", "Menlo", "Consolas", monospace !important; width: 100% !important; } #api-learner-panel .log-entry { display: flex !important; align-items: baseline !important; margin-bottom: 4px !important; padding: 2px 0 !important; word-break: break-all !important; } #api-learner-panel .log-dot { display: inline-block !important; width: 5px !important; height: 5px !important; border-radius: 50% !important; margin-right: 8px !important; flex-shrink: 0 !important; } #api-learner-panel .log-content { flex: 1 !important; display: flex !important; flex-wrap: wrap !important; align-items: baseline !important; gap: 4px !important; } #api-learner-panel .log-time { color: #999999 !important; font-size: 10px !important; } #api-learner-panel .log-tag { display: inline-block !important; padding: 0 4px !important; border-radius: 3px !important; font-size: 10px !important; font-weight: 500 !important; font-family: "SF Mono", "Monaco", monospace !important; } #api-learner-panel .log-entry.info .log-dot, #api-learner-panel .log-entry.info .log-tag { background: #2196f3 !important; color: #2196f3 !important; } #api-learner-panel .log-entry.success .log-dot, #api-learner-panel .log-entry.success .log-tag { background: #4caf50 !important; color: #4caf50 !important; } #api-learner-panel .log-entry.error .log-dot, #api-learner-panel .log-entry.error .log-tag { background: #f44336 !important; color: #f44336 !important; } #api-learner-panel .log-entry.warn .log-dot, #api-learner-panel .log-entry.warn .log-tag { background: #ff9800 !important; color: #ff9800 !important; } #api-learner-panel .log-entry.info .log-tag { background: #e3f2fd !important; } #api-learner-panel .log-entry.success .log-tag { background: #e8f5e9 !important; } #api-learner-panel .log-entry.error .log-tag { background: #ffebee !important; } #api-learner-panel .log-entry.warn .log-tag { background: #fff3e0 !important; } #api-learner-panel .log-container::-webkit-scrollbar { width: 5px !important; } #api-learner-panel .log-container::-webkit-scrollbar-track { background: transparent !important; } #api-learner-panel .log-container::-webkit-scrollbar-thumb { background: #dddddd !important; border-radius: 3px !important; } #api-learner-panel .footer { color: #60a5fa !important; } #api-learner-panel .log-entry.success .log-tag { background: rgba(34, 197, 94, 0.15) !important; color: #4ade80 !important; } #api-learner-panel .log-entry.error .log-tag { background: rgba(239, 68, 68, 0.15) !important; color: #f87171 !important; } #api-learner-panel .log-entry.warn .log-tag { background: rgba(249, 115, 22, 0.15) !important; color: #fb923c !important; } #api-learner-panel .log-container::-webkit-scrollbar { width: 6px !important; } #api-learner-panel .log-container::-webkit-scrollbar-track { background: transparent !important; } #api-learner-panel .log-container::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.15) !important; border-radius: 3px !important; } #api-learner-panel .log-container::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.25) !important; } #api-learner-panel .footer { display: block !important; padding: 10px 15px !important; border-top: 1px solid #dddddd !important; text-align: center !important; width: 100% !important; background: #ffffff !important; } #api-learner-panel button { display: inline-block !important; padding: 8px 16px !important; border-radius: 4px !important; cursor: pointer !important; font-size: 13px !important; font-weight: 600 !important; line-height: 1.2 !important; background-color: #2196f3 !important; color: #ffffff !important; margin-left: 8px !important; transition: all 0.2s ease !important; border: none !important; } #api-learner-panel button#toggle-learning-btn[data-state="running"] { background-color: #f44336 !important; } #api-learner-panel button:hover { opacity: 0.9 !important; } #api-learner-panel button:active { transform: translateY(1px) !important; } #api-learner-panel .incompatible-banner { display: flex !important; align-items: center !important; padding: 12px 15px !important; background: #fff9e6 !important; border-bottom: 1px solid #ffe082 !important; } #api-learner-panel .warning-box .warning-icon { display: inline-block !important; width: 14px !important; height: 14px !important; background: #d97706 !important; border-radius: 50% !important; margin-right: 6px !important; vertical-align: middle !important; position: relative !important; } #api-learner-panel .warning-box .warning-icon::after { content: \'!\' !important; position: absolute !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; color: white !important; font-size: 10px !important; font-weight: bold !important; line-height: 1 !important; } #api-learner-panel .incompatible-banner .warning-icon { display: inline-block !important; width: 16px !important; height: 16px !important; background: #f57c00 !important; border-radius: 50% !important; margin-right: 8px !important; position: relative !important; flex-shrink: 0 !important; } #api-learner-panel .incompatible-banner .warning-icon::after { content: \'!\' !important; position: absolute !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; color: white !important; font-size: 11px !important; font-weight: bold !important; line-height: 1 !important; } #api-learner-panel .incompatible-banner .warning-content { flex: 1 !important; } #api-learner-panel .incompatible-banner .warning-title { font-size: 14px !important; font-weight: bold !important; color: #f57c00 !important; margin-bottom: 3px !important; } #api-learner-panel .incompatible-banner .warning-message { font-size: 12px !important; color: #f57c00 !important; line-height: 1.4 !important; opacity: 0.85 !important; } #api-learner-panel.incompatible-mode { border: 2px solid #ffe082 !important; box-shadow: 0 2px 8px rgba(245, 124, 0, 0.15) !important; } #api-learner-panel.incompatible-mode .header { background: #fffde7 !important; color: #f57c00 !important; }';
    const UI = {
        logs: [],
        logBuffer: [],
        logUpdateTimeout: null,
        statistics: {
            total: 0,
            completed: 0,
            learned: 0,
            failed: 0,
            skipped: 0
        },
        createPanel: () => {
            const panel = document.createElement("div");
            panel.id = "api-learner-panel";
            panel.innerHTML = `\n            <div class="header">\n                cela学习助手\n            </div>\n            <div class="content">\n                <div class="status-row">\n                    <div class="status-indicator" id="status-indicator" data-state="idle"></div>\n                    <span id="learner-status">就绪</span>\n                </div>\n                ${CONFIG.WARNING_BATCH_LEARNING && CONFIG.GWYPX_MODE ? `\n                <div class="warning-box">\n                    <span class="warning-icon"></span>\n                    党校分院:慎用批量学习功能,可能被系统检测\n                </div>\n                ` : ""}\n                <div class="statistics">\n                    <div class="stat-item">总计: <span id="stat-total">0</span></div>\n                    <div class="stat-item">已完成: <span id="stat-completed">0</span></div>\n                    <div class="stat-item">新学习: <span id="stat-learned">0</span></div>\n                    <div class="stat-item">失败: <span id="stat-failed">0</span></div>\n                    <div class="stat-item">跳过: <span id="stat-skipped">0</span></div>\n                </div>\n                <div class="progress-bar"><div id="learner-progress-inner" data-state="idle" style="width: 0% !important;"></div></div>\n\n                <div class="log-container"></div>\n            </div>\n            <div class="footer">\n                <button id="toggle-learning-btn" data-state="stopped">开始学习</button>\n            </div>\n        `;
            document.body.appendChild(panel);
            UI.addStyles();
            UI.initEventListeners();
        },
        log: function(message, type = "info") {
            const timestamp = (new Date).toLocaleTimeString();
            const logMessage = `[${timestamp}] ${message}`;
            this.logBuffer.push({
                message: logMessage,
                type: type
            });
            if (this.logUpdateTimeout) clearTimeout(this.logUpdateTimeout);
            this.logUpdateTimeout = setTimeout(() => this.flushLogBuffer(), CONSTANTS.UI_LIMITS.LOG_FLUSH_DELAY);
            if (typeof CONFIG !== "undefined" && CONFIG.DEBUG_MODE) {
                const debugMessage = `[API Learner Debug] ${logMessage}`;
                console.log(debugMessage);
                this.logs.push(debugMessage);
            }
        },
        initEventListeners: function() {
            EventBus.subscribe(CONSTANTS.EVENTS.LOG, ({message: message, type: type}) => this.log(message, type));
            EventBus.subscribe(CONSTANTS.EVENTS.STATUS_UPDATE, status => this.updateStatus(status));
            EventBus.subscribe(CONSTANTS.EVENTS.PROGRESS_UPDATE, progress => this.updateProgress(progress));
            EventBus.subscribe(CONSTANTS.EVENTS.STATISTICS_UPDATE, stats => this.updateStatistics(stats));
            EventBus.subscribe(CONSTANTS.EVENTS.LEARNING_START, () => {
                const toggleBtn = document.getElementById(CONSTANTS.SELECTORS.TOGGLE_BTN.replace("#", ""));
                if (toggleBtn) {
                    toggleBtn.setAttribute("data-state", "running");
                    toggleBtn.textContent = "停止学习";
                }
                const progressInner = document.getElementById("learner-progress-inner");
                if (progressInner) {
                    progressInner.setAttribute("data-state", "running");
                    progressInner.setAttribute("data-animate", "true");
                }
            });
            EventBus.subscribe(CONSTANTS.EVENTS.LEARNING_STOP, () => {
                const toggleBtn = document.getElementById(CONSTANTS.SELECTORS.TOGGLE_BTN.replace("#", ""));
                if (toggleBtn) {
                    toggleBtn.setAttribute("data-state", "stopped");
                    toggleBtn.textContent = "开始学习";
                }
                UI.updateProgress(0);
                const progressInner = document.getElementById("learner-progress-inner");
                if (progressInner) {
                    progressInner.setAttribute("data-state", "idle");
                    progressInner.removeAttribute("data-animate");
                }
            });
            EventBus.subscribe(CONSTANTS.EVENTS.COURSE_COMPLETE, () => {
                const progressInner = document.getElementById("learner-progress-inner");
                if (progressInner) {
                    progressInner.setAttribute("data-state", "completed");
                    progressInner.removeAttribute("data-animate");
                }
            });
            EventBus.subscribe(CONSTANTS.EVENTS.COURSE_START, ({course: course, index: index, total: total}) => {
                this.log(`[START] 处理第 ${index}/${total} 门课程: ${course.title}`);
            });
            EventBus.subscribe(CONSTANTS.EVENTS.COURSE_COMPLETE, ({course: course}) => {
                this.log(`[OK] 课程学习完成: ${course.title}`, "success");
            });
            EventBus.subscribe(CONSTANTS.EVENTS.COURSE_SKIP, ({course: course, reason: reason}) => {
                this.log(`[SKIP] 课程已完成,跳过: ${course.title} (${reason})`, "success");
            });
            EventBus.subscribe(CONSTANTS.EVENTS.COURSE_ERROR, ({course: course, reason: reason}) => {
                this.log(`[ERR] 课程处理失败: ${course.title} - ${reason}`, "error");
            });
            EventBus.subscribe(CONSTANTS.EVENTS.PROGRESS_REPORT, _data => {});
            EventBus.subscribe(CONSTANTS.EVENTS.PROGRESS_SUCCESS, ({message: message, _progress: _progress}) => {
                this.log(message, "success");
            });
            EventBus.subscribe(CONSTANTS.EVENTS.PROGRESS_ERROR, ({message: message}) => {
                this.log(message, "warn");
            });
        },
        flushLogBuffer: function() {
            const logContainer = document.querySelector(CONSTANTS.SELECTORS.LOG_CONTAINER);
            if (!logContainer || this.logBuffer.length === 0) return;
            const tagLabels = {
                START: "INIT",
                STOP: "STOP",
                SKIP: "SKIP",
                ERR: "ERR",
                WARN: "WARN",
                OK: "OK",
                PROG: "PROG",
                GET: "GET",
                BATCH: "BATCH",
                SPEED: "FAST",
                SYNC: "SYNC",
                SIGNAL: "SIG"
            };
            const fragment = document.createDocumentFragment();
            this.logBuffer.forEach(log => {
                const logEntry = document.createElement("div");
                logEntry.className = `log-entry ${log.type}`;
                const dot = document.createElement("span");
                dot.className = "log-dot";
                const content = document.createElement("span");
                content.className = "log-content";
                const tagMatch = log.message.match(/^\[([A-Z]+)\]\s*(.*)/);
                if (tagMatch) {
                    const tagType = tagMatch[1];
                    const tagLabel = tagLabels[tagType] || tagType;
                    const messageText = tagMatch[2];
                    const time = (new Date).toLocaleTimeString("zh-CN", {
                        hour12: false
                    });
                    const tagSpan = document.createElement("span");
                    tagSpan.className = "log-tag";
                    tagSpan.textContent = tagLabel;
                    const msgText = document.createTextNode(messageText);
                    content.appendChild(document.createTextNode(`[${time}] `));
                    content.appendChild(tagSpan);
                    content.appendChild(msgText);
                } else {
                    content.textContent = log.message;
                }
                logEntry.appendChild(dot);
                logEntry.appendChild(content);
                fragment.appendChild(logEntry);
            });
            logContainer.appendChild(fragment);
            logContainer.scrollTop = logContainer.scrollHeight;
            const entries = logContainer.querySelectorAll(".log-entry");
            if (entries.length > CONSTANTS.UI_LIMITS.MAX_LOG_ENTRIES) {
                for (let i = 0; i < entries.length - CONSTANTS.UI_LIMITS.MAX_LOG_ENTRIES; i++) {
                    entries[i].remove();
                }
            }
            this.logBuffer = [];
        },
        updateStatus: status => {
            const statusEl = document.getElementById(CONSTANTS.SELECTORS.STATUS_LABEL.replace("#", ""));
            const indicator = document.getElementById("status-indicator");
            if (statusEl) {
                let html = status || "";
                html = html.replace(/【([^【】]+)】/g, '<span class="course-title">$1</span>');
                html = html.replace(/(\d+\s*\/\s*\d+)/g, '<span class="stat-num">$1</span>');
                statusEl.innerHTML = html;
            }
            if (indicator) {
                const statusLower = (status || "").toLowerCase();
                if (statusLower.includes("学习中") || statusLower.includes("运行")) {
                    indicator.setAttribute("data-state", "running");
                } else if (statusLower.includes("完成") || statusLower.includes("成功")) {
                    indicator.setAttribute("data-state", "completed");
                } else if (statusLower.includes("停止") || statusLower.includes("暂停") || statusLower.includes("错误")) {
                    indicator.setAttribute("data-state", "error");
                } else {
                    indicator.setAttribute("data-state", "idle");
                }
            }
        },
        updateProgress: percentage => {
            const progressInner = document.getElementById(CONSTANTS.SELECTORS.PROGRESS_INNER.replace("#", ""));
            if (!progressInner) {
                console.log("[UI] 进度条元素未找到");
                return;
            }
            let percent;
            if (typeof percentage === "number") {
                percent = percentage;
            } else if (typeof percentage === "object" && percentage !== null) {
                percent = percentage.percent || percentage.percentage || 0;
            } else {
                percent = 0;
            }
            percent = Math.max(0, Math.min(100, percent));
            progressInner.style.setProperty("width", `${percent}%`, "important");
            const computedStyle = window.getComputedStyle(progressInner);
            console.log(`[UI] 更新进度条: ${percent}%, 实际宽度: ${computedStyle.width}, 显示: ${computedStyle.display}`);
        },
        updateStatistics: stats => {
            Object.assign(UI.statistics, stats);
            const totalEl = document.getElementById(CONSTANTS.SELECTORS.STAT_TOTAL.replace("#", ""));
            const completedEl = document.getElementById(CONSTANTS.SELECTORS.STAT_COMPLETED.replace("#", ""));
            const learnedEl = document.getElementById(CONSTANTS.SELECTORS.STAT_LEARNED.replace("#", ""));
            const failedEl = document.getElementById(CONSTANTS.SELECTORS.STAT_FAILED.replace("#", ""));
            const skippedEl = document.getElementById(CONSTANTS.SELECTORS.STAT_SKIPPED.replace("#", ""));
            if (totalEl) totalEl.textContent = UI.statistics.total;
            if (completedEl) completedEl.textContent = UI.statistics.completed;
            if (learnedEl) learnedEl.textContent = UI.statistics.learned;
            if (failedEl) failedEl.textContent = UI.statistics.failed;
            if (skippedEl) skippedEl.textContent = UI.statistics.skipped;
        },
        addStyles: () => {
            {
                const styleSheet = document.createElement("style");
                styleSheet.type = "text/css";
                styleSheet.textContent = UI_CSS_CONTENT;
                document.head.appendChild(styleSheet);
            }
        },
        setIncompatible: reason => {
            UI.updateStatus("当前页面暂不兼容");
            UI.log(`[兼容性检查] ${reason}`, "warn");
            const panel = document.getElementById("api-learner-panel");
            if (!panel) return;
            panel.classList.add("incompatible-mode");
            if (panel.querySelector(".incompatible-banner")) return;
            const warningBanner = document.createElement("div");
            warningBanner.className = "incompatible-banner";
            warningBanner.innerHTML = `\n            <div class="warning-icon"></div>\n            <div class="warning-content">\n                <div class="warning-title">当前环境暂不支持</div>\n                <div class="warning-message">${reason}</div>\n            </div>\n        `;
            const header = panel.querySelector(".header");
            if (header) {
                panel.insertBefore(warningBanner, header);
            }
        },
        exportLogs: () => {
            if (UI.logs.length === 0) {
                alert("没有可导出的调试日志。");
                return;
            }
            const blob = new Blob([ UI.logs.join("\r\n") ], {
                type: "text/plain;charset=utf-8"
            });
            const url = URL.createObjectURL(blob);
            const a = document.createElement("a");
            a.href = url;
            a.download = `api_learner_debug_log_${(new Date).toISOString().slice(0, 19).replace(/:/g, "-")}.txt`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        },
        resetToggleButton(statusText = "学习完成") {
            const toggleBtn = document.getElementById(CONSTANTS.SELECTORS.TOGGLE_BTN.replace("#", ""));
            if (toggleBtn) {
                toggleBtn.setAttribute("data-state", "stopped");
                toggleBtn.textContent = "开始学习";
            }
            EventBus.publish(CONSTANTS.EVENTS.STATUS_UPDATE, statusText);
        }
    };
    let currentPlayerLearningId = null;
    const CbeadLearner = {
        get _pageValidator() {
            return createPageValidator({
                whitelist: CBEAD_CONSTANTS.PAGE_TYPE_WHITELIST,
                pageTypes: CBEAD_CONSTANTS.PAGE_TYPES,
                customMessages: {
                    home_v: "⚠️ 当前是展示主页页面,没有可学习的课程列表。请进入课程详情页或列表页。",
                    default: "⚠️ 当前页面不支持自动学习。请进入专题详情页或课程列表页。"
                }
            });
        },
        getCurrentPlayerLearningId() {
            return currentPlayerLearningId;
        },
        setCurrentPlayerLearningId(id) {
            const oldId = currentPlayerLearningId;
            currentPlayerLearningId = id;
            console.log(`[CbeadLearner] 📍 更新播放页学习任务ID: ${oldId || "none"} → ${id || "none"}`);
        },
        _validatePageType(pageType) {
            return this._pageValidator.validate(pageType);
        },
        async selectAndExecute() {
            if (!CONFIG.CBEAD_MODE) {
                return null;
            }
            const pageType = CbeadHandler.identifyPage();
            if (!this._validatePageType(pageType)) {
                return false;
            }
            const href = window.location.href;
            if (href.includes("study/course/detail")) {
                return await this._handlePlayerPage();
            }
            if (href.includes("branch-list-v")) {
                return await this._handleBranchListPage();
            }
            if (href.includes("/center/my/course")) {
                return await this._handleColumnPage();
            }
            if (href.includes("train-new/class-detail")) {
                return await this._handleColumnPage();
            }
            return null;
        },
        async _handlePlayerPage() {
            return await this.startPlayerFlow();
        },
        async _handleColumnPage() {
            const {findSignUpButton: findSignUpButton, getSignUpButtonText: getSignUpButtonText} = await Promise.resolve().then(function() {
                return domHelper;
            });
            const signUpButton = findSignUpButton();
            if (signUpButton) {
                const buttonText = getSignUpButtonText(signUpButton);
                const errorMsg = `🚫 该专栏/专题班未报名,无法开始自动学习。请先点击"${buttonText}"按钮完成报名。`;
                console.error(`[CbeadLearner] ${errorMsg}`);
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: errorMsg,
                    type: "error"
                });
                EventBus.publish(CONSTANTS.EVENTS.STATUS_UPDATE, "未报名");
                throw new Error("专栏/专题班未报名");
            }
            const sourceUrl = this._getSourceUrl();
            if (!sourceUrl) {
                this._saveSourceUrl();
                console.log(`[CbeadLearner] 📌 保存来源 URL: ${window.location.href}`);
            }
            await this.startBatchFlow();
            return true;
        },
        async _handleBranchListPage() {
            this._clearBranchListUrl();
            const sourceUrl = this._getSourceUrl();
            if (!sourceUrl) {
                console.log(`[CbeadLearner] 💡 进入列表页,请点击"开始学习"按钮开始批量学习`);
                return CONSTANTS.WAITING_FOR_USER;
            }
            console.log(`[CbeadLearner] 🔄 检测到批量学习流程,继续执行...`);
            await this.startBranchListFlow();
            return true;
        },
        async startPlayerFlow() {
            EventBus.publish(CONSTANTS.EVENTS.STATUS_UPDATE, "学习中");
            const playerParams = CbeadHandler.extractPlayerParams();
            const currentPageCourseId = playerParams?.uuid;
            if (currentPageCourseId && currentPlayerLearningId === currentPageCourseId) {
                console.log(`[CbeadLearner] ⏭️ 播放页学习任务已在进行中,跳过重复执行: ${currentPageCourseId.substring(0, 8)}...`);
                return false;
            }
            if (currentPageCourseId) {
                this.setCurrentPlayerLearningId(currentPageCourseId);
            }
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: "🎬 检测到企业分院播放页,直接处理当前视频",
                type: "info"
            });
            const isReallyCompleted = await CbeadHandler.isCourseReallyCompleted();
            if (isReallyCompleted) {
                this.setCurrentPlayerLearningId(null);
                return await this._handleAllChaptersCompleted();
            }
            return await this._learnCurrentVideo();
        },
        async _handleAllChaptersCompleted() {
            console.log("[CbeadLearner] ✅ 检测到所有章节已完成,跳过播放");
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: "✅ 该课程所有章节已完成!",
                type: "success"
            });
            UI.resetToggleButton("学习完成");
            return false;
        },
        async _learnCurrentVideo() {
            const playerParams = CbeadHandler.extractPlayerParams();
            if (!playerParams || !playerParams.uuid) {
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "❌ 无法从URL提取视频UUID",
                    type: "error"
                });
                return false;
            }
            const courseTitle = CbeadHandler.extractPageCourseTitle() || document.title;
            const currentCourse = {
                id: playerParams.uuid,
                courseId: playerParams.uuid,
                dsUnitId: playerParams.uuid,
                title: courseTitle,
                courseName: courseTitle,
                source: "cbead_current_video"
            };
            LearningState.reset();
            const success = await this._executeVideoLearning(currentCourse);
            if (success) {
                return await this._handleVideoCompleted(currentCourse);
            } else {
                await this._handleVideoFailed();
                return false;
            }
        },
        async _executeVideoLearning(course) {
            return await CbeadHandler.learnWithRealPlayback(course);
        },
        async _handleVideoCompleted(course) {
            const isReallyCompleted = await CbeadHandler.isCourseReallyCompleted();
            if (!isReallyCompleted) {
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "✅ 当前章节学习完成,继续下一章节...",
                    type: "info"
                });
                await new Promise(resolve => setTimeout(resolve, 1e3));
                const {Learner: Learner} = await Promise.resolve().then(function() {
                    return learner;
                });
                await Learner.startLearning();
                return true;
            }
            console.log("[CbeadLearner] ✅ 课程学习完成");
            this.setCurrentPlayerLearningId(null);
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: "✅ 当前视频学习完成!",
                type: "success"
            });
            const returnUrl = this._getSourceUrl();
            if (!returnUrl) {
                console.warn("[CbeadLearner] ⚠️ 未找到来源 URL,无法返回继续学习");
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "⚠️ 学习完成,但无法返回来源页面",
                    type: "warn"
                });
                UI.resetToggleButton("学习完成");
                return false;
            }
            console.log(`[CbeadLearner] 🔄 返回来源页面: ${returnUrl}`);
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: `🔄 返回列表页扫描继续...`,
                type: "info"
            });
            await new Promise(resolve => setTimeout(resolve, 2e3));
            if (returnUrl && returnUrl.startsWith("#")) {
                const baseUrl = window.location.href.split("#")[0];
                window.location.href = baseUrl + returnUrl;
            } else {
                window.location.href = returnUrl;
            }
            UI.resetToggleButton("学习完成");
            return false;
        },
        async _handleVideoFailed() {
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: "❌ 视频学习失败",
                type: "error"
            });
            UI.resetToggleButton("学习失败");
        },
        async startBatchFlow() {
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: "📋 检测到专题班/专栏,开始批量学习流程",
                type: "info"
            });
            const allCourses = await CbeadHandler.scanCoursesFromColumnPage();
            if (allCourses.length === 0) {
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "❌ 未找到课程",
                    type: "error"
                });
                return false;
            }
            const learningList = CbeadHandler.getSortedLearningList(allCourses);
            if (learningList.length === 0) {
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "✅ 所有课程已完成!",
                    type: "success"
                });
                return false;
            }
            const totalCourses = allCourses.length;
            const skippedCount = totalCourses - learningList.length;
            EventBus.publish(CONSTANTS.EVENTS.STATISTICS_UPDATE, {
                total: totalCourses,
                completed: skippedCount,
                learned: 0,
                failed: 0,
                skipped: skippedCount
            });
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: `📚 准备学习 ${learningList.length} 门课程`,
                type: "info"
            });
            const firstCourse = learningList[0];
            await this._navigateToCourse(firstCourse);
            return true;
        },
        async startBranchListFlow() {
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: "📋 检测到分支列表页,开始边扫描边学习",
                type: "info"
            });
            const scanPage = this._loadScanPage();
            let currentPage = scanPage;
            console.log(`[CbeadLearner] 📄 当前扫描页码: ${currentPage}`);
            const result = await this._scanAndLearnBranchList(currentPage);
            if (result && result.completed) {
                this._clearScanPage();
                this._clearSourceUrl();
                console.log(`[CbeadLearner] 🧹 清除来源 URL`);
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "✅ 所有课程已完成!",
                    type: "success"
                });
                UI.resetToggleButton("学习完成");
            } else if (result && result.continue) {
                this._saveScanPage(result.nextPage);
            }
            return true;
        },
        async _scanAndLearnBranchList(startPage) {
            const pageCourses = await CbeadScanner.scanCoursesFromBranchListPage(startPage);
            if (!pageCourses || pageCourses.length === 0) {
                console.log("[CbeadLearner] 当前页没有找到课程,停止扫描");
                return {
                    completed: true
                };
            }
            const incompleteCourses = pageCourses.filter(c => c.progress < 100);
            if (incompleteCourses.length > 0) {
                this._saveSourceUrl();
                console.log(`[CbeadLearner] 📌 保存来源 URL: ${window.location.href}`);
                await this._navigateToCourse(incompleteCourses[0]);
                return {
                    continue: true,
                    nextPage: startPage
                };
            }
            return await this._scanAndLearnBranchList(startPage + 1);
        },
        _loadScanPage() {
            const data = SessionHelper.get("cbeadScanPage");
            return data ? parseInt(data, 10) : 1;
        },
        _saveScanPage(page) {
            SessionHelper.set("cbeadScanPage", page.toString());
        },
        _clearScanPage() {
            SessionHelper.remove("cbeadScanPage");
        },
        _clearBranchListUrl() {
            SessionHelper.remove("cbeadBranchListUrl");
        },
        _saveSourceUrl() {
            const sourceUrl = `#${window.location.href.split("#")[1] || ""}`;
            SessionHelper.set("cbeadLearnSourceUrl", sourceUrl);
            return sourceUrl;
        },
        _getSourceUrl() {
            return SessionHelper.get("cbeadLearnSourceUrl");
        },
        _clearSourceUrl() {
            SessionHelper.remove("cbeadLearnSourceUrl");
        },
        async selectCourse(options = {}) {
            const {scanMethod: scanMethod} = options;
            if (!scanMethod) {
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "❌ 未提供扫描方法",
                    type: "error"
                });
                return false;
            }
            const allCourses = await scanMethod();
            if (allCourses.length === 0) {
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "❌ 未找到课程",
                    type: "error"
                });
                return false;
            }
            const learningList = CbeadHandler.getSortedLearningList(allCourses);
            if (learningList.length === 0) {
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "✅ 所有课程已完成!",
                    type: "success"
                });
                return false;
            }
            const totalCourses = allCourses.length;
            const skippedCount = totalCourses - learningList.length;
            EventBus.publish(CONSTANTS.EVENTS.STATISTICS_UPDATE, {
                total: totalCourses,
                completed: skippedCount,
                learned: 0,
                failed: 0,
                skipped: skippedCount
            });
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: `📚 准备学习 ${learningList.length} 门课程`,
                type: "info"
            });
            const firstCourse = learningList[0];
            await this._navigateToCourse(firstCourse);
            return true;
        },
        async _navigateToCourse(course) {
            const courseProgress = course.progress || 0;
            EventBus.publish(CONSTANTS.EVENTS.STATUS_UPDATE, `学习【${course.title}】 ${courseProgress}%`);
            EventBus.publish(CONSTANTS.EVENTS.PROGRESS_UPDATE, courseProgress);
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: `\n📖 开始学习: ${course.title} (${courseProgress}%)`,
                type: "info"
            });
            this._saveSourceUrl();
            const savedUrl = this._getSourceUrl() || "";
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: `📌 保存来源页: ${savedUrl}`,
                type: "info"
            });
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: `🔄 正在跳转到播放页...`,
                type: "info"
            });
            await new Promise(resolve => setTimeout(resolve, 1e3));
            window.location.href = course.link;
        }
    };
    var learner$1 = Object.freeze({
        __proto__: null,
        CbeadLearner: CbeadLearner,
        default: CbeadLearner
    });
    const Validator = {
        validateCourseId(courseId, methodName = "Method") {
            if (!courseId || typeof courseId !== "string" || courseId.trim() === "") {
                throw new Error(`${methodName}: 课程ID无效 (courseId: "${courseId}")`);
            }
        },
        validateNumber(value, paramName, methodName = "Method", min = 0, max = Infinity) {
            const num = Number(value);
            if (isNaN(num) || num < min || num > max) {
                throw new Error(`${methodName}: ${paramName}无效 (值: ${value}, 范围: ${min}-${max})`);
            }
            return num;
        }
    };
    const GWYPX_API_CONFIG = {
        baseUrl: null,
        endpoints: {
            APP_INIT: "/pcApi/api/portal/app/init",
            COURSE_GET: "/pcApi/api/course/web/get",
            STUDY_START: "/pcApi/api/study/start",
            STUDY_END: "/pcApi/api/study/v2/end",
            STUDY_PROGRESS: "/pcApi/api/study/progress",
            TRAINEE_INFO: "/pcApi/api/getTrainee",
            COURSE_QUERY_REL: "/pcApi/api/courseuser/web/queryRel",
            COURSE_ADD_REL: "/pcApi/api/courseuser/web/addRel",
            PERSONAL_COURSES: "/pcApi/api/personal/queryDataUnenrolled",
            COURSE_BY_CATEGORY: "/pcApi/api/portal/course/getPageByCategory",
            COURSE_RECOMMEND: "/pcApi/api/portal/course/recommend",
            GET_TOPIC: "/pcApi/api/portal/course/getTopic"
        },
        getBaseUrl() {
            const config = ServiceLocator.get(ServiceNames.CONFIG);
            this.baseUrl = config?.GWYPX_API_BASE || `https://${window.location.hostname}`;
            return this.baseUrl;
        },
        getUrl(endpoint) {
            const baseUrl = this.getBaseUrl();
            const endpointPath = this.endpoints[endpoint] || endpoint;
            return baseUrl + endpointPath;
        }
    };
    const GwypxApi = {
        ...ApiCore,
        isSuccessResponse(result) {
            return result && (result.success === true || result.code === 200 || result.code === 2e4 || result.state === 2e4 || result.status === "success" || result.status === "ok");
        },
        _getIdCardHash() {
            let hash = "";
            let source = "";
            const cookieMatch = document.cookie.match(/idCardHash=([^;]+)/);
            if (cookieMatch) {
                hash = decodeURIComponent(cookieMatch[1]);
                source = "cookie";
            }
            if (!hash) {
                const deepFind = (obj, k) => {
                    if (!obj || typeof obj !== "object") return null;
                    if (obj[k]) return obj[k];
                    for (const key in obj) {
                        if (Object.prototype.hasOwnProperty.call(obj, key)) {
                            const res = deepFind(obj[key], k);
                            if (res) return res;
                        }
                    }
                    return null;
                };
                const candidates = [ {
                    obj: window.userInfo,
                    name: "window.userInfo"
                }, {
                    obj: window.traineeInfo,
                    name: "window.traineeInfo"
                }, {
                    obj: window.subSiteConfig,
                    name: "window.subSiteConfig"
                } ];
                for (const c of candidates) {
                    if (c.obj) {
                        hash = c.obj.idCardHash || deepFind(c.obj, "idCardHash");
                        if (hash) {
                            source = c.name;
                            break;
                        }
                    }
                }
            }
            if (!hash) {
                try {
                    const app = document.querySelector("#app");
                    const vue = app?.__vue__ || app?.__vue_app__;
                    const store = vue?.$store || vue?.store;
                    if (store?.state?.user?.info?.idCardHash) {
                        hash = store.state.user.info.idCardHash;
                        source = "Vuex.state.user.info";
                    }
                    if (!hash) {
                        const found = deepFind(store?.state, "idCardHash");
                        if (found) {
                            hash = found;
                            source = "Vuex.deepFind";
                        }
                    }
                } catch (e) {}
            }
            if (!hash) {
                hash = localStorage.getItem("idCardHash");
                if (hash) source = "localStorage";
            }
            if (!hash) {
                for (let i = 0; i < localStorage.length; i++) {
                    const key = localStorage.key(i);
                    const val = localStorage.getItem(key);
                    if (val && val.includes("idCardHash")) {
                        try {
                            const parsed = JSON.parse(val);
                            const findHash = obj => {
                                if (!obj || typeof obj !== "object") return null;
                                if (obj.idCardHash) return obj.idCardHash;
                                for (const k in obj) {
                                    const res = findHash(obj[k]);
                                    if (res) return res;
                                }
                                return null;
                            };
                            const res = findHash(parsed);
                            if (res) {
                                hash = res;
                                source = `localStorage.${key}`;
                                break;
                            }
                        } catch (e) {}
                    }
                }
            }
            const finalHash = hash || sessionStorage.getItem("idCardHash") || "";
            if (finalHash) {
                console.log(`[GwypxApi] 找到 idCardHash: ${finalHash.substring(0, 8)}... 来自 ${source || "sessionStorage"}`);
            }
            return finalHash;
        },
        _prepareHeaders(customHeaders = {}, data = null) {
            const headers = ApiCore._prepareHeaders.call(this, customHeaders, data);
            delete headers["Authorization"];
            delete headers["X-Auth-Token"];
            if (data) {
                headers["Content-Type"] = "application/json";
            }
            headers["Referer"] = window.location.href + (window.location.href.includes("?") ? "&" : "?") + "_cela_t=" + Date.now();
            return headers;
        },
        async request(options) {
            const originalLog = this._log;
            this._log = (msg, level) => {
                if (msg.includes("未找到认证token")) return;
                originalLog.call(this, msg, level);
            };
            try {
                return await ApiCore.request.call(this, options);
            } finally {
                this._log = originalLog;
            }
        },
        async getPlayInfo(courseId) {
            try {
                Validator.validateCourseId(courseId, "getPlayInfo");
                const url = GWYPX_API_CONFIG.getUrl("COURSE_GET");
                const payload = {
                    courseId: courseId,
                    idCardHash: this._getIdCardHash()
                };
                console.log("[GwypxApi] 正在获取播放信息, payload:", payload);
                const response = await this.post(url, JSON.stringify(payload), {
                    headers: {
                        "Content-Type": "application/json"
                    }
                });
                console.log("[GwypxApi] 播放信息响应:", response);
                if (response && response.data) {
                    const studyTimes = response.data.studyTimes || 0;
                    const progress = parseInt(response.data.percentage || "0", 10);
                    return {
                        courseId: courseId,
                        title: response.data.name,
                        duration: response.data.courseDuration || response.data.duration || 0,
                        studyTimes: studyTimes,
                        progress: progress,
                        idCardHash: payload.idCardHash
                    };
                }
                if (response && response.name && response.courseId) {
                    return {
                        courseId: courseId,
                        title: response.name,
                        duration: response.courseDuration || response.duration || 0,
                        idCardHash: payload.idCardHash
                    };
                }
                console.warn("[GwypxApi] getPlayInfo: 响应格式无法识别", response);
                return null;
            } catch (error) {
                if (error.message.includes("课程ID无效")) {
                    throw error;
                }
                console.error("[GwypxApi] getPlayInfo failed:", error);
                return null;
            }
        },
        async studyStart(courseId, tbtpId = null) {
            Validator.validateCourseId(courseId, "studyStart");
            const url = GWYPX_API_CONFIG.getUrl("STUDY_START");
            const payload = {
                courseId: courseId,
                idCardHash: this._getIdCardHash(),
                studyType: "VIDEO",
                tbtpId: tbtpId
            };
            console.log(`[GwypxApi] 发送 studyStart: ${courseId}, tbtpId: ${tbtpId}`);
            return await this.post(url, JSON.stringify(payload));
        },
        async _relAction(courseId, endpointKey, actionName) {
            Validator.validateCourseId(courseId, actionName);
            const payload = {
                courseId: courseId,
                idCardHash: this._getIdCardHash()
            };
            console.log(`[GwypxApi] ${actionName}: ${courseId}`);
            return await this.post(GWYPX_API_CONFIG.getUrl(endpointKey), JSON.stringify(payload));
        },
        async queryRel(courseId) {
            return await this._relAction(courseId, "COURSE_QUERY_REL", "查询课程关联状态");
        },
        async addRel(courseId) {
            return await this._relAction(courseId, "COURSE_ADD_REL", "建立课程关联");
        },
        async reportProgress(playInfo, studyTimes = 60, totalStudyTimes = 60) {
            const url = GWYPX_API_CONFIG.getUrl("STUDY_PROGRESS");
            const payload = {
                courseId: playInfo.courseId,
                idCardHash: playInfo.idCardHash || this._getIdCardHash(),
                studyTimes: studyTimes,
                totalStudyTimes: totalStudyTimes,
                tbtpId: playInfo.tbtpId || null
            };
            console.log(`[GwypxApi] 发送进度同步 (studyTimes: ${studyTimes}): ${playInfo.courseId}, tbtpId: ${payload.tbtpId}`);
            return await this.post(url, JSON.stringify(payload));
        },
        async reportEnd(playInfo) {
            const url = GWYPX_API_CONFIG.getUrl("STUDY_END");
            const payload = {
                courseId: playInfo.courseId,
                idCardHash: playInfo.idCardHash || this._getIdCardHash(),
                tbtpId: playInfo.tbtpId || null,
                studyTimes: playInfo.studyTimes || 60
            };
            console.log(`[GwypxApi] 发送完成确认 (v2/end): ${playInfo.courseId}, studyTimes: ${payload.studyTimes}`);
            return await this.post(url, JSON.stringify(payload));
        },
        async getPersonalCourses(pageNum = 0, pageSize = 15, filters = {}) {
            pageNum = Validator.validateNumber(pageNum, "pageNum", "getPersonalCourses", 0);
            pageSize = Validator.validateNumber(pageSize, "pageSize", "getPersonalCourses", 1, 100);
            const url = GWYPX_API_CONFIG.getUrl("PERSONAL_COURSES");
            const payload = {
                pagenum: pageNum,
                pageSize: pageSize,
                isComplete: filters.isComplete || null,
                isExcellent: filters.isExcellent || null,
                isFinished: filters.isFinished || null,
                year: filters.year || null,
                idCardHash: this._getIdCardHash(),
                completeSf: filters.completeSf || 0
            };
            console.log(`[GwypxApi] 获取个人中心课程列表: 第${pageNum + 1}页, 每页${pageSize}`);
            return await this.post(url, JSON.stringify(payload));
        },
        async getCoursesByCategory(pageNum = 0, pageSize = 15, categoryId = null) {
            pageNum = Validator.validateNumber(pageNum, "pageNum", "getCoursesByCategory", 0);
            pageSize = Validator.validateNumber(pageSize, "pageSize", "getCoursesByCategory", 1, 100);
            const url = GWYPX_API_CONFIG.getUrl("COURSE_BY_CATEGORY");
            const payload = {
                pagenum: pageNum,
                pagesize: pageSize,
                name: "",
                idCardHash: this._getIdCardHash()
            };
            if (categoryId) {
                payload.categoryId = categoryId;
            }
            console.log(`[GwypxApi] 获取分类课程: categoryId=${categoryId || "默认"}, 第${pageNum + 1}页, 每页${pageSize}`);
            return await this.post(url, JSON.stringify(payload));
        },
        async getRecommendCourses(pageNum = 0, pageSize = 15, recommendation = "new") {
            const url = GWYPX_API_CONFIG.getUrl("COURSE_RECOMMEND");
            const payload = {
                pagenum: pageNum,
                pagesize: pageSize,
                recommendation: recommendation,
                name: "",
                idCardHash: this._getIdCardHash()
            };
            return await this.post(url, JSON.stringify(payload));
        },
        async getTopic(topicId) {
            const url = GWYPX_API_CONFIG.getUrl("GET_TOPIC");
            const payload = {
                id: topicId,
                idCardHash: this._getIdCardHash()
            };
            return await this.post(url, JSON.stringify(payload));
        },
        async getSubjectColumnCourses(code, pageNum = 0, pageSize = 1e6) {
            const url = GWYPX_API_CONFIG.getUrl("COURSE_BY_CATEGORY");
            const payload = {
                code: code,
                pagesize: pageSize,
                pagenum: pageNum,
                idCardHash: this._getIdCardHash()
            };
            console.log(`[GwypxApi] 获取专栏课程: code=${code}`);
            return await this.post(url, JSON.stringify(payload));
        }
    };
    const LEARNER_CONSTANTS = {
        PROGRESS_INCREMENT: 60,
        INITIAL_TIME_OFFSET: 60,
        MAX_SYNC_LOOPS: 60,
        SUPER_FAST_INTERVAL: 5e3,
        NORMAL_INTERVAL: 1e4,
        BATCH_PAGE_SIZE: 20,
        BATCH_REQUEST_DELAY: 1e3,
        MASK_WAIT_TIME: 2e3,
        API_REL_DELAY: 1e3
    };
    class GlobalStateManager {
        constructor() {
            this.STORAGE_KEY = "__CELA_GWYPX_STATE__";
        }
        getState() {
            if (!window[this.STORAGE_KEY]) {
                window[this.STORAGE_KEY] = {
                    isRunning: false,
                    stopRequested: false,
                    startTime: null,
                    loopCount: 0
                };
            }
            return window[this.STORAGE_KEY];
        }
        resetState() {
            if (window[this.STORAGE_KEY]) {
                window[this.STORAGE_KEY] = {
                    isRunning: false,
                    stopRequested: false,
                    startTime: null,
                    loopCount: 0
                };
            }
        }
        cleanup() {
            delete window[this.STORAGE_KEY];
        }
        setRunning(isRunning) {
            const state = this.getState();
            state.isRunning = isRunning;
            if (isRunning) {
                state.startTime = Date.now();
                state.loopCount = 0;
            }
        }
        requestStop() {
            const state = this.getState();
            state.stopRequested = true;
        }
        shouldStop() {
            return this.getState().stopRequested;
        }
        isRunning() {
            return this.getState().isRunning;
        }
        incrementLoopCount() {
            const state = this.getState();
            state.loopCount = (state.loopCount || 0) + 1;
            return state.loopCount;
        }
        getLoopCount() {
            return this.getState().loopCount || 0;
        }
        exceedsMaxLoops(maxLoops = LEARNER_CONSTANTS.MAX_SYNC_LOOPS) {
            return this.getLoopCount() >= maxLoops;
        }
    }
    const stateManager = new GlobalStateManager;
    const GwypxPlayerFlow = {
        async startPlayerFlow() {
            if (stateManager.isRunning()) {
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "[WARN] 学习流程已在运行中,跳过重复启动",
                    type: "warn"
                });
                return false;
            }
            stateManager.setRunning(true);
            try {
                const urlParams = new URLSearchParams(window.location.search);
                const courseId = urlParams.get("courseId") || urlParams.get("id");
                const tbtpId = urlParams.get("tbtpId");
                if (!courseId) {
                    EventBus.publish(CONSTANTS.EVENTS.LOG, {
                        message: "[ERR] 无法识别课程ID",
                        type: "error"
                    });
                    return false;
                }
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: `[PLAY] 开始党校分院播放页流程 (ID: ${courseId})`,
                    type: "info"
                });
                const playInfo = await GwypxApi.getPlayInfo(courseId);
                if (!playInfo) {
                    EventBus.publish(CONSTANTS.EVENTS.LOG, {
                        message: "[ERR] 获取播放信息失败,可能未识别学员身份",
                        type: "error"
                    });
                    return false;
                }
                playInfo.tbtpId = tbtpId;
                if (playInfo.progress >= 100) {
                    return await this._confirmCompletion(playInfo, "该课程已完成 (100%)!正在执行最终确认...");
                }
                const {detectMask: detectMask} = await Promise.resolve().then(function() {
                    return domHelper;
                });
                const mask = detectMask();
                if (mask.exists) {
                    EventBus.publish(CONSTANTS.EVENTS.LOG, {
                        message: "[PROTECT] 检测到系统弹窗,等待自动处理...",
                        type: "info"
                    });
                    await new Promise(resolve => setTimeout(resolve, LEARNER_CONSTANTS.MASK_WAIT_TIME));
                } else {
                    const relStatus = await GwypxApi.queryRel(courseId);
                    if (!relStatus?.data) {
                        EventBus.publish(CONSTANTS.EVENTS.LOG, {
                            message: "[CLICK] 正在自动关联课程 (建立学习关系)...",
                            type: "info"
                        });
                        await GwypxApi.addRel(courseId);
                        await new Promise(resolve => setTimeout(resolve, LEARNER_CONSTANTS.API_REL_DELAY));
                    }
                }
                await GwypxApi.studyStart(courseId, playInfo.tbtpId);
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "[SIGNAL] 已发送开始学习信号",
                    type: "info"
                });
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "[SYNC] 启动持续进度同步机制...",
                    type: "info"
                });
                let cumulativeTime = (playInfo.studyTimes || 0) + LEARNER_CONSTANTS.INITIAL_TIME_OFFSET;
                while (true) {
                    if (stateManager.shouldStop()) {
                        EventBus.publish(CONSTANTS.EVENTS.LOG, {
                            message: "[STOP] 收到停止请求,终止进度同步",
                            type: "warn"
                        });
                        return false;
                    }
                    if (stateManager.exceedsMaxLoops()) {
                        const elapsed = Math.floor((Date.now() - stateManager.getState().startTime) / 1e3);
                        EventBus.publish(CONSTANTS.EVENTS.LOG, {
                            message: `[WARN] 已达到最大同步次数限制 (${LEARNER_CONSTANTS.MAX_SYNC_LOOPS}次),已运行 ${elapsed} 秒。为防止内存溢出已停止。如果进度未满,请刷新页面继续。`,
                            type: "error"
                        });
                        UI.resetToggleButton("同步超时");
                        return false;
                    }
                    stateManager.incrementLoopCount();
                    const result = await GwypxApi.reportProgress(playInfo, cumulativeTime, LEARNER_CONSTANTS.PROGRESS_INCREMENT);
                    const currentProgress = parseInt(result?.courseProgress?.percentage || result?.data?.percentage || 0);
                    EventBus.publish(CONSTANTS.EVENTS.LOG, {
                        message: `[PROG] 进度 ${currentProgress}% - 循环 ${stateManager.getLoopCount()}/${LEARNER_CONSTANTS.MAX_SYNC_LOOPS}`,
                        type: "info"
                    });
                    EventBus.publish(CONSTANTS.EVENTS.STATUS_UPDATE, `学习中 ${currentProgress}%`);
                    EventBus.publish(CONSTANTS.EVENTS.PROGRESS_UPDATE, currentProgress);
                    if (currentProgress >= 100) {
                        return await this._confirmCompletion(playInfo, "课程已达到 100% 完成!正在进行最终确认...");
                    }
                    cumulativeTime += LEARNER_CONSTANTS.PROGRESS_INCREMENT;
                    const waitTime = CONFIG.SUPER_FAST_MODE ? LEARNER_CONSTANTS.SUPER_FAST_INTERVAL : LEARNER_CONSTANTS.NORMAL_INTERVAL;
                    await new Promise(resolve => setTimeout(resolve, waitTime));
                }
            } catch (error) {
                console.error("[GwypxPlayerFlow] startPlayerFlow error:", error);
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: `[ERR] 学习流程异常: ${error.message}`,
                    type: "error"
                });
                return false;
            } finally {
                stateManager.resetState();
            }
        },
        async _confirmCompletion(playInfo, logPrefix) {
            console.log(`[GwypxPlayerFlow] [OK] ${logPrefix}`);
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: `[OK] ${logPrefix}`,
                type: "success"
            });
            const endResult = await GwypxApi.reportEnd(playInfo);
            if (GwypxApi.isSuccessResponse(endResult)) {
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "[DONE] 课程已确认结课!",
                    type: "success"
                });
            }
            UI.resetToggleButton("学习完成");
            return true;
        }
    };
    const GwypxProcessor = {
        async completeCourseByApi(courseId, title) {
            if (!courseId) {
                throw new Error("课程ID无效");
            }
            console.log(`[GwypxProcessor] 开始学习: ${title} (ID: ${courseId})`);
            const playInfo = await GwypxApi.getPlayInfo(courseId);
            if (!playInfo) {
                throw new Error("获取播放信息失败");
            }
            console.log(`[GwypxProcessor] 播放信息: 进度${playInfo.progress}%, 已学${playInfo.studyTimes}秒`);
            if (playInfo.progress >= 100) {
                console.log(`[GwypxProcessor] 课程已完成,跳过`);
                EventBus.publish(CONSTANTS.EVENTS.COURSE_SKIP, {
                    course: {
                        id: courseId,
                        title: title
                    },
                    reason: "已完成"
                });
                return {
                    skipped: true,
                    success: true
                };
            }
            await GwypxApi.studyStart(courseId, playInfo.tbtpId);
            EventBus.publish(CONSTANTS.EVENTS.STATUS_UPDATE, title);
            console.log(`[GwypxProcessor] 已发送开始学习信号`);
            const durationSeconds = (playInfo.duration || 0) * 60;
            const neededIterations = durationSeconds > 0 ? Math.ceil(durationSeconds / 60) + 5 : 100;
            const maxAttempts = Math.min(neededIterations, 100);
            let cumulativeTime = (playInfo.studyTimes || 0) + 60;
            let knownProgress = playInfo.progress || 0;
            if (knownProgress > 0) {
                EventBus.publish(CONSTANTS.EVENTS.PROGRESS_UPDATE, knownProgress);
            }
            for (let i = 0; i < maxAttempts; i++) {
                if (stateManager.shouldStop()) {
                    console.warn(`[GwypxProcessor] 收到停止请求,终止学习`);
                    return {
                        success: false,
                        skipped: false,
                        progress: knownProgress,
                        stopped: true
                    };
                }
                playInfo.studyTimes = cumulativeTime;
                console.log(`[GwypxProcessor] 发送 end (${i + 1}/${maxAttempts}), studyTimes=${cumulativeTime}`);
                const endResp = await GwypxApi.reportEnd(playInfo);
                cumulativeTime += 60;
                const endProgress = parseInt(endResp?.data?.percentage || endResp?.data?.progress || -1);
                if (endProgress >= 0) knownProgress = Math.max(knownProgress, endProgress);
                if (endProgress >= 100) {
                    console.log(`[GwypxProcessor] ✅ 服务端返回进度 ${endProgress}%,提前结束`);
                    EventBus.publish(CONSTANTS.EVENTS.COURSE_COMPLETE, {
                        course: {
                            id: courseId,
                            title: title
                        }
                    });
                    EventBus.publish(CONSTANTS.EVENTS.PROGRESS_UPDATE, 100);
                    return {
                        success: true,
                        skipped: false
                    };
                }
                if (i > 0 && i % 3 === 0) {
                    const verifyInfo = await GwypxApi.getPlayInfo(courseId);
                    if (verifyInfo && verifyInfo.progress >= 0) knownProgress = Math.max(knownProgress, verifyInfo.progress);
                    if (verifyInfo && verifyInfo.progress >= 100) {
                        console.log(`[GwypxProcessor] ✅ 验证进度 ${verifyInfo.progress}%,提前结束`);
                        EventBus.publish(CONSTANTS.EVENTS.COURSE_COMPLETE, {
                            course: {
                                id: courseId,
                                title: title
                            }
                        });
                        EventBus.publish(CONSTANTS.EVENTS.PROGRESS_UPDATE, 100);
                        return {
                            success: true,
                            skipped: false
                        };
                    }
                    if (verifyInfo && verifyInfo.progress >= 0) {
                        EventBus.publish(CONSTANTS.EVENTS.PROGRESS_UPDATE, verifyInfo.progress);
                    }
                }
                EventBus.publish(CONSTANTS.EVENTS.STATUS_UPDATE, `学习中 ${Math.min(Math.round((i + 1) / maxAttempts * 100), 99)}%`);
                await new Promise(resolve => setTimeout(resolve, 500));
            }
            console.log(`[GwypxProcessor] end 循环完成,正在验证进度...`);
            const verifyInfo = await GwypxApi.getPlayInfo(courseId);
            if (verifyInfo && verifyInfo.progress >= 100) {
                console.log(`[GwypxProcessor] ✅ 课程已完成! (验证进度: ${verifyInfo.progress}%)`);
                EventBus.publish(CONSTANTS.EVENTS.COURSE_COMPLETE, {
                    course: {
                        id: courseId,
                        title: title
                    }
                });
                EventBus.publish(CONSTANTS.EVENTS.STATUS_UPDATE, "学习中");
                EventBus.publish(CONSTANTS.EVENTS.PROGRESS_UPDATE, 100);
                return {
                    success: true,
                    skipped: false
                };
            }
            console.warn(`[GwypxProcessor] ⚠️ 验证进度: ${verifyInfo?.progress || 0}%,未达 100%`);
            return {
                success: false,
                skipped: false,
                progress: verifyInfo?.progress || 0,
                error: "verify_failed"
            };
        }
    };
    const GwypxLearner = {
        _log(message, type = "info") {
            console.log(`[GwypxLearner] ${message}`);
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: message,
                type: type
            });
        },
        get _pageValidator() {
            return createPageValidator({
                whitelist: GWYPX_CONSTANTS.PAGE_TYPE_WHITELIST,
                pageTypes: GwypxHandler.PAGE_TYPES,
                customMessages: {
                    index: "[WARN] 党校分院首页暂不支持自动学习。请进入课程播放页或个人中心。",
                    default: "[WARN] 当前页面不支持自动学习。请进入课程播放页或个人中心。"
                }
            });
        },
        _validatePageType(pageType) {
            return this._pageValidator.validate(pageType);
        },
        async selectAndExecute() {
            if (!CONFIG.GWYPX_MODE) return null;
            const pageType = GwypxHandler.identifyPage();
            if (!this._validatePageType(pageType)) {
                return false;
            }
            if (pageType === GwypxHandler.PAGE_TYPES.PLAYER) {
                return await GwypxPlayerFlow.startPlayerFlow();
            }
            if (pageType === GwypxHandler.PAGE_TYPES.CENTER) {
                return await this.startCategoryBatchFlow();
            }
            if (pageType === GwypxHandler.PAGE_TYPES.COMMEND_INDEX) {
                return await this._handleCommendIndex();
            }
            if (pageType === GwypxHandler.PAGE_TYPES.SUBJECT_COLUMN_DETAIL || pageType === GwypxHandler.PAGE_TYPES.SUBJECT_DETAIL) {
                return await this._handleSubjectColumnDetail();
            }
            return null;
        },
        _publishStats(stats) {
            Utils.publishStats(stats);
        },
        async _processCourses(courses, flowName) {
            if (!courses || courses.length === 0) {
                this._log("⚠️ 未获取到课程列表", "warn");
                return false;
            }
            this._log(`✅ 获取到 ${courses.length} 门课程`);
            let learned = 0, failed = 0, skipped = 0;
            for (let i = 0; i < courses.length; i++) {
                const {Learner: Learner} = await Promise.resolve().then(function() {
                    return learner;
                });
                if (Learner && Learner.stopRequested) {
                    this._log("🛑 用户停止学习", "warn");
                    break;
                }
                const c = courses[i];
                const courseId = String(c.id);
                const name = c.name || `课程${courseId}`;
                if (this._isCourseCompleted(c)) {
                    this._log(`[SKIP] ${name} (已完成)`, "info");
                    skipped++;
                    continue;
                }
                this._log(`[学习] ${i + 1}/${courses.length}: ${name}`);
                try {
                    const result = await GwypxProcessor.completeCourseByApi(courseId, name);
                    if (result.success) learned++; else failed++;
                } catch (e) {
                    this._log(`❌ ${name}: ${e.message}`, "error");
                    failed++;
                }
            }
            this._log(`🎉 ${flowName}完成: ${learned}学习 ${skipped}跳过 ${failed}失败`, "success");
            return true;
        },
        async _handleCommendIndex() {
            this._log("📋 检测到课程推荐页,开始扫描...");
            try {
                const resp = await GwypxApi.getRecommendCourses(0, 50, "new");
                return await this._processCourses(resp?.datalist, "课程推荐");
            } catch (e) {
                this._log(`❌ 课程推荐页处理失败: ${e.message}`, "error");
                return false;
            }
        },
        async _handleSubjectColumnDetail() {
            this._log("📋 检测到专栏详情页,开始扫描...");
            try {
                const topicId = new URLSearchParams(window.location.search).get("id");
                if (!topicId) {
                    this._log("❌ 无法获取专栏ID", "error");
                    return false;
                }
                const topicResp = await GwypxApi.getTopic(topicId);
                const code = topicResp?.data?.code;
                if (!code) {
                    this._log("❌ 无法获取专栏编码", "error");
                    return false;
                }
                this._log(`📚 专栏: ${topicResp.data.name}`);
                const courseResp = await GwypxApi.getSubjectColumnCourses(code);
                return await this._processCourses(courseResp?.datalist, "专栏");
            } catch (e) {
                this._log(`❌ 专栏详情页处理失败: ${e.message}`, "error");
                return false;
            }
        },
        async _executeBatchFlow(fetchPageFunc, flowName) {
            if (stateManager.isRunning()) {
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: `[WARN] ${flowName}已在运行中,跳过重复启动`,
                    type: "warn"
                });
                return {
                    success: false,
                    alreadyRunning: true
                };
            }
            stateManager.setRunning(true);
            EventBus.publish(CONSTANTS.EVENTS.LEARNING_START);
            const stats = {
                totalCourses: 0,
                completedCourses: 0,
                skippedCourses: 0,
                failedCourses: 0
            };
            try {
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: `[BATCH] 开始${flowName}...`,
                    type: "info"
                });
                let pageNum = 0;
                const pageSize = LEARNER_CONSTANTS.BATCH_PAGE_SIZE;
                let totalElements = 0;
                while (!stateManager.shouldStop()) {
                    EventBus.publish(CONSTANTS.EVENTS.LOG, {
                        message: `[GET] 正在获取第 ${pageNum + 1} 页课程...`,
                        type: "info"
                    });
                    let response;
                    try {
                        response = await fetchPageFunc(pageNum, pageSize);
                    } catch (e) {
                        console.error(`[GwypxLearner] 获取课程请求失败:`, e);
                        EventBus.publish(CONSTANTS.EVENTS.LOG, {
                            message: `[ERR] 获取第 ${pageNum + 1} 页课程失败: ${e.message}`,
                            type: "error"
                        });
                        break;
                    }
                    if (!response || typeof response !== "object" || !response.datalist) {
                        console.warn("[GwypxLearner] 获取课程响应无效:", response);
                        EventBus.publish(CONSTANTS.EVENTS.LOG, {
                            message: `[ERR] 获取课程响应无效`,
                            type: "error"
                        });
                        break;
                    }
                    const courseList = response.datalist || [];
                    totalElements = response.totalelements || 0;
                    if (courseList.length === 0) {
                        EventBus.publish(CONSTANTS.EVENTS.LOG, {
                            message: `[OK] 所有课程已学习完成`,
                            type: "success"
                        });
                        break;
                    }
                    if (pageNum === 0 && courseList.length > 0) {
                        console.log("[GwypxLearner] 课程数据示例:", JSON.stringify(courseList[0], null, 2));
                    }
                    for (let i = 0; i < courseList.length; i++) {
                        if (stateManager.shouldStop()) {
                            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                                message: "[STOP] 收到停止请求,终止学习",
                                type: "warn"
                            });
                            break;
                        }
                        const item = courseList[i];
                        const course = this._normalizeCourseItem(item);
                        if (this._isCourseCompleted(item)) {
                            stats.skippedCourses++;
                            console.log(`[GwypxLearner] 跳过已学习课程: ${course.title}`);
                            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                                message: `[SKIP] ${course.title} 已学习,跳过`,
                                type: "info"
                            });
                            continue;
                        }
                        stats.totalCourses++;
                        EventBus.publish(CONSTANTS.EVENTS.STATUS_UPDATE, course.title);
                        try {
                            const result = await GwypxProcessor.completeCourseByApi(course.courseId, course.title);
                            if (result.skipped) {
                                stats.skippedCourses++;
                                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                                    message: `[SKIP] ${course.title} 已完成,跳过`,
                                    type: "info"
                                });
                            } else if (result.success) {
                                stats.completedCourses++;
                                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                                    message: `[OK] ${course.title} 学习完成`,
                                    type: "success"
                                });
                            } else {
                                stats.failedCourses++;
                                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                                    message: `[WARN] ${course.title} 学习失败`,
                                    type: "warn"
                                });
                            }
                            this._publishStats(stats);
                        } catch (error) {
                            stats.failedCourses++;
                            const errorMsg = error?.message || String(error);
                            console.error(`[GwypxLearner] 学习 ${course.title} 失败:`, error);
                            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                                message: `[ERR] ${course.title} 学习失败: ${errorMsg}`,
                                type: "error"
                            });
                            this._publishStats(stats);
                        }
                        if (i < courseList.length - 1) {
                            await new Promise(resolve => setTimeout(resolve, LEARNER_CONSTANTS.BATCH_REQUEST_DELAY));
                        }
                    }
                    EventBus.publish(CONSTANTS.EVENTS.LOG, {
                        message: `[BOOK] 第 ${pageNum + 1} 页学习完成: 完成 ${stats.completedCourses}, 跳过 ${stats.skippedCourses}, 失败 ${stats.failedCourses}`,
                        type: "info"
                    });
                    if (courseList.length < pageSize || stats.completedCourses + stats.skippedCourses >= totalElements) {
                        break;
                    }
                    pageNum++;
                }
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: `🎉 ${flowName}完成!总计: ${stats.totalCourses}, 完成: ${stats.completedCourses}, 跳过: ${stats.skippedCourses}, 失败: ${stats.failedCourses}`,
                    type: "success"
                });
                EventBus.publish(CONSTANTS.EVENTS.STATUS_UPDATE, `学习完成 ${stats.completedCourses}/${stats.totalCourses}`);
                return {
                    success: true,
                    stats: stats
                };
            } catch (error) {
                console.error(`[GwypxLearner] ${flowName} error:`, error);
                const errorMsg = error?.message || String(error);
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: `[ERR] ${flowName}异常: ${errorMsg}`,
                    type: "error"
                });
                return {
                    success: false,
                    error: error,
                    stats: stats
                };
            } finally {
                stateManager.resetState();
                EventBus.publish(CONSTANTS.EVENTS.LEARNING_STOP);
            }
        },
        _normalizeCourseItem(item) {
            return {
                courseId: item.id || item.courseId || item.tbtpId,
                title: item.name || item.title || item.courseName || item.tbtpName,
                percentage: item.percentage,
                studyStatus: item.studyStatus,
                creditHour: item.creditHour
            };
        },
        _isCourseCompleted(item) {
            return item.showStatusMsg === "已学习" || item.studyStatus === "2" || item.percentage === "100%";
        },
        async startCategoryBatchFlow() {
            return await this._executeBatchFlow(async (pageNum, pageSize) => await GwypxApi.getCoursesByCategory(pageNum, pageSize), "分类课程批量学习");
        }
    };
    const ENVIRONMENT_IDS = {
        PUDONG: "pudong",
        CBEAD: "cbead",
        DX: "dx"
    };
    const ApiFactory = {
        _instances: {
            [ENVIRONMENT_IDS.PUDONG]: null,
            [ENVIRONMENT_IDS.CBEAD]: null,
            [ENVIRONMENT_IDS.DX]: null
        },
        createApi(envId) {
            if (this._instances[envId]) {
                return this._instances[envId];
            }
            let apiInstance;
            switch (envId) {
              case ENVIRONMENT_IDS.PUDONG:
                apiInstance = PudongApi;
                break;

              case ENVIRONMENT_IDS.CBEAD:
                apiInstance = CbeadApi;
                break;

              case ENVIRONMENT_IDS.DX:
                apiInstance = GwypxApi;
                break;

              default:
                throw new Error(`未知的 API 类型: ${envId}`);
            }
            this._instances[envId] = apiInstance;
            return apiInstance;
        },
        getPudongApi() {
            return this.createApi(ENVIRONMENT_IDS.PUDONG);
        },
        getCbeadApi() {
            return this.createApi(ENVIRONMENT_IDS.CBEAD);
        },
        getGwypxApi() {
            return this.createApi(ENVIRONMENT_IDS.DX);
        },
        getCurrentApi() {
            const config = ServiceLocator.get(ServiceNames.CONFIG);
            if (config?.PUDONG_MODE) {
                return this.getPudongApi();
            }
            if (config?.CBEAD_MODE) {
                return this.getCbeadApi();
            }
            if (config?.GWYPX_MODE) {
                return this.getGwypxApi();
            }
            return this.getPudongApi();
        },
        clearCache() {
            Object.keys(this._instances).forEach(key => {
                this._instances[key] = null;
            });
        },
        getCurrentConfig() {
            const config = ServiceLocator.get(ServiceNames.CONFIG);
            if (config?.PUDONG_MODE) return PUDONG_API_CONFIG;
            if (config?.CBEAD_MODE) return CBEAD_API_CONFIG;
            if (config?.GWYPX_MODE) return GWYPX_API_CONFIG;
            return PUDONG_API_CONFIG;
        }
    };
    const API$1 = Object.assign({}, PudongApi, CbeadApi, {
        factory: ApiFactory,
        _getCurrentApi() {
            const config = ServiceLocator.get(ServiceNames.CONFIG);
            if (config?.PUDONG_MODE) return PudongApi;
            if (config?.CBEAD_MODE) return CbeadApi;
            if (config?.GWYPX_MODE) return GwypxApi;
            return PudongApi;
        },
        async reportProgress(playInfo, currentTime) {
            return this._getCurrentApi().reportProgress(playInfo, currentTime);
        },
        async reportProgressWithDelay(playInfo, currentTime) {
            return this._getCurrentApi().reportProgressWithDelay(playInfo, currentTime);
        }
    });
    const LOG_LEVELS = {
        DEBUG: 0,
        INFO: 1,
        WARN: 2,
        ERROR: 3,
        NONE: 4
    };
    const defaultConfig = {
        level: LOG_LEVELS.INFO,
        showTimestamp: true,
        showModule: true,
        disableInProduction: true
    };
    let config = {
        ...defaultConfig
    };
    function getTimestamp() {
        const now = new Date;
        return now.toISOString().split("T")[1].replace("Z", "").slice(0, -1);
    }
    function formatMessage(level, module, args) {
        const parts = [];
        if (config.showTimestamp) {
            parts.push(`[${getTimestamp()}]`);
        }
        parts.push(`[${level}]`);
        if (config.showModule && module) {
            parts.push(`[${module}]`);
        }
        return [ parts.join(" "), ...args ];
    }
    const Logger = {
        setLevel(level) {
            if (typeof level === "string") {
                const upperLevel = level.toUpperCase();
                config.level = LOG_LEVELS[upperLevel] !== undefined ? LOG_LEVELS[upperLevel] : LOG_LEVELS.INFO;
            } else if (typeof level === "number") {
                config.level = level >= 0 && level <= 4 ? level : LOG_LEVELS.INFO;
            }
            if (typeof window !== "undefined") {
                const isDev = window.location?.hostname?.includes("localhost") || window.location?.hostname?.includes("dev");
                if (isDev && config.level === LOG_LEVELS.INFO) {
                    config.level = LOG_LEVELS.DEBUG;
                }
            }
        },
        getLevel() {
            return config.level;
        },
        debug(...args) {
            if (config.level <= LOG_LEVELS.DEBUG) {
                console.debug(...formatMessage("DEBUG", null, args));
            }
        },
        debugM(module, ...args) {
            if (config.level <= LOG_LEVELS.DEBUG) {
                console.debug(...formatMessage("DEBUG", module, args));
            }
        },
        info(...args) {
            if (config.level <= LOG_LEVELS.INFO) {
                console.info(...formatMessage("INFO", null, args));
            }
        },
        infoM(module, ...args) {
            if (config.level <= LOG_LEVELS.INFO) {
                console.info(...formatMessage("INFO", module, args));
            }
        },
        warn(...args) {
            if (config.level <= LOG_LEVELS.WARN) {
                console.warn(...formatMessage("WARN", null, args));
            }
        },
        warnM(module, ...args) {
            if (config.level <= LOG_LEVELS.WARN) {
                console.warn(...formatMessage("WARN", module, args));
            }
        },
        error(...args) {
            if (config.level <= LOG_LEVELS.ERROR) {
                console.error(...formatMessage("ERROR", null, args));
            }
        },
        errorM(module, ...args) {
            if (config.level <= LOG_LEVELS.ERROR) {
                console.error(...formatMessage("ERROR", module, args));
            }
        },
        log(level, ...args) {
            const upperLevel = level.toUpperCase();
            const levelValue = LOG_LEVELS[upperLevel] !== undefined ? LOG_LEVELS[upperLevel] : LOG_LEVELS.INFO;
            if (config.level <= levelValue) {
                console.log(...formatMessage(upperLevel, null, args));
            }
        },
        group(label, collapsed = false) {
            if (config.level <= LOG_LEVELS.DEBUG) {
                if (collapsed) {
                    console.groupCollapsed(label);
                } else {
                    console.group(label);
                }
            }
        },
        groupEnd() {
            if (config.level <= LOG_LEVELS.DEBUG) {
                console.groupEnd();
            }
        },
        time(label) {
            if (config.level <= LOG_LEVELS.DEBUG) {
                console.time(label);
            }
        },
        timeEnd(label) {
            if (config.level <= LOG_LEVELS.DEBUG) {
                console.timeEnd(label);
            }
        },
        table(label, data) {
            if (config.level <= LOG_LEVELS.DEBUG && Array.isArray(data)) {
                this.group(label);
                console.table(data);
                this.groupEnd();
            }
        },
        configure(newConfig) {
            config = {
                ...config,
                ...newConfig
            };
        },
        reset() {
            config = {
                ...defaultConfig
            };
        }
    };
    function createLogger(moduleName) {
        return {
            debug: (...args) => Logger.debugM(moduleName, ...args),
            info: (...args) => Logger.infoM(moduleName, ...args),
            warn: (...args) => Logger.warnM(moduleName, ...args),
            error: (...args) => Logger.errorM(moduleName, ...args),
            group: (label, collapsed) => Logger.group(`[${moduleName}] ${label}`, collapsed),
            time: label => Logger.time(`[${moduleName}] ${label}`),
            timeEnd: label => Logger.timeEnd(`[${moduleName}] ${label}`)
        };
    }
    const SM_LOGGER = createLogger("PudongStateManager");
    const PudongStateManager = {
        status: CONSTANTS.LEARNING_STATES.IDLE,
        currentCourse: null,
        progress: {
            currentPercent: 0,
            watchedSeconds: 0,
            totalSeconds: 0,
            currentChapter: 0,
            totalChapters: 0
        },
        failureReason: null,
        async startCourse(course, {index: index, total: total} = {}) {
            this.currentCourse = course;
            this.status = CONSTANTS.LEARNING_STATES.PREPARING;
            this.failureReason = null;
            this.progress = {
                currentPercent: 0,
                watchedSeconds: 0,
                totalSeconds: 0,
                currentChapter: 0,
                totalChapters: 0
            };
            EventBus.publish(CONSTANTS.EVENTS.COURSE_START, {
                course: course,
                status: this.status,
                index: index,
                total: total
            });
            SM_LOGGER.info(`开始课程: ${course.title}`);
        },
        async updateProgress(updates) {
            this.progress = {
                ...this.progress,
                ...updates
            };
            EventBus.publish(CONSTANTS.EVENTS.PROGRESS_UPDATE, {
                percent: this.progress.currentPercent,
                watched: this.progress.watchedSeconds,
                total: this.progress.totalSeconds
            });
        },
        async setLearning() {
            this.status = CONSTANTS.LEARNING_STATES.LEARNING;
            SM_LOGGER.debug("进入学习状态");
        },
        async complete(result = {}) {
            this.status = CONSTANTS.LEARNING_STATES.COMPLETED;
            EventBus.publish(CONSTANTS.EVENTS.COURSE_COMPLETE, {
                course: this.currentCourse,
                result: result
            });
            SM_LOGGER.info(`课程完成: ${this.currentCourse?.title}`, result);
        },
        async skip(reason) {
            this.status = CONSTANTS.LEARNING_STATES.SKIPPED;
            EventBus.publish(CONSTANTS.EVENTS.COURSE_SKIP, {
                course: this.currentCourse,
                reason: reason
            });
            SM_LOGGER.info(`课程跳过: ${this.currentCourse?.title}, 原因: ${reason}`);
        },
        async fail(reason, error = null) {
            this.status = CONSTANTS.LEARNING_STATES.FAILED;
            this.failureReason = reason;
            EventBus.publish(CONSTANTS.EVENTS.COURSE_ERROR, {
                course: this.currentCourse,
                reason: reason,
                error: error
            });
            SM_LOGGER.error(`课程失败: ${this.currentCourse?.title}, 原因: ${reason}`, error);
        },
        async reset() {
            this.status = CONSTANTS.LEARNING_STATES.IDLE;
            this.currentCourse = null;
            this.failureReason = null;
            this.progress = {
                currentPercent: 0,
                watchedSeconds: 0,
                totalSeconds: 0,
                currentChapter: 0,
                totalChapters: 0
            };
            SM_LOGGER.info("状态已重置");
        },
        isLearning() {
            return this.status === CONSTANTS.LEARNING_STATES.LEARNING;
        },
        isCompleted() {
            return this.status === CONSTANTS.LEARNING_STATES.COMPLETED;
        },
        getState() {
            return {
                status: this.status,
                course: this.currentCourse,
                progress: this.progress,
                failureReason: this.failureReason
            };
        }
    };
    const PROC_LOGGER = createLogger("PudongProcessor");
    const PudongProcessor = {
        async processCourse(course, options = {}) {
            const {skipCompleted: skipCompleted = true, index: index, total: total} = options;
            const courseId = course.id || course.courseId;
            course.dsUnitId;
            PROC_LOGGER.info(`开始处理课程: ${course.title}`, {
                courseId: courseId
            });
            try {
                const prepResult = await this.prepare(course, {
                    skipCompleted: skipCompleted,
                    index: index,
                    total: total
                });
                if (prepResult.action === "skip") {
                    PROC_LOGGER.info(`课程跳过: ${course.title}, 原因: ${prepResult.reason}`);
                    return {
                        action: "skip",
                        course: course,
                        reason: prepResult.reason
                    };
                }
                if (prepResult.action === "fail") {
                    PROC_LOGGER.warn(`课程准备失败: ${course.title}`);
                    return {
                        action: "fail",
                        course: course,
                        reason: prepResult.reason
                    };
                }
                const execResult = await this.execute(course, prepResult.playInfo);
                if (!execResult.success) {
                    await PudongStateManager.fail(execResult.reason || "学习执行失败");
                    return {
                        action: "fail",
                        course: course,
                        reason: execResult.reason
                    };
                }
                await this.cleanup();
                await PudongStateManager.complete(execResult);
                PROC_LOGGER.info(`课程完成: ${course.title}`, execResult);
                return {
                    action: "complete",
                    course: course,
                    result: execResult
                };
            } catch (error) {
                PROC_LOGGER.error(`课程处理异常: ${course.title}`, error);
                await PudongStateManager.fail("unknown_error", error);
                return {
                    action: "fail",
                    course: course,
                    reason: error.message
                };
            }
        },
        async prepare(course, options = {}) {
            const {skipCompleted: skipCompleted = true, index: index, total: total} = options;
            const courseId = course.id || course.courseId;
            const coursewareId = course.dsUnitId;
            await PudongStateManager.startCourse(course, {
                index: index,
                total: total
            });
            if (skipCompleted && PUDONG_CONSTANTS.SKIP_COMPLETED_COURSES) {
                try {
                    const completionCheck = await PudongApi.checkCompletion(courseId, coursewareId);
                    if (completionCheck.isCompleted) {
                        await PudongStateManager.skip(`已完成 (${completionCheck.finishedRate}%)`);
                        return {
                            action: "skip",
                            reason: "课程已完成"
                        };
                    }
                } catch (e) {
                    PROC_LOGGER.warn("完成度检查失败,继续学习", e);
                }
            }
            let playInfo;
            try {
                playInfo = await PudongApi.getPlayInfo(courseId, course.dsUnitId, course.durationStr);
            } catch (e) {
                PROC_LOGGER.warn("获取播放信息失败,使用默认值", e);
                playInfo = {
                    courseId: courseId,
                    coursewareId: coursewareId || courseId,
                    videoId: `mock_${courseId}`,
                    duration: CONSTANTS.TIME_FORMATS.DEFAULT_DURATION,
                    lastLearnedTime: 0
                };
            }
            if (!playInfo) {
                await PudongStateManager.fail("无法获取播放信息");
                return {
                    action: "fail",
                    reason: "无法获取课程播放信息"
                };
            }
            await PudongStateManager.updateProgress({
                totalSeconds: playInfo.duration,
                watchedSeconds: playInfo.lastLearnedTime
            });
            const progressPercent = Math.floor(playInfo.lastLearnedTime / playInfo.duration * 100);
            if (progressPercent >= PUDONG_CONSTANTS.COMPLETION_THRESHOLD) {
                await PudongStateManager.skip(`进度已达 ${progressPercent}%`);
                return {
                    action: "skip",
                    reason: "播放信息确认已完成"
                };
            }
            PROC_LOGGER.info(`课程准备完成: ${course.title}`, {
                progress: `${progressPercent}%`,
                duration: playInfo.duration
            });
            return {
                action: "learn",
                playInfo: playInfo
            };
        },
        async execute(course, playInfo) {
            await PudongStateManager.setLearning();
            try {
                const courseInfo = {
                    ...course,
                    ...playInfo,
                    title: course.title || course.courseName,
                    courseId: course.id || course.courseId
                };
                const success = await PudongApi.reportProgress(playInfo, playInfo.duration - 30);
                if (success) {
                    return {
                        success: true,
                        reason: "策略执行成功"
                    };
                } else {
                    return {
                        success: false,
                        reason: "策略执行失败"
                    };
                }
            } catch (error) {
                return {
                    success: false,
                    reason: error.message
                };
            }
        },
        async cleanup() {
            PROC_LOGGER.debug("课后清理完成");
        }
    };
    const PudongLearner = {
        PAGE_TYPES: PUDONG_CONSTANTS.PAGE_TYPES,
        get _pageValidator() {
            return createPageValidator({
                whitelist: PUDONG_CONSTANTS.PAGE_TYPE_WHITELIST,
                pageTypes: PUDONG_CONSTANTS.PAGE_TYPES,
                customMessages: {
                    index: "⚠️ 首页暂不支持自动学习,请进入专栏或课程页面",
                    default: "⚠️ 当前页面不支持自动学习。请进入课程播放页或列表页。"
                }
            });
        },
        _validatePageType(pageType) {
            return this._pageValidator.validate(pageType);
        },
        identifyPage() {
            return PudongHandler.identifyPage();
        },
        isPudongMode() {
            return PudongHandler.isPudongMode();
        },
        async selectAndExecute() {
            if (!this.isPudongMode()) {
                return null;
            }
            const pageType = this.identifyPage();
            if (!this._validatePageType(pageType)) {
                return false;
            }
            if (pageType === this.PAGE_TYPES.PLAYER) {
                return await this._handlePlayerPage();
            }
            if (pageType === this.PAGE_TYPES.COLUMN) {
                return await this._handleColumnPage();
            }
            if (pageType === this.PAGE_TYPES.INDEX) {
                return await this._handleIndexPage();
            }
            if (pageType === this.PAGE_TYPES.COURSE_LIST) {
                return await this._handleCourseListPage();
            }
            return null;
        },
        async _handlePlayerPage() {
            console.log("[PudongLearner] 处理浦东播放页");
            try {
                const result = await PudongHandler.startPlayerFlow();
                if (result) {
                    EventBus.publish(CONSTANTS.EVENTS.LEARNING_START, {
                        source: "pudong_player"
                    });
                    return true;
                }
                return false;
            } catch (error) {
                console.error("[PudongLearner] 播放页处理失败:", error);
                return false;
            }
        },
        async _batchLearn(courses, pageLabel) {
            if (!courses || courses.length === 0) {
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "⚠️ 未扫描到课程",
                    type: "warn"
                });
                return false;
            }
            console.log(`[PudongLearner] 获取到 ${courses.length} 门课程`);
            EventBus.publish(CONSTANTS.EVENTS.STATISTICS_UPDATE, {
                total: courses.length,
                completed: 0,
                learned: 0,
                failed: 0,
                skipped: 0
            });
            const {Learner: Learner} = await Promise.resolve().then(function() {
                return learner;
            });
            const results = await this.processCourses(courses, {
                stopChecker: () => Learner && Learner.stopRequested
            });
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: `🎉 ${pageLabel}完成: ${results.completed}学习 ${results.skipped}跳过 ${results.failed}失败`,
                type: "success"
            });
            return true;
        },
        async _handleColumnPage() {
            console.log("[PudongLearner] 处理浦东专栏页");
            try {
                return await this._batchLearn(await PudongHandler.scanCourses(), "专栏");
            } catch (error) {
                console.error("[PudongLearner] 专栏页处理失败:", error);
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: `❌ 专栏页处理失败: ${error.message}`,
                    type: "error"
                });
                return false;
            }
        },
        async _handleIndexPage() {
            console.log("[PudongLearner] 处理浦东首页");
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: "⚠️ 首页暂不支持自动学习,请进入专栏或课程页面",
                type: "warn"
            });
            return false;
        },
        async _handleCourseListPage() {
            console.log("[PudongLearner] 处理浦东课程列表页");
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: "📋 检测到课程列表页,开始扫描...",
                type: "info"
            });
            try {
                return await this._batchLearn(await PudongHandler.scanCourses(), "课程列表");
            } catch (error) {
                console.error("[PudongLearner] 课程列表页处理失败:", error);
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: `❌ 课程列表页处理失败: ${error.message}`,
                    type: "error"
                });
                return false;
            }
        },
        async processCourses(courses, options = {}) {
            const results = {
                total: courses.length,
                completed: 0,
                learned: 0,
                skipped: 0,
                failed: 0,
                details: []
            };
            const {stopChecker: stopChecker = null} = options;
            for (let i = 0; i < courses.length; i++) {
                if (stopChecker && stopChecker()) {
                    console.log("[PudongLearner] 用户停止学习");
                    break;
                }
                const course = courses[i];
                const isLast = i === courses.length - 1;
                try {
                    const result = await PudongProcessor.processCourse(course, {
                        skipCompleted: true,
                        index: i + 1,
                        total: courses.length
                    });
                    if (result.action === "complete") {
                        results.completed++;
                        results.learned++;
                    } else if (result.action === "skip") {
                        results.skipped++;
                    } else {
                        results.failed++;
                    }
                    results.details.push({
                        courseId: course.id || course.courseId,
                        title: course.title || course.courseName,
                        action: result.action,
                        reason: result.reason
                    });
                    EventBus.publish(CONSTANTS.EVENTS.STATISTICS_UPDATE, results);
                    if (!isLast && result.action !== "fail") {
                        await PudongProcessor.coolingDown(isLast, stopChecker);
                    }
                } catch (error) {
                    console.error(`[PudongLearner] 处理课程失败: ${course.title}`, error);
                    results.failed++;
                    results.details.push({
                        courseId: course.id || course.courseId,
                        title: course.title || course.courseName,
                        action: "error",
                        reason: error.message
                    });
                }
            }
            console.log("[PudongLearner] 批量处理完成", results);
            return results;
        },
        async getPlayerCoursewareList() {
            const courseId = this._extractCourseIdFromUrl();
            if (!courseId) {
                console.warn("[PudongLearner] 无法提取课程ID");
                return [];
            }
            return await PudongApi.getCoursewareList(courseId);
        },
        _extractCourseIdFromUrl() {
            const url = new URL(window.location.href);
            return url.searchParams.get("courseId") || url.searchParams.get("id");
        },
        async reset() {
            await PudongStateManager.reset();
        },
        getState() {
            return PudongStateManager.getState();
        }
    };
    const FlowOrchestrator = {
        async selectAndExecute() {
            if (CONFIG.GWYPX_MODE) {
                const gwypxResult = await GwypxLearner.selectAndExecute();
                if (gwypxResult !== null) {
                    return gwypxResult;
                }
            }
            if (CONFIG.CBEAD_MODE) {
                const cbeadResult = await CbeadLearner.selectAndExecute();
                if (cbeadResult !== null) {
                    return cbeadResult;
                }
            }
            if (CONFIG.PUDONG_MODE) {
                const pudongResult = await PudongLearner.selectAndExecute();
                if (pudongResult !== null) {
                    return pudongResult;
                }
            }
            return null;
        }
    };
    function _buildPagePatterns() {
        const patterns = {};
        for (const [platform, pages] of Object.entries(CONSTANTS.PAGE_CONFIG)) {
            for (const [key, config] of Object.entries(pages)) {
                const paths = Array.isArray(config.path) ? config.path : [ config.path ];
                patterns[`${platform}_${key}`] = paths.map(p => new RegExp(p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
            }
        }
        return patterns;
    }
    const _patterns = _buildPagePatterns();
    const UrlParser = {
        PAGE_PATTERNS: _patterns,
        extractIdFromUrl(url) {
            if (!url) return null;
            const urlObj = new URL(url, "http://localhost");
            let id = urlObj.searchParams.get("id") || urlObj.searchParams.get("courseId");
            if (id) return id;
            const hash = urlObj.hash;
            if (!hash) return null;
            if (hash.includes("?")) {
                const hashSearch = hash.split("?")[1];
                const hashParams = new URLSearchParams(hashSearch);
                id = hashParams.get("id");
                if (id) return id;
            }
            let match = hash.match(/[?&]id=([^&]+)/);
            if (match) return match[1];
            match = hash.match(/branch-list-v\/([a-f0-9-]+)/);
            if (match) return match[1];
            match = hash.match(/detail\/\d+&([a-f0-9-]{36})/);
            if (match) return match[1];
            match = hash.match(/detail\/(?:[^/]*@@)?([a-f0-9-]{36})/i);
            if (match) return match[1];
            return null;
        },
        isIdRequiredPage(href) {
            return Object.values(_patterns).some(patterns => patterns.some(re => re.test(href)));
        },
        isChannelListPage(href) {
            return href.includes("channelList");
        },
        isPudongSpecialPage(PudongHandler) {
            if (!PudongHandler) return false;
            return PudongHandler.identifyPage() === PudongHandler.PAGE_TYPES.COLUMN;
        },
        isHomePage(href) {
            for (const pages of Object.values(CONSTANTS.PAGE_CONFIG)) {
                for (const [key, config] of Object.entries(pages)) {
                    if (key === "INDEX" || key === "HOME_V") {
                        const paths = Array.isArray(config.path) ? config.path : [ config.path ];
                        if (paths.some(p => href.includes(p))) return true;
                    }
                }
            }
            if (href.includes("pagehome/index")) return true;
            const homeElement = document?.querySelector('[module-name="nc.pagehome.index"]');
            return !!homeElement;
        }
    };
    let API = null;
    function setAPI(api) {
        API = api;
    }
    const Learner = {
        state: CONSTANTS.LEARNING_STATES.IDLE,
        isRunning: false,
        stopRequested: false,
        stop: function() {
            this.isRunning = false;
            this.stopRequested = true;
            this.state = CONSTANTS.LEARNING_STATES.IDLE;
            const gwypxState = window.__CELA_GWYPX_STATE__;
            if (gwypxState) {
                gwypxState.stopRequested = true;
            }
            if (API && API.abortController) {
                API.abortController.abort();
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "正在中止所有网络请求...",
                    type: "info"
                });
            }
            const toggleBtn = document.getElementById(CONSTANTS.SELECTORS.TOGGLE_BTN.replace("#", ""));
            if (toggleBtn) {
                toggleBtn.setAttribute("data-state", "stopped");
                toggleBtn.textContent = "开始学习";
            }
            EventBus.publish(CONSTANTS.EVENTS.LEARNING_STOP);
            EventBus.publish(CONSTANTS.EVENTS.STATUS_UPDATE, "已停止");
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: "[STOP] 学习流程已停止",
                type: "warn"
            });
        },
        hasValidId: function() {
            if (CONFIG.IS_PORTAL || CONFIG.UNSUPPORTED_BRANCH) return false;
            const href = window.location.href;
            if (UrlParser.isHomePage(href)) return false;
            if (UrlParser.isChannelListPage(href)) return false;
            const needsId = UrlParser.isIdRequiredPage(href);
            const id = UrlParser.extractIdFromUrl(href);
            if (id) return true;
            if (needsId) return false;
            const hasCourseElements = CONSTANTS.COURSE_SELECTORS.some(selector => document.querySelector(selector));
            if (hasCourseElements) {
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "[校验] 虽然URL没发现ID,但页面检测到课程元素,允许启动",
                    type: "info"
                });
                return true;
            }
            return false;
        },
        async startLearning() {
            try {
                const result = await FlowOrchestrator.selectAndExecute();
                if (result === CONSTANTS.WAITING_FOR_USER) {
                    console.log("[Learner] 用户主动点击按钮,开始学习流程");
                    const {CbeadLearner: CbeadLearner} = await Promise.resolve().then(function() {
                        return learner$1;
                    });
                    await CbeadLearner.startBranchListFlow();
                    return;
                }
                if (result === null) {
                    UI.resetToggleButton("页面不支持");
                    return;
                }
                if (result === false) {
                    UI.resetToggleButton("页面不支持");
                    return;
                }
                if (result === true) {
                    if (!this.stopRequested) {
                        UI.resetToggleButton();
                    }
                    return;
                }
            } catch (error) {
                this._handleLearningError(error);
            }
        },
        _handleLearningError(error) {
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: `学习流程出错: ${error.message}`,
                type: "error"
            });
            console.error("学习流程错误:", error);
            UI.resetToggleButton("学习出错");
        }
    };
    var learner = Object.freeze({
        __proto__: null,
        Learner: Learner,
        setAPI: setAPI
    });
    const LearningStrategies = {
        async instant_finish(context) {
            const {duration: duration} = context;
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: "[SPEED] 采用极速完成策略 - 直接冲刺",
                type: "info"
            });
            const learner = ServiceLocator.get(ServiceNames.LEARNER);
            const delay = Math.floor(Math.random() * 500 + 500);
            const steps = 5;
            const stepDelay = delay / steps;
            for (let i = 0; i < steps; i++) {
                if (learner && learner.stopRequested) {
                    EventBus.publish(CONSTANTS.EVENTS.LOG, {
                        message: "[STOP] 用户中断学习",
                        type: "warn"
                    });
                    return false;
                }
                await new Promise(resolve => setTimeout(resolve, stepDelay));
            }
            const finalTime = Math.max(0, duration - 30);
            if (learner && learner.stopRequested) {
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: "🛑 用户中断学习",
                    type: "warn"
                });
                return false;
            }
            const api = ServiceLocator.get(ServiceNames.API);
            return await api.reportProgressWithDelay(context.playInfo, finalTime);
        }
    };
    function setupDependencyInjection() {
        if (typeof window !== "undefined") {
            window.UI = UI;
            window.CbeadLearner = CbeadLearner;
        }
        ServiceLocator.register(ServiceNames.UI, UI);
        ServiceLocator.register(ServiceNames.API, API$1);
        ServiceLocator.register(ServiceNames.LEARNER, Learner);
        ServiceLocator.register(ServiceNames.CONFIG, CONFIG);
        setAPI(API$1);
    }
    function initScript() {
        startMaskObserver();
        Settings.load();
        setupDependencyInjection();
        UI.createPanel();
        detectEnvironment();
        if (CONFIG.PUDONG_MODE) {
            PudongHandler.init();
        }
        if (CONFIG.CBEAD_MODE) {
            CbeadHandler.init();
            setupRouteListener();
            setupProgressErrorMonitor();
        }
        if (CONFIG.GWYPX_MODE) {
            GwypxHandler.init();
        }
        GM_registerMenuCommand("导出调试日志", UI.exportLogs, "e");
        const toggleBtn = document.getElementById(CONSTANTS.SELECTORS.TOGGLE_BTN.replace("#", ""));
        if (toggleBtn) {
            toggleBtn.addEventListener("click", () => {
                const isRunning = toggleBtn.getAttribute("data-state") === "running";
                if (isRunning) {
                    Learner.stop();
                } else {
                    Learner.stopRequested = false;
                    Learner.isRunning = true;
                    Learner.state = CONSTANTS.LEARNING_STATES.LEARNING;
                    EventBus.publish(CONSTANTS.EVENTS.LEARNING_START);
                    EventBus.publish(CONSTANTS.EVENTS.STATUS_UPDATE, "学习中...");
                    Learner.startLearning().catch(error => {
                        EventBus.publish(CONSTANTS.EVENTS.LOG, {
                            message: `❌ 启动学习流程失败: ${error.message}`,
                            type: "error"
                        });
                        Learner.stop();
                    });
                }
            });
        }
        EventBus.publish(CONSTANTS.EVENTS.LOG, {
            message: "[START] cela学习助手 初始化完成",
            type: "success"
        });
        setTimeout(() => {
            checkAndStartAutoLearning();
        }, 1e3);
    }
    let isAutoStarting = false;
    function checkAndStartAutoLearning() {
        const isCbeadPlayer = CONFIG.CBEAD_MODE && window.location.href.includes("study/course/detail");
        const isGwypxPlayer = CONFIG.GWYPX_MODE && window.location.href.includes("/pcPage/commend/coursedetail");
        if (isCbeadPlayer || isGwypxPlayer) {
            console.log(`[Init] 🔍 检测到${isCbeadPlayer ? "企业" : "党校"}分院播放页,准备开始学习...`);
            if (isAutoStarting) {
                console.log("[Init] ⚠️ 学习任务已在进行中,跳过重复启动");
                return;
            }
            isAutoStarting = true;
            console.log("[Init] 🔒 设置防重复标志,开始学习...");
            const toggleBtn = document.getElementById("toggle-learning-btn");
            if (toggleBtn) {
                toggleBtn.setAttribute("data-state", "learning");
                toggleBtn.textContent = "停止学习";
            }
            Learner.startLearning();
            setTimeout(() => {
                isAutoStarting = false;
                console.log("[Init] 🔓 重置防重复标志(超时重置)");
            }, 6e4);
        }
    }
    function handleErrorPageNavigation(currentUrl, lastUrl) {
        if (currentUrl.includes("study/errors/") && lastUrl.includes("study/course/detail")) {
            console.warn("[Init] 检测到跳转到错误页面!");
            console.warn("[Init] 可能原因:课程不存在、无访问权限或已被删除");
            const uuidMatch = currentUrl.match(/errors\/([a-f0-9-]+)/);
            if (uuidMatch) {
                const failedUuid = uuidMatch[1];
                console.warn(`[Init] 失败课程 UUID: ${failedUuid}`);
                EventBus.publish(CONSTANTS.EVENTS.LOG, {
                    message: `❌ 课程加载失败(UUID: ${failedUuid.substring(0, 8)}...),可能无访问权限`,
                    type: "error"
                });
                LearningState.markFailed("课程加载失败(跳转到错误页面)");
                setTimeout(() => {
                    if (typeof CbeadHandler !== "undefined" && CbeadHandler.returnToList) {
                        console.log("[Init] 返回列表页...");
                        const returnUrl = "#/branch-list-v";
                        CbeadHandler.returnToList(returnUrl);
                    }
                }, 2e3);
            }
            return true;
        }
        return false;
    }
    function handlePageChange(currentUrl, lastUrl) {
        try {
            console.log(`[Init] 检测到路由变化:`);
            console.log(`   - 旧 URL: ${lastUrl}`);
            console.log(`   - 新 URL: ${currentUrl}`);
            handleErrorPageNavigation(currentUrl, lastUrl);
            if (currentUrl.includes("study/course/detail") && !lastUrl.includes("study/course/detail")) {
                console.log("[Init] 导航到播放页,立即检查批量学习任务...");
                checkAndStartAutoLearning();
            }
            if (currentUrl.includes("branch-list-v") || currentUrl.includes("center/my/course") || currentUrl.includes("class-detail")) {
                console.log("[Init] 导航到列表页,触发学习流程...");
                setTimeout(() => {
                    CbeadLearner.selectAndExecute();
                }, 2e3);
            }
            return true;
        } catch (error) {
            console.error("[Init] 页面变化处理出错:", error);
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: `路由变化处理失败: ${error.message}`,
                type: "error"
            });
            return false;
        }
    }
    function setupRouteListener() {
        if (!CONFIG.CBEAD_MODE) {
            console.log("[Init] 非企业分院环境,不设置路由监听");
            return;
        }
        console.log("[Init] 设置路由监听(企业分院 SPA 模式)");
        let lastUrl = window.location.href;
        window.addEventListener("hashchange", () => {
            const currentUrl = window.location.href;
            if (currentUrl !== lastUrl) {
                handlePageChange(currentUrl, lastUrl);
                lastUrl = currentUrl;
            }
        });
        const urlCheckInterval = setInterval(() => {
            const currentUrl = window.location.href;
            if (currentUrl !== lastUrl) {
                handlePageChange(currentUrl, lastUrl);
                lastUrl = currentUrl;
            }
        }, 1e3);
        window.__celaAutoUrlCheckInterval = urlCheckInterval;
        console.log("[Init] 路由监听已启动(hashchange + 轮询)");
    }
    function setupProgressErrorMonitor() {
        console.log("[Init] 📡 设置进度错误监听器(企业分院专用)...");
        LearningState.reset();
        const skipCurrentCourse = reason => {
            if (LearningState.isFailed()) {
                console.log("[ProgressError] ⚠️ 跳过已触发,忽略重复调用");
                return;
            }
            LearningState.markFailed(reason);
            console.warn(`[ProgressError] 🚨 检测到进度上报失败,标记当前课程为失败: ${reason}`);
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: `❌ 进度上报失败(${reason}),标记当前课程为失败`,
                type: "error"
            });
            EventBus.publish(CONSTANTS.EVENTS.LOG, {
                message: "💡 服务器无法记录学习进度,继续播放无意义",
                type: "info"
            });
            EventBus.publish("course:failed", {
                reason: `进度上报失败: ${reason}`
            });
            setTimeout(() => {
                if (typeof Learner !== "undefined" && Learner.state === CONSTANTS.LEARNING_STATES.LEARNING) {
                    console.log("[ProgressError] 🛑 停止当前学习流程...");
                    Learner.stop();
                }
                console.log("[ProgressError] ✅ 已标记当前课程为失败");
            }, 500);
        };
        window.celaAutoResetCourseFail = () => {
            LearningState.reset();
        };
        const originalXHROpen = XMLHttpRequest.prototype.open;
        const originalXHRSend = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.open = function(method, url) {
            this._method = method;
            this._url = url;
            return originalXHROpen.apply(this, arguments);
        };
        XMLHttpRequest.prototype.send = function() {
            if (this._url && this._url.includes("update-progress")) {
                this.addEventListener("load", function() {
                    if (this.status === 422) {
                        console.error("[ProgressError] POST 422:", this._url);
                        skipCurrentCourse("422 Unprocessable Content");
                    }
                });
            }
            return originalXHRSend.apply(this, arguments);
        };
        const originalFetch = window.fetch;
        window.fetch = function(url, options) {
            return originalFetch.apply(this, arguments).then(response => {
                if (typeof url === "string" && url.includes("update-progress") && response.status === 422) {
                    console.error("[ProgressError] POST 422:", url);
                    skipCurrentCourse("422 Unprocessable Content");
                }
                return response;
            });
        };
        console.log("[Init] ✅ 进度错误监听器已启动(422 错误将自动跳过课程)");
    }
    function muteSingleMedia(media, type) {
        media.muted = true;
        media.volume = 0;
        if (type === "video" && window.player && typeof window.player.muted === "function") {
            try {
                window.player.muted(true);
            } catch (e) {}
        }
        console.log(`[Init] ${type} 已静音`);
    }
    function muteSingleVideo(video, index) {
        muteSingleMedia(video, "video");
        console.log(`[Init] video #${index + 1} 已静音`);
    }
    function muteSingleAudio(audio, index) {
        audio.muted = true;
        audio.volume = 0;
        audio.pause();
        console.log(`[Init] audio #${index + 1} 已静音并暂停`);
    }
    function muteExistingMedia(selector, type) {
        const elements = document.querySelectorAll(selector);
        console.log(`[Init] 找到 ${elements.length} 个现有的 ${type} 元素`);
        elements.forEach((element, index) => muteSingleMedia(element, type));
    }
    function startPollingMute() {
        let pollCount = 0;
        const maxPolls = 100;
        const pollIntervalMs = 100;
        const pollInterval = setInterval(() => {
            pollCount++;
            const allVideos = document.querySelectorAll("video");
            let hasUnmuted = false;
            allVideos.forEach(video => {
                if (!video.muted || video.volume !== 0) {
                    muteSingleVideo(video, 0);
                    if (!hasUnmuted) {
                        console.log(`[Init] 轮询发现未静音的 video,立即静音 (第${pollCount}次)`);
                        hasUnmuted = true;
                    }
                }
            });
            const allAudios = document.querySelectorAll("audio");
            allAudios.forEach(audio => {
                if (!audio.muted || audio.volume !== 0) {
                    muteSingleAudio(audio, 0);
                    console.log(`[Init] 轮询发现未静音的 audio,立即静音 (第${pollCount}次)`);
                }
            });
            if (pollCount >= maxPolls) {
                clearInterval(pollInterval);
                console.log("[Init] 高频轮询完成");
            }
        }, pollIntervalMs);
        window.__celaAutoPollInterval = pollInterval;
        console.log(`[Init] 高频轮询已启动(每${pollIntervalMs}ms,持续10秒)`);
        return pollInterval;
    }
    function muteNewMediaElement(node) {
        if (node.nodeName === "VIDEO") {
            console.log("[Init] MutationObserver: 检测到新创建的 video 元素,立即静音并暂停");
            muteSingleVideo(node, 0);
            node.autoplay = false;
            node.pause();
        }
        if (node.nodeName === "AUDIO") {
            console.log("[Init] MutationObserver: 检测到新创建的 audio 元素,立即静音并暂停");
            muteSingleAudio(node, 0);
            node.autoplay = false;
        }
        if (node.querySelectorAll) {
            node.querySelectorAll("video").forEach(video => {
                console.log("[Init] MutationObserver: 检测到新创建的子 video 元素,立即静音并暂停");
                muteSingleVideo(video, 0);
                video.autoplay = false;
                video.pause();
            });
            node.querySelectorAll("audio").forEach(audio => {
                console.log("[Init] MutationObserver: 检测到新创建的子 audio 元素,立即静音并暂停");
                muteSingleAudio(audio, 0);
                audio.autoplay = false;
            });
        }
    }
    function startMuteMutationObserver() {
        const observer = new MutationObserver(mutations => {
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => muteNewMediaElement(node));
            });
        });
        observer.observe(document.documentElement, {
            childList: true,
            subtree: true
        });
        console.log("[Init] MutationObserver 已启动,将持续监听并静音新媒体元素");
        return observer;
    }
    function clickMaskButtons() {
        console.log("[Init] 检查遮罩并点击按钮...");
        const maskCheck = detectMask();
        if (maskCheck.exists) {
            console.log(`[Init] 检测到 ${maskCheck.masks.length} 个遮罩,尝试点击按钮...`);
            const clickResult = clickMaskButton();
            console.log(`[Init] 已点击 ${clickResult.clicked} 个遮罩按钮`);
        }
    }
    function immediateMuteAllVideos() {
        console.log("[Init] 立即静音模式启动...");
        muteExistingMedia("video", "video");
        muteExistingMedia("audio", "audio");
        startPollingMute();
        startMuteMutationObserver();
        clickMaskButtons();
    }
    const hasVideoElement = document.querySelector("video") !== null;
    const isPlayerPage = window.location.href.includes("study/course/detail") || window.location.href.includes("/pcPage/commend/coursedetail");
    if (isPlayerPage && hasVideoElement) {
        immediateMuteAllVideos();
    }
    setTimeout(initScript, 1e3);
    window.addEventListener("beforeunload", () => {
        if (window.__celaAutoUrlCheckInterval) {
            clearInterval(window.__celaAutoUrlCheckInterval);
        }
        if (window.__celaAutoPollInterval) {
            clearInterval(window.__celaAutoPollInterval);
        }
    });
    exports.API = API$1;
    exports.ApiFactory = ApiFactory;
    exports.CONFIG = CONFIG;
    exports.CONSTANTS = CONSTANTS;
    exports.CbeadHandler = CbeadHandler;
    exports.CbeadLearner = CbeadLearner;
    exports.CourseAdapter = CourseAdapter;
    exports.EventBus = EventBus;
    exports.Learner = Learner;
    exports.LearningStrategies = LearningStrategies;
    exports.PudongHandler = PudongHandler;
    exports.RequestQueue = RequestQueue;
    exports.Settings = Settings;
    exports.UI = UI;
    exports.Utils = Utils;
    exports.detectEnvironment = detectEnvironment;
    return exports;
})({});