获取习题主考点

确定多考点习题的主考点

As of 2021-01-18. See the latest version.

// ==UserScript==
// @name         获取习题主考点
// @namespace    http://tampermonkey.net/
// @version      1.3.0
// @description  确定多考点习题的主考点
// @author       Jin
// @match        http://*.com/*/report/detail/*
// @match        http://*.com/*/paper/detail/*
// @match        http://*.com/*/paper/make
// @match        http://*.com/*/ques/search?f=1*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_download
// @grant        window.confirm
// ==/UserScript==


(async function() {
    'use strict';

    // Your code here...
    // 添加到试题篮 按钮
    let newEl = document.createElement("div");
    newEl.innerHTML = `<a href="javascript:void(0)" class="btn btn-orange btn-lg  btn-scale" title="油猴脚本,先导入“提取的主知识点.txt”,再将“知识点-待定”的习题添加到试题篮">添加多考点习题到<i class="icon i-basket"></i></a>`
    newEl = newEl.firstElementChild;
    newEl.onclick = async (event)=>{ // load or add
        await loadExerciseInfoListFromTxtFile();
        addUnclassifiedExerciseToCart(event);
    };

    const referenceEl = document.querySelector("div.fixed-bottom > div");
    if(referenceEl){ referenceEl.appendChild(newEl); }

    // step1 载入“提取的主知识点.txt”
    async function loadExerciseInfoListFromTxtFile(event){
        debugger
        // GM_deleteValue("autoGetMainPoint");
        // exInfo=[id, 知识点, 试卷名, 题号, 题型, 全文]
        let autoGetMainPoint = GM_getValue("autoGetMainPoint", {
            exInfoList:[["",,,,,],],
            step:0, // 是否自动处理
            expirationDate:0 // 是否自动处理
        });

        //习题id的匹配数量
        let exIds = autoGetMainPoint.exInfoList.map(a=>a[0].toLowerCase());
        let includesCount = [...document.querySelectorAll("fieldset")].map( el=>el.id ).filter( a=>exIds.includes(a) ).length;

        // 载入“提取的主知识点.txt”
        if( includesCount === 0 ){
            // 检查试题蓝是否为空,可能不准
            if( !document.querySelector("#divCartBox > div > a > span:nth-child(1)").textContent.startsWith("0/") ){
                showMessage("请先清空试题篮");
                return;
            }
            // 载入文件
            const textContent = await OpenAndReadLocalTxtFile("提取的主知识点.txt")
            autoGetMainPoint.exInfoList = textContent.split("\n")
                .map(row=>row.trimStart().trimEnd())
                .filter(row=>row)
                .map(row=>row.split("\t"));

            // 再次 校验习题id是否全部匹配
            exIds = autoGetMainPoint.exInfoList.map(a=>a[0].toLowerCase());
            includesCount = [...document.querySelectorAll("fieldset")].map( el=>el.id ).filter( a=>exIds.includes(a) ).length;
            if( includesCount === 0 ){
                showMessage("习题id全不匹配,", false, false);
            }
        }
        // 保存信息
        await GM_setValue("autoGetMainPoint", autoGetMainPoint);
    }

    // step2 开始添加到试题篮
    async function addUnclassifiedExerciseToCart(event){
        let autoGetMainPoint = GM_getValue("autoGetMainPoint");

        // 添加“知识点-待定”习题到试题篮
        let count=0;
        for( let fieldset of document.querySelectorAll("fieldset") ){
            // 查找未分类习题
            const exList = autoGetMainPoint.exInfoList.filter(exInfo=>exInfo[1]==="知识点-待定").filter( a=>a[0].toLowerCase()===fieldset.id ); // 可能有重题
            if( exList.length === 0 ){ continue }

            location.href = "#" + fieldset.id; // 滑动页面
            await new Promise(r => setTimeout(r, 1000)); //

            let el = fieldset.nextElementSibling.querySelector("div.fieldtip-right > a.add");
            if( el.querySelector("i.i-subtract") ){ continue }

            // 检查试题蓝是否已满
            let chartCounts = document.querySelector("#divCartBox > div > a > span:nth-child(1)").textContent.split("/");
            if( chartCounts[0] === chartCounts[1] ){
                showMessage("试题篮已满,请组卷后再次添加");
                break;
            }

            el.click(); // 点击添加按钮
            count += 1;

            // 同时保持习题全文,作为识别特征
            fieldset.querySelector("div.pt1 span.qseq")?.remove();
            fieldset.querySelector("div.pt1 a.ques-source")?.remove();
            fieldset.querySelector("div.pt6.ques-report")?.remove();
            exList.forEach(exInfo=>exInfo[5]=fieldset.textContent);

            // 逐题保存信息
            await GM_setValue("autoGetMainPoint", autoGetMainPoint);

            await new Promise( r => setTimeout(r, 1000*2*(0.5+Math.random())) );
        }

        // 下载 或 组卷,需手动点击按钮
        if( count===0 ){
            event.target.innerHTML = "下载所有试卷<br\>的习题主知识点";
            event.target.onclick = downloadAllAndClear;
        }else{
            event.target.innerHTML = "一键开始组卷并<br\>提取习题主知识点";
            event.target.onclick = async ()=>{
                // 一键 按知识点组卷
                let autoGetMainPoint = GM_getValue("autoGetMainPoint", {step:0, expirationDate:0});
                autoGetMainPoint.step = 3; //自动开始组卷
                autoGetMainPoint.expirationDate = Date.now()+10*1000;
                await GM_setValue("autoGetMainPoint", autoGetMainPoint);
                // 点击 进入组卷
                document.querySelector("#divCartBox > div > a").click()
            }
        }
    }

    // step3 组卷页面,不完成组卷,通过文字比对确定习题,而不是id
    if( location.pathname.endsWith("/paper/make") ){
        // 是否自动处理
        let autoGetMainPoint = GM_getValue("autoGetMainPoint", {step:0, expirationDate:0});
        if( autoGetMainPoint.step !== 3 ){ return; }
        if( autoGetMainPoint.expirationDate < Date.now() ){ return; }

        await new Promise(r => {console.log("等待2000ms"); setTimeout(r, 2000);}); //

        // 按知识点排序
        let el = document.querySelector("#group-order > span:nth-child(2)");
        if( !el.classList.contains("active") ){
            // 保存信息
            autoGetMainPoint.step = 3;
            autoGetMainPoint.expirationDate = Date.now()+10*1000;
            await GM_setValue("autoGetMainPoint", autoGetMainPoint);
            // 点击 按知识点 按钮
            el.click()
            //点击 确认 按钮,页面将会自动刷新,
            await new Promise( r => setTimeout(r, 500) ); // 等待弹框
            document.querySelector("div.box-wrapper input.btn.btn-blue.btn-lg").click();
            return
        }

        // 逐 知识点 子标题
        for( let exampartEl of document.querySelectorAll("#divQues div.exampart") ){
            const titleEl = exampartEl.querySelector("div.questypetitle");
            let pointCode = titleEl.getAttribute("cate").toLowerCase();
            let pointName = GM_getValue(pointCode, "知识点-找不到/"+pointCode); // 需要先在 组卷中心 知识点挑题 页面,点击“保存2”按钮
            // 逐题
            for( let fieldset of exampartEl.querySelectorAll("fieldset") ){
                // 清理题号、题源
                fieldset.querySelector("div.pt1 span.qseq")?.remove();
                fieldset.querySelector("div.pt1 a.ques-source")?.remove();
                fieldset.querySelector("div.pt6.ques-report")?.remove();
                // 全文匹配
                let exList = autoGetMainPoint.exInfoList // 可能有重题
                    .filter(exInfo=>exInfo[1]==="知识点-待定")
                    .filter(exInfo=>exInfo[5]===fieldset.textContent );
                // fieldset.textContent可能含题源,尝试取得题源
                if( exList.length === 0 ){
                    let textContent = fieldset.textContent.split(/)(.+)/)[1]; // 去掉题源,标记为首个")"
                    exList = autoGetMainPoint.exInfoList // 可能有重题
                        .filter(exInfo=>exInfo[1]==="知识点-待定")
                        .filter(exInfo=>exInfo[5]===textContent );
                }
                // 异常
                if( exList.length === 0 ){
                    console.log("未匹配到习题:"+fieldset.textContent);
                    showMessage("未匹配到习题", false, false);
                }
                // 添加知识点信息
                exList.forEach(exInfo=>exInfo[1]=pointName+";");//“;”作为标记
            }
        }
        // 保存信息
        autoGetMainPoint.step = 0; //停止自动处理
        autoGetMainPoint.expirationDate = 0;
        await GM_setValue("autoGetMainPoint", autoGetMainPoint);

        debugger
        // 全部完成则自动下载
        if( !autoGetMainPoint.exInfoList.find(exInfo=>exInfo[1]==="知识点-待定") ){
            await downloadAllAndClear();
            // 点击 清空试题,
            await new Promise( r => setTimeout(r, 5000) ); // 等待弹框
            debugger
            // window.confirm=()=>true;//避免出现confirm对话框,需要手动点击
            //document.querySelector("#sortBox > div.pb10 > a:nth-child(2)").click();
            // QuesCart.clear(subject,null,function(){setTimeout(close, 5000)})
            // QuesCart.clear 跳过 window.confirm
            const [a,b,c] = [subject, null, function(){setTimeout(close, 5000)}];
            $.post("/" + subject + "/paper/clearcart", {
                r: Math.random()
            }, function(b) {
                b && 0 == b.S && (QuesCart.init(a, !0),
                                  showMessage(b.M));
                c && c();
                $(".added").removeClass("added").closest(".fieldtip").prev("fieldset").css({
                    color: "#000"
                })
            })
            // 跳转页面到组卷中心
        }

    }

    // step4 下载"*.习题备注.txt"
    async function downloadAllAndClear(){
        let autoGetMainPoint = GM_getValue("autoGetMainPoint");
        let exInfoList = autoGetMainPoint.exInfoList.slice(1);
        // 按试卷下载
        for( let paperName of new Set(exInfoList.map(a=>a[2])) ){
            let text = exInfoList.filter( a=>a[2]===paperName )
                .filter( a=>a[1].endsWith(";") ) // 新增知识点标记
                .sort((a,b) => a[3]-b[3])// 按题号排序
                .map( a=> a[3]+".【备注】"+a[1] ) // 如“1.【备注】*”
                .join("\n");
            if( !text ){ continue }

            paperName = paperName.split(".").shift();
            text = paperName+"答案\n"+text;

            let filename = paperName+".习题备注.txt"
            download(filename, text);
            await new Promise(r => {console.log("等待下载:"+filename); setTimeout(r, 1000);}); //
        }

        // 清理
        GM_deleteValue("autoGetMainPoint");
    }

    async function OpenAndReadLocalTxtFile(checkFileName){
        const element = document.createElement('input');
        element.setAttribute('type', 'file' );
        element.setAttribute('accept', '.txt');
        element.style.display = 'none';
        document.body.appendChild(element);

        let result = "";
        element.onchange = async (event)=>{
            if( checkFileName && event.target.files[0].name!==checkFileName ){
                showMessage("文件名必须是:"+checkFileName);
                return;
            }
            result = await event.target.files[0].text();
            console.log("读取了文件:"+event.target.files[0].name);
        }

        element.click()
        while( !result ) {
            await new Promise(r => {console.log("等待读取文件"); setTimeout(r, 500);});
        }

        element.remove();

        return result;
    }

    function download(filename, text) {
        var element = document.createElement('a');
        element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
        element.setAttribute('download', filename);
        element.style.display = 'none';
        document.body.appendChild(element);
        element.click();
        document.body.removeChild(element);
    }
})();


// 保存 考点列表
(async function(){
    'use strict';

    // 组卷中心,知识点挑题
    if( !/\/ques\/search/.test(location.pathname) ){ return; }

    // 等待考点列表载入
    await new Promise(r => {console.log("等待考点列表载入"); setTimeout(r, 500);});
    while(!document.querySelector(".qseq") && !document.querySelector("#divTree") ) {
        await new Promise(r => {console.log("等待考点列表载入"); setTimeout(r, 500);});
    }

    // 插入考点列表 保存按钮
    let pointEl = document.querySelector("#divTree .tree-head");
    if( pointEl && pointEl.firstElementChild.textContent==="考点列表" ){
        pointEl.lastElementChild.appendChild(createSavePointButton());
        console.log("插入了考点列表保存钮数。");
    }

    // 保存 考点列表 到 GM_setValue
    function createSavePointButton(){
        let newElement = document.createElement('div');
        newElement.innerHTML = '<li title="油猴脚本,保存考点列表到浏览器,用于脚本:获取习题主考点">保存2</li>';
        newElement = newElement.firstElementChild;
        // 功能脚本
        newElement.onclick = async function(event){
            // 三级知识点
            let points = document.querySelectorAll("#divTree2 > div > ul > li > ul > li > ul > li > a");
            let text = "";
            for( let a3 of points ){
                let a2=a3.parentElement.parentElement.previousElementSibling; // 二级知识点
                let a1=a2.parentElement.parentElement.previousElementSibling; // 一级知识点
                let tagName ="知识点-" + a1.innerText+"/"+a2.innerText+"/"+a3.innerText;
                tagName=tagName.replace(/\s+/g,"");
                let tagCode = a3.getAttribute("pk").toLowerCase();
                await GM_setValue(tagCode, tagName);
                text += tagCode + "\t" + tagName+"\n";
            }
            // 输出到console
            console.log(text);
            showMessage("保存了" + points.length + "个考点到浏览器");
        };
        return newElement;
    }

})();