fenbiCopy

通过ctrl+q复制粉笔练习题,导出错题为json格式

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

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.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         fenbiCopy
// @namespace    http://tampermonkey.net/
// @version      0.6.1
// @description  通过ctrl+q复制粉笔练习题,导出错题为json格式
// @author       You
// @match        *://*.fenbi.com/*
// @match        *://*.mbadashi.com/*
// @icon         https://www.google.com/s2/favicons?domain=fenbi.com
// @grant        none
// @license MIT
// ==/UserScript==


(function() {
    'use strict';
var $ = selector => {
    return document.querySelector(selector)
}

var $$ = selector => {
    return document.querySelectorAll(selector)
}

/**
 * Export JSON
 */
function exportJSON(data = {}, filename) {
    let link = document.createElement('a')
    if (!filename) {
        filename = `${Date.now()}.json`
    }
    if (!/\.json$/.test(filename)) {
        filename += '.json'
    }
    link.download = filename
    link.href =
        'data:application/json;charset=utf-8,' +
        encodeURIComponent(JSON.stringify(data))
    link.click()
    link = null
}

const headers = {
    Accept: 'application/json',
    'Content-Type': 'application/json',
}

function get(url) {
    return fetch(url, {
        method: 'GET',
        credentials: 'include',
        headers: headers,
    })
        .then(response => {
            return handleResponse(url, response)
        })
        .catch(error => {
            console.error(`GET Request fail. url:${url}. message:${error}`)
            return Promise.reject({
                error: {
                    message: 'GET Request failed.',
                },
            })
        })
}

function post(url, data) {
    return fetch(url, {
        method: 'POST',
        credentials: 'include',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: data,
    })
        .then(response => {
            return handleResponse(url, response)
        })
        .catch(err => {
            console.error(`Request failed. Url = ${url} . Message = ${err}`)
            return { error: { message: 'Request failed.' } }
        })
}


function handleResponse(url, response) {
    let res = response
    if (res.status === 200) {
        return res.json()
    } else {
        console.error(`Request fail. url:${url}`)
        Promise.reject({
            error: {
                message: 'Request failed due to server error',
            },
        })
    }
}

/**
 * 查询题目列表的id
 * @param {*} categoryId
 * 言语:22017
 * 数量:22018
 * 判断:22019
 * 资料:22020
 * 常识:22021
 * @returns Promise
 */
const getQuestionIdByCategoryId = categoryId => {
    const url = `https://tiku.fenbi.com/api/xingce/errors?categoryId=${categoryId}&offset=0&limit=10000&order=asc&timeRange=0&app=web&kav=12&version=3.0.0.0`

    return get(url)
}

const getCategoryTree = () => {
    const url = `https://tiku.fenbi.com/api/xingce/errors/keypoint-tree?timeRange=0&app=web&kav=12&version=3.0.0.0`

    return get(url)
}

// 通过题目id查询题目内容以及解析
const getSolutionById = ids => {
    const url = `https://tiku.fenbi.com/api/xingce/solutions?ids=${ids}&app=web&kav=12&version=3.0.0.0`
    // get(url).then(res => {
    //     // content: 内容
    //     // correctAnswer: 正确答案
    //     // accessories 选项
    //     // solution: 解析
    //     // source:来源
    //     // keyPoint: { id, name } 题目分类
    //     // questionMeta: { mostWrongAnswer }
    // })
    return get(url)
}

// 通过题目id查询题目内容以及解析
const getSolutionByIds = async questionIds => {
    const sliceQuestionIds = []
    for (let i = 0; i < questionIds.length; i += 500) {
        sliceQuestionIds.push(questionIds.slice(i, i+500))
    }
    let solution = []
    for (let i = 0; i < sliceQuestionIds.length; i++) {
        const s = await get(`https://tiku.fenbi.com/api/xingce/solutions?ids=${sliceQuestionIds[i].toString()}&app=web&kav=12&version=3.0.0.0`)
        console.log('插入一次', sliceQuestionIds[i], s)
        solution = solution.concat(s)
    }
    return solution
}

const exportWrongQuestion = async () => {
    // 言语:22017
    // 数量:22018
    // 判断:22019
    // 资料:22020
    // 常识:22021
    // const categoryList = [22017, 22018, 22019, 22020, 22021]
    const data = {}
    const questionIds = []
    const keyPointTree = await getCategoryTree()
    keyPointTree &&
        keyPointTree.forEach(item => {
            questionIds.push(...item.questionIds)
        })
    // 试着
    console.log('questionIds', questionIds)
    const sliceQuestionIds = []
    for (let i = 0; i < questionIds.length; i += 500) {
        sliceQuestionIds.push(questionIds.slice(i, i+500))
    }
    let solution = []
    for (let i = 0; i < sliceQuestionIds.length; i++) {
        const s = await getSolutionById(sliceQuestionIds[i].toString())
        console.log('插入一次', sliceQuestionIds[i], s)
        solution = solution.concat(s)
    }
    data.question = solution
    data.category = keyPointTree
    exportJSON(data, 'fenbiData')
}

const registerExportBtn = () => {
    var html = `<button class="export-question">导出错题</button>`
    var wrap = document.querySelector('#keypoint-list .sort-filter')
    wrap && wrap.insertAdjacentHTML('beforeend', html)
    wrap && wrap.addEventListener('click', e => {
        var el = e.target
        if (el && el.classList.contains('export-question')) {
            console.log('导出错题')
            exportWrongQuestion()
        }
    })
}

var sleep = (timeout = 1000) => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve()
        }, timeout)
    })
}

// 导出阅读题
var exportRead = async () => {
    const set = new Set()
    for (let i = 0; i < 15; i++) {
        const data = await post(
            'https://tiku.fenbi.com/api/xingce/exercises?app=web&kav=12&version=3.0.0.0',
            'type=3&keypointId=22339&limit=100&exerciseTimeMode=2'
        )
        await sleep(3000)
        data.sheet.questionIds.forEach(item => set.add(item))
        console.log(`第${i + 1}次加载100题`)
    }
    const result = await getSolutionByIds([...set])
    exportJSON(result, 'fenbiData')
}

window.exportRead = exportRead

var insertInputEl = () => {
    const el = document.createElement('input')
    const btn = document.createElement('button')
    el.className = 'input-answer'
    btn.className = 'btn-answer'
    btn.innerHTML = '点击提交'
    $('.exam-detail') && $('.exam-detail').appendChild(el)
    $('.exam-detail') && $('.exam-detail').appendChild(btn)
    $('.btn-answer') && $('.btn-answer').addEventListener('click', () => {
        selectAnswer()
    })
    console.log('插入成功吗')
}

var copyText = text => {
    var textarea = document.createElement('textarea')
    document.body.appendChild(textarea)
    // 隐藏此输入框
    textarea.style.position = 'fixed'
    textarea.style.clip = 'rect(0 0 0 0)'
    textarea.style.top = '10px'
    // 赋值
    textarea.value = text
    // 选中
    textarea.select()
    // 复制
    document.execCommand('copy', true)
    // 移除输入框
    document.body.removeChild(textarea)
}

var fbRegisterEventTotal = (pos) => {
    const html = $('.exam-detail').children
    const items = $$('.chapter-control-item')
    if (!items) return ;

    const item = items[pos]
    console.log('item', item)
    const question = item.querySelectorAll('span')
    const start = parseInt(question[0].textContent) - 1
    const end = parseInt(question[question.length-1].textContent)
    const html2 = Array.from(html).filter((item, index) => index >= start && index < end)
    let str = ''
    html2.forEach(item => str += item.innerHTML)
    const result = `<section _ngcontent-fenbi-web-exams-c64="" class="exam-detail bg-color-gray-bold">${str}</section>`
    return result.replaceAll('\x3C!---->', '')
}

var fbRegisterEvent = () => {
    // 按ctrl + Q, 复制到剪切板。
    window.addEventListener('keydown', e => {
        if (e.ctrlKey && e.keyCode === 81) {
            console.log('已粘贴到剪切板')
            var html = ''
            if (window.location.href.includes('www.mbadashi.com')) {
               html = $('.multiple-choice-content').innerHTML.replace(/<pre/g, "<div").replace(/<\/pre>/g, "</div>")
            } else {
                html = $('.exam-detail').innerHTML
            }
            copyText(html)
        } else if (e.altKey && e.keyCode >= 49 && e.keyCode <= 53) {
            html = fbRegisterEventTotal(e.keyCode-49)
            copyText(html)
        }
    })
}

var selectAnswer = () => {
    const questionList = $$('.exam-detail .options')

    const answer = $('.input-answer').value.replace(/ /g, '')
    for (let i = 0; i < questionList.length; i++) {
        // 选项
        const options = questionList[i].querySelectorAll('.theme-ques-option')
        const s = parseInt(answer[i])
        options[s-1].click()
    }
}

var fbMain = () => {
    fbRegisterEvent()
    setTimeout(() => {
        registerExportBtn()
        insertInputEl()
    }, 3000)
}

fbMain()

    // Your code here...
})();