Greasy Fork

Greasy Fork is available in English.

网课全能助手|超星学习通|智慧树|智慧职教|中国大学mooc|自动答题|自动刷课|一键操作|超全题库|可视化配置

支持超星学习通、智慧树、智慧职教、中国大学mooc等平台,包含自动答题、自动刷课、任务点自动跳转、全网检索答案、chatgpt对接、音频视频自动静音播放、可视化参数配置等功能。

当前为 2025-12-23 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name       				网课全能助手|超星学习通|智慧树|智慧职教|中国大学mooc|自动答题|自动刷课|一键操作|超全题库|可视化配置
// @version    				1.0.0
// @description				支持超星学习通、智慧树、智慧职教、中国大学mooc等平台,包含自动答题、自动刷课、任务点自动跳转、全网检索答案、chatgpt对接、音频视频自动静音播放、可视化参数配置等功能。
// @icon                                https://apps.chaoxing.com/res/images/apk/logo.png
// @author     				iKaiKail
// @email      				[email protected]
// @license    				MIT
// @match      				*://*.zhihuishu.com/*
// @match      				*://*.chaoxing.com/*
// @match      				*://*.edu.cn/*
// @match      				*://*.org.cn/*
// @match      				*://*.xueyinonline.com/*
// @match      				*://*.hnsyu.net/*
// @match      				*://*.qutjxjy.cn/*
// @match      				*://*.ynny.cn/*
// @match      				*://*.hnvist.cn/*
// @match      				*://*.fjlecb.cn/*
// @match      				*://*.gdhkmooc.com/*
// @match      				*://*.cugbonline.cn/*
// @match      				*://*.zjelib.cn/*
// @match      				*://*.cqrspx.cn/*
// @match      				*://*.neauce.com/*
// @match      				*://*.zhihui-yun.com/*
// @match      				*://*.cqie.cn/*
// @match      				*://*.ccqmxx.com/*
// @match      				*://*.icve.com.cn/*
// @match      				*://*.course.icve.com.cn/*
// @match      				*://*.courshare.cn/*
// @match      				*://*.webtrn.cn/*
// @match      				*://*.zjy2.icve.com.cn/*
// @match      				*://*.zyk.icve.com.cn/*
// @match      				*://*.icourse163.org/*
// @match      				*://*.nbdlib.cn/*
// @require      https://update.greasyfork.icu/scripts/559955/1719602/vueglobalprodjs.js
// @require      https://update.greasyfork.icu/scripts/559954/1719600/indexiifeminjs.js
// @require      https://update.greasyfork.icu/scripts/559953/1719599/globaliifeminjs.js
// @require      data:application/javascript,window.Vue%3DVue%3B
// @require      https://update.greasyfork.icu/scripts/559952/1719598/piniaiifeprodjs.js
// @require      https://update.greasyfork.icu/scripts/559951/1719596/indexfullminjs.js
// @require      https://update.greasyfork.icu/scripts/559950/1719593/md5minjs.js
// @require      https://update.greasyfork.icu/scripts/559949/1719590/jqueryminjs.js
// @resource     element-plus  https://cdn.staticfile.org/element-plus/2.3.12/index.css
// @resource     ttf           https://www.forestpolice.org/ttf/2.0/table.json
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_info
// @grant        GM_xmlhttpRequest
// @grant        GM_listValues
// @grant        GM_deleteValue
// @grant        GM_notification
// @grant        GM_getTab
// @grant        GM_saveTab
// @grant        unsafeWindow
// @grant        GM_addValueChangeListener
// @grant        GM_removeValueChangeListener
// @run-at     				document-start
// @namespace    			https://github.com/ikaikail
// @homepage     			https://github.com/ikaikail
// @connect      cx.icodef.com
// @connect      tk.enncy.cn
// @connect      api.muketool.com
// @connect      api.tikuhai.com
// @connect      dati.bobo91.com
// @connect      icodef.com
// @connect      ocsjs.com
// @connect      localhost
// @connect      127.0.0.1
// ==/UserScript==

(function() {
    'use strict';
    
    // 添加样式
    (t=>{if(typeof GM_addStyle=="function"){GM_addStyle(t);return}const i=document.createElement("style");i.textContent=t,document.head.append(i)})(' .dialog-footer button[data-v-6ed29f7f]:first-child{margin-right:10px}#csbutton[data-v-6ed29f7f]{position:fixed;bottom:20px;right:20px;z-index:99999}#zeokdjg[data-v-c3c6b09f]{position:fixed;left:10px;bottom:50vh;z-index:9999}.question_btn[data-v-c3c6b09f]{width:40px;height:40px;border-radius:5px;margin:5px}.question_div[data-v-c3c6b09f]{height:200px}.question_ti[data-v-c3c6b09f]{margin:10px 0 20px}.cx_log[data-v-c3c6b09f]{margin:2px 0}.status_log[data-v-c3c6b09f]{margin-top:10px}.dialog-footer button[data-v-c3c6b09f]:first-child{margin-right:10px}#csbutton[data-v-c3c6b09f]{position:fixed;bottom:20px;right:20px;z-index:99999} ');
    
    // 初始化Vue和相关库
    const Vue = window.Vue;
    const ElementPlus = window.ElementPlus;
    const pinia = window.Pinia.createPinia();
    Vue.use(ElementPlus);
    Vue.use(pinia);
    
    // 配置管理
    const getConfig = () => {
        const config = GM_getValue("config");
        return config || {
            debugger: false,
            autoAnswer: true,
            autoVideo: true,
            autoJump: true,
            autoSubmit: true,
            thtoken: "",
            yztoken: "",
            gptKey: "",
            gptModel: "gpt-3.5-turbo",
            gpt: false,
            gptType: ["0", "1", "2", "3", "4", "5", "6", "7"],
            interval: 3,
            answerInterval: 3,
            minAccuracy: 0.8,
            autoExam: true,
            hideExam: false
        };
    };
    
    // 用户配置项
    const userConfig = [
        {
            name: "base",
            label: "基础配置",
            config: [
                { name: "interval", label: "通用间隔(秒)", type: "number", value: 3, desc: "通用间隔,用于脚本运行切换" },
                { name: "answerInterval", label: "答题间隔(秒)", type: "number", value: 3, desc: "控制答题速度" },
                { name: "thtoken", label: "题库海秘钥", type: "input", value: "", desc: "非必填,购买后可获得,填写完请保存再刷新页面" },
                { name: "yztoken", label: "一之题库秘钥", type: "input", value: "", desc: "非必填,购买后可获得,填写完请保存再刷新页面" },
                { name: "gptKey", label: "ChatGPT API Key", type: "input", value: "", desc: "非必填,用于简答题AI辅助" },
                { name: "gptModel", label: "ChatGPT模型", type: "input", value: "gpt-3.5-turbo", desc: "ChatGPT模型选择" }
            ]
        },
        {
            name: "chapter",
            label: "章节配置",
            config: [
                { name: "autoAnswer", label: "自动答题", type: "switch", value: true, desc: "开启后,会自动答题" },
                { name: "autoVideo", label: "自动视频", type: "switch", value: true, desc: "开启后,会自动观看视频" },
                { name: "autoJump", label: "自动切换", type: "switch", value: true, desc: "开启后,会自动切换章节" },
                { name: "autoSubmit", label: "自动提交", type: "switch", value: true, desc: "开启后,会自动提交答案" },
                { name: "minAccuracy", label: "最低正确率", type: "input", value: 0.8, desc: "不满足最低正确率则不会自动提交答案" }
            ]
        },
        {
            name: "exam",
            label: "作业/考试配置",
            config: [
                { name: "autoExam", label: "考试自动切换", type: "switch", value: true, desc: "开启后,会考试会自动切换" },
                { name: "gpt", label: "启用ChatGPT", type: "switch", value: false, desc: "开启后,简答题会使用ChatGPT辅助" }
            ]
        }
    ];
    
    // 状态管理
    const useformStore = pinia.defineStore({
        id: "formstore",
        state: () => ({
            forminput: getConfig(),
            dialogV: false,
            activeName: "base"
        }),
        actions: {
            saveConfig(forminput) {
                GM_setValue("config", forminput);
            }
        }
    });
    
    // 应用组件
    const App = Vue.defineComponent({
        setup() {
            const formstoreObj = useformStore();
            const { forminput, dialogV, activeName } = Vue.toRefs(formstoreObj);
            const ruleFormRef = Vue.ref();
            
            const rules = Vue.reactive({
                interval: [
                    { required: true, message: "间隔时间不能为空" },
                    { type: "number", message: "间隔时间必须为数字" },
                    { validator: (rule, value) => value >= 1 ? Promise.resolve() : Promise.reject("间隔时间必须大于等于1") }
                ],
                answerInterval: [
                    { required: true, message: "答题间隔不能为空" },
                    { type: "number", message: "答题间隔必须为数字" },
                    { validator: (rule, value) => value >= 1 ? Promise.resolve() : Promise.reject("答题间隔必须大于等于1") }
                ]
            });
            
            const submitForm = async (formEl) => {
                if (!formEl) return;
                await formEl.validate((valid, fields) => {
                    if (valid) {
                        formstoreObj.saveConfig(forminput.value);
                        ElementPlus.ElNotification({
                            title: "Success",
                            message: "配置保存成功,请自行刷新页面",
                            type: "success"
                        });
                        dialogV.value = false;
                    }
                });
            };
            
            return {
                dialogV,
                activeName,
                ruleFormRef,
                forminput,
                rules,
                submitForm,
                userConfig
            };
        },
        template: `
            <div>
                <el-button type="primary" id="csbutton" @click="dialogV = !dialogV" circle>
                    <el-icon><el-icon-setting /></el-icon>
                </el-button>
                
                <el-dialog
                    v-model="dialogV"
                    title="iKaiKail网课全能助手配置"
                    width="30%"
                    :modal="false"
                    :center="true"
                    :draggable="true"
                >
                    <el-form
                        ref="ruleFormRef"
                        :rules="rules"
                        :model="forminput"
                        class="demo-ruleForm"
                    >
                        <el-tabs v-model="activeName" class="demo-tabs">
                            <el-tab-pane
                                v-for="item in userConfig"
                                :key="item.name"
                                :label="item.label"
                                :name="item.name"
                            >
                                <el-form-item
                                    v-for="item1 in item.config"
                                    :key="item1.name"
                                    :label="item1.label"
                                    :prop="item1.name"
                                >
                                    <el-tooltip
                                        class="box-item"
                                        effect="dark"
                                        :content="item1.desc || ''"
                                        placement="top"
                                    >
                                        <template v-if="item1.type === 'switch'">
                                            <el-switch v-model="forminput[item1.name]" />
                                        </template>
                                        <template v-else-if="item1.type === 'input'">
                                            <el-input v-model="forminput[item1.name]" placeholder="请输入" />
                                        </template>
                                        <template v-else-if="item1.type === 'number'">
                                            <el-input-number v-model="forminput[item1.name]" :min="1" />
                                        </template>
                                        <template v-else>
                                            <el-input v-model="forminput[item1.name]" placeholder="请输入" />
                                        </template>
                                    </el-tooltip>
                                </el-form-item>
                            </el-tab-pane>
                        </el-tabs>
                    </el-form>
                    <template #footer>
                        <span class="dialog-footer">
                            <el-button @click="dialogV = false">取消</el-button>
                            <el-button type="primary" @click="submitForm(ruleFormRef)">保存</el-button>
                        </span>
                    </template>
                </el-dialog>
            </div>
        `
    });
    
    // API服务类
    class ServerApi {
        constructor(window2 = unsafeWindow) {
            this.api1 = "http://api.tikuhai.com";
            this.api2 = "http://cx.icodef.com/wyn-nb?v=4";
            this.api3 = "https://tk.enncy.cn/query";
            this.api4 = "https://api.muketool.com/cx/v2/query";
            this.windowz = window2;
        }
        
        async defaultRequest(url, method, data = {}, headers = {}, type = false) {
            const defaultConfig = getConfig();
            if (type) {
                headers = {
                    "Content-Type": method === "POST" ? "application/json" : "text/plain",
                    Referer: this.windowz.location.href,
                    v: GM_info.script.version,
                    key: defaultConfig.thtoken || "",
                    uid: unsafeWindow.uid || (unsafeWindow.getCookie && unsafeWindow.getCookie.call(unsafeWindow, "_uid")) || "",
                    ...headers
                };
            }
            
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method,
                    url,
                    data: JSON.stringify(data),
                    headers,
                    timeout: 10000,
                    onload: (res) => resolve(res),
                    ontimeout: () => reject("timeout"),
                    onerror: (err) => reject(err)
                });
            });
        }
        
        async getAnswer(questionData) {
            const defaultConfig = getConfig();
            questionData = { key: defaultConfig.thtoken || "", ...questionData };
            
            return new Promise((resolve) => {
                this.defaultRequest(`${this.api1}/search`, "POST", questionData, {}, true)
                    .then((res) => {
                        const data = JSON.parse(res.responseText);
                        if (data.code === -1) {
                            this.s2(data.data);
                            resolve({ form: "题库海", answer: data.msg || "" });
                        } else {
                            resolve({ 
                                form: "题库海", 
                                answer: data.data.answer || data.msg || "", 
                                num: data.data.num || "", 
                                usenum: data.data.usenum || ""
                            });
                        }
                    })
                    .catch((e) => {
                        resolve({ form: "题库海", answer: "" });
                    });
            });
        }
        
        async getAnswer2(questionData) {
            let ip = Array.from({ length: 4 }, () => Math.floor(255 * Math.random())).join(".");
            
            return new Promise((resolve) => {
                let ques = { question: questionData.question };
                this.defaultRequest(this.api2, "POST", ques, {
                    "Content-Type": "application/json",
                    Authorization: getConfig().yztoken,
                    "X-Forwarded-For": ip,
                    "X-Real-IP": ip
                })
                .then((response) => {
                    const res = JSON.parse(response.responseText);
                    let answer = "";
                    if (res.code === 1) {
                        let data = res.data.replace(/javascript:void\(0\);/g, "").trim().replace(/\n/g, "");
                        if (!(data.includes("叛逆") || data.includes("公众号") || data.includes("李恒雅") || data.includes("一之"))) {
                            answer = data.split("#");
                        }
                    }
                    resolve({ form: "一之题库", answer });
                })
                .catch(() => {
                    resolve({ form: "一之题库", answer: "" });
                });
            });
        }
        
        async getAnswer3(questionData) {
            return new Promise((resolve) => {
                const ques = { token: getConfig().enncytoken, title: questionData.question };
                this.defaultRequest(this.api3, "POST", ques)
                .then((response) => {
                    const res = JSON.parse(response.responseText);
                    resolve({ form: "言溪题库", answer: res.code === 1 ? res.data.answer : "" });
                })
                .catch(() => {
                    resolve({ form: "言溪题库", answer: "" });
                });
            });
        }
        
        async getAnswer4(questionData) {
            return new Promise((resolve) => {
                const ques = { question: questionData.question, type: parseInt(questionData.type) };
                this.defaultRequest(this.api4, "POST", ques, { "Content-Type": "application/json" })
                .then((response) => {
                    const res = JSON.parse(response.responseText);
                    resolve({ form: "free4", answer: res.code === 1 ? res.data.split("#") : "" });
                })
                .catch(() => {
                    resolve({ form: "free4", answer: "" });
                });
            });
        }
        
        async s(questionList, url) {
            return new Promise(async (resolve) => {
                const ques = { questionList, url };
                await this.defaultRequest(`${this.api1}/save1`, "POST", ques, { "Content-Type": "application/json" })
                    .then((response) => resolve())
                    .catch((e) => resolve());
            });
        }
        
        async s2(data) {
            if (!data.url) return;
            
            return new Promise(async (resolve) => {
                try {
                    const response = await this.defaultRequest(data.url, "GET", null, {});
                    const html = response.responseText;
                    let document1 = new DOMParser().parseFromString(html, "text/html");
                    let questionList = document1.getElementsByClassName("Py-mian1");
                    let questionListHtml = [];
                    
                    for (let i = 0; i < questionList.length; i++) {
                        try {
                            if (i === 0) continue;
                            
                            let questionTitle = this.removeHtml(questionList[i].getElementsByClassName("Py-m1-title")[0].innerHTML);
                            let questionTypeMatch = questionTitle.match(/\[(.*?)\]/);
                            if (!questionTypeMatch) continue;
                            
                            let questionType = questionTypeMatch[1];
                            if (questionType === "单选题" || questionType === "多选题") {
                                questionTitle = questionTitle
                                    .replace(/[0-9]{1,3}.\s/gi, "")
                                    .replace(/(^\s*)|(\s*$)/g, "")
                                    .replace(/^【.*?】\s*/, "")
                                    .replace(/\[(.*?)\]\s*/, "")
                                    .replace(/\s*(\d+\.\d+分)$/, "");
                                
                                let optionHtml = $(questionList[i]).find("ul.answerList li.clearfix");
                                let optionText = [];
                                
                                optionHtml.each(function(index, item) {
                                    let abcd = String.fromCharCode(65 + index) + ".";
                                    let optionTemp = this.removeHtml(item.innerHTML);
                                    if (optionTemp.indexOf(abcd) === 0) {
                                        optionTemp = optionTemp.replace(abcd, "").trim();
                                    }
                                    optionText.push(optionTemp);
                                }.bind(this));
                                
                                questionListHtml.push({
                                    question: questionTitle,
                                    type: this.getQuestionType(questionType),
                                    options: optionText,
                                    questionData: questionList[i].innerHTML
                                });
                            }
                        } catch (e) {
                            continue;
                        }
                    }
                    
                    let postData = { questionList: questionListHtml, url: data.url };
                    await this.defaultRequest(data.url1, "POST", postData, {}, true);
                } catch (e) {
                    // 忽略错误
                } finally {
                    resolve();
                }
            });
        }
        
        removeHtml(html) {
            return html.replace(/<[^>]*>/g, "");
        }
        
        getQuestionType(typeStr) {
            const typeMap = {
                "单选题": 1,
                "多选题": 2,
                "判断题": 3
            };
            return typeMap[typeStr] || 1;
        }
    }
    
    // 工具函数
    const $ = {
        uuid() {
            return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, function(c) {
                const r = Math.random() * 16 | 0;
                const v = c === "x" ? r : r & 3 | 8;
                return v.toString(16);
            });
        },
        random(min, max) {
            return Math.round(Math.random() * (max - min)) + min;
        },
        async sleep(period) {
            return new Promise((resolve) => {
                setTimeout(resolve, period);
            });
        },
        isInBrowser() {
            return typeof window !== "undefined" && typeof window.document !== "undefined";
        },
        isInTopWindow() {
            return self === top;
        }
    };
    
    // 防抖函数
    function debounce(func, wait, options) {
        let lastArgs, lastThis, maxWait, result, timerId, lastCallTime, lastInvokeTime = 0, leading = false, maxing = false, trailing = true;
        
        if (typeof func != "function") {
            throw new TypeError("Expected a function");
        }
        
        wait = Number(wait) || 0;
        if (typeof options === "object") {
            leading = !!options.leading;
            maxing = "maxWait" in options;
            maxWait = maxing ? Math.max(Number(options.maxWait) || 0, wait) : maxWait;
            trailing = "trailing" in options ? !!options.trailing : trailing;
        }
        
        function invokeFunc(time) {
            const args = lastArgs, thisArg = lastThis;
            lastArgs = lastThis = void 0;
            lastInvokeTime = time;
            result = func.apply(thisArg, args);
            return result;
        }
        
        function leadingEdge(time) {
            lastInvokeTime = time;
            timerId = setTimeout(timerExpired, wait);
            return leading ? invokeFunc(time) : result;
        }
        
        function remainingWait(time) {
            const timeSinceLastCall = time - lastCallTime, timeSinceLastInvoke = time - lastInvokeTime, timeWaiting = wait - timeSinceLastCall;
            return maxing ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting;
        }
        
        function shouldInvoke(time) {
            const timeSinceLastCall = time - lastCallTime, timeSinceLastInvoke = time - lastInvokeTime;
            return lastCallTime === void 0 || timeSinceLastCall >= wait || timeSinceLastCall < 0 || (maxing && timeSinceLastInvoke >= maxWait);
        }
        
        function timerExpired() {
            const time = Date.now();
            if (shouldInvoke(time)) {
                return trailingEdge(time);
            }
            timerId = setTimeout(timerExpired, remainingWait(time));
        }
        
        function trailingEdge(time) {
            timerId = void 0;
            if (trailing && lastArgs) {
                return invokeFunc(time);
            }
            lastArgs = lastThis = void 0;
            return result;
        }
        
        function cancel() {
            if (timerId !== void 0) {
                clearTimeout(timerId);
            }
            lastInvokeTime = 0;
            lastArgs = lastCallTime = lastThis = timerId = void 0;
        }
        
        function flush() {
            return timerId === void 0 ? result : trailingEdge(Date.now());
        }
        
        function debounced() {
            const time = Date.now(), isInvoking = shouldInvoke(time);
            lastArgs = arguments;
            lastThis = this;
            lastCallTime = time;
            
            if (isInvoking) {
                if (timerId === void 0) {
                    return leadingEdge(lastCallTime);
                }
                if (maxing) {
                    clearTimeout(timerId);
                    timerId = setTimeout(timerExpired, wait);
                    return invokeFunc(lastCallTime);
                }
            }
            if (timerId === void 0) {
                timerId = setTimeout(timerExpired, wait);
            }
            return result;
        }
        
        debounced.cancel = cancel;
        debounced.flush = flush;
        return debounced;
    }
    
    // 字符串工具类
    class StringUtils {
        constructor(_text) {
            this._text = _text;
        }
        
        static nowrap(str, replace_str) {
            return (str == null ? void 0 : str.replace(/\n/g, replace_str)) || "";
        }
        
        nowrap(replace_str) {
            this._text = StringUtils.nowrap(this._text, replace_str);
            return this;
        }
        
        static nospace(str) {
            return (str == null ? void 0 : str.replace(/ +/g, " ")) || "";
        }
        
        nospace() {
            this._text = StringUtils.nospace(this._text);
            return this;
        }
        
        static noSpecialChar(str) {
            return (str == null ? void 0 : str.replace(/[^\w\s]/gi, "")) || "";
        }
        
        noSpecialChar() {
            this._text = StringUtils.noSpecialChar(this._text);
            return this;
        }
        
        static max(str, len) {
            return str.length > len ? str.substring(0, len) + "..." : str;
        }
        
        max(len) {
            this._text = StringUtils.max(this._text, len);
            return this;
        }
        
        static hide(str, start, end, replacer = "*") {
            return str.substring(0, start) + str.substring(start, end).replace(/./g, replacer) + str.substring(end);
        }
        
        hide(start, end, replacer = "*") {
            this._text = StringUtils.hide(this._text, start, end, replacer);
            return this;
        }
        
        static of(text) {
            return new StringUtils(text);
        }
        
        toString() {
            return this._text;
        }
    }
    
    // 答案匹配函数
    function compareTwoStrings(first, second) {
        first = first.replace(/\s+/g, "");
        second = second.replace(/\s+/g, "");
        
        if (first === second) return 1;
        if (first.length < 2 || second.length < 2) return 0;
        
        let firstBigrams = new Map();
        for (let i = 0; i < first.length - 1; i++) {
            const bigram = first.substring(i, i + 2);
            const count = firstBigrams.has(bigram) ? firstBigrams.get(bigram) + 1 : 1;
            firstBigrams.set(bigram, count);
        }
        
        let intersectionSize = 0;
        for (let i = 0; i < second.length - 1; i++) {
            const bigram = second.substring(i, i + 2);
            const count = firstBigrams.has(bigram) ? firstBigrams.get(bigram) : 0;
            if (count > 0) {
                firstBigrams.set(bigram, count - 1);
                intersectionSize++;
            }
        }
        
        return 2 * intersectionSize / (first.length + second.length - 2);
    }
    
    function findBestMatch(mainString, targetStrings) {
        if (!Array.isArray(targetStrings) || !targetStrings.length) {
            throw new Error("Bad arguments: First argument should be a string, second should be an array of strings");
        }
        
        const ratings = [];
        let bestMatchIndex = 0;
        
        for (let i = 0; i < targetStrings.length; i++) {
            const currentTargetString = targetStrings[i];
            const currentRating = compareTwoStrings(mainString, currentTargetString);
            ratings.push({ target: currentTargetString, rating: currentRating });
            if (currentRating > ratings[bestMatchIndex].rating) {
                bestMatchIndex = i;
            }
        }
        
        const bestMatch = ratings[bestMatchIndex];
        return { ratings, bestMatch, bestMatchIndex };
    }
    
    // 初始化应用
    function initApp() {
        // 挂载Vue应用
        const app = Vue.createApp(App);
        app.use(pinia);
        app.use(ElementPlus);
        app.mount(document.createElement('div'));
        
        // 初始化服务API
        const serverApi = new ServerApi();
        
        // 在这里添加更多初始化逻辑
        console.log("iKaiKail网课全能助手已加载");
    }
    
    // 页面加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initApp);
    } else {
        initApp();
    }
    
})();