fenbiCopy

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

// ==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...
})();