agentsclimarketplace

Icve Exam Extract

Skill BitsAstro/Icve-Exam-Extract

An automated tool based on Kimi WebBridge to extract wrong answers from icve exam review pages and format them into a structured text notebook.

Install
npx -y skills add BitsAstro/Icve-Exam-Extract

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • 25 days oldThe repository was created 25 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

用 Kimi WebBridge 从 icve/zjy2 的 viewExam 考试回看页(如 https://zjy2.icve.com.cn/study/viewExam?...)提取所有"红色背景=做错"的题目,并整理成可直接粘贴进 Word 的结构化格式。 触发场景:用户给出 icve 的 viewExam 类 URL,要求"提取错题/做错的题/红题",或要整理考试错题本。

SKILL.md

5.3 KB, as published. Nobody here has run it

icve viewExam 错题提取(Kimi WebBridge)

通过 Kimi WebBridge 操控用户已登录的浏览器(含隐私窗口),读取答题卡红色(错)题号并抓取每题完整内容。

前置条件

  • Kimi WebBridge daemon 运行在 http://127.0.0.1:10086(二进制 C:\Users\a\.kimi-webbridge\bin\kimi-webbridge.exe)。
  • 浏览器扩展已连接(curl http://127.0.0.1:10086/statusextension_connected)。
  • 用户已在浏览器(隐私窗口需扩展开启"隐私模式时启用")打开目标 viewExam 页面并登录。

调用约定(Windows)

  • 每个请求体写成 JSON 文件(避免 PowerShell 行内传参把中文变 ?),用 curl.exe -s -X POST http://127.0.0.1:10086/command -H "Content-Type: application/json" --data-binary "@<文件>" 发送。
  • 请求体必须带顶层 "session" 字段(同一任务固定一个名字,如 exam-extract)。

步骤

1. 借用用户当前标签(必须传真实 URL)

find_tab 即使 active:true 也要求 url 匹配已打开的标签——直接传用户给的 URL + active:true

{"action":"find_tab","args":{"url":"<用户给的viewExam完整URL>","active":true},"session":"exam-extract"}

2. 合并脚本:扫红色错题号 + 抓题目数据

evaluate 执行下面 JS(一次性返回结构化 JSON):

  • 红色(错) vs 蓝色(对) 背景色挂在答题卡 .tagItemcomputed style 上:错=rgb(159,19,18)、对=rgb(21,51,152)。(注意:链接本身 background 是 transparent,要查 .tagItem 元素!)
  • 每题是 .subjectDet,字段:题号/题型/分值 .xvhao;题干 .seeTitle .htmlP.ql-editor;用户得分 .titleBox .el-tag;选项 .optionList.el-radio__label(单选)/.el-checkbox__label(多选);正确答案 .answer;解析 .analysis .htmlP.ql-editor
(() => {
  const tags = Array.from(document.querySelectorAll('.tagItem'));
  const wrong = [];
  for (const tag of tags) {
    const txt = (tag.textContent || '').trim();
    const n = Number(txt);
    if (!Number.isInteger(n) || n < 1 || n > 500) continue;
    const cs = getComputedStyle(tag);
    const bg = cs.backgroundColor;
    const m = bg.match(/\d+/g);
    const p = m ? [+m[0], +m[1], +m[2]] : null;
    if (p && p[0] > 120 && p[1] < 100 && p[2] < 100) wrong.push(n);
  }
  const wset = new Set(wrong);
  const subs = Array.from(document.querySelectorAll('.subjectDet'));
  const out = [];
  for (const sub of subs) {
    const xvhao = sub.querySelector('.xvhao');
    if (!xvhao) continue;
    const mm = (xvhao.textContent || '').match(/(\d+)/);
    if (!mm) continue;
    const num = parseInt(mm[1], 10);
    if (!wset.has(num)) continue;
    const header = (xvhao.textContent || '').trim();
    const stemEl = sub.querySelector('.seeTitle .htmlP.ql-editor');
    const stem = stemEl ? stemEl.textContent.trim() : '';
    const tagEl = sub.querySelector('.titleBox .el-tag');
    const userScore = tagEl ? tagEl.textContent.trim() : '';
    const optLabels = sub.querySelectorAll('.optionList .el-radio__label, .optionList .el-checkbox__label');
    const options = Array.from(optLabels).map(o => o.textContent.trim());
    const ansEl = sub.querySelector('.answer');
    const correctAnswer = ansEl ? ansEl.textContent.trim() : '';
    let analysis = '';
    const anaEl = sub.querySelector('.analysis .htmlP.ql-editor');
    if (anaEl) analysis = anaEl.textContent.trim();
    if (!analysis) { const a2 = sub.querySelector('.analysis'); if (a2) analysis = a2.textContent.replace('解析:','').replace('解析:','').trim(); }
    out.push({num, header, stem, userScore, options, correctAnswer, analysis});
  }
  out.sort((a,b)=>a.num-b.num);
  return JSON.stringify({wrongCount: wrong.length, count: out.length, questions: out});
})()

写 JSON 文件时注意:JS 里的 /\d+/g/(\d+)/ 在 JSON 字符串中反斜杠要写成 \\d

3. 格式化输出(Python)

curl 返回包在 {"ok":true,"data":{"type":"string","value":"<内层JSON>"}},先取 data.valuejson.loads

  • 题头解析坑:Python 原始字符串 r'...' 里的 \u3010 不会被解释成【。改用 header.find('【') 按括号切分最稳:
    def parse_header(header):
        i1=header.find('【'); i2=header.find('】'); i3=header.find('('); i4=header.find(')')
        if i1>=0 and i2>i1 and i3>i2 and i4>i3:
            return header[:i1].strip().rstrip('.'), header[i1+1:i2].strip(), header[i3+1:i4].strip()
        return None,None,None
    
  • 输出模板(每题间空一行):
    [题号].【[题型]】([分值])
    [题干]
    [用户得分] 分
    [选项A内容]
    [选项B内容]
    ...
    正确答案: [答案]
    解析:[解析]
    
  • 写出 <阶段>测试_错题整理.txt(UTF-8)。

收尾

  • 删掉中间临时 JSON(请求体、data 文件),保留格式脚本和最终 txt。
  • 若多阶段,各自成文件;可后续合并或导出 Word/PDF。

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.