// ==UserScript==
// @name 课程助手(一体化 · 课程列表 + 刷课)
// @name 课程助手(一体化 · 课程列表 + 刷课)
// @version 1.0.6
// @author isMobile / ecust
// @description 课程列表进度面板 + 一键进入课程刷课 + 原超星/智慧树播放答题(整合版)
// @license MIT
// @icon data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext x='50' y='54' text-anchor='middle' dominant-baseline='central' font-size='82' font-family='Apple Color Emoji,Segoe UI Emoji,Noto Color Emoji,sans-serif'%3E%F0%9F%92%AF%3C/text%3E%3C/svg%3E
// @match *://*.chaoxing.com/*
// @match *://*.edu.cn/*
// @match *://*.nbdlib.cn/*
// @match *://*.hnsyu.net/*
// @match *://*.gdhkmooc.com/*
// @match *://*.zhihuishu.com/*
// @match https://p.s.ecust.edu.cn/*
// @match *://*.ecust.edu.cn/*
// @require https://lib.baomitu.com/vue/3.4.31/vue.global.prod.js
// @require https://lib.baomitu.com/vue-demi/0.14.7/index.iife.js
// @require data:application/javascript,window.Vue%3DVue%3B
// @require https://lib.baomitu.com/element-plus/2.7.2/index.full.min.js
// @require https://lib.baomitu.com/pinia/2.3.1/pinia.iife.min.js
// @require https://lib.baomitu.com/rxjs/7.8.2/rxjs.umd.min.js
// @require https://lib.baomitu.com/blueimp-md5/2.19.0/js/md5.min.js
// @resource ElementPlus https://lib.baomitu.com/element-plus/2.7.2/index.css
// @resource ElementPlusStyle https://lib.baomitu.com/element-plus/2.8.2/index.min.css
// @resource ttf https://www.forestpolice.org/ttf/2.0/table.json
// @connect *
// @connect search.tikuhai.com
// @connect api.tikuhai.com
// @connect cx.icodef.com
// @connect 62.234.36.191
// @grant GM_addStyle
// @grant GM_getResourceText
// @grant GM_getValue
// @grant GM_info
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @run-at document-start
// @antifeature ads 公众号或者app辅助答题
// @antifeature payment 付费答题
// ==/UserScript==
try { console.log('[课程助手] v1.0.6 loaded @ ' + location.href); } catch (e) {}
// ------------------------------------------------------------------
// 出错兜底:任何一半初始化失败,右下角直接显示,不再「什么都不出现」
// ------------------------------------------------------------------
window.__cxhFatal = function (tag, err) {
var msg = (err && (err.stack || err.message)) || String(err);
try { console.error('[课程助手] ' + tag, err); } catch (e) {}
var paint = function () {
try {
var d = document;
if (!d || !d.documentElement) return;
var box = d.getElementById('cxh-fatal');
if (!box) {
box = d.createElement('div');
box.id = 'cxh-fatal';
box.style.cssText = 'position:fixed;right:16px;bottom:16px;z-index:2147483647;max-width:520px;'
+ 'max-height:60vh;overflow:auto;'
+ 'padding:12px 14px;border-radius:10px;background:#fff1f0;border:1px solid #ffa39e;color:#a8071a;'
+ 'font:12px/1.6 system-ui,-apple-system,Segoe UI,Microsoft YaHei,sans-serif;'
+ 'box-shadow:0 8px 24px rgba(0,0,0,.18);white-space:pre-wrap;word-break:break-word';
(d.body || d.documentElement).appendChild(box);
}
box.textContent = '【课程助手】' + tag + '\n' + msg;
} catch (e) {}
};
paint();
setTimeout(paint, 400);
setTimeout(paint, 1500);
setTimeout(paint, 4000);
};
// ==================================================================
// [1] 课程列表面板(自己带 IIFE,不依赖任何第三方库)
// ==================================================================
try {
(function () {
'use strict';
// ---------------------------------------------------------------------------
// 基础
// ---------------------------------------------------------------------------
const W = (() => {
try { return typeof unsafeWindow !== 'undefined' && unsafeWindow ? unsafeWindow : window; }
catch (e) { return window; }
})();
const STORE_KEY = 'cx_course_helper_v1';
const LIST_URL = 'https://p.s.ecust.edu.cn/i/base';
const DEBUG = false;
const log = (...a) => { if (DEBUG) console.log('[cxh]', ...a); };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// 列表页可能被门户 /i/base 以 iframe 嵌入:UI 挂到顶层,导航也走顶层
const IS_TOP = (() => { try { return window.top === window; } catch (e) { return false; } })();
function topWin() { try { return window.top; } catch (e) { return null; } }
function topDoc() { try { const d = window.top.document; return d && d.body ? d : null; } catch (e) { return null; } }
function uiDoc() { return (!IS_TOP && topDoc()) || document; }
function navigate(url, replace) {
const tw = topWin();
if (!IS_TOP && tw) {
try {
if (replace) tw.location.replace(url); else tw.location.href = url;
return;
} catch (e) { /* cross-origin:退回当前框架 */ }
}
if (replace) location.replace(url); else location.assign(url);
}
function listTopUrl() {
try {
const tw = topWin();
if (tw && tw.location && /^https?:/i.test(tw.location.href)) return tw.location.href;
} catch (e) { /* ignore */ }
try { if (/^https?:/i.test(location.href)) return location.href; } catch (e2) { /* ignore */ }
return LIST_URL;
}
// ---------------------------------------------------------------------------
// 存储
// ---------------------------------------------------------------------------
function rawGet(key, def) {
try {
if (typeof GM_getValue === 'function') {
const v = GM_getValue(key);
if (v === undefined || v === null) return def;
return typeof v === 'string' ? JSON.parse(v) : v;
}
const s = localStorage.getItem(key);
return s ? JSON.parse(s) : def;
} catch (e) { return def; }
}
function rawSet(key, val) {
try {
if (typeof GM_setValue === 'function') GM_setValue(key, JSON.stringify(val));
else localStorage.setItem(key, JSON.stringify(val));
} catch (e) { /* ignore */ }
}
function loadState() {
const s = rawGet(STORE_KEY, null) || {};
return {
version: 1,
courses: s.courses || {},
scan: Object.assign({ running: false, keys: [], index: 0, returnUrl: LIST_URL, startedAt: 0 }, s.scan || {}),
run: Object.assign({ running: false, queue: [], current: null, courseUrl: '', lastDone: -1, attempts: 0, phase: '', sectionId: '', startedAt: 0 }, s.run || {}),
checked: Array.isArray(s.checked) ? s.checked : [],
cfg: Object.assign({ commitAuto: true }, s.cfg || {}),
lock: s.lock || { tab: '', ts: 0 },
log: Array.isArray(s.log) ? s.log : [],
listUrl: s.listUrl || LIST_URL
};
}
function saveState(s) {
s.updatedAt = Date.now();
rawSet(STORE_KEY, s);
}
function pushLog(s, msg) {
s.log.push({ t: new Date().toTimeString().slice(0, 8), m: String(msg) });
if (s.log.length > 80) s.log = s.log.slice(-80);
}
// 标签页标识:window.name 可跨域、跨页面导航保留(优先用顶层窗口)
const TAB_TAG = '__cxh_tab__';
const NAME_WIN = (() => { try { return window.top || window; } catch (e) { return window; } })();
const TAB_ID = (() => {
const tryWin = (win) => {
try {
const name = String(win.name || '');
const i = name.indexOf(TAB_TAG);
if (i >= 0) return { id: name.slice(i + TAB_TAG.length).split('|')[0], ok: true };
const id = Math.random().toString(36).slice(2) + Date.now().toString(36);
win.name = name + TAB_TAG + id;
return { id, ok: true };
} catch (e) { return { id: '', ok: false }; }
};
let r = tryWin(NAME_WIN);
if (!r.ok) r = tryWin(W);
return r.id || ('t' + Math.random().toString(36).slice(2));
})();
function lockFresh(s) { return s.lock && s.lock.ts && Date.now() - s.lock.ts < 25000; }
function lockMine(s) { return !s.lock.tab || s.lock.tab === TAB_ID || !lockFresh(s); }
function claimLock(s) {
if (!lockMine(s)) return false;
s.lock = { tab: TAB_ID, ts: Date.now() };
return true;
}
function forceLock(s) { s.lock = { tab: TAB_ID, ts: Date.now() }; }
function touchLock(s) {
if (s.lock.tab === TAB_ID) s.lock.ts = Date.now();
}
// ---------------------------------------------------------------------------
// 页面识别
// ---------------------------------------------------------------------------
function isListPage() {
return !!(document.querySelector('div.wzy_couros') && document.querySelector('li[cid][clazzid]'));
}
function isCoursePage() {
return !!(document.querySelector('#courseId') && document.querySelector('.fanyaChapter'));
}
function isPlayPage() {
return location.pathname.indexOf('/mycourse/studentstudy') !== -1;
}
// ---------------------------------------------------------------------------
// 样式
// ---------------------------------------------------------------------------
const CSS = `
#cxh-panel{position:fixed;right:16px;bottom:16px;width:322px;z-index:2147483000;background:#fff;border:1px solid rgba(31,113,224,.14);border-radius:14px;box-shadow:0 18px 50px rgba(15,35,60,.22),0 2px 8px rgba(15,35,60,.08);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei",sans-serif;font-size:13px;color:#1f2937;overflow:hidden;display:flex;flex-direction:column;max-height:calc(100vh - 28px);animation:cxh-in .3s cubic-bezier(.2,.8,.2,1)}
#cxh-panel .cxh-hd{display:flex;align-items:center;gap:8px;padding:10px 12px;background:linear-gradient(135deg,#1f71e0 0%,#3f8cff 100%);color:#fff;cursor:move;user-select:none;box-shadow:inset 0 1px 0 rgba(255,255,255,.14);flex:0 0 auto}
#cxh-panel .cxh-logo{width:20px;height:20px;border-radius:7px;background:rgba(255,255,255,.2);display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;flex:0 0 auto}
#cxh-panel .cxh-title{font-weight:600;font-size:13px;letter-spacing:.2px}
#cxh-panel .cxh-tag{font-size:10px;line-height:16px;padding:0 6px;border-radius:8px;background:rgba(255,255,255,.18);color:#eaf3ff;border:1px solid rgba(255,255,255,.22)}
#cxh-panel .cxh-fold{margin-left:auto;border:0;background:transparent;color:#fff;cursor:pointer;font-size:15px;line-height:1;padding:2px 4px;border-radius:6px}
#cxh-panel .cxh-fold:hover{background:rgba(255,255,255,.16)}
#cxh-panel .cxh-bd{padding:10px 12px 12px;max-height:calc(100vh - 90px);overflow:auto;overflow-x:hidden;min-height:0;flex:1 1 auto;transition:max-height .28s ease,opacity .2s ease,padding .2s ease;scrollbar-width:thin}
#cxh-panel.cxh-collapsed .cxh-bd{max-height:0;opacity:0;padding-top:0;padding-bottom:0}
#cxh-panel .cxh-sum{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px;font-size:12px;color:#4b5563;line-height:1.6}
#cxh-panel .cxh-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 7px;border-radius:999px;background:#f3f6fb;border:1px solid #e8eef7;font-size:11px;color:#475569;transition:background .15s}
#cxh-panel .cxh-chip b{font-weight:600}
#cxh-panel .cxh-chip.done{background:#e9f9f1;border-color:#c9f0dd;color:#06875b}
#cxh-panel .cxh-chip.partial{background:#fff5e9;border-color:#ffe3c2;color:#b45309}
#cxh-panel .cxh-chip.unknown{background:#f4f6f9;border-color:#e6eaf0;color:#64748b}
#cxh-panel .cxh-chip.scan{background:#eaf3ff;border-color:#cfe2ff;color:#176ae5}
#cxh-panel .cxh-row{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}
#cxh-panel button.cxh-btn{border:1px solid #dbe3ee;background:#fff;color:#334155;border-radius:8px;padding:5px 9px;font-size:12px;cursor:pointer;line-height:18px;transition:border-color .15s,color .15s,background .15s,transform .1s,box-shadow .15s}
#cxh-panel button.cxh-btn:hover{border-color:#9dc0f5;color:#176ae5;background:#f7fbff;box-shadow:0 1px 4px rgba(31,113,224,.12)}
#cxh-panel button.cxh-btn:active{transform:scale(.97)}
#cxh-panel button.cxh-btn[disabled]{opacity:.45;cursor:not-allowed;box-shadow:none}
#cxh-panel button.cxh-primary{background:linear-gradient(135deg,#1f71e0,#3f8cff);border-color:transparent;color:#fff;box-shadow:0 4px 12px rgba(31,113,224,.28)}
#cxh-panel button.cxh-primary:hover{background:linear-gradient(135deg,#1a63c9,#3280ec);color:#fff}
#cxh-panel .cxh-prog{display:none;height:6px;background:#eef3fa;border-radius:999px;overflow:hidden;margin:2px 0 8px;position:relative}
#cxh-panel .cxh-prog.on{display:block}
#cxh-panel .cxh-prog i{display:block;height:100%;width:0;background:linear-gradient(90deg,#1f71e0,#5aa2ff);border-radius:999px;transition:width .35s ease}
#cxh-panel .cxh-prog span{position:absolute;right:0;top:-18px;font-size:11px;color:#6b7280}
#cxh-panel .cxh-list{max-height:min(240px,30vh);overflow:auto;margin-top:2px;padding-bottom:2px;scrollbar-width:thin}
#cxh-panel .cxh-item{display:flex;align-items:center;gap:8px;padding:6px 4px;border-radius:8px;font-size:12px;transition:background .15s}
#cxh-panel .cxh-item:hover{background:#f5f9ff}
#cxh-panel .cxh-item-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#26364a}
#cxh-panel .cxh-item-pending{color:#b45309;font-size:11px;white-space:nowrap;background:#fff5e9;border:1px solid #ffe3c2;border-radius:999px;padding:1px 7px}
#cxh-panel .cxh-item button{border:1px solid #cfe2ff;color:#176ae5;background:#eef6ff;border-radius:999px;font-size:11px;padding:2px 10px;cursor:pointer;line-height:16px;transition:background .15s,color .15s,transform .1s}
#cxh-panel .cxh-item button:hover{background:#176ae5;color:#fff;border-color:#176ae5}
#cxh-panel .cxh-item button:active{transform:scale(.95)}
#cxh-panel .cxh-empty{color:#9aa5b1;font-size:11px;padding:8px 4px;text-align:center}
#cxh-panel .cxh-log{max-height:68px;overflow:auto;font-size:11px;line-height:1.6;color:#6b7280;background:#fff;border-top:1px solid #eef1f5;padding-top:6px;margin-top:8px;scrollbar-width:thin}
#cxh-panel .cxh-log div{white-space:normal;word-break:break-all}
@keyframes cxh-in{from{opacity:0;transform:translateY(10px) scale(.98)}to{opacity:1;transform:none}}
@keyframes cxh-pop{from{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}
.cxh-ov{position:absolute;inset:0;pointer-events:none;z-index:9}
.cxh-ov .cxh-check{position:absolute;left:10px;top:10px;pointer-events:auto;background:#fff;border-radius:5px;padding:3px;box-shadow:0 1px 5px rgba(0,0,0,.22);cursor:pointer;line-height:0}
.cxh-ov .cxh-check input{width:16px;height:16px;margin:0;cursor:pointer}
.cxh-ov .cxh-badge{position:absolute;right:10px;top:10px;pointer-events:auto;padding:2px 9px;border-radius:11px;font-size:12px;line-height:18px;color:#fff;background:#9aa5b1;white-space:nowrap;box-shadow:0 1px 4px rgba(0,0,0,.16);font-family:-apple-system,"Microsoft YaHei",sans-serif}
.cxh-ov .cxh-badge.done{background:#00a870}
.cxh-ov .cxh-badge.partial{background:#ed7b2f}
.cxh-ov .cxh-badge.unknown{background:#9aa5b1}
.cxh-ov .cxh-badge.running{background:#176ae5}
.cxh-ov .cxh-badge.blocked{background:#d54941}
.cxh-ov .cxh-run{position:absolute;right:10px;bottom:10px;pointer-events:auto;font-size:12px;line-height:18px;padding:3px 10px;border:1px solid #176ae5;color:#176ae5;background:#fff;border-radius:12px;cursor:pointer;font-family:-apple-system,"Microsoft YaHei",sans-serif}
.cxh-ov .cxh-run:hover{background:#176ae5;color:#fff}
#cxh-pill{position:fixed;left:16px;bottom:16px;z-index:2147483000;display:none;align-items:center;gap:8px;padding:8px 12px;background:#fff;border:1px solid #d9e2ec;border-radius:22px;box-shadow:0 6px 24px rgba(15,35,60,.18);font-family:-apple-system,"Microsoft YaHei",sans-serif;font-size:12px;color:#1f2937}
#cxh-pill.on{display:flex}
#cxh-pill .cxh-pill-txt{max-width:420px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#cxh-pill button{border:1px solid #d54941;background:#fff;color:#d54941;border-radius:12px;padding:2px 9px;font-size:12px;cursor:pointer}
#cxh-pill button.cxh-pill-verify{border-color:#176ae5;color:#176ae5}
`;
function injectCss(doc) {
doc = doc || document;
try {
if (doc.getElementById && doc.getElementById('cxh-css')) return;
const st = doc.createElement('style');
st.id = 'cxh-css';
st.textContent = CSS;
(doc.head || doc.documentElement).appendChild(st);
} catch (e) { /* ignore */ }
}
function injectCssAll() {
injectCss(document);
const d = uiDoc();
if (d !== document) injectCss(d);
}
// ---------------------------------------------------------------------------
// 面板 / 悬浮条
// ---------------------------------------------------------------------------
let panelEl = null;
function ensurePanel() {
const doc = uiDoc();
if (panelEl && panelEl.ownerDocument && panelEl.ownerDocument.body && panelEl.ownerDocument.body.contains(panelEl)) return panelEl;
const existing = doc.getElementById('cxh-panel');
if (existing) { panelEl = existing; return panelEl; }
const el = doc.createElement('div');
el.id = 'cxh-panel';
el.innerHTML =
'
课课程助手一体化
' +
'' +
'
正在读取课程…
' +
'
' +
' ' +
' ' +
' ' +
' ' +
' ' +
'
' +
'
' +
'
' +
'
' +
'
';
doc.body.appendChild(el);
el.addEventListener('click', onPanelClick);
// 简易拖动:按住标题栏
const hd = el.querySelector('.cxh-hd');
let drag = null;
hd.addEventListener('mousedown', (e) => {
if (e.target && e.target.classList && e.target.classList.contains('cxh-fold')) return;
const r = el.getBoundingClientRect();
drag = { x: e.clientX - r.left, y: e.clientY - r.top };
e.preventDefault();
});
doc.addEventListener('mousemove', (e) => {
if (!drag) return;
el.style.left = Math.max(0, e.clientX - drag.x) + 'px';
el.style.top = Math.max(0, e.clientY - drag.y) + 'px';
el.style.right = 'auto';
el.style.bottom = 'auto';
});
doc.addEventListener('mouseup', () => { drag = null; });
panelEl = el;
return el;
}
function onPanelClick(e) {
const runBtn = e.target && e.target.closest ? e.target.closest('[data-run]') : null;
if (runBtn) {
e.preventDefault();
e.stopPropagation();
const st = loadState();
if (st.run.running) { alert('正在刷课,请等当前课程结束'); return; }
startKeys([runBtn.getAttribute('data-run')]);
return;
}
const btn = e.target && e.target.closest ? e.target.closest('[data-cxh]') : null;
if (btn) {
e.preventDefault();
e.stopPropagation();
const act = btn.getAttribute('data-cxh');
if (act === 'scan') startScan();
else if (act === 'all') checkAllUndone();
else if (act === 'clear') clearChecked();
else if (act === 'start') startChecked();
else if (act === 'stop') stopAll();
return;
}
if (e.target && e.target.classList && e.target.classList.contains('cxh-fold')) {
panelEl.classList.toggle('cxh-collapsed');
e.target.textContent = panelEl.classList.contains('cxh-collapsed') ? '+' : '-';
}
}
function ensurePill() {
const doc = uiDoc();
let el = doc.getElementById('cxh-pill');
if (!el) {
el = doc.createElement('div');
el.id = 'cxh-pill';
el.innerHTML = '';
el.querySelector('.cxh-pill-verify').addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); goVerify(); });
el.querySelector('.cxh-pill-stop').addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); stopAll(); });
doc.body.appendChild(el);
}
return el;
}
function setPill(text) {
const el = ensurePill();
el.classList.add('on');
el.querySelector('.cxh-pill-txt').textContent = text;
}
function hidePill() {
const el = uiDoc().getElementById('cxh-pill');
if (el) el.classList.remove('on');
}
// ---------------------------------------------------------------------------
// 课程卡解析
// ---------------------------------------------------------------------------
const keyOf = (li) => li.getAttribute('cid') + ':' + li.getAttribute('clazzid');
const nameOf = (li) => {
const n = li.querySelector('dt.wzy_couros_name');
return n ? n.textContent.trim() : (li.getAttribute('cid') || '');
};
function findCard(key) {
const [cid, clz] = key.split(':');
const list = document.querySelectorAll('li[cid][clazzid]');
for (const li of list) {
if (li.getAttribute('cid') === cid && li.getAttribute('clazzid') === clz) return li;
}
return null;
}
function allCards() {
return Array.from(document.querySelectorAll('li[cid][clazzid]'));
}
function statusText(rec) {
if (!rec) return { cls: 'unknown', text: '未检测' };
if (rec.status === 'running') return { cls: 'running', text: '刷课中' };
if (rec.status === 'blocked') return { cls: 'blocked', text: '卡住了' };
if (rec.status === 'closed') {
return { cls: 'blocked', text: rec.chapters === 0 ? '无章节' : '已结课' };
}
if (typeof rec.pending === 'number' && rec.pending >= 0) {
if (rec.pending === 0) return { cls: 'done', text: '已完成' };
return { cls: 'partial', text: '待完成 ' + rec.pending };
}
if (!rec.total) return { cls: 'unknown', text: '未检测' };
if (rec.done >= rec.total) return { cls: 'done', text: '已完成 ' + rec.total + '/' + rec.total };
return { cls: 'partial', text: rec.done + '/' + rec.total + ' · ' + Math.round(rec.done / rec.total * 100) + '%' };
}
function ensureCard(li) {
if (li.querySelector(':scope > .cxh-ov')) return;
if (getComputedStyle(li).position === 'static') li.style.position = 'relative';
const key = keyOf(li);
const ov = document.createElement('div');
ov.className = 'cxh-ov';
const label = document.createElement('label');
label.className = 'cxh-check';
label.title = '选择这门课';
const cb = document.createElement('input');
cb.type = 'checkbox';
label.appendChild(cb);
const badge = document.createElement('span');
badge.className = 'cxh-badge unknown';
const run = document.createElement('button');
run.type = 'button';
run.className = 'cxh-run';
run.textContent = '刷这门';
ov.appendChild(label);
ov.appendChild(badge);
ov.appendChild(run);
li.appendChild(ov);
label.addEventListener('click', (e) => { e.stopPropagation(); });
cb.addEventListener('click', (e) => { e.stopPropagation(); });
badge.addEventListener('click', (e) => { e.stopPropagation(); });
run.addEventListener('click', (e) => {
e.preventDefault(); e.stopPropagation();
startKeys([key], nameOf(li));
});
cb.addEventListener('change', () => {
const s = loadState();
const i = s.checked.indexOf(key);
if (cb.checked && i < 0) s.checked.push(key);
if (!cb.checked && i >= 0) s.checked.splice(i, 1);
saveState(s);
refreshPanel();
});
// 兜底:阻止其它冒泡
li.addEventListener('mousedown', (e) => { if (e.target.closest('.cxh-ov')) e.stopPropagation(); }, true);
}
function decorateAll() {
allCards().forEach(ensureCard);
refreshBadges();
}
function refreshBadges() {
const s = loadState();
allCards().forEach((li) => {
const key = keyOf(li);
const rec = s.courses[key];
const badge = li.querySelector(':scope > .cxh-ov > .cxh-badge');
const cb = li.querySelector(':scope > .cxh-ov > .cxh-check > input');
if (!badge) return;
const st = s.run.running && s.run.current === key
? { cls: 'running', text: '刷课中' }
: statusText(rec);
badge.className = 'cxh-badge ' + st.cls;
badge.textContent = st.text;
badge.title = rec && rec.reason ? rec.reason : '';
if (cb) cb.checked = s.checked.indexOf(key) >= 0;
});
}
function refreshPanel() {
if (!panelEl) return;
const doc = panelEl.ownerDocument || document;
const s = loadState();
const cards = allCards();
let done = 0, partial = 0, unknown = 0;
cards.forEach((li) => {
const st = statusText(s.courses[keyOf(li)]);
if (st.cls === 'done') done++;
else if (st.cls === 'partial') partial++;
else unknown++;
});
const sum = panelEl.querySelector('#cxh-sum');
if (sum) {
if (s.scan.running) {
const total = s.scan.total || cards.length;
sum.innerHTML = '检测中 ' + Math.min(s.scan.index + 1, total) + '/' + total + '';
} else {
sum.innerHTML =
'已完成 ' + done + '' +
'未完成 ' + partial + '' +
'未检测 ' + unknown + '' +
'已选 ' + s.checked.length + '';
}
}
const prog = panelEl.querySelector('#cxh-prog');
if (prog) {
if (s.scan.running) {
const total = s.scan.total || 0;
prog.classList.add('on');
const pct = total ? Math.round(s.scan.index / total * 100) : 0;
const fill = prog.querySelector('i');
const label = prog.querySelector('span');
if (fill) fill.style.width = pct + '%';
if (label) label.textContent = '检测中 ' + Math.min(s.scan.index + 1, total) + '/' + total;
} else {
prog.classList.remove('on');
}
}
const logBox = panelEl.querySelector('#cxh-log');
if (logBox) {
logBox.innerHTML = s.log.slice(-6).map((x) => '' + x.t + ' ' + escapeHtml(x.m) + '
').join('');
}
const listBox = panelEl.querySelector('#cxh-list');
if (listBox) {
const items = cards.map((li) => ({ key: keyOf(li), name: nameOf(li), rec: s.courses[keyOf(li)] }))
.filter((x) => x.rec && x.rec.status !== 'done' && x.rec.status !== 'closed' && (x.rec.pending > 0 || x.rec.status === 'unknown'))
.sort((a, b) => (b.rec.pending || 0) - (a.rec.pending || 0));
if (!items.length) {
listBox.innerHTML = '没有未完成的课(先点「刷新进度」)
';
} else {
listBox.innerHTML = items.map((x) =>
'' + escapeHtml(x.name) + '' +
'' + (x.rec.pending > 0 ? '待完成 ' + x.rec.pending : '未检测') + '' +
'
'
).join('');
}
}
const busy = s.scan.running || s.run.running;
panelEl.querySelectorAll('[data-cxh]').forEach((b) => {
const act = b.getAttribute('data-cxh');
b.disabled = busy && (act === 'scan' || act === 'start');
});
refreshBadges();
}
function escapeHtml(s) {
return String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));
}
// ---------------------------------------------------------------------------
// 打开课程:劫持 window.open / 新标签,拿到课程 URL 后当前标签跳转
// ---------------------------------------------------------------------------
function absolute(u) {
try { return new URL(u, location.href).href; } catch (e) { return u; }
}
function makeStub(onUrl) {
const loc = {};
Object.defineProperty(loc, 'href', { get() { return ''; }, set(v) { if (v) onUrl(String(v)); } });
loc.replace = (v) => { if (v) onUrl(String(v)); };
loc.assign = (v) => { if (v) onUrl(String(v)); };
loc.reload = () => {};
const doc = { write() {}, open() {}, close() {}, body: {}, documentElement: {} };
const stub = { closed: false, focus() {}, blur() {}, close() {}, postMessage() {}, print() {}, document: doc, name: '' };
Object.defineProperty(stub, 'location', {
get() { return loc; },
set(v) { if (typeof v === 'string') onUrl(v); }
});
return stub;
}
// 拦截“打开课程”的各种方式:window.open / 空窗口后设 location / a.click() / form.submit
let pendingCapture = null;
let testMode = false;
let formCapture = null;
let scanBusy = false;
let lastBadHtml = '';
let lastBadName = '';
let suppressUntil = 0;
let interceptorsInstalled = false;
function emitCapture(raw) {
if (!raw) return;
const url = absolute(String(raw));
if (!/^https?:/i.test(url)) return;
if (url === location.href) return;
console.log('[cxh] captured:', url);
const p = pendingCapture;
if (!p) return;
p.last = url;
if (p.settle) clearTimeout(p.settle);
// 站点可能先 open 再补参数/多次设置 location:等 0.7 秒取最后一次
p.settle = setTimeout(() => {
if (pendingCapture !== p) return;
pendingCapture = null;
clearTimeout(p.timer);
p.resolve(p.last);
}, 700);
}
function waitCapture(action, timeoutMs) {
return new Promise((resolve) => {
if (pendingCapture) { clearTimeout(pendingCapture.timer); pendingCapture = null; }
const timer = setTimeout(() => {
if (pendingCapture && pendingCapture.timer === timer) {
const last = pendingCapture.last;
pendingCapture = null;
resolve(last || null);
}
}, timeoutMs);
pendingCapture = { resolve, timer, last: null, settle: null };
try { action(); } catch (e) { console.warn('[cxh] click error', e); }
});
}
function waitForm(action, timeoutMs) {
return new Promise((resolve) => {
if (formCapture) { clearTimeout(formCapture.timer); formCapture = null; }
const timer = setTimeout(() => {
if (formCapture && formCapture.timer === timer) formCapture = null;
resolve(null);
}, timeoutMs);
formCapture = { resolve, timer };
try { action(); } catch (e) { console.warn('[cxh] click error', e); }
});
}
function resolveFormUrl(u) {
if (!formCapture) return false;
const cap = formCapture;
formCapture = null;
clearTimeout(cap.timer);
cap.resolve({ action: absolute(String(u)), method: 'GET', enctype: '', data: {} });
suppressUntil = Date.now() + 3000;
return true;
}
function installInterceptors() {
if (interceptorsInstalled) return;
interceptorsInstalled = true;
const origOpen = W.open;
W.open = function (url, name, features) {
if (!formCapture && !pendingCapture && Date.now() < suppressUntil) {
return makeStub(emitCapture);
}
if (formCapture) {
const u = url ? String(url) : '';
if (u && u !== 'about:blank') {
resolveFormUrl(u);
return makeStub(emitCapture);
}
if (!u || u === 'about:blank') {
return makeStub((v) => { if (v) resolveFormUrl(v); });
}
}
if (pendingCapture) {
const u = url ? String(url) : '';
if (u && u !== 'about:blank' && /^https?:/i.test(absolute(u))) {
emitCapture(u);
return makeStub(emitCapture);
}
if (!u || u === 'about:blank') {
return makeStub(emitCapture);
}
}
return origOpen.apply(W, arguments);
};
try {
const ap = W.HTMLAnchorElement && W.HTMLAnchorElement.prototype;
if (ap && ap.click) {
const orig = ap.click;
ap.click = function () {
if (formCapture && this.href && /^https?:/i.test(this.href)) {
resolveFormUrl(this.href);
return;
}
if (pendingCapture && this.href && /^https?:/i.test(this.href) && this.href !== location.href) {
emitCapture(this.href);
return;
}
return orig.apply(this, arguments);
};
}
} catch (e) { /* ignore */ }
try {
const fp = W.HTMLFormElement && W.HTMLFormElement.prototype;
if (fp && fp.submit) {
const orig = fp.submit;
fp.submit = function () {
if (!formCapture && !pendingCapture && Date.now() < suppressUntil) {
return;
}
if (formCapture && this.action && /^https?:/i.test(this.action)) {
try {
const fd = new W.FormData(this);
const data = {};
fd.forEach((v, k) => { data[k] = v; });
const cap = formCapture;
formCapture = null;
clearTimeout(cap.timer);
console.log('[cxh] form captured:', this.action, data);
cap.resolve({ action: this.action, method: (this.method || 'get').toUpperCase(), enctype: this.enctype || '', data });
} catch (e3) { console.warn('[cxh] form capture error', e3); }
return;
}
if (pendingCapture && this.action && /^https?:/i.test(this.action)) {
if (testMode) { emitCapture(this.action); return; }
try {
const fd = new W.FormData(this);
const obj = {};
fd.forEach((v, k) => { obj[k] = v; });
console.log('[cxh] form submit:', this.action, obj);
} catch (e3) { /* ignore */ }
// 表单可能带 POST 参数:让它在当前标签原样提交,别只取 action 拼 GET
try { this.target = '_self'; this.setAttribute('target', '_self'); } catch (e2) { /* ignore */ }
if (pendingCapture) { clearTimeout(pendingCapture.timer); pendingCapture = null; }
}
return orig.apply(this, arguments);
};
}
} catch (e) { /* ignore */ }
try {
const et = W.EventTarget && W.EventTarget.prototype;
if (et && et.dispatchEvent) {
const orig = et.dispatchEvent;
et.dispatchEvent = function (ev) {
try {
if (formCapture && this && this.tagName === 'A' && this.href && /^https?:/i.test(this.href)) {
resolveFormUrl(this.href);
return false;
}
if (pendingCapture && this && this.tagName === 'A' && this.href && /^https?:/i.test(this.href) && this.href !== location.href) {
emitCapture(this.href);
return false;
}
} catch (e2) { /* ignore */ }
return orig.apply(this, arguments);
};
}
} catch (e) { /* ignore */ }
document.addEventListener('click', (e) => {
const a = e.target && e.target.closest ? e.target.closest('a[href]') : null;
if (!a || !a.href || !/^https?:/i.test(a.href)) return;
if (formCapture && (a.target === '_blank' || a.getAttribute('target') === '_blank')) {
e.preventDefault();
e.stopImmediatePropagation();
resolveFormUrl(a.href);
return;
}
if (!pendingCapture) return;
if (a.target === '_blank' || a.getAttribute('target') === '_blank') {
e.preventDefault();
e.stopImmediatePropagation();
emitCapture(a.href);
}
}, true);
document.addEventListener('submit', (e) => {
if (formCapture) {
const f = e.target;
if (f && f.action && /^https?:/i.test(f.action)) {
e.preventDefault();
try {
const fd = new W.FormData(f);
const data = {};
fd.forEach((v, k) => { data[k] = v; });
const cap = formCapture;
formCapture = null;
clearTimeout(cap.timer);
console.log('[cxh] form captured:', f.action, data);
cap.resolve({ action: f.action, method: (f.method || 'get').toUpperCase(), enctype: f.enctype || '', data });
} catch (e3) { console.warn('[cxh] form capture error', e3); }
}
return;
}
if (!pendingCapture) return;
const f = e.target;
if (f && f.action && /^https?:/i.test(f.action)) {
if (testMode) { e.preventDefault(); emitCapture(f.action); return; }
// 保留 POST 参数,改成当前标签提交
try { f.target = '_self'; f.setAttribute('target', '_self'); } catch (e2) { /* ignore */ }
if (pendingCapture) { clearTimeout(pendingCapture.timer); pendingCapture = null; }
}
}, true);
}
// 跨域 iframe 时顶层不可控:先不自动开独立标签(框架页地址单独打开会报“请求参数失效”)
function ensureStandalone() { return true; }
async function enterCourse(li, replace) {
installInterceptors();
const url = await waitCapture(() => li.click(), 15000);
// /dz/openhlcourse 是 POST 接口:直接 GET 必报“请求参数失效”,改为找到表单原样提交
if (url && /openhlcourse/i.test(url)) {
const form = document.querySelector('form[action*="openhlcourse"]');
if (form) {
console.log('[cxh] submit openhlcourse form in place');
try { form.target = '_self'; form.setAttribute('target', '_self'); } catch (e) { /* ignore */ }
try { W.HTMLFormElement.prototype.submit.call(form); } catch (e2) { /* ignore */ }
return true;
}
console.warn('[cxh] openhlcourse without form');
const st = loadState();
pushLog(st, '捕获到 openhlcourse(POST 地址),但页面里没有对应表单');
saveState(st);
return false;
}
if (url && /^https?:/i.test(url) && url !== location.href) {
console.log('[cxh] course url:', url);
navigate(url, !!replace);
return true;
}
console.warn('[cxh] 未能捕获课程链接,可能站点改了打开方式');
const s = loadState();
pushLog(s, '未能捕获课程链接:请把控制台输出发给作者');
saveState(s);
if (isListPage()) {
refreshPanel();
alert('课程助手:未能捕获课程链接。\n请确认点击课程卡后地址栏出现了 /mycourse/stu?... 的地址,并把控制台(F12)里 [cxh] 开头的报错发给作者。');
}
return false;
}
// ---------------------------------------------------------------------------
// 列表页
// ---------------------------------------------------------------------------
function initList() {
injectCssAll();
ensurePanel();
decorateAll();
refreshPanel();
const s0 = loadState();
if (s0.scan.running || s0.run.running) {
if (s0.lock.tab !== TAB_ID && lockFresh(s0)) {
pushLog(s0, '另一个标签页正在运行,本页只读');
saveState(s0);
return;
}
claimLock(s0);
touchLock(s0);
saveState(s0);
}
// 卡片异步刷新时重新挂
let timer = null;
const mo = new MutationObserver(() => {
clearTimeout(timer);
timer = setTimeout(() => {
decorateAll();
refreshPanel();
}, 250);
});
mo.observe(document.body, { childList: true, subtree: true });
setInterval(() => {
refreshPanel();
}, 2000);
resumeList();
}
function resumeList() {
const s = loadState();
if (s.run.running) {
s.run.running = false;
s.run.current = null;
s.run.queue = [];
saveState(s);
}
if (s.scan.running && !scanBusy) {
// 后台扫描在页面刷新后会中断,重置状态
s.scan.running = false;
saveState(s);
refreshPanel();
}
}
function scanNext() {
const s = loadState();
if (!s.scan.running) return;
while (s.scan.index < s.scan.keys.length) {
const key = s.scan.keys[s.scan.index];
const li = findCard(key);
if (!li) { s.scan.index++; continue; }
saveState(s);
refreshPanel();
enterCourse(li, true).then((ok) => {
if (ok) return;
const st = loadState();
st.scan.running = false;
pushLog(st, '检测中止:无法进入课程');
saveState(st);
refreshPanel();
});
return;
}
s.scan.running = false;
pushLog(s, '进度检测完成');
saveState(s);
refreshPanel();
}
async function testCapture() {
const cards = allCards();
if (!cards.length) { alert('没有找到课程卡'); return; }
const li = cards[0];
installInterceptors();
const s = loadState();
pushLog(s, '测试抓取:点第一张卡「' + nameOf(li) + '」');
saveState(s);
refreshPanel();
testMode = true;
let url = null;
try { url = await waitCapture(() => li.click(), 15000); } finally { testMode = false; }
const st = loadState();
if (url) {
pushLog(st, '测试抓取成功:' + url);
saveState(st);
refreshPanel();
alert('捕获到课程链接:\n' + url + '\n\n把这个地址发我即可。');
} else {
pushLog(st, '测试抓取失败:15 秒内没拿到 URL');
saveState(st);
refreshPanel();
alert('15 秒内没有捕获到 URL。\n如果又弹出了新标签,说明站点用了别的打开方式;请把控制台(F12)里 [cxh] 开头的输出发我。');
}
}
function gmReq(opts) {
return new Promise((resolve) => {
try {
GM_xmlhttpRequest(Object.assign({}, opts, {
onload: (r) => resolve({ status: r.status, finalUrl: r.finalUrl || r.responseURL || opts.url, html: r.responseText || '' }),
onerror: () => resolve(null),
ontimeout: () => resolve(null)
}));
} catch (e) { resolve(null); }
});
}
async function testBackend() {
const cards = allCards();
if (!cards.length) { alert('没有找到课程卡'); return; }
const li = cards.find((c) => c.getAttribute('cid') === '266384206') || cards[0];
installInterceptors();
const s = loadState();
pushLog(s, '后台试抓:点「' + nameOf(li) + '」并拦截表单…');
saveState(s);
refreshPanel();
const info = await waitForm(() => li.click(), 10000);
if (!info) {
const st = loadState();
pushLog(st, '后台试抓:10 秒内没抓到表单');
saveState(st);
refreshPanel();
alert('10 秒内没有抓到表单。');
return;
}
const body = new URLSearchParams(info.data).toString();
const method = (info.method || 'GET').toUpperCase();
let reqUrl = info.action;
let reqData = null;
if (method === 'GET') {
reqUrl += (reqUrl.indexOf('?') === -1 ? '?' : '&') + body;
} else {
reqData = body;
}
console.log('[cxh] backend request', method, reqUrl, info.enctype, info.data);
if (typeof GM_xmlhttpRequest !== 'function') { alert('当前脚本管理器不支持 GM_xmlhttpRequest'); return; }
const r1 = await gmReq({
method, url: reqUrl, data: reqData, timeout: 25000,
headers: method === 'GET' ? {} : { 'Content-Type': 'application/x-www-form-urlencoded' }
});
if (!r1) { alert('第一次请求失败'); return; }
let summary = '① ' + method + ' ' + r1.status + '\n最终: ' + r1.finalUrl + '\nHTML: ' + r1.html.length + ' 字节';
let ok = /已完成任务点/.test(r1.html);
summary += '\n有进度: ' + ok;
const cpi = (r1.html.match(/[?&]cpi=(\d+)/) || [])[1] || '';
let q1 = {};
try { q1 = new URL(r1.finalUrl).searchParams; } catch (e) { q1 = new URLSearchParams(); }
const courseId = q1.get('courseId') || '';
const clazzid = q1.get('clazzid') || '';
const enc1 = q1.get('enc') || '';
summary += '\ncpi=' + cpi + ' courseId=' + courseId;
if (!ok) {
const v2m = r1.html.match(/href="([^"]*v=2[^"]*fromMiddle=1[^"]*)"/);
if (v2m) {
let v2 = '';
try { v2 = new URL(v2m[1].replace(/&/g, '&'), r1.finalUrl || location.href).href; } catch (e) { v2 = ''; }
if (v2) {
const r2 = await gmReq({ method: 'GET', url: v2, timeout: 20000 });
if (r2) {
const ok2 = /已完成任务点/.test(r2.html);
summary += '\n② V2 ' + r2.status + ':' + r2.html.length + ' 字节,有进度: ' + ok2 + '\nV2最终: ' + r2.finalUrl;
if (ok2) ok = true;
} else summary += '\n② V2 请求失败';
}
}
}
if (!ok && cpi && courseId) {
const cat = 'https://mooc2-ans.chaoxing.com/mooc2-ans/mycourse/stu?courseid=' + encodeURIComponent(courseId) +
'&clazzid=' + encodeURIComponent(clazzid) + '&cpi=' + encodeURIComponent(cpi) +
'&enc=' + encodeURIComponent(enc1) + '&pageHeader=1&v=2&hideHead=';
const r3 = await gmReq({ method: 'GET', url: cat, timeout: 20000 });
if (r3) {
const ok3 = /已完成任务点/.test(r3.html);
summary += '\n③ 目录 ' + r3.status + ':' + r3.html.length + ' 字节,有进度: ' + ok3 + '\n目录最终: ' + r3.finalUrl;
if (ok3) ok = true;
} else summary += '\n③ 目录请求失败';
}
console.log('[cxh] backend summary:', summary);
const st = loadState();
pushLog(st, '后台结果:' + (ok ? '拿到进度' : '未拿到进度'));
saveState(st);
refreshPanel();
alert('后台请求结果\n' + summary);
}
function parseLegacyCourse(html) {
try {
const doc = new DOMParser().parseFromString(html, 'text/html');
const counts = Array.from(doc.querySelectorAll('input.knowledgeJobCount')).map((el) => parseInt(el.value || '0', 10) || 0);
let pending = counts.reduce((a, b) => a + b, 0);
// 新版目录:h2 里有「已完成任务点: x/y」
const head = doc.querySelector('h2.xs_head_name');
const m = head ? head.textContent.match(/(\d+)\s*\/\s*(\d+)/) : null;
let done = -1;
let total = -1;
if (m) {
done = parseInt(m[1], 10);
total = parseInt(m[2], 10);
pending = Math.max(0, total - done);
}
let chapters = doc.querySelectorAll('a[href*="studentstudy"]').length;
const newChapters = doc.querySelectorAll('div.chapter_item[id^="cur"]').length;
if (newChapters > chapters) chapters = newChapters;
const closed = /结课模式|任务点无法完成/.test(html);
// 真正的目录页(有进度头 或 明确写着“暂无章节内容”);否则是门户/承诺书壳
const catalog = !!head || /暂无章节内容/.test(html);
return { pending, chapters, closed, done, total, catalog };
} catch (e) {
return { pending: -1, chapters: 0, closed: false, done: -1, total: -1, catalog: false };
}
}
function findV2Url(html, base) {
const m = html.match(/href="([^"]*v=2[^"]*fromMiddle=1[^"]*)"/);
if (!m) return '';
try { return new URL(m[1].replace(/&/g, '&'), base || location.href).href; } catch (e) { return ''; }
}
function dumpDiagnosticHtml() {
if (!lastBadHtml) { alert('还没有诊断数据:先点「刷新进度」扫一遍'); return; }
try {
const blob = new Blob([lastBadHtml], { type: 'text/html;charset=utf-8' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'diagnostic-' + String(lastBadName || 'course').replace(/[\\/:*?"<>|]/g, '_') + '.html';
document.body.appendChild(a);
a.click();
setTimeout(() => { URL.revokeObjectURL(a.href); a.remove(); }, 1000);
alert('已保存诊断 HTML:' + lastBadName);
} catch (e) { alert('保存失败:' + (e && e.message ? e.message : e)); }
}
function findDataUrl(html, base) {
const m = html.match(/data-url="([^"]*mycourse\/[^"]*)"/i);
if (!m) return '';
let u;
try { u = new URL(m[1], base || location.href); } catch (e) { return ''; }
try {
const b = new URL(base || location.href);
b.searchParams.forEach((v, k) => { if (!u.searchParams.has(k)) u.searchParams.set(k, v); });
const cid = b.searchParams.get('courseid') || b.searchParams.get('courseId');
if (cid) {
u.searchParams.set('courseId', cid);
u.searchParams.set('courseid', cid);
}
} catch (e) { /* ignore */ }
return u.href;
}
async function startScan() {
if (scanBusy) { alert('正在检测,请稍候'); return; }
const cards = allCards();
if (!cards.length) { alert('当前页面没有找到课程卡片'); return; }
scanBusy = true;
const s0 = loadState();
s0.scan = { running: true, total: cards.length, index: 0, startedAt: Date.now() };
s0.run.running = false;
pushLog(s0, '开始检测 ' + cards.length + ' 门课程(后台请求,不跳页)');
saveState(s0);
refreshPanel();
try {
// 先装拦截器,否则点卡片会真的开新标签
installInterceptors();
for (let i = 0; i < cards.length; i++) {
const st = loadState();
if (!st.scan.running) break;
st.scan.index = i;
saveState(st);
refreshPanel();
const li = cards[i];
const key = keyOf(li);
const name = nameOf(li);
const info = await waitForm(() => li.click(), 3000);
if (!info) {
const st2 = loadState();
st2.courses[key] = Object.assign({}, st2.courses[key], { name, status: 'unknown', reason: '没抓到打开参数', ts: Date.now() });
saveState(st2);
continue;
}
const method = (info.method || 'GET').toUpperCase();
let url = info.action;
let data = null;
const body = new URLSearchParams(info.data).toString();
if (method === 'GET') url += (url.indexOf('?') === -1 ? '?' : '&') + body;
else data = body;
const r = await gmReq({
method, url, data, timeout: 20000,
headers: method === 'GET' ? {} : { 'Content-Type': 'application/x-www-form-urlencoded' }
});
const st3 = loadState();
if (r && r.status === 200) {
let p = parseLegacyCourse(r.html);
let usedUrl = url;
let usedHtml = r.html;
// 老版页面没有章节/任务点时,尝试「体验新版」入口
if (p.chapters === 0 && p.pending === 0 && !p.closed) {
const v2 = findV2Url(r.html, r.finalUrl);
if (v2) {
const r2 = await gmReq({ method: 'GET', url: v2, timeout: 20000 });
if (r2 && r2.status === 200) {
const p2 = parseLegacyCourse(r2.html);
if (p2.catalog || p2.chapters > 0 || p2.pending > 0 || p2.total > 0) { p = p2; usedUrl = v2; usedHtml = r2.html; }
}
}
}
// 还是空:跟门户页里的 data-url(/mycourse/studentcourse)再取一次
if (p.chapters === 0 && p.pending === 0 && !p.closed) {
const du = findDataUrl(r.html, r.finalUrl);
if (du) {
const r3 = await gmReq({ method: 'GET', url: du, timeout: 20000 });
if (r3 && r3.status === 200) {
const p3 = parseLegacyCourse(r3.html);
if (p3.catalog || p3.chapters > 0 || p3.pending > 0 || p3.total > 0) { p = p3; usedUrl = du; usedHtml = r3.html; }
}
}
}
const noChapters = p.chapters === 0 && p.pending === 0;
if (noChapters) {
lastBadHtml = usedHtml;
lastBadName = name;
pushLog(st3, (p.catalog ? '无章节:' : '未识别到章节:') + name);
}
const status = (p.closed && p.pending === 0)
? 'closed'
: (p.pending > 0 ? 'partial' : (p.chapters > 0 ? 'done' : (p.catalog ? 'closed' : 'unknown')));
st3.courses[key] = Object.assign({}, st3.courses[key], {
name,
pending: p.pending,
chapters: p.chapters,
done: p.done,
total: p.total,
closed: p.closed || (noChapters && p.catalog),
openUrl: usedUrl,
status,
reason: p.closed ? '结课模式' : (noChapters ? (p.catalog ? '无章节' : '门户/承诺书页') : ''),
ts: Date.now()
});
} else {
st3.courses[key] = Object.assign({}, st3.courses[key], { name, status: 'unknown', reason: '请求失败 ' + (r ? r.status : ''), ts: Date.now() });
}
st3.scan.index = i + 1;
saveState(st3);
refreshPanel();
}
} catch (e) {
console.warn('[cxh] scan error', e);
const se = loadState();
pushLog(se, '检测出错:' + (e && e.message ? e.message : e));
saveState(se);
} finally {
const st4 = loadState();
st4.scan.running = false;
saveState(st4);
refreshPanel();
scanBusy = false;
}
}
function isUndone(rec) {
if (!rec) return true;
if (rec.status === 'closed') return false;
if (typeof rec.pending === 'number') return rec.pending > 0;
if (rec.total) return rec.done < rec.total;
return true;
}
function checkAllUndone() {
const s = loadState();
s.checked = [];
allCards().forEach((li) => {
const key = keyOf(li);
if (isUndone(s.courses[key])) s.checked.push(key);
});
saveState(s);
refreshPanel();
}
function clearChecked() {
const s = loadState();
s.checked = [];
saveState(s);
refreshPanel();
}
function startChecked() {
const s = loadState();
const keys = s.checked.filter((k) => isUndone(s.courses[k]));
if (!keys.length) { alert('请先勾选要刷的课程(已完成的课程会被跳过)'); return; }
startKeys(keys);
}
function startKeys(keys) {
if (!keys.length) return;
const s = loadState();
const key = keys[0];
const rec = s.courses[key];
if (!rec || !rec.openUrl) { alert('还没有这门课的打开参数,请先点「刷新进度」'); return; }
formCapture = null;
pendingCapture = null;
suppressUntil = 0;
s.run = {
running: true, queue: keys.slice(1), current: key, attempts: 0,
startedAt: Date.now(), tab: 'cxh_course'
};
saveState(s);
const w = window.open(rec.openUrl, 'cxh_course');
if (!w) {
s.run.running = false;
saveState(s);
alert('浏览器拦截了新标签,请允许本站弹窗后再试');
return;
}
pushLog(s, '开始刷:' + (rec.name || key) + '(队列 ' + keys.length + ' 门)');
saveState(s);
refreshPanel();
}
// ---------------------------------------------------------------------------
// 课程页
// ---------------------------------------------------------------------------
function readCourse() {
const q = (sel) => document.querySelector(sel);
const courseId = (q('#courseId') || {}).value || '';
const clazzId = (q('#clazzId') || {}).value || '';
const head = q('h2.xs_head_name');
const m = (head ? head.textContent : '').match(/(\d+)\s*\/\s*(\d+)/);
const sections = Array.from(document.querySelectorAll('div.chapter_item[id^="cur"]')).map((el) => {
const oc = el.getAttribute('onclick') || '';
const a = oc.match(/toOld\(\s*'([^']+)'\s*,\s*'([^']+)'\s*,\s*'([^']+)'/);
return {
el,
id: el.id.slice(3),
title: el.getAttribute('title') || '',
done: !!el.querySelector('.catalog_state.icon_yiwanc'),
args: a ? [a[1], a[2], a[3], 0] : null
};
});
return {
courseId, clazzId,
done: m ? parseInt(m[1], 10) : 0,
total: m ? parseInt(m[2], 10) : 0,
sections
};
}
function initCourse() {
injectCssAll();
const data = readCourse();
if (!data.courseId) return;
const key = data.courseId + ':' + data.clazzId;
const s = loadState();
const rec = s.courses[key] || {};
// 被动记录:用户自己点进课程也更新状态
if (data.total > 0) {
rec.name = rec.name || (document.querySelector('.courseName') || {}).textContent || '';
rec.done = data.done;
rec.total = data.total;
rec.percent = Math.round(data.done / data.total * 100);
rec.pending = Math.max(0, data.total - data.done);
rec.closed = false;
rec.status = data.done >= data.total ? 'done' : 'partial';
rec.reason = '';
rec.ts = Date.now();
s.courses[key] = rec;
saveState(s);
}
if (s.scan.running) return;
if (s.run.running && s.run.current === key) {
forceLock(s);
touchLock(s);
setPill('正在复核:' + (rec.name || '') + ' ' + data.done + '/' + data.total);
runOnCourse(s, key, data);
return;
}
if (s.run.running && s.run.current && s.run.current !== key) {
pushLog(s, '进入了非目标课程,返回列表');
saveState(s);
navigate(s.listUrl || LIST_URL, true);
return;
}
// 非流程:只记录进度,不显示悬浮条
}
function scanOnCourse(s, key, data) {
const rec = s.courses[key] || {};
rec.name = rec.name || (document.querySelector('dt.wzy_couros_name') || {}).textContent || '';
rec.done = data.done;
rec.total = data.total;
rec.percent = data.total ? Math.round(data.done / data.total * 100) : 0;
rec.status = data.total && data.done >= data.total ? 'done' : 'partial';
rec.ts = Date.now();
s.courses[key] = rec;
s.scan.index++;
pushLog(s, '[' + (rec.name || key) + '] ' + data.done + '/' + data.total);
saveState(s);
navigate(s.scan.returnUrl || LIST_URL, true);
}
function runOnCourse(s, key, data) {
const rec = s.courses[key] || {};
rec.name = rec.name || '';
rec.done = data.done;
rec.total = data.total;
rec.percent = data.total ? Math.round(data.done / data.total * 100) : 0;
rec.status = data.total && data.done >= data.total ? 'done' : 'partial';
rec.ts = Date.now();
s.courses[key] = rec;
s.run.courseUrl = location.href;
if (!data.total) { leaveKey(s, key, 'blocked', '没有读到任务点总数'); saveState(s); backToList(s); return; }
if (data.done >= data.total) {
pushLog(s, '[' + (rec.name || key) + '] 已完成 ' + data.done + '/' + data.total);
leaveKey(s, key, 'done', '');
saveState(s);
backToList(s);
return;
}
const first = data.sections.find((x) => !x.done);
if (!first) {
pushLog(s, '[' + (rec.name || key) + '] 读到 ' + data.done + '/' + data.total + ',但定位不到未完成章节');
leaveKey(s, key, 'blocked', '进度未满但目录里找不到未完成章节');
saveState(s);
backToList(s);
return;
}
if (s.run.phase === 'verify') {
if (data.done <= s.run.lastDone) s.run.attempts = (s.run.attempts || 0) + 1;
else s.run.attempts = 0;
if (s.run.attempts >= 2) {
pushLog(s, '[' + (rec.name || key) + '] 连续两次回到课程页仍未推进,标记卡住');
leaveKey(s, key, 'blocked', '章节「' + first.title + '」反复尝试无进展');
saveState(s);
backToList(s);
return;
}
}
s.run.lastDone = data.done;
s.run.phase = 'play';
s.run.sectionId = first.id;
pushLog(s, '[' + (rec.name || key) + '] 进入「' + first.title + '」 (' + data.done + '/' + data.total + ')');
saveState(s);
setPill('正在进入「' + first.title + '」');
clickSection(first);
}
function clickSection(sec) {
const go = () => {
installInterceptors();
waitCapture(() => {
try { sec.el.click(); } catch (e) { /* ignore */ }
}, 8000).then((url) => {
if (url) navigate(url, false);
else if (typeof W.toOld === 'function' && sec.args) {
try { W.toOld.apply(W, sec.args); } catch (e) { console.warn('[cxh] toOld error', e); }
}
});
};
// 等站点的 toOld 就绪(外部 JS 偶尔比我们晚)
let tries = 0;
const t = setInterval(() => {
tries++;
if (typeof W.toOld === 'function' || tries > 20) {
clearInterval(t);
go();
}
}, 250);
}
function leaveKey(s, key, status, reason) {
const rec = s.courses[key] || {};
rec.status = status;
rec.reason = reason || '';
rec.ts = Date.now();
s.courses[key] = rec;
s.run.current = null;
s.run.lastDone = -1;
s.run.attempts = 0;
s.run.phase = '';
if (s.run.queue.length) s.run.current = s.run.queue.shift();
else s.run.running = false;
}
function backToList(s) {
saveState(s);
navigate(s.listUrl || LIST_URL, true);
}
// ---------------------------------------------------------------------------
// 学习页(studentstudy)
// ---------------------------------------------------------------------------
function chapterLooksDone() {
const media = Array.from(document.querySelectorAll('video,audio')).filter((m) => m.currentSrc || m.src);
if (media.some((m) => !m.ended && !m.paused)) return false;
// 章节测验/作业:旧脚本提交后会显示“已完成/待批阅”,或父级带 ans-job-finished
const works = Array.from(document.querySelectorAll('iframe')).filter((f) => (f.src || '').indexOf('api/work') !== -1);
for (const f of works) {
const p = f.parentElement;
if (p && (p.className || '').indexOf('ans-job-finished') !== -1) continue;
let txt = '';
try { txt = f.contentDocument && f.contentDocument.documentElement ? f.contentDocument.documentElement.innerText : ''; } catch (e) { txt = ''; }
if (/已完成|待批阅/.test(txt)) continue;
return false;
}
const tasks = Array.from(document.querySelectorAll('iframe')).filter((f) => f.parentElement && f.parentElement.querySelector('.ans-job-icon'));
if (tasks.length) {
return tasks.every((f) => {
const cls = (f.parentElement && f.parentElement.className) || '';
return cls.indexOf('ans-job-finished') !== -1;
});
}
// 没有任何任务点:视为已完成(旧脚本会立刻跳下一章)
return true;
}
let verifying = false;
function runCourseName(key) {
const s = loadState();
const rec = s.courses[key];
return rec && rec.name ? rec.name : key;
}
async function verifyAndNext() {
if (verifying) return;
verifying = true;
try {
const s = loadState();
if (!s.run.running) return;
const key = s.run.current;
const rec = s.courses[key] || {};
if (rec.openUrl) {
const r = await gmReq({ method: 'GET', url: rec.openUrl, timeout: 20000 });
if (r && r.status === 200) {
const p = parseLegacyCourse(r.html);
s.courses[key] = Object.assign({}, rec, {
pending: p.pending,
chapters: p.chapters,
closed: p.closed,
status: p.closed ? 'closed' : (p.pending === 0 ? 'done' : 'partial'),
ts: Date.now()
});
}
}
let next = null;
while (s.run.queue.length) {
const k = s.run.queue.shift();
if (s.courses[k] && s.courses[k].openUrl) { next = k; break; }
}
if (!next) {
s.run.running = false;
s.run.current = null;
pushLog(s, '队列已刷完');
saveState(s);
hidePill();
try { window.close(); } catch (e) { /* ignore */ }
return;
}
s.run.current = next;
saveState(s);
location.href = s.courses[next].openUrl;
} finally {
verifying = false;
}
}
function goVerify() { verifyAndNext(); }
// ---------------------------------------------------------------------------
// 在线学习诚信承诺书:进课程先弹的那个窗口,不点掉后面什么都不发生
// ---------------------------------------------------------------------------
function elVisible(el) {
if (!el) return false;
try {
if (el.offsetParent !== null) return true;
const cs = getComputedStyle(el);
return !!cs && cs.display !== 'none' && cs.visibility !== 'hidden';
} catch (e) { return false; }
}
function commitmentBox() {
const el = document.getElementById('showCommitmentId');
if (el && elVisible(el)) return el;
const alt = document.querySelector('.commitment-content-dialog');
if (alt && elVisible(alt)) return (alt.closest && alt.closest('.maskDiv')) || alt;
return null;
}
// 勾选「我已完整知晓并自愿遵守上述内容」→ 点「开始学习」
function agreeCommitment(box) {
const cb = box.querySelector('input.agreeButton')
|| document.getElementById('learnCommit-bottom-div')
|| box.querySelector('input[type=checkbox]');
if (cb && !cb.checked) {
try { cb.click(); } catch (e) { /* ignore */ }
}
const btns = Array.from(box.querySelectorAll('a.commitBtn, a, button'))
.filter((b) => /开始学习|同意|进入学习|确定/.test((b.textContent || '').trim()));
// 勾上之后站点才把可点的「开始学习」换出来
const enabled = btns.find((b) => b.classList.contains('agreeStart') && elVisible(b));
if (enabled) {
try { enabled.click(); return true; } catch (e) { /* ignore */ }
}
// 宁可等下一轮,也不点那个灰色禁用按钮 —— 点了会弹 alert 把流程卡死
if ((cb && cb.checked) || !cb) {
const anyVis = btns.find(elVisible);
if (anyVis) {
try { anyVis.click(); return true; } catch (e) { /* ignore */ }
}
}
return false;
}
let commitWatch = null;
let commitLastSeen = 0;
function watchCommitment() {
if (commitWatch) return;
const startedAt = Date.now();
commitWatch = setInterval(() => {
let box = null;
try { box = commitmentBox(); } catch (e) { box = null; }
if (!box) {
// 从没见过就 60 秒后收工;见过就继续盯 20 分钟(切章节时还会再弹)
const idle = commitLastSeen ? 20 * 60 * 1000 : 60000;
if (Date.now() - Math.max(commitLastSeen, startedAt) > idle) {
clearInterval(commitWatch);
commitWatch = null;
}
return;
}
commitLastSeen = Date.now();
const s = loadState();
if (s.cfg && s.cfg.commitAuto === false) {
if (s.run.running) setPill('需要先手动勾选「在线学习诚信承诺书」');
return;
}
const ok = agreeCommitment(box);
if (s.run.running || s.scan.running) {
setPill(ok ? '已自动同意诚信承诺书,正在进入课程…' : '正在勾选诚信承诺书…');
}
if (ok && !s.log.some((x) => x.m === '已自动同意诚信承诺书')) {
pushLog(s, '已自动同意诚信承诺书');
saveState(s);
}
}, 800);
}
// 进课程后视频不自己播:轻推一下(只在刚进页面那 20 秒内,避免和弹题暂停打架)
async function nudgePlay() {
const vids = Array.from(document.querySelectorAll('video'));
for (const v of vids) {
if (!v.paused || v.ended) continue;
try { await v.play(); return true; }
catch (e) {
try { v.muted = true; await v.play(); return true; } catch (e2) { /* ignore */ }
}
}
return false;
}
// 认不出来的弹窗:把它的 id/class/文字打到控制台,方便下次定位
function dumpVisibleModals() {
const sel = '#showCommitmentId, .maskDiv, .popDiv, .layui-layer, .el-dialog, .aui-dialog, [class*="mask"], [class*="popup"], [class*="dialog"]';
const out = [];
document.querySelectorAll(sel).forEach((el) => {
if (!elVisible(el)) return;
const txt = (el.textContent || '').replace(/\s+/g, ' ').trim().slice(0, 80);
if (!txt) return;
out.push({ id: el.id || '', cls: String(el.className || '').slice(0, 60), text: txt });
});
if (out.length) {
console.warn('[课程助手] 页面上有这些可见弹窗:', out);
try {
const s = loadState();
pushLog(s, '可见弹窗:' + out.map((o) => (o.id || o.cls.split(' ')[0]) + '「' + o.text.slice(0, 18) + '」').join(' / '));
saveState(s);
setPill('检测到弹窗:' + out.map((o) => o.id || o.cls.split(' ')[0]).join(' / '));
} catch (e) { /* ignore */ }
return out;
}
return [];
}
function initPlay() {
injectCssAll();
const s = loadState();
if (!s.run.running) return;
const m = location.href.match(/[?&]courseId=(\d+)/i);
const cid = m ? m[1] : '';
if (cid && s.run.current && s.run.current.split(':')[0] !== cid) return;
setPill('刷课中:' + runCourseName(s.run.current));
// 刚进学习页时视频常常不会自己播:轻推几下,只在最开始 20 秒内
let nudges = 0;
const nudgeTimer = setInterval(async () => {
nudges++;
const st0 = loadState();
if (!st0.run.running || nudges > 7) { clearInterval(nudgeTimer); return; }
if (await nudgePlay()) { clearInterval(nudgeTimer); return; }
}, 3000);
let doneAt = 0;
let lastHref = location.href;
let chapterStart = Date.now();
setInterval(() => {
const st = loadState();
if (!st.run.running) { hidePill(); return; }
if (location.href !== lastHref) { lastHref = location.href; doneAt = 0; chapterStart = Date.now(); return; }
if (Date.now() - chapterStart < 60000) return;
if (!chapterLooksDone()) { doneAt = 0; return; }
if (!doneAt) { doneAt = Date.now(); return; }
if (Date.now() - doneAt < 20000) return;
verifyAndNext();
}, 3000);
}
function initCourseHome() {
const s = loadState();
if (!s.run.running) return;
const a = document.querySelector('a[href*="studentstudy"]');
if (a && a.href) {
location.href = a.href;
return;
}
setPill('课程主页没有找到章节入口');
}
// ---------------------------------------------------------------------------
// 其它页面
// ---------------------------------------------------------------------------
function initOther() {
const s = loadState();
if (!s.scan.running && !s.run.running) return;
if (!IS_TOP) return;
// 门户壳页面(/i/base)里有 iframe,交给子框架处理,避免误报
if (document.querySelector('iframe')) return;
injectCssAll();
setPill('当前页面不是课程页,流程已暂停(可点停止)');
}
// ---------------------------------------------------------------------------
// 启动
// ---------------------------------------------------------------------------
function boot() {
injectCssAll();
try { watchCommitment(); } catch (e) { /* ignore */ }
// 兜底:扫描期间站点误开的 chaoxing 标签,自动关掉
try {
const s0 = loadState();
if (s0.scan.running && !s0.run.running && /(^|\.)chaoxing\.com$/i.test(location.hostname)) {
setTimeout(() => { try { window.close(); } catch (e) { /* ignore */ } }, 1200);
}
} catch (e) { /* ignore */ }
let tries = 0;
const tick = () => {
if (isListPage()) { initList(); return; }
if (isCoursePage()) { initCourse(); return; }
if (location.pathname.indexOf('/mycourse/studentcourse') !== -1) { initCourseHome(); return; }
if (isPlayPage()) { initPlay(); return; }
// 门户首页 + 内嵌 iframe 都异步渲染,等足 60 秒再放弃
if (++tries < 240) { setTimeout(tick, 250); return; }
try {
console.warn('[课程助手] 60 秒内没认出新页面', location.href,
{wzy_couros: !!document.querySelector('div.wzy_couros'),
card: !!document.querySelector('li[cid][clazzid]'),
readyState: document.readyState});
} catch (e) { /* ignore */ }
initOther();
};
tick();
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot, { once: true });
else boot();
// 心跳:流程进行中刷新锁
setInterval(() => {
const s = loadState();
if ((s.scan.running || s.run.running) && s.lock.tab === TAB_ID) {
s.lock.ts = Date.now();
saveState(s);
}
}, 5000);
function stopAll() {
const s = loadState();
s.scan.running = false;
s.run.running = false;
s.run.current = null;
s.run.queue = [];
s.run.phase = '';
pushLog(s, '已停止');
saveState(s);
hidePill();
if (!isListPage()) navigate(s.listUrl || LIST_URL, true);
else refreshPanel();
}
})();
} catch (e) { window.__cxhFatal('课程列表面板初始化失败', e); }
// ------------------------------------------------------------------
// 原脚本里用到 proxyLoc(...),但这份副本里没有它的定义(引用 10 处、定义 0 处),
// 补一个等价实现:读写 location / URL 的 href、protocol 等。
// ------------------------------------------------------------------
function proxyLoc(loc) {
if (!loc || typeof loc !== 'object') return loc;
try {
return new Proxy(loc, {
get: function (t, k) {
var v = t[k];
return typeof v === 'function' ? v.bind(t) : v;
},
set: function (t, k, v) {
try { t[k] = v; }
catch (e) { if (k === 'href') { try { t.assign(v); } catch (e2) {} } }
return true;
}
});
} catch (e) { return loc; }
}
// ==================================================================
// [2] 原「刷课脚本」
// 必须留在顶层:它内部依赖 sloppy 模式,包进严格模式函数会连环报错
// ==================================================================
try {
var __cxhSkipLegacy = /^(p\.s\.ecust\.edu\.cn|s\.ecust\.edu\.cn)$/i.test(location.hostname);
if (__cxhSkipLegacy) {
console.log('[课程助手] 课程列表页,不加载原刷课脚本');
} else if (typeof Vue === 'undefined' || typeof ElementPlus === 'undefined'
|| typeof Pinia === 'undefined' || typeof rxjs === 'undefined' || typeof md5 === 'undefined') {
console.warn('[课程助手] 缺少依赖库(Vue/ElementPlus/Pinia/rxjs/md5),已跳过原刷课脚本;列表面板不受影响');
} else {
(e=>{if(typeof GM_addStyle=="function"){GM_addStyle(e);return}const i=document.createElement("style");i.textContent=e,document.head.append(i)})(' .main-page .guide-page{box-sizing:border-box;max-height:min(400px,calc(100vh - 160px));max-height:min(400px,calc(100dvh - 160px));overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain;padding:2px 4px 2px 0;color:#4e5969;font-size:12px;line-height:1.7;scrollbar-width:thin;scrollbar-color:#c7d7eb transparent}.main-page .guide-page:focus-visible{outline:2px solid #176ae5;outline-offset:2px;border-radius:8px}.main-page .guide-header{margin:0 0 10px;padding:11px 12px;border:1px solid #d9e8fc;border-radius:9px;background:linear-gradient(120deg,#edf5ff 0%,#f8fbff 100%)}.main-page .guide-heading-row{display:flex;align-items:center;justify-content:space-between;gap:8px}.main-page .guide-title{margin:0;color:#174b94;font-size:13px;font-weight:600;line-height:1.6}.main-page .guide-tag{flex-shrink:0;padding:1px 7px;border:1px solid #d4e5fc;border-radius:20px;background-color:#fff;color:#2262b5;font-size:10px;line-height:18px}.main-page .guide-subtitle{margin:3px 0 0;color:#61758e;font-size:11px}.main-page .guide-list{display:grid;gap:8px;margin:0;padding:0;list-style:none}.main-page .guide-card{min-width:0;padding:10px;border:1px solid #e4eaf2;border-radius:8px;background-color:#fff}.main-page .guide-card-heading{display:flex;align-items:center;gap:8px;margin-bottom:6px}.main-page .guide-number{display:inline-flex;align-items:center;justify-content:center;flex:0 0 24px;height:24px;border-radius:7px;background-color:#eaf3ff;color:#176ae5;font-size:11px;font-weight:600;line-height:1;font-variant-numeric:tabular-nums}.main-page .guide-card-title{margin:0;color:#263a55;font-size:12px;font-weight:600;line-height:1.6}.main-page .guide-copy{margin:0;overflow-wrap:anywhere}.main-page .guide-flow{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px;margin:10px 0 0;padding:9px 4px 7px;border-radius:7px;background-color:#f3f7fd;list-style:none}.main-page .guide-flow-step{position:relative;display:flex;flex-direction:column;align-items:center;gap:4px;color:#3a5a83;font-size:10px;line-height:18px;text-align:center}.main-page .guide-flow-step+.guide-flow-step:before{position:absolute;top:8px;left:-5px;width:5px;height:5px;border-top:1px solid #9cb8da;border-right:1px solid #9cb8da;content:"";transform:rotate(45deg)}.main-page .guide-flow-number{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:1px solid #d5e4f8;border-radius:50%;background-color:#fff;color:#176ae5;font-size:11px;font-weight:600;line-height:1}.main-page{--app-font-family: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--el-font-family: var(--app-font-family);z-index:100003;position:fixed;color:#1f2329;font-family:var(--app-font-family)!important;font-size:14px;line-height:1.5715;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility}.main-page *,.main-page input,.main-page button,.main-page textarea{font-family:var(--app-font-family)!important;letter-spacing:0}.main-page .el-card,.main-page .el-tabs,.main-page .el-text,.main-page .el-button,.main-page .el-input,.main-page .el-input__inner,.main-page .el-input-number,.main-page .el-table{font-family:var(--app-font-family)!important}.main-page .overlay{position:fixed;top:0;left:0;right:0;bottom:0;z-index:1001}.main-page .el-card{border:0}.main-page .card-header{display:flex;justify-content:space-between;flex-direction:row;align-items:center;margin:0;padding:0;cursor:move}.main-page .card-header .title{font-size:14px;display:flex;align-items:center;justify-content:center;font-weight:500}.main-page .warning-icon{margin-left:5px}.main-page .zoom-icon{cursor:pointer}.main-page .zoom-icon.is-spaced{margin-left:8px}.main-page .minus{margin:5px 10px -10px 0}.main-page .compact-divider{margin:0}.main-page .demo-tabs{display:initial}.main-page .el-card__header{background-color:#1f71e0;color:#fff;padding:7px 10px 7px 16px;margin:0}.main-page .el-card__body{padding:0 16px 20px}.main-page .el-tabs__nav-wrap:after{height:1px}.main-page .el-tabs__active-bar{background-color:#176ae5}.main-page .el-tabs__item{font-size:13px;height:34px}.main-page .el-tabs__item.is-top{font-weight:400;color:#4e5969;padding:0 8px 0 12px}.main-page .el-tabs__item.is-active{font-weight:500;color:#176ae5;padding:0 8px 0 12px}.main-page .script-home{padding-top:2px}.main-page .announcement-board{box-sizing:border-box;margin:2px 0 10px;padding:8px 10px;border:1px solid #bae0ff;border-radius:6px;background-color:#e6f4ff}.main-page .announcement-heading{display:flex;align-items:center;gap:6px;margin-bottom:4px;color:#0958d9;font-size:12px;font-weight:600;line-height:20px}.main-page .announcement-heading:before{content:"";width:6px;height:6px;flex:0 0 auto;border-radius:50%;background-color:#1677ff}.main-page .announcement-list{display:grid;gap:3px;margin:0;padding:0;list-style:none}.main-page .announcement-item{color:#1f2329;font-size:12px;line-height:20px;word-break:break-word}.main-page .log .el-text{font-weight:400;white-space:normal}.main-page .log-time{font-weight:400}.main-page .log-action-link{color:#176ae5;cursor:pointer;text-decoration:none}.main-page .log-action-link:hover{color:#409eff;text-decoration:underline}.main-page .log-divider{margin:0}.main-page .token-input,.main-page .question-list{font-size:12px}.main-page .token-label{border-radius:0}.main-page .question_table{width:625px}.main-page .answer-legend{display:flex;flex-wrap:wrap;align-items:center;gap:6px 16px;padding:10px 2px 8px;font-size:11px;line-height:18px}.main-page .answer-legend>span{display:inline-flex;align-items:center;gap:5px}.main-page .answer-legend>span:before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}.main-page .answer-result{line-height:1.7;overflow-wrap:anywhere}.main-page .answer-result--success{color:#15803d}.main-page .answer-result--searching{color:#a15c08}.main-page .answer-result--pending{color:#697586}.main-page .answer-result--error{color:#c73e38}.main-page .setting{margin-top:-8px;font-size:14px}.main-page .setting-section-title{font-size:13px}.main-page .setting-checkbox{margin-bottom:6px}.main-page .setting-number{margin-top:6px}.main-page .setting .el-form-item{margin-bottom:0} ');
(function (vue, pinia, rxjs, md5, ElementPlus) {
'use strict';
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
/*! Element Plus Icons Vue v2.3.2 */
var _sfc_main118 = /* @__PURE__ */ vue.defineComponent({
name: "FullScreen",
__name: "full-screen",
setup(__props) {
return (_ctx, _cache) => (vue.openBlock(), vue.createElementBlock("svg", {
xmlns: "http://www.w3.org/2000/svg",
viewBox: "0 0 1024 1024"
}, [
vue.createElementVNode("path", {
fill: "currentColor",
d: "m160 96.064 192 .192a32 32 0 0 1 0 64l-192-.192V352a32 32 0 0 1-64 0V96h64zm0 831.872V928H96V672a32 32 0 1 1 64 0v191.936l192-.192a32 32 0 1 1 0 64zM864 96.064V96h64v256a32 32 0 1 1-64 0V160.064l-192 .192a32 32 0 1 1 0-64zm0 831.872-192-.192a32 32 0 0 1 0-64l192 .192V672a32 32 0 1 1 64 0v256h-64z"
})
]));
}
}), full_screen_default = _sfc_main118;
var _sfc_main169 = /* @__PURE__ */ vue.defineComponent({
name: "Minus",
__name: "minus",
setup(__props) {
return (_ctx, _cache) => (vue.openBlock(), vue.createElementBlock("svg", {
xmlns: "http://www.w3.org/2000/svg",
viewBox: "0 0 1024 1024"
}, [
vue.createElementVNode("path", {
fill: "currentColor",
d: "M128 544h768a32 32 0 1 0 0-64H128a32 32 0 0 0 0 64"
})
]));
}
}), minus_default = _sfc_main169;
var _sfc_main288 = /* @__PURE__ */ vue.defineComponent({
name: "Warning",
__name: "warning",
setup(__props) {
return (_ctx, _cache) => (vue.openBlock(), vue.createElementBlock("svg", {
xmlns: "http://www.w3.org/2000/svg",
viewBox: "0 0 1024 1024"
}, [
vue.createElementVNode("path", {
fill: "currentColor",
d: "M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m0 832a384 384 0 0 0 0-768 384 384 0 0 0 0 768m48-176a48 48 0 1 1-96 0 48 48 0 0 1 96 0m-48-464a32 32 0 0 1 32 32v288a32 32 0 0 1-64 0V288a32 32 0 0 1 32-32"
})
]));
}
}), warning_default = _sfc_main288;
var _GM_getResourceText = /* @__PURE__ */ (() => typeof GM_getResourceText != "undefined" ? GM_getResourceText : void 0)();
var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)();
var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)();
var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)();
const getScriptInfo = () => {
return {
name: _GM_info.script.name,
author: _GM_info.script.author,
namespace: _GM_info.script.namespace,
version: _GM_info.script.version,
description: _GM_info.script.description
};
};
const OPEN_API_BASE_URL = "https://search.tikuhai.com";
const EXACT_SEARCH_PATH = "/api/open/v1/search/exact";
const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
const apiError = (message, status) => ({
success: false,
data: null,
error: {
code: status && status >= 400 ? `HTTP_${status}` : "CLIENT_ERROR",
message,
details: null
},
meta: null
});
const parseExactResponse = (payload, _status, _question) => {
if (!isObject(payload) || typeof payload.success !== "boolean" || !("data" in payload) || !("error" in payload) || !("meta" in payload)) {
return apiError("接口响应格式不正确");
}
return payload;
};
const getAnswerValues = (response, question) => {
var _a;
if (!response.success || !response.data || !Array.isArray(response.data.items))
return null;
const items = response.data.items;
const stem = ((_a = question.searchText) == null ? void 0 : _a.stem) ?? question.title;
const matches = items.length === 1 ? items : items.filter((item) => item.stem === stem);
const answer = matches.length === 1 ? matches[0].answer : null;
if (!answer || !Array.isArray(answer.values) || answer.values.length === 0)
return null;
if (answer.kind === "none" || answer.kind === "missing")
return null;
return answer.values;
};
function extractRawTextFromElement(element) {
if (!element)
return "";
const visit = (node) => {
if (node.nodeType === 3 || node.nodeType === 4)
return node.nodeValue || "";
if (node.nodeType === 11) {
return Array.from(node.childNodes).map(visit).join("");
}
if (node.nodeType !== 1)
return "";
const current = node;
if (current.tagName.toLowerCase() === "img") {
const src = current.getAttribute("src");
return src == null ? "
" : `
`;
}
if (current.tagName.toLowerCase() === "br")
return "\n";
return Array.from(current.childNodes).map(visit).join("");
};
return visit(element);
}
function extractRawTextFromHtml(html, ownerDocument) {
if (html == null || html === "")
return "";
const documentRef = ownerDocument || (typeof document !== "undefined" ? document : null);
if (!documentRef)
return html;
const container = documentRef.createElement("div");
container.innerHTML = html;
return extractRawTextFromElement(container);
}
function stripOptionLabel(text) {
if (text == null)
return "";
return text.replace(/^\s*[A-Za-z]\s*[..、::\]\)]/, "");
}
function stripQuestionTypeLabel(text) {
if (text == null)
return "";
return text.replace(/^\s*[【\[]\s*(?:(?:单选|多选|单项选择|多项选择|选择|判断|填空|简答|问答|论述|计算|分析|阅读理解|完形填空|排序|连线|其它|其他)题|名词解释)\s*[】\]]/u, "");
}
function extractJudgementToken(element, text = "") {
const values = [text];
if (element) {
const collect = (candidate) => values.push(
candidate.getAttribute("aria-label") || "",
candidate.getAttribute("data") || "",
candidate.getAttribute("data-value") || "",
candidate.getAttribute("value") || "",
candidate.getAttribute("title") || "",
candidate.textContent || ""
);
collect(element);
if (typeof element.querySelectorAll === "function") {
element.querySelectorAll("[aria-label],[data],[data-value],[value],[title]").forEach(collect);
}
}
const normalize2 = (value) => value.trim().toLowerCase().replace(/^[a-z0-9]+[..、::\]\)]\s*/i, "").replace(/[\s..、::()[\]{}]/g, "");
for (const value of values) {
const normalized = normalize2(value);
if (/^(?:正确|是|对|√|t|true|right)$/.test(normalized) || /^(?:正确|是|对)选择$/.test(normalized))
return "对";
if (/^(?:错误|否|错|×|x|f|false|wrong)$/.test(normalized) || /^(?:错误|否|错)选择$/.test(normalized))
return "错";
}
return "";
}
const VIDEO_QUIZ_INPUTS = '.ans-videoquiz-opt input[type="radio"], .ans-videoquiz-opt input[type="checkbox"], input[name="ans-videoquiz-opt"][type="radio"], input[name="ans-videoquiz-opt"][type="checkbox"]';
const parseVideoQuiz = (root, inputs) => {
const titles = root.querySelectorAll(".tkItem_title, .ans-videoquiz-title");
if (titles.length !== 1 || !inputs.length || new Set(inputs.map((input) => `${input.type}:${input.name}`)).size !== 1)
return null;
const stem = stripQuestionTypeLabel(extractRawTextFromElement(titles[0]));
const rows = inputs.map((input) => input.closest("label, .ans-videoquiz-opt") || input);
const texts = rows.map((row, index) => {
const raw = extractRawTextFromElement(row);
const withoutLabel = stripOptionLabel(raw);
return raw === withoutLabel ? raw.replace(new RegExp(`^\\s*${String.fromCharCode(65 + index)}\\s+(?=\\S)`), "") : withoutLabel;
});
if (!stem.trim() || texts.some((text) => !text.trim()))
return null;
const judgements = texts.map((text) => extractJudgementToken(null, text));
const isJudgement = inputs[0].type === "radio" && texts.length === 2 && judgements.includes("对") && judgements.includes("错");
const type = inputs[0].type === "checkbox" ? "1" : isJudgement ? "3" : "0";
return {
inputs,
texts,
judgements,
question: {
element: root,
type,
title: stem,
options: Object.fromEntries(texts.map((text, index) => [text, rows[index]])),
optionsText: texts,
searchText: { stem, options: isJudgement ? judgements : texts },
answer: [],
answerStatus: "searching",
workType: "video",
refer: root.ownerDocument.URL
}
};
};
const normalize = (text, document2) => stripOptionLabel(extractRawTextFromHtml(text, document2)).replace(/
/g, "$1").replace(/[\s\u00a0]+/g, "").toLowerCase();
const similarity = (left, right) => {
if (!left || !right)
return 0;
if (left === right)
return 100;
const a = Array.from(left);
const b = Array.from(right);
const distances = Array.from({ length: a.length + 1 }, (_, i) => i);
b.forEach((character, row) => {
let diagonal = distances[0];
distances[0] = row + 1;
a.forEach((other, column) => {
const above = distances[column + 1];
distances[column + 1] = character === other ? diagonal : Math.min(diagonal, above, distances[column]) + 1;
diagonal = above;
});
});
return Math.round((1 - distances[a.length] / Math.max(a.length, b.length)) * 100);
};
const matchVideoQuizAnswers = (parsed, answers, threshold = 85) => {
if (!answers.length || parsed.question.type !== "1" && answers.length !== 1)
return null;
const document2 = parsed.question.element.ownerDocument;
const texts = parsed.texts.map((text) => normalize(text, document2));
const minimum = Number.isFinite(threshold) ? Math.max(0, Math.min(100, threshold)) : 85;
const selected = /* @__PURE__ */ new Set();
for (const answer of answers) {
if (!["string", "number", "boolean"].includes(typeof answer))
return null;
const raw = String(answer).trim();
const normalized = normalize(raw, document2);
if (!normalized)
return null;
let matches;
if (parsed.question.type === "3") {
const token = extractJudgementToken(null, raw);
matches = token ? parsed.judgements.flatMap((value, index) => value === token ? [index] : []) : [];
} else {
matches = texts.flatMap((text, index) => text === normalized ? [index] : []);
}
const letter = raw.match(/^([A-Z])[..、::\]\)]?$/i);
if (letter) {
const index = letter[1].toUpperCase().charCodeAt(0) - 65;
if (index < parsed.inputs.length)
matches = [.../* @__PURE__ */ new Set([...matches, index])];
}
if (!matches.length && !letter && parsed.question.type !== "3") {
const scores = texts.map((text) => similarity(text, normalized));
const best = Math.max(...scores);
if (best >= minimum && best > 0)
matches = scores.flatMap((score, index) => score === best ? [index] : []);
}
if (matches.length !== 1)
return null;
selected.add(parsed.inputs[matches[0]]);
}
return selected.size ? selected : null;
};
const applyVideoQuizAnswers = (parsed, selected, isCurrent) => {
if (parsed.inputs.some((input) => input.disabled && input.checked !== selected.has(input)))
return false;
for (const input of parsed.inputs) {
if (!isCurrent() || !input.isConnected)
return false;
const wanted = selected.has(input);
if (input.checked === wanted)
continue;
if (input.type === "checkbox" || wanted)
input.click();
}
return isCurrent() && parsed.inputs.every((input) => input.checked === selected.has(input));
};
const VIDEO_QUIZ_SETTING = "视频弹题自动处理";
const OPTION_INPUTS = VIDEO_QUIZ_INPUTS;
const visible = (element) => {
if (!element.isConnected || !element.getClientRects().length)
return false;
const view = element.ownerDocument.defaultView;
for (let current = element; current; current = current.parentElement) {
const style2 = view == null ? void 0 : view.getComputedStyle(current);
if (current.hidden || current.getAttribute("aria-hidden") === "true" || (style2 == null ? void 0 : style2.display) === "none" || (style2 == null ? void 0 : style2.visibility) === "hidden" || (style2 == null ? void 0 : style2.visibility) === "collapse")
return false;
}
return true;
};
const usable = (element) => !!element && visible(element) && !element.matches(':disabled, [disabled], [aria-disabled="true"], .disabled');
const findQuiz = (document2) => Array.from(document2.querySelectorAll(".ans-videoquiz")).find(visible);
const questionKey = (root) => JSON.stringify([
root.getAttribute("data-question-id"),
Array.from(root.querySelectorAll(".tkItem_title, .ans-videoquiz-title, .ans-videoquiz-opt")).map((element) => [
element.textContent,
Array.from(element.querySelectorAll("img")).map((image) => image.getAttribute("src"))
]),
Array.from(root.querySelectorAll(OPTION_INPUTS)).map((input) => [input.name, input.type, input.value])
]);
const watchVideoQuiz = (options) => {
const { document: document2, signal } = options;
let state;
let disposed = false;
let timer2;
let poll;
let observer;
const cancelPending = () => {
if (timer2 !== void 0)
clearTimeout(timer2);
timer2 = void 0;
};
const cancelSearch = () => {
var _a;
const request = state == null ? void 0 : state.request;
if (request && ((_a = state == null ? void 0 : state.question) == null ? void 0 : _a.answerStatus) === "searching" && !signal.aborted && options.isCurrent()) {
state.question.answerStatus = "error";
state.question.answer = ["题目已关闭或查询已取消"];
}
if (state)
state.request = void 0;
request == null ? void 0 : request.abort();
};
const dispose = () => {
if (disposed)
return;
disposed = true;
cancelPending();
cancelSearch();
if (poll !== void 0)
clearInterval(poll);
observer == null ? void 0 : observer.disconnect();
document2.removeEventListener("click", trackManualClick, true);
signal.removeEventListener("abort", dispose);
state = void 0;
};
const current = () => !disposed && !signal.aborted && options.isCurrent();
const hasActiveQuiz = () => current() && !!findQuiz(document2);
const actionFor = (quiz) => {
if (quiz.blocked || quiz.request)
return;
if (quiz.submittedAt === null && usable(quiz.root.querySelector("#videoquiz-submit")))
return "submit";
if (!quiz.continued && usable(quiz.root.querySelector("#videoquiz-continue")))
return "continue";
};
const scan = () => {
if (!current())
return dispose();
const root = findQuiz(document2);
if (!root) {
const released = !!state;
cancelSearch();
state = void 0;
cancelPending();
if (released)
options.onRelease();
return;
}
const key = questionKey(root);
if (!state || state.root !== root || state.key !== key) {
cancelPending();
cancelSearch();
state = { root, key, submittedAt: null, continued: false, blocked: false, warned: false };
options.log("检测到视频弹题,将使用已配置题库搜索答案。", "primary");
}
if (!options.isEnabled()) {
cancelPending();
if (state.request && state.question) {
state.question.answerStatus = "error";
state.question.answer = ["已关闭视频弹题自动处理"];
}
return cancelSearch();
}
const quiz = state;
const action = actionFor(quiz);
if (!action) {
if (quiz.submittedAt !== null && Date.now() - quiz.submittedAt >= 15e3 && !quiz.warned) {
quiz.warned = true;
options.log("视频弹题仍未关闭,请检查提交结果并手动处理;不会重复提交。", "warning");
}
return cancelPending();
}
if (timer2 !== void 0)
return;
const delay = options.delayMs();
timer2 = setTimeout(async () => {
var _a, _b, _c, _d, _e;
timer2 = void 0;
if (!current())
return dispose();
if (!options.isEnabled() || state !== quiz || findQuiz(document2) !== root || questionKey(root) !== key || actionFor(quiz) !== action)
return scan();
let activeRequest;
let awaitingAnswer = false;
try {
if (action === "submit") {
const inputs = Array.from(root.querySelectorAll(OPTION_INPUTS)).filter((input) => visible(input.closest(".ans-videoquiz-opt, label") || input));
const editable = root.querySelectorAll('textarea, select, input[type="text"], input:not([type]), [contenteditable="true"]');
const parsed = Array.from(editable).some(visible) ? null : parseVideoQuiz(root, inputs);
if (!parsed || inputs.every((input) => input.disabled)) {
quiz.blocked = true;
options.log("视频弹题题干或选项无法识别,请手动作答后继续播放。", "warning");
return;
}
const request = new AbortController();
activeRequest = request;
quiz.request = request;
const stillCurrent = () => current() && options.isEnabled() && state === quiz && quiz.request === request && !request.signal.aborted && quiz.submittedAt === null && findQuiz(document2) === root && questionKey(root) === key;
const question = quiz.question = ((_a = options.publishQuestion) == null ? void 0 : _a.call(options, parsed.question)) ?? parsed.question;
options.log("正在搜索视频弹题答案…", "primary");
awaitingAnswer = true;
const response = await options.search(question, { signal: request.signal, isCurrent: stillCurrent });
awaitingAnswer = false;
if (!stillCurrent())
return scan();
const answers = getAnswerValues(response, question);
const selected = (answers == null ? void 0 : answers.length) ? matchVideoQuizAnswers(parsed, answers, (_b = options.similarityThreshold) == null ? void 0 : _b.call(options)) : null;
if (!selected) {
quiz.request = void 0;
quiz.blocked = true;
question.answerStatus = "error";
question.answer = [(answers == null ? void 0 : answers.length) ? "题库答案无法完整且唯一地匹配视频选项,请手动作答" : ((_c = response.error) == null ? void 0 : _c.message) || (((_d = response.meta) == null ? void 0 : _d.billing_result) === "exhausted" ? "题库额度已耗尽,请检查密钥和余额" : "题库未返回可用答案,请手动作答")];
options.log(`视频弹题搜题未完成,点击查看原因`, "warning");
return;
}
quiz.applying = true;
const applied = applyVideoQuizAnswers(parsed, selected, stillCurrent);
quiz.applying = false;
if (!stillCurrent())
return scan();
if (!applied) {
quiz.request = void 0;
quiz.blocked = true;
question.answerStatus = "error";
question.answer = ["题库答案已找到,但页面选项无法完整选择,请手动作答"];
options.log("视频弹题选项未完整填写,已停止自动提交。", "warning");
return;
}
question.answer = answers;
question.answerStatus = "success";
quiz.request = void 0;
const submit = root.querySelector("#videoquiz-submit");
if (!current() || !options.isEnabled() || findQuiz(document2) !== root || questionKey(root) !== key || !usable(submit)) {
quiz.blocked = true;
options.log("已填写视频弹题答案,但提交按钮不可用,请手动确认。", "warning");
return scan();
}
quiz.submittedAt = Date.now();
submit.click();
options.log("已按题库答案提交视频弹题,等待页面反馈。", "primary");
} else {
quiz.continued = true;
(_e = root.querySelector("#videoquiz-continue")) == null ? void 0 : _e.click();
}
} catch {
if (!current() || state !== quiz || !options.isEnabled() || findQuiz(document2) !== root || questionKey(root) !== key || awaitingAnswer && quiz.request !== activeRequest)
return scan();
quiz.request = void 0;
quiz.applying = false;
quiz.blocked = true;
if (quiz.question) {
quiz.question.answerStatus = "error";
quiz.question.answer = ["视频弹题搜题或填写失败,请手动处理"];
}
options.log("视频弹题自动处理失败,请在播放器内手动完成。", "danger");
}
scan();
}, Number.isFinite(delay) ? Math.max(1e3, delay) : 3e3);
};
const trackManualClick = (event) => {
var _a, _b;
if (!current() || !state)
return;
const element = event.target;
if (state.request && !state.applying && ((_a = element == null ? void 0 : element.closest) == null ? void 0 : _a.call(element, '.ans-videoquiz-opt, input[name="ans-videoquiz-opt"]')) && state.root.contains(element)) {
if (state.question) {
state.question.answerStatus = "error";
state.question.answer = ["已切换为手动作答"];
}
state.blocked = true;
cancelSearch();
}
const button = (_b = element == null ? void 0 : element.closest) == null ? void 0 : _b.call(element, "#videoquiz-submit, #videoquiz-continue");
if (!button || !state.root.contains(button))
return;
if (button.id === "videoquiz-submit")
state.submittedAt = Date.now();
else
state.continued = true;
if (state.request && state.question) {
state.question.answerStatus = "error";
state.question.answer = ["已切换为手动作答"];
}
state.blocked = false;
cancelSearch();
cancelPending();
};
if (current()) {
observer = new MutationObserver(scan);
observer.observe(document2.documentElement, {
childList: true,
subtree: true,
characterData: true,
attributes: true,
attributeFilter: ["class", "style", "hidden", "disabled", "aria-hidden", "aria-disabled"]
});
poll = setInterval(scan, 500);
document2.addEventListener("click", trackManualClick, true);
signal.addEventListener("abort", dispose, { once: true });
scan();
}
return { hasActiveQuiz, dispose };
};
const CONFIG_STORAGE_KEY = "config";
const LEGACY_CONFIG_STORAGE_KEY = "globalConfig";
const isPlainObject = (value) => {
return Object.prototype.toString.call(value) === "[object Object]";
};
const cloneLatestValue = (value) => {
if (Array.isArray(value)) {
return value.map((item) => cloneLatestValue(item));
}
if (isPlainObject(value)) {
return Object.fromEntries(
Object.entries(value).map(([key, item]) => [key, cloneLatestValue(item)])
);
}
return value;
};
const getItemName = (item) => {
if (!isPlainObject(item) || typeof item.name !== "string")
return null;
return item.name;
};
const mergeArrayByLatestFields = (latestArray, storedArray) => {
const canMergeByName = latestArray.every((item) => getItemName(item) !== null);
if (!canMergeByName)
return cloneLatestValue(storedArray.length ? storedArray : latestArray);
return latestArray.map((latestItem) => {
const latestName = getItemName(latestItem);
const storedItem = storedArray.find((item) => getItemName(item) === latestName);
return mergeByLatestFields(latestItem, storedItem);
});
};
const mergeByLatestFields = (latestValue, storedValue) => {
if (Array.isArray(latestValue)) {
return mergeArrayByLatestFields(latestValue, Array.isArray(storedValue) ? storedValue : []);
}
if (isPlainObject(latestValue)) {
const storedObject = isPlainObject(storedValue) ? storedValue : {};
return Object.fromEntries(
Object.entries(latestValue).map(([key, latestItem]) => [
key,
mergeByLatestFields(latestItem, storedObject[key])
])
);
}
return storedValue === void 0 ? cloneLatestValue(latestValue) : storedValue;
};
const migrateConfig = (storedConfig, defaultConfig) => {
storedConfig = cloneLatestValue(storedConfig);
if (isPlainObject(storedConfig) && isPlainObject(storedConfig.platformParams)) {
const cx = storedConfig.platformParams.cx;
if (isPlainObject(cx) && Array.isArray(cx.parts)) {
const chapter = cx.parts.find((part) => isPlainObject(part) && part.name === "章节设置");
if (isPlainObject(chapter) && Array.isArray(chapter.params)) {
const old = chapter.params.find((param) => isPlainObject(param) && param.name === "视频弹题自动处理(题库搜索)") ?? chapter.params.find((param) => isPlainObject(param) && param.name === "视频弹题自动处理(随机作答)");
const latest = chapter.params.some((param) => isPlainObject(param) && param.name === VIDEO_QUIZ_SETTING);
if (isPlainObject(old) && !latest)
old.name = VIDEO_QUIZ_SETTING;
}
}
}
const mergedConfig = mergeByLatestFields(defaultConfig, storedConfig);
mergedConfig.version = defaultConfig.version;
mergedConfig.queryApis[0].url = defaultConfig.queryApis[0].url;
mergedConfig.otherParams.params[0].min = defaultConfig.otherParams.params[0].min;
return mergedConfig;
};
const getLocalStorageConfig = () => {
try {
return localStorage.getItem(CONFIG_STORAGE_KEY) || "";
} catch (error) {
console.error(error);
return "";
}
};
const setLocalStorageConfig = (serializedConfig) => {
try {
localStorage.setItem(CONFIG_STORAGE_KEY, serializedConfig);
} catch (error) {
console.error(error);
}
};
const getStoredConfigText = () => {
const gmConfig = _GM_getValue(CONFIG_STORAGE_KEY);
if (typeof gmConfig === "string" && gmConfig)
return gmConfig;
const localConfig = getLocalStorageConfig();
if (localConfig)
return localConfig;
const legacyConfig = _GM_getValue(LEGACY_CONFIG_STORAGE_KEY);
if (typeof legacyConfig === "string" && legacyConfig)
return legacyConfig;
return "";
};
const persistConfig = (config2) => {
const serializedConfig = JSON.stringify(config2);
_GM_setValue(CONFIG_STORAGE_KEY, serializedConfig);
_GM_setValue(LEGACY_CONFIG_STORAGE_KEY, serializedConfig);
setLocalStorageConfig(serializedConfig);
};
const useConfigStore = pinia.defineStore("configStore", {
state: () => {
const scriptInfo = getScriptInfo();
const defaultConfig = {
version: scriptInfo.version,
isMinus: false,
position: {
x: "800px",
y: "200px"
},
menuIndex: "0",
platformName: "cx",
platformParams: {
cx: {
name: "超星网课助手",
parts: [
{
name: "章节设置",
params: [
// {
// name: '视频速率',
// value: 1,
// type: 'number',
// },
{
name: "章节作业自动提交",
value: true,
type: "boolean"
},
{
name: "是否自动下一章节",
value: true,
type: "boolean"
},
{
name: "只答题,不做其他",
value: false,
type: "boolean"
},
{
name: VIDEO_QUIZ_SETTING,
value: true,
type: "boolean"
}
]
},
{
name: "考试设置",
params: [{
name: "是否自动切换",
value: true,
type: "boolean"
}]
}
]
},
zhs: {
name: "智慧树网课助手",
parts: [{
name: "答题设置",
params: [{
name: "是否自动切换",
value: true,
type: "boolean"
}]
}]
},
unknown: {
name: "未知平台",
parts: [{
name: "答题设置",
params: [{
name: "是否自动切换",
value: true,
type: "boolean"
}]
}]
}
},
// 没答案自动选择
otherParams: {
name: "其他参数",
params: [
// {
// name: '没答案随机选择',
// value: true,
// type: 'boolean',
// },
{
name: "切换、答题间隔,单位秒",
value: 3,
type: "number",
min: 1
},
{
name: "正确率达到多少自动提交",
value: 85,
type: "number"
},
{
name: "答案相似度超过多少选择",
value: 85,
type: "number",
min: 0,
max: 100,
step: 1
}
]
},
queryApis: [
{
name: "题库",
token: "",
url: "https://search.tikuhai.com"
}
]
};
let globalConfig = defaultConfig;
const storedConfig = getStoredConfigText();
if (storedConfig) {
try {
const parsedStoredConfig = JSON.parse(storedConfig);
globalConfig = migrateConfig(parsedStoredConfig, defaultConfig);
} catch (error) {
console.error(error);
}
}
persistConfig(globalConfig);
return globalConfig;
},
actions: {}
});
const formatDateTime = (dt) => {
dt.getFullYear();
dt.getMonth() + 1;
dt.getDate();
let hours = dt.getHours();
let minutes = dt.getMinutes();
let seconds = dt.getSeconds();
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
};
const pad = (n) => {
return n < 10 ? "0" + n : n.toString();
};
const getDateTime = () => {
let now = /* @__PURE__ */ new Date();
return formatDateTime(now);
};
const useLogStore = pinia.defineStore("logStore", {
state: () => ({
logList: []
}),
actions: {
addLog(message, type) {
const log = {
message,
time: getDateTime(),
type
};
this.logList.push(log);
}
}
});
const useQuestionStore = pinia.defineStore("questionStore", {
state: () => ({
questionList: []
}),
actions: {
addQuestion(question) {
this.questionList.push(question);
},
clearQuestion() {
this.questionList = [];
}
}
});
const _hoisted_1$6 = {
key: 0,
class: "announcement-board",
"aria-label": "公告"
};
const _hoisted_2$5 = { class: "announcement-heading" };
const _hoisted_3$3 = { class: "announcement-list" };
const _hoisted_4$3 = ["innerHTML"];
const _sfc_main$9 = /* @__PURE__ */ vue.defineComponent({
__name: "index",
props: {
title: { default: "公告" },
items: {}
},
setup(__props) {
return (_ctx, _cache) => {
return _ctx.items.length ? (vue.openBlock(), vue.createElementBlock("section", _hoisted_1$6, [
vue.createElementVNode("div", _hoisted_2$5, vue.toDisplayString(_ctx.title), 1),
vue.createElementVNode("div", _hoisted_3$3, [
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(_ctx.items, (item) => {
return vue.openBlock(), vue.createElementBlock("div", {
key: item.id,
class: "announcement-item",
innerHTML: item.content
}, null, 8, _hoisted_4$3);
}), 128))
])
])) : vue.createCommentVNode("", true);
};
}
});
const sleep = (second) => {
return new Promise((resolve) => setTimeout(resolve, second * 1e3));
};
const NOTICE_API_URL = "https://api.tikuhai.com/def/notice";
let noticeCache = null;
let noticeRequest = null;
const createNoticeHeaders = (referer) => {
const _self = _unsafeWindow;
const getCookie = typeof _self.getCookie === "function" ? _self.getCookie.bind(_self) : () => "";
return {
"Content-Type": "application/json",
"referer": referer,
"u": _self.uid || getCookie("UID") || getCookie("_uid") || "",
"t": Math.floor((/* @__PURE__ */ new Date()).getTime() / 1e3).toString()
};
};
const withScriptInfoParams = (url2) => {
const scriptInfo = getScriptInfo();
const requestUrl = new URL(url2);
requestUrl.searchParams.set("s", scriptInfo.author || "OnlineCourseHelper");
requestUrl.searchParams.set("v", scriptInfo.version);
return requestUrl.toString();
};
const getAnswer = async (question, context = {}) => {
var _a, _b, _c;
const isCurrent = () => {
var _a2, _b2;
return !((_a2 = context.signal) == null ? void 0 : _a2.aborted) && (((_b2 = context.isCurrent) == null ? void 0 : _b2.call(context)) ?? true);
};
if (!isCurrent())
return apiError("请求已取消");
const configStore = useConfigStore();
const queryApi = configStore.queryApis[0];
const consumerToken = ((_a = queryApi == null ? void 0 : queryApi.token) == null ? void 0 : _a.trim()) || "";
const apiBaseUrl = (queryApi == null ? void 0 : queryApi.url) || OPEN_API_BASE_URL;
let url2;
try {
const baseUrl = new URL(apiBaseUrl);
if (proxyLoc(baseUrl).protocol !== "https:")
return apiError("开放 API 地址必须使用 HTTPS");
url2 = withScriptInfoParams(new URL(EXACT_SEARCH_PATH, baseUrl).toString());
} catch {
return apiError("开放 API 地址不正确,请检查配置");
}
const headers = {
"Content-Type": "application/json"
};
if (consumerToken)
headers["X-Consumer-Token"] = consumerToken;
const data = JSON.stringify({
"stem": ((_b = question.searchText) == null ? void 0 : _b.stem) ?? question.title,
"options": ((_c = question.searchText) == null ? void 0 : _c.options) ?? question.optionsText,
"type": question.type
});
if (!context.skipDelay)
await sleep(configStore.otherParams.params[0].value);
if (!isCurrent())
return apiError("请求已取消");
return new Promise((resolve) => {
var _a2, _b2;
let settled = false;
let request;
const finish = (result) => {
var _a3;
if (settled)
return;
settled = true;
(_a3 = context.signal) == null ? void 0 : _a3.removeEventListener("abort", abort);
resolve(result);
};
const abort = () => {
finish(apiError("请求已取消"));
request == null ? void 0 : request.abort();
};
(_a2 = context.signal) == null ? void 0 : _a2.addEventListener("abort", abort, { once: true });
if (!isCurrent())
return abort();
try {
request = _GM_xmlhttpRequest({
url: url2,
method: "POST",
headers,
data,
timeout: 15e3,
onload: (response) => {
var _a3;
if (!isCurrent())
return finish(apiError("请求已取消"));
let result;
try {
result = parseExactResponse(JSON.parse(response.responseText), response.status);
} catch (e) {
result = apiError("接口响应解析出错");
}
if (response.status === 401 && ((_a3 = result.error) == null ? void 0 : _a3.code) === "CLIENT_ERROR") {
result = apiError("题库鉴权失败,请填写或检查卡密后刷新重试", response.status);
}
finish(result);
},
onerror: () => finish(apiError("请求出错")),
ontimeout: () => finish(apiError("请求超时")),
onabort: () => finish(apiError("请求已取消"))
});
if ((_b2 = context.signal) == null ? void 0 : _b2.aborted)
request == null ? void 0 : request.abort();
} catch {
finish(apiError("请求出错"));
}
});
};
const toAnnouncementItems = (notice) => {
const content = notice.trim();
return content ? [{ id: "remote-notice", content }] : [];
};
const getNotice = async () => {
if (noticeCache) {
return noticeCache.items;
}
if (noticeRequest) {
return noticeRequest;
}
noticeRequest = new Promise((resolve) => {
_GM_xmlhttpRequest({
url: withScriptInfoParams(NOTICE_API_URL),
method: "GET",
headers: createNoticeHeaders(proxyLoc(window.location).href),
timeout: 1e4,
onload: (response) => {
try {
const apiResponse = JSON.parse(response.responseText);
resolve(apiResponse.code === 1 ? toAnnouncementItems(apiResponse.data || "") : []);
} catch (e) {
resolve([]);
}
},
onerror: () => resolve([]),
ontimeout: () => resolve([])
});
}).then((items) => {
noticeCache = {
items
};
return items;
}).finally(() => {
noticeRequest = null;
});
return noticeRequest;
};
const _hoisted_1$5 = { class: "script-home" };
const ANSWER_TAB_NAME$1 = "1";
const _sfc_main$8 = /* @__PURE__ */ vue.defineComponent({
__name: "index",
props: {
logList: {}
},
setup(__props) {
const configStore = useConfigStore();
const announcements = vue.ref([]);
vue.onMounted(async () => {
announcements.value = await getNotice();
});
const handleLogClick = (event) => {
const target = event.target;
const actionElement = target == null ? void 0 : target.closest('[data-log-action="show-answer-tab"]');
if (!actionElement)
return;
event.preventDefault();
configStore.menuIndex = ANSWER_TAB_NAME$1;
};
return (_ctx, _cache) => {
const _component_el_text = vue.resolveComponent("el-text");
const _component_el_divider = vue.resolveComponent("el-divider");
const _component_el_scrollbar = vue.resolveComponent("el-scrollbar");
return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$5, [
vue.createVNode(_sfc_main$9, { items: announcements.value }, null, 8, ["items"]),
vue.createVNode(_component_el_scrollbar, {
always: "",
class: "log",
"max-height": "230px",
onClick: handleLogClick
}, {
default: vue.withCtx(() => [
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(_ctx.logList, (item, index) => {
return vue.openBlock(), vue.createElementBlock("div", { key: index }, [
vue.createVNode(_component_el_text, {
class: "log-time",
size: "small",
type: "info"
}, {
default: vue.withCtx(() => [
vue.createTextVNode(vue.toDisplayString(item.time), 1)
]),
_: 2
}, 1024),
vue.createVNode(_component_el_text, null, {
default: vue.withCtx(() => [
vue.createTextVNode(" ")
]),
_: 1
}),
vue.createVNode(_component_el_text, {
type: item.type ? item.type : "primary",
size: "small",
innerHTML: item.message
}, null, 8, ["type", "innerHTML"]),
vue.createVNode(_component_el_divider, {
class: "log-divider",
"border-style": "dashed"
})
]);
}), 128))
]),
_: 1
})
]);
};
}
});
const _hoisted_1$4 = { class: "setting" };
const _hoisted_2$4 = { class: "setting-section-title" };
const _hoisted_3$2 = { class: "setting-section-title" };
const _sfc_main$7 = /* @__PURE__ */ vue.defineComponent({
__name: "index",
props: {
globalConfig: {}
},
setup(__props) {
return (_ctx, _cache) => {
const _component_el_divider = vue.resolveComponent("el-divider");
const _component_el_checkbox = vue.resolveComponent("el-checkbox");
const _component_el_input_number = vue.resolveComponent("el-input-number");
const _component_el_form_item = vue.resolveComponent("el-form-item");
return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$4, [
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(_ctx.globalConfig.platformParams[_ctx.globalConfig.platformName].parts, (part) => {
return vue.openBlock(), vue.createElementBlock("div", {
key: part.name
}, [
vue.createVNode(_component_el_divider, { "border-style": "dashed" }, {
default: vue.withCtx(() => [
vue.createElementVNode("span", _hoisted_2$4, vue.toDisplayString(part.name), 1)
]),
_: 2
}, 1024),
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(part.params, (param) => {
return vue.openBlock(), vue.createElementBlock(vue.Fragment, {
key: param.name
}, [
param.type === "boolean" ? (vue.openBlock(), vue.createBlock(_component_el_checkbox, {
key: 0,
class: "setting-checkbox",
modelValue: param.value,
"onUpdate:modelValue": ($event) => param.value = $event,
label: param.name,
size: "small"
}, null, 8, ["modelValue", "onUpdate:modelValue", "label"])) : (vue.openBlock(), vue.createBlock(_component_el_form_item, {
key: 1,
label: param.name,
required: ""
}, {
default: vue.withCtx(() => [
vue.createVNode(_component_el_input_number, {
modelValue: param.value,
"onUpdate:modelValue": ($event) => param.value = $event,
min: param.min ?? 3e3,
max: param.max,
step: param.step ?? 1e3,
"controls-position": "right",
size: "small"
}, null, 8, ["modelValue", "onUpdate:modelValue", "min", "max", "step"])
]),
_: 2
}, 1032, ["label"]))
], 64);
}), 128))
]);
}), 128)),
vue.createElementVNode("div", null, [
vue.createVNode(_component_el_divider, { "border-style": "dashed" }, {
default: vue.withCtx(() => [
vue.createElementVNode("span", _hoisted_3$2, vue.toDisplayString(_ctx.globalConfig.otherParams.name), 1)
]),
_: 1
}),
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(_ctx.globalConfig.otherParams.params, (item) => {
return vue.openBlock(), vue.createElementBlock(vue.Fragment, {
key: item.name
}, [
item.type === "boolean" ? (vue.openBlock(), vue.createBlock(_component_el_checkbox, {
key: 0,
modelValue: item.value,
"onUpdate:modelValue": ($event) => item.value = $event,
size: "small"
}, null, 8, ["modelValue", "onUpdate:modelValue"])) : (vue.openBlock(), vue.createBlock(_component_el_form_item, {
key: 1,
class: "setting-number",
label: item.name,
required: "",
size: "small"
}, {
default: vue.withCtx(() => [
vue.createVNode(_component_el_input_number, {
modelValue: item.value,
"onUpdate:modelValue": ($event) => item.value = $event,
min: item.min ?? 3,
max: item.max ?? 100,
step: item.step ?? 1,
size: "small"
}, null, 8, ["modelValue", "onUpdate:modelValue", "min", "max", "step"])
]),
_: 2
}, 1032, ["label"]))
], 64);
}), 128))
])
]);
};
}
});
const _hoisted_1$3 = { class: "question_table" };
const _hoisted_2$3 = /* @__PURE__ */ vue.createStaticVNode('有答案查询中等待中未找到 / 失败
', 1);
const _hoisted_3$1 = ["innerHTML"];
const _hoisted_4$2 = { key: 0 };
const _hoisted_5 = { key: 1 };
const _hoisted_6 = ["innerHTML"];
const _sfc_main$6 = /* @__PURE__ */ vue.defineComponent({
__name: "QuestionTable",
props: {
questionList: {}
},
setup(__props) {
const isTokenEditing = vue.ref(false);
const configStore = useConfigStore();
const getDisplayTitle = (question) => {
var _a;
return ((_a = question.searchText) == null ? void 0 : _a.stem) || question.title;
};
const getAnswerStatus = (question) => question.answerStatus ?? (question.answer.length ? "success" : "pending");
return (_ctx, _cache) => {
const _component_el_button = vue.resolveComponent("el-button");
const _component_el_input = vue.resolveComponent("el-input");
const _component_el_table_column = vue.resolveComponent("el-table-column");
const _component_el_table = vue.resolveComponent("el-table");
const _component_el_empty = vue.resolveComponent("el-empty");
return vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [
vue.createVNode(_component_el_input, {
class: "token-input",
modelValue: vue.unref(configStore).queryApis[0].token,
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => vue.unref(configStore).queryApis[0].token = $event),
type: isTokenEditing.value ? "text" : "password",
onFocus: _cache[1] || (_cache[1] = ($event) => isTokenEditing.value = true),
onBlur: _cache[2] || (_cache[2] = ($event) => isTokenEditing.value = false),
placeholder: "填写题库平台分配给你的密钥"
}, {
prepend: vue.withCtx(() => [
vue.createVNode(_component_el_button, { class: "token-label" }, {
default: vue.withCtx(() => [
vue.createTextVNode("密钥")
]),
_: 1
})
]),
_: 1
}, 8, ["modelValue", "type"]),
vue.withDirectives(vue.createElementVNode("div", _hoisted_1$3, [
_hoisted_2$3,
vue.createVNode(_component_el_table, {
stripe: "",
data: _ctx.questionList,
"max-height": "400",
class: "question-list"
}, {
default: vue.withCtx(() => [
vue.createVNode(_component_el_table_column, {
type: "index",
width: "40"
}),
vue.createVNode(_component_el_table_column, {
prop: "title",
label: "题目",
width: "370"
}, {
default: vue.withCtx((scope) => [
vue.createElementVNode("span", {
innerHTML: getDisplayTitle(scope.row)
}, null, 8, _hoisted_3$1)
]),
_: 1
}),
vue.createVNode(_component_el_table_column, {
prop: "answer",
label: "答案",
width: "215"
}, {
default: vue.withCtx((scope) => [
vue.createElementVNode("div", {
class: vue.normalizeClass(["answer-result", `answer-result--${getAnswerStatus(scope.row)}`])
}, [
getAnswerStatus(scope.row) === "pending" ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_4$2, "等待查询…")) : getAnswerStatus(scope.row) === "searching" ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_5, "正在查询答案…")) : (vue.openBlock(), vue.createElementBlock("div", {
key: 2,
innerHTML: scope.row.answer.join()
}, null, 8, _hoisted_6))
], 2)
]),
_: 1
})
]),
_: 1
}, 8, ["data"])
], 512), [
[vue.vShow, _ctx.questionList.length]
]),
vue.withDirectives(vue.createElementVNode("div", null, [
vue.createVNode(_component_el_empty, { description: "该页面无需答题" })
], 512), [
[vue.vShow, !_ctx.questionList.length]
])
], 64);
};
}
});
const _export_sfc = (sfc, props) => {
const target = sfc.__vccOpts || sfc;
for (const [key, val] of props) {
target[key] = val;
}
return target;
};
const _sfc_main$5 = {};
const _hoisted_1$2 = {
class: "guide-page",
"aria-label": "使用教程",
tabindex: "0"
};
const _hoisted_2$2 = /* @__PURE__ */ vue.createStaticVNode('01
脚本免费,题库单独收费
脚本开源,功能除接入的第三方题库外,完全免费,进入相应页面即可使用。题库资费由第三方题库开发者决定,与脚本作者无关。如不满意,可以自行接入自己的题库。
02
填写题库凭证
题库需要分配给你的密钥。填写后会自动保存,请依次操作:
- 1打开「答题」
- 2填写密钥
- 3刷新页面
', 2);
const _hoisted_4$1 = [
_hoisted_2$2
];
function _sfc_render$1(_ctx, _cache) {
return vue.openBlock(), vue.createElementBlock("section", _hoisted_1$2, _hoisted_4$1);
}
const Tutorial = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["render", _sfc_render$1]]);
const _sfc_main$4 = {};
const _hoisted_1$1 = {
class: "guide-page",
"aria-label": "使用协议",
tabindex: "0"
};
const _hoisted_2$1 = /* @__PURE__ */ vue.createStaticVNode('01
学习与研究用途
本脚本仅供学习和研究目的使用,并应在24小时内删除。脚本的使用不应违反任何法律法规及学术道德标准。
02
遵守法律法规
用户在使用脚本时,必须遵守所有适用的法律法规。任何由于使用脚本而引起的违法行为或不当行为,其产生的一切后果由用户自行承担。
03
自行评估使用风险
开发者不对用户使用脚本所产生的任何直接或间接后果负责。用户应自行评估使用脚本的风险,并对任何可能的负面影响承担全责。
04
声明目的
本声明的目的在于提醒用户注意相关法律法规与风险,确保用户在明智、合法的前提下使用脚本。
05
有疑问时停止使用
如用户在使用脚本的过程中有任何疑问,建议立即停止使用,并删除所有相关文件。
06
最终解释权
本免责声明的最终解释权归脚本开发者所有。
', 2);
const _hoisted_4 = [
_hoisted_2$1
];
function _sfc_render(_ctx, _cache) {
return vue.openBlock(), vue.createElementBlock("section", _hoisted_1$1, _hoisted_4);
}
const ScriptTip = /* @__PURE__ */ _export_sfc(_sfc_main$4, [["render", _sfc_render]]);
function isFunction(value) {
return typeof value === "function";
}
function hasLift(source) {
return isFunction(source === null || source === void 0 ? void 0 : source.lift);
}
function operate(init) {
return function(source) {
if (hasLift(source)) {
return source.lift(function(liftedSource) {
try {
return init(liftedSource, this);
} catch (err) {
this.error(err);
}
});
}
throw new TypeError("Unable to lift unknown Observable type");
};
}
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(d2, b2) {
d2.__proto__ = b2;
} || function(d2, b2) {
for (var p in b2)
if (Object.prototype.hasOwnProperty.call(b2, p))
d2[p] = b2[p];
};
return extendStatics(d, b);
};
function __extends(d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() {
this.constructor = d;
}
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) {
return value instanceof P ? value : new P(function(resolve) {
resolve(value);
});
}
return new (P || (P = Promise))(function(resolve, reject) {
function fulfilled(value) {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
}
function rejected(value) {
try {
step(generator["throw"](value));
} catch (e) {
reject(e);
}
}
function step(result) {
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
}
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() {
if (t[0] & 1)
throw t[1];
return t[1];
}, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() {
return this;
}), g;
function verb(n) {
return function(v) {
return step([n, v]);
};
}
function step(op) {
if (f)
throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _)
try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done)
return t;
if (y = 0, t)
op = [op[0] & 2, t.value];
switch (op[0]) {
case 0:
case 1:
t = op;
break;
case 4:
_.label++;
return { value: op[1], done: false };
case 5:
_.label++;
y = op[1];
op = [0];
continue;
case 7:
op = _.ops.pop();
_.trys.pop();
continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
_ = 0;
continue;
}
if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
_.label = op[1];
break;
}
if (op[0] === 6 && _.label < t[1]) {
_.label = t[1];
t = op;
break;
}
if (t && _.label < t[2]) {
_.label = t[2];
_.ops.push(op);
break;
}
if (t[2])
_.ops.pop();
_.trys.pop();
continue;
}
op = body.call(thisArg, _);
} catch (e) {
op = [6, e];
y = 0;
} finally {
f = t = 0;
}
if (op[0] & 5)
throw op[1];
return { value: op[0] ? op[1] : void 0, done: true };
}
}
function __values(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m)
return m.call(o);
if (o && typeof o.length === "number")
return {
next: function() {
if (o && i >= o.length)
o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
}
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m)
return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done)
ar.push(r.value);
} catch (error) {
e = { error };
} finally {
try {
if (r && !r.done && (m = i["return"]))
m.call(i);
} finally {
if (e)
throw e.error;
}
}
return ar;
}
function __spreadArray(to, from2, pack) {
if (pack || arguments.length === 2)
for (var i = 0, l = from2.length, ar; i < l; i++) {
if (ar || !(i in from2)) {
if (!ar)
ar = Array.prototype.slice.call(from2, 0, i);
ar[i] = from2[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from2));
}
function __await(v) {
return this instanceof __await ? (this.v = v, this) : new __await(v);
}
function __asyncGenerator(thisArg, _arguments, generator) {
if (!Symbol.asyncIterator)
throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function() {
return this;
}, i;
function awaitReturn(f) {
return function(v) {
return Promise.resolve(v).then(f, reject);
};
}
function verb(n, f) {
if (g[n]) {
i[n] = function(v) {
return new Promise(function(a, b) {
q.push([n, v, a, b]) > 1 || resume(n, v);
});
};
if (f)
i[n] = f(i[n]);
}
}
function resume(n, v) {
try {
step(g[n](v));
} catch (e) {
settle(q[0][3], e);
}
}
function step(r) {
r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r);
}
function fulfill(value) {
resume("next", value);
}
function reject(value) {
resume("throw", value);
}
function settle(f, v) {
if (f(v), q.shift(), q.length)
resume(q[0][0], q[0][1]);
}
}
function __asyncValues(o) {
if (!Symbol.asyncIterator)
throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function() {
return this;
}, i);
function verb(n) {
i[n] = o[n] && function(v) {
return new Promise(function(resolve, reject) {
v = o[n](v), settle(resolve, reject, v.done, v.value);
});
};
}
function settle(resolve, reject, d, v) {
Promise.resolve(v).then(function(v2) {
resolve({ value: v2, done: d });
}, reject);
}
}
typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
var isArrayLike = function(x) {
return x && typeof x.length === "number" && typeof x !== "function";
};
function isPromise(value) {
return isFunction(value === null || value === void 0 ? void 0 : value.then);
}
function createErrorClass(createImpl) {
var _super = function(instance) {
Error.call(instance);
instance.stack = new Error().stack;
};
var ctorFunc = createImpl(_super);
ctorFunc.prototype = Object.create(Error.prototype);
ctorFunc.prototype.constructor = ctorFunc;
return ctorFunc;
}
var UnsubscriptionError = createErrorClass(function(_super) {
return function UnsubscriptionErrorImpl(errors) {
_super(this);
this.message = errors ? errors.length + " errors occurred during unsubscription:\n" + errors.map(function(err, i) {
return i + 1 + ") " + err.toString();
}).join("\n ") : "";
this.name = "UnsubscriptionError";
this.errors = errors;
};
});
function arrRemove(arr, item) {
if (arr) {
var index = arr.indexOf(item);
0 <= index && arr.splice(index, 1);
}
}
var Subscription = function() {
function Subscription2(initialTeardown) {
this.initialTeardown = initialTeardown;
this.closed = false;
this._parentage = null;
this._finalizers = null;
}
Subscription2.prototype.unsubscribe = function() {
var e_1, _a, e_2, _b;
var errors;
if (!this.closed) {
this.closed = true;
var _parentage = this._parentage;
if (_parentage) {
this._parentage = null;
if (Array.isArray(_parentage)) {
try {
for (var _parentage_1 = __values(_parentage), _parentage_1_1 = _parentage_1.next(); !_parentage_1_1.done; _parentage_1_1 = _parentage_1.next()) {
var parent_1 = _parentage_1_1.value;
parent_1.remove(this);
}
} catch (e_1_1) {
e_1 = { error: e_1_1 };
} finally {
try {
if (_parentage_1_1 && !_parentage_1_1.done && (_a = _parentage_1.return))
_a.call(_parentage_1);
} finally {
if (e_1)
throw e_1.error;
}
}
} else {
_parentage.remove(this);
}
}
var initialFinalizer = this.initialTeardown;
if (isFunction(initialFinalizer)) {
try {
initialFinalizer();
} catch (e) {
errors = e instanceof UnsubscriptionError ? e.errors : [e];
}
}
var _finalizers = this._finalizers;
if (_finalizers) {
this._finalizers = null;
try {
for (var _finalizers_1 = __values(_finalizers), _finalizers_1_1 = _finalizers_1.next(); !_finalizers_1_1.done; _finalizers_1_1 = _finalizers_1.next()) {
var finalizer = _finalizers_1_1.value;
try {
execFinalizer(finalizer);
} catch (err) {
errors = errors !== null && errors !== void 0 ? errors : [];
if (err instanceof UnsubscriptionError) {
errors = __spreadArray(__spreadArray([], __read(errors)), __read(err.errors));
} else {
errors.push(err);
}
}
}
} catch (e_2_1) {
e_2 = { error: e_2_1 };
} finally {
try {
if (_finalizers_1_1 && !_finalizers_1_1.done && (_b = _finalizers_1.return))
_b.call(_finalizers_1);
} finally {
if (e_2)
throw e_2.error;
}
}
}
if (errors) {
throw new UnsubscriptionError(errors);
}
}
};
Subscription2.prototype.add = function(teardown) {
var _a;
if (teardown && teardown !== this) {
if (this.closed) {
execFinalizer(teardown);
} else {
if (teardown instanceof Subscription2) {
if (teardown.closed || teardown._hasParent(this)) {
return;
}
teardown._addParent(this);
}
(this._finalizers = (_a = this._finalizers) !== null && _a !== void 0 ? _a : []).push(teardown);
}
}
};
Subscription2.prototype._hasParent = function(parent) {
var _parentage = this._parentage;
return _parentage === parent || Array.isArray(_parentage) && _parentage.includes(parent);
};
Subscription2.prototype._addParent = function(parent) {
var _parentage = this._parentage;
this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;
};
Subscription2.prototype._removeParent = function(parent) {
var _parentage = this._parentage;
if (_parentage === parent) {
this._parentage = null;
} else if (Array.isArray(_parentage)) {
arrRemove(_parentage, parent);
}
};
Subscription2.prototype.remove = function(teardown) {
var _finalizers = this._finalizers;
_finalizers && arrRemove(_finalizers, teardown);
if (teardown instanceof Subscription2) {
teardown._removeParent(this);
}
};
Subscription2.EMPTY = function() {
var empty = new Subscription2();
empty.closed = true;
return empty;
}();
return Subscription2;
}();
Subscription.EMPTY;
function isSubscription(value) {
return value instanceof Subscription || value && "closed" in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe);
}
function execFinalizer(finalizer) {
if (isFunction(finalizer)) {
finalizer();
} else {
finalizer.unsubscribe();
}
}
var config = {
onUnhandledError: null,
onStoppedNotification: null,
Promise: void 0,
useDeprecatedSynchronousErrorHandling: false,
useDeprecatedNextContext: false
};
var timeoutProvider = {
setTimeout: function(handler, timeout) {
var args = [];
for (var _i = 2; _i < arguments.length; _i++) {
args[_i - 2] = arguments[_i];
}
return setTimeout.apply(void 0, __spreadArray([handler, timeout], __read(args)));
},
clearTimeout: function(handle) {
return (clearTimeout)(handle);
},
delegate: void 0
};
function reportUnhandledError(err) {
timeoutProvider.setTimeout(function() {
{
throw err;
}
});
}
function noop() {
}
function errorContext(cb) {
{
cb();
}
}
var Subscriber = function(_super) {
__extends(Subscriber2, _super);
function Subscriber2(destination) {
var _this = _super.call(this) || this;
_this.isStopped = false;
if (destination) {
_this.destination = destination;
if (isSubscription(destination)) {
destination.add(_this);
}
} else {
_this.destination = EMPTY_OBSERVER;
}
return _this;
}
Subscriber2.create = function(next, error, complete) {
return new SafeSubscriber(next, error, complete);
};
Subscriber2.prototype.next = function(value) {
if (this.isStopped)
;
else {
this._next(value);
}
};
Subscriber2.prototype.error = function(err) {
if (this.isStopped)
;
else {
this.isStopped = true;
this._error(err);
}
};
Subscriber2.prototype.complete = function() {
if (this.isStopped)
;
else {
this.isStopped = true;
this._complete();
}
};
Subscriber2.prototype.unsubscribe = function() {
if (!this.closed) {
this.isStopped = true;
_super.prototype.unsubscribe.call(this);
this.destination = null;
}
};
Subscriber2.prototype._next = function(value) {
this.destination.next(value);
};
Subscriber2.prototype._error = function(err) {
try {
this.destination.error(err);
} finally {
this.unsubscribe();
}
};
Subscriber2.prototype._complete = function() {
try {
this.destination.complete();
} finally {
this.unsubscribe();
}
};
return Subscriber2;
}(Subscription);
var _bind = Function.prototype.bind;
function bind(fn, thisArg) {
return _bind.call(fn, thisArg);
}
var ConsumerObserver = function() {
function ConsumerObserver2(partialObserver) {
this.partialObserver = partialObserver;
}
ConsumerObserver2.prototype.next = function(value) {
var partialObserver = this.partialObserver;
if (partialObserver.next) {
try {
partialObserver.next(value);
} catch (error) {
handleUnhandledError(error);
}
}
};
ConsumerObserver2.prototype.error = function(err) {
var partialObserver = this.partialObserver;
if (partialObserver.error) {
try {
partialObserver.error(err);
} catch (error) {
handleUnhandledError(error);
}
} else {
handleUnhandledError(err);
}
};
ConsumerObserver2.prototype.complete = function() {
var partialObserver = this.partialObserver;
if (partialObserver.complete) {
try {
partialObserver.complete();
} catch (error) {
handleUnhandledError(error);
}
}
};
return ConsumerObserver2;
}();
var SafeSubscriber = function(_super) {
__extends(SafeSubscriber2, _super);
function SafeSubscriber2(observerOrNext, error, complete) {
var _this = _super.call(this) || this;
var partialObserver;
if (isFunction(observerOrNext) || !observerOrNext) {
partialObserver = {
next: observerOrNext !== null && observerOrNext !== void 0 ? observerOrNext : void 0,
error: error !== null && error !== void 0 ? error : void 0,
complete: complete !== null && complete !== void 0 ? complete : void 0
};
} else {
var context_1;
if (_this && config.useDeprecatedNextContext) {
context_1 = Object.create(observerOrNext);
context_1.unsubscribe = function() {
return _this.unsubscribe();
};
partialObserver = {
next: observerOrNext.next && bind(observerOrNext.next, context_1),
error: observerOrNext.error && bind(observerOrNext.error, context_1),
complete: observerOrNext.complete && bind(observerOrNext.complete, context_1)
};
} else {
partialObserver = observerOrNext;
}
}
_this.destination = new ConsumerObserver(partialObserver);
return _this;
}
return SafeSubscriber2;
}(Subscriber);
function handleUnhandledError(error) {
{
reportUnhandledError(error);
}
}
function defaultErrorHandler(err) {
throw err;
}
var EMPTY_OBSERVER = {
closed: true,
next: noop,
error: defaultErrorHandler,
complete: noop
};
var observable = function() {
return typeof Symbol === "function" && Symbol.observable || "@@observable";
}();
function identity(x) {
return x;
}
function pipeFromArray(fns) {
if (fns.length === 0) {
return identity;
}
if (fns.length === 1) {
return fns[0];
}
return function piped(input) {
return fns.reduce(function(prev, fn) {
return fn(prev);
}, input);
};
}
var Observable = function() {
function Observable2(subscribe) {
if (subscribe) {
this._subscribe = subscribe;
}
}
Observable2.prototype.lift = function(operator) {
var observable2 = new Observable2();
observable2.source = this;
observable2.operator = operator;
return observable2;
};
Observable2.prototype.subscribe = function(observerOrNext, error, complete) {
var _this = this;
var subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);
errorContext(function() {
var _a = _this, operator = _a.operator, source = _a.source;
subscriber.add(operator ? operator.call(subscriber, source) : source ? _this._subscribe(subscriber) : _this._trySubscribe(subscriber));
});
return subscriber;
};
Observable2.prototype._trySubscribe = function(sink) {
try {
return this._subscribe(sink);
} catch (err) {
sink.error(err);
}
};
Observable2.prototype.forEach = function(next, promiseCtor) {
var _this = this;
promiseCtor = getPromiseCtor(promiseCtor);
return new promiseCtor(function(resolve, reject) {
var subscriber = new SafeSubscriber({
next: function(value) {
try {
next(value);
} catch (err) {
reject(err);
subscriber.unsubscribe();
}
},
error: reject,
complete: resolve
});
_this.subscribe(subscriber);
});
};
Observable2.prototype._subscribe = function(subscriber) {
var _a;
return (_a = this.source) === null || _a === void 0 ? void 0 : _a.subscribe(subscriber);
};
Observable2.prototype[observable] = function() {
return this;
};
Observable2.prototype.pipe = function() {
var operations = [];
for (var _i = 0; _i < arguments.length; _i++) {
operations[_i] = arguments[_i];
}
return pipeFromArray(operations)(this);
};
Observable2.prototype.toPromise = function(promiseCtor) {
var _this = this;
promiseCtor = getPromiseCtor(promiseCtor);
return new promiseCtor(function(resolve, reject) {
var value;
_this.subscribe(function(x) {
return value = x;
}, function(err) {
return reject(err);
}, function() {
return resolve(value);
});
});
};
Observable2.create = function(subscribe) {
return new Observable2(subscribe);
};
return Observable2;
}();
function getPromiseCtor(promiseCtor) {
var _a;
return (_a = promiseCtor !== null && promiseCtor !== void 0 ? promiseCtor : config.Promise) !== null && _a !== void 0 ? _a : Promise;
}
function isObserver(value) {
return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);
}
function isSubscriber(value) {
return value && value instanceof Subscriber || isObserver(value) && isSubscription(value);
}
function isInteropObservable(input) {
return isFunction(input[observable]);
}
function isAsyncIterable(obj) {
return Symbol.asyncIterator && isFunction(obj === null || obj === void 0 ? void 0 : obj[Symbol.asyncIterator]);
}
function createInvalidObservableTypeError(input) {
return new TypeError("You provided " + (input !== null && typeof input === "object" ? "an invalid object" : "'" + input + "'") + " where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.");
}
function getSymbolIterator() {
if (typeof Symbol !== "function" || !Symbol.iterator) {
return "@@iterator";
}
return Symbol.iterator;
}
var iterator = getSymbolIterator();
function isIterable(input) {
return isFunction(input === null || input === void 0 ? void 0 : input[iterator]);
}
function readableStreamLikeToAsyncGenerator(readableStream) {
return __asyncGenerator(this, arguments, function readableStreamLikeToAsyncGenerator_1() {
var reader, _a, value, done;
return __generator(this, function(_b) {
switch (_b.label) {
case 0:
reader = readableStream.getReader();
_b.label = 1;
case 1:
_b.trys.push([1, , 9, 10]);
_b.label = 2;
case 2:
return [4, __await(reader.read())];
case 3:
_a = _b.sent(), value = _a.value, done = _a.done;
if (!done)
return [3, 5];
return [4, __await(void 0)];
case 4:
return [2, _b.sent()];
case 5:
return [4, __await(value)];
case 6:
return [4, _b.sent()];
case 7:
_b.sent();
return [3, 2];
case 8:
return [3, 10];
case 9:
reader.releaseLock();
return [7];
case 10:
return [2];
}
});
});
}
function isReadableStreamLike(obj) {
return isFunction(obj === null || obj === void 0 ? void 0 : obj.getReader);
}
function innerFrom(input) {
if (input instanceof Observable) {
return input;
}
if (input != null) {
if (isInteropObservable(input)) {
return fromInteropObservable(input);
}
if (isArrayLike(input)) {
return fromArrayLike(input);
}
if (isPromise(input)) {
return fromPromise(input);
}
if (isAsyncIterable(input)) {
return fromAsyncIterable(input);
}
if (isIterable(input)) {
return fromIterable(input);
}
if (isReadableStreamLike(input)) {
return fromReadableStreamLike(input);
}
}
throw createInvalidObservableTypeError(input);
}
function fromInteropObservable(obj) {
return new Observable(function(subscriber) {
var obs = obj[observable]();
if (isFunction(obs.subscribe)) {
return obs.subscribe(subscriber);
}
throw new TypeError("Provided object does not correctly implement Symbol.observable");
});
}
function fromArrayLike(array) {
return new Observable(function(subscriber) {
for (var i = 0; i < array.length && !subscriber.closed; i++) {
subscriber.next(array[i]);
}
subscriber.complete();
});
}
function fromPromise(promise) {
return new Observable(function(subscriber) {
promise.then(function(value) {
if (!subscriber.closed) {
subscriber.next(value);
subscriber.complete();
}
}, function(err) {
return subscriber.error(err);
}).then(null, reportUnhandledError);
});
}
function fromIterable(iterable) {
return new Observable(function(subscriber) {
var e_1, _a;
try {
for (var iterable_1 = __values(iterable), iterable_1_1 = iterable_1.next(); !iterable_1_1.done; iterable_1_1 = iterable_1.next()) {
var value = iterable_1_1.value;
subscriber.next(value);
if (subscriber.closed) {
return;
}
}
} catch (e_1_1) {
e_1 = { error: e_1_1 };
} finally {
try {
if (iterable_1_1 && !iterable_1_1.done && (_a = iterable_1.return))
_a.call(iterable_1);
} finally {
if (e_1)
throw e_1.error;
}
}
subscriber.complete();
});
}
function fromAsyncIterable(asyncIterable) {
return new Observable(function(subscriber) {
process(asyncIterable, subscriber).catch(function(err) {
return subscriber.error(err);
});
});
}
function fromReadableStreamLike(readableStream) {
return fromAsyncIterable(readableStreamLikeToAsyncGenerator(readableStream));
}
function process(asyncIterable, subscriber) {
var asyncIterable_1, asyncIterable_1_1;
var e_2, _a;
return __awaiter(this, void 0, void 0, function() {
var value, e_2_1;
return __generator(this, function(_b) {
switch (_b.label) {
case 0:
_b.trys.push([0, 5, 6, 11]);
asyncIterable_1 = __asyncValues(asyncIterable);
_b.label = 1;
case 1:
return [4, asyncIterable_1.next()];
case 2:
if (!(asyncIterable_1_1 = _b.sent(), !asyncIterable_1_1.done))
return [3, 4];
value = asyncIterable_1_1.value;
subscriber.next(value);
if (subscriber.closed) {
return [2];
}
_b.label = 3;
case 3:
return [3, 1];
case 4:
return [3, 11];
case 5:
e_2_1 = _b.sent();
e_2 = { error: e_2_1 };
return [3, 11];
case 6:
_b.trys.push([6, , 9, 10]);
if (!(asyncIterable_1_1 && !asyncIterable_1_1.done && (_a = asyncIterable_1.return)))
return [3, 8];
return [4, _a.call(asyncIterable_1)];
case 7:
_b.sent();
_b.label = 8;
case 8:
return [3, 10];
case 9:
if (e_2)
throw e_2.error;
return [7];
case 10:
return [7];
case 11:
subscriber.complete();
return [2];
}
});
});
}
function createOperatorSubscriber(destination, onNext, onComplete, onError, onFinalize) {
return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);
}
var OperatorSubscriber = function(_super) {
__extends(OperatorSubscriber2, _super);
function OperatorSubscriber2(destination, onNext, onComplete, onError, onFinalize, shouldUnsubscribe) {
var _this = _super.call(this, destination) || this;
_this.onFinalize = onFinalize;
_this.shouldUnsubscribe = shouldUnsubscribe;
_this._next = onNext ? function(value) {
try {
onNext(value);
} catch (err) {
destination.error(err);
}
} : _super.prototype._next;
_this._error = onError ? function(err) {
try {
onError(err);
} catch (err2) {
destination.error(err2);
} finally {
this.unsubscribe();
}
} : _super.prototype._error;
_this._complete = onComplete ? function() {
try {
onComplete();
} catch (err) {
destination.error(err);
} finally {
this.unsubscribe();
}
} : _super.prototype._complete;
return _this;
}
OperatorSubscriber2.prototype.unsubscribe = function() {
var _a;
if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {
var closed_1 = this.closed;
_super.prototype.unsubscribe.call(this);
!closed_1 && ((_a = this.onFinalize) === null || _a === void 0 ? void 0 : _a.call(this));
}
};
return OperatorSubscriber2;
}(Subscriber);
function executeSchedule(parentSubscription, scheduler, work, delay, repeat) {
if (delay === void 0) {
delay = 0;
}
if (repeat === void 0) {
repeat = false;
}
var scheduleSubscription = scheduler.schedule(function() {
work();
if (repeat) {
parentSubscription.add(this.schedule(null, delay));
} else {
this.unsubscribe();
}
}, delay);
parentSubscription.add(scheduleSubscription);
if (!repeat) {
return scheduleSubscription;
}
}
function map(project, thisArg) {
return operate(function(source, subscriber) {
var index = 0;
source.subscribe(createOperatorSubscriber(subscriber, function(value) {
subscriber.next(project.call(thisArg, value, index++));
}));
});
}
function mergeInternals(source, subscriber, project, concurrent, onBeforeNext, expand, innerSubScheduler, additionalFinalizer) {
var buffer = [];
var active = 0;
var index = 0;
var isComplete = false;
var checkComplete = function() {
if (isComplete && !buffer.length && !active) {
subscriber.complete();
}
};
var outerNext = function(value) {
return active < concurrent ? doInnerSub(value) : buffer.push(value);
};
var doInnerSub = function(value) {
expand && subscriber.next(value);
active++;
var innerComplete = false;
innerFrom(project(value, index++)).subscribe(createOperatorSubscriber(subscriber, function(innerValue) {
onBeforeNext === null || onBeforeNext === void 0 ? void 0 : onBeforeNext(innerValue);
if (expand) {
outerNext(innerValue);
} else {
subscriber.next(innerValue);
}
}, function() {
innerComplete = true;
}, void 0, function() {
if (innerComplete) {
try {
active--;
var _loop_1 = function() {
var bufferedValue = buffer.shift();
if (innerSubScheduler) {
executeSchedule(subscriber, innerSubScheduler, function() {
return doInnerSub(bufferedValue);
});
} else {
doInnerSub(bufferedValue);
}
};
while (buffer.length && active < concurrent) {
_loop_1();
}
checkComplete();
} catch (err) {
subscriber.error(err);
}
}
}));
};
source.subscribe(createOperatorSubscriber(subscriber, outerNext, function() {
isComplete = true;
checkComplete();
}));
return function() {
additionalFinalizer === null || additionalFinalizer === void 0 ? void 0 : additionalFinalizer();
};
}
function mergeMap(project, resultSelector, concurrent) {
if (concurrent === void 0) {
concurrent = Infinity;
}
if (isFunction(resultSelector)) {
return mergeMap(function(a, i) {
return map(function(b, ii) {
return resultSelector(a, b, i, ii);
})(innerFrom(project(a, i)));
}, concurrent);
} else if (typeof resultSelector === "number") {
concurrent = resultSelector;
}
return operate(function(source, subscriber) {
return mergeInternals(source, subscriber, project, concurrent);
});
}
function mergeAll(concurrent) {
if (concurrent === void 0) {
concurrent = Infinity;
}
return mergeMap(identity, concurrent);
}
function concatAll() {
return mergeAll(1);
}
function concatMap(project, resultSelector) {
return isFunction(resultSelector) ? mergeMap(project, resultSelector, 1) : mergeMap(project, 1);
}
class IframeUtils {
static getIframes(element) {
return Array.from(element.querySelectorAll("iframe"));
}
static getAllNestedIframes(element) {
const iframes = IframeUtils.getIframes(element);
if (iframes.length === 0) {
return rxjs.of([]);
} else {
return rxjs.from(iframes).pipe(
mergeMap(
(iframe) => new rxjs.Observable((subscriber) => {
if (iframe.contentDocument) {
const iframeDocument = iframe.contentDocument;
const nestedIframes = IframeUtils.getAllNestedIframes(iframeDocument.documentElement);
nestedIframes.subscribe((nestedIframes2) => {
subscriber.next([iframe, ...nestedIframes2]);
subscriber.complete();
});
} else {
subscriber.next([]);
subscriber.complete();
}
})
),
concatAll(),
rxjs.toArray()
);
}
}
}
var Typr$1 = {};
var Typr = {};
Typr.parse = function(buff) {
var bin = Typr._bin;
var data = new Uint8Array(buff);
var tag = bin.readASCII(data, 0, 4);
if (tag == "ttcf") {
var offset = 4;
bin.readUshort(data, offset);
offset += 2;
bin.readUshort(data, offset);
offset += 2;
var numF = bin.readUint(data, offset);
offset += 4;
var fnts = [];
for (var i = 0; i < numF; i++) {
var foff = bin.readUint(data, offset);
offset += 4;
fnts.push(Typr._readFont(data, foff));
}
return fnts;
} else
return [Typr._readFont(data, 0)];
};
Typr._readFont = function(data, offset) {
var bin = Typr._bin;
var ooff = offset;
bin.readFixed(data, offset);
offset += 4;
var numTables = bin.readUshort(data, offset);
offset += 2;
bin.readUshort(data, offset);
offset += 2;
bin.readUshort(data, offset);
offset += 2;
bin.readUshort(data, offset);
offset += 2;
var tags = [
"cmap",
"head",
"hhea",
"maxp",
"hmtx",
"name",
"OS/2",
"post",
//"cvt",
//"fpgm",
"loca",
"glyf",
"kern",
//"prep"
//"gasp"
"CFF ",
"GPOS",
"GSUB",
"SVG "
//"VORG",
];
var obj = { _data: data, _offset: ooff };
var tabs = {};
for (var i = 0; i < numTables; i++) {
var tag = bin.readASCII(data, offset, 4);
offset += 4;
bin.readUint(data, offset);
offset += 4;
var toffset = bin.readUint(data, offset);
offset += 4;
var length = bin.readUint(data, offset);
offset += 4;
tabs[tag] = { offset: toffset, length };
}
for (var i = 0; i < tags.length; i++) {
var t = tags[i];
if (tabs[t])
obj[t.trim()] = Typr[t.trim()].parse(data, tabs[t].offset, tabs[t].length, obj);
}
return obj;
};
Typr._tabOffset = function(data, tab, foff) {
var bin = Typr._bin;
var numTables = bin.readUshort(data, foff + 4);
var offset = foff + 12;
for (var i = 0; i < numTables; i++) {
var tag = bin.readASCII(data, offset, 4);
offset += 4;
bin.readUint(data, offset);
offset += 4;
var toffset = bin.readUint(data, offset);
offset += 4;
bin.readUint(data, offset);
offset += 4;
if (tag == tab)
return toffset;
}
return 0;
};
Typr._bin = {
readFixed: function(data, o) {
return (data[o] << 8 | data[o + 1]) + (data[o + 2] << 8 | data[o + 3]) / (256 * 256 + 4);
},
readF2dot14: function(data, o) {
var num = Typr._bin.readShort(data, o);
return num / 16384;
},
readInt: function(buff, p) {
return Typr._bin._view(buff).getInt32(p);
},
readInt8: function(buff, p) {
return Typr._bin._view(buff).getInt8(p);
},
readShort: function(buff, p) {
return Typr._bin._view(buff).getInt16(p);
},
readUshort: function(buff, p) {
return Typr._bin._view(buff).getUint16(p);
},
readUshorts: function(buff, p, len) {
var arr = [];
for (var i = 0; i < len; i++)
arr.push(Typr._bin.readUshort(buff, p + i * 2));
return arr;
},
readUint: function(buff, p) {
return Typr._bin._view(buff).getUint32(p);
},
readUint64: function(buff, p) {
return Typr._bin.readUint(buff, p) * (4294967295 + 1) + Typr._bin.readUint(buff, p + 4);
},
readASCII: function(buff, p, l) {
var s = "";
for (var i = 0; i < l; i++)
s += String.fromCharCode(buff[p + i]);
return s;
},
readUnicode: function(buff, p, l) {
var s = "";
for (var i = 0; i < l; i++) {
var c = buff[p++] << 8 | buff[p++];
s += String.fromCharCode(c);
}
return s;
},
_tdec: typeof window !== "undefined" && window["TextDecoder"] ? new window["TextDecoder"]() : null,
readUTF8: function(buff, p, l) {
var tdec = Typr._bin._tdec;
if (tdec && p == 0 && l == buff.length)
return tdec["decode"](buff);
return Typr._bin.readASCII(buff, p, l);
},
readBytes: function(buff, p, l) {
var arr = [];
for (var i = 0; i < l; i++)
arr.push(buff[p + i]);
return arr;
},
readASCIIArray: function(buff, p, l) {
var s = [];
for (var i = 0; i < l; i++)
s.push(String.fromCharCode(buff[p + i]));
return s;
},
_view: function(buff) {
return buff._dataView || (buff._dataView = buff.buffer ? new DataView(buff.buffer, buff.byteOffset, buff.byteLength) : new DataView(new Uint8Array(buff).buffer));
}
};
Typr._lctf = {};
Typr._lctf.parse = function(data, offset, length, font, subt) {
var bin = Typr._bin;
var obj = {};
var offset0 = offset;
bin.readFixed(data, offset);
offset += 4;
var offScriptList = bin.readUshort(data, offset);
offset += 2;
var offFeatureList = bin.readUshort(data, offset);
offset += 2;
var offLookupList = bin.readUshort(data, offset);
offset += 2;
obj.scriptList = Typr._lctf.readScriptList(data, offset0 + offScriptList);
obj.featureList = Typr._lctf.readFeatureList(data, offset0 + offFeatureList);
obj.lookupList = Typr._lctf.readLookupList(data, offset0 + offLookupList, subt);
return obj;
};
Typr._lctf.readLookupList = function(data, offset, subt) {
var bin = Typr._bin;
var offset0 = offset;
var obj = [];
var count = bin.readUshort(data, offset);
offset += 2;
for (var i = 0; i < count; i++) {
var noff = bin.readUshort(data, offset);
offset += 2;
var lut = Typr._lctf.readLookupTable(data, offset0 + noff, subt);
obj.push(lut);
}
return obj;
};
Typr._lctf.readLookupTable = function(data, offset, subt) {
var bin = Typr._bin;
var offset0 = offset;
var obj = { tabs: [] };
obj.ltype = bin.readUshort(data, offset);
offset += 2;
obj.flag = bin.readUshort(data, offset);
offset += 2;
var cnt = bin.readUshort(data, offset);
offset += 2;
var ltype = obj.ltype;
for (var i = 0; i < cnt; i++) {
var noff = bin.readUshort(data, offset);
offset += 2;
var tab = subt(data, ltype, offset0 + noff, obj);
obj.tabs.push(tab);
}
return obj;
};
Typr._lctf.numOfOnes = function(n) {
var num = 0;
for (var i = 0; i < 32; i++)
if ((n >>> i & 1) != 0)
num++;
return num;
};
Typr._lctf.readClassDef = function(data, offset) {
var bin = Typr._bin;
var obj = [];
var format = bin.readUshort(data, offset);
offset += 2;
if (format == 1) {
var startGlyph = bin.readUshort(data, offset);
offset += 2;
var glyphCount = bin.readUshort(data, offset);
offset += 2;
for (var i = 0; i < glyphCount; i++) {
obj.push(startGlyph + i);
obj.push(startGlyph + i);
obj.push(bin.readUshort(data, offset));
offset += 2;
}
}
if (format == 2) {
var count = bin.readUshort(data, offset);
offset += 2;
for (var i = 0; i < count; i++) {
obj.push(bin.readUshort(data, offset));
offset += 2;
obj.push(bin.readUshort(data, offset));
offset += 2;
obj.push(bin.readUshort(data, offset));
offset += 2;
}
}
return obj;
};
Typr._lctf.getInterval = function(tab, val) {
for (var i = 0; i < tab.length; i += 3) {
var start = tab[i], end = tab[i + 1];
tab[i + 2];
if (start <= val && val <= end)
return i;
}
return -1;
};
Typr._lctf.readCoverage = function(data, offset) {
var bin = Typr._bin;
var cvg = {};
cvg.fmt = bin.readUshort(data, offset);
offset += 2;
var count = bin.readUshort(data, offset);
offset += 2;
if (cvg.fmt == 1)
cvg.tab = bin.readUshorts(data, offset, count);
if (cvg.fmt == 2)
cvg.tab = bin.readUshorts(data, offset, count * 3);
return cvg;
};
Typr._lctf.coverageIndex = function(cvg, val) {
var tab = cvg.tab;
if (cvg.fmt == 1)
return tab.indexOf(val);
if (cvg.fmt == 2) {
var ind = Typr._lctf.getInterval(tab, val);
if (ind != -1)
return tab[ind + 2] + (val - tab[ind]);
}
return -1;
};
Typr._lctf.readFeatureList = function(data, offset) {
var bin = Typr._bin;
var offset0 = offset;
var obj = [];
var count = bin.readUshort(data, offset);
offset += 2;
for (var i = 0; i < count; i++) {
var tag = bin.readASCII(data, offset, 4);
offset += 4;
var noff = bin.readUshort(data, offset);
offset += 2;
var feat = Typr._lctf.readFeatureTable(data, offset0 + noff);
feat.tag = tag.trim();
obj.push(feat);
}
return obj;
};
Typr._lctf.readFeatureTable = function(data, offset) {
var bin = Typr._bin;
var offset0 = offset;
var feat = {};
var featureParams = bin.readUshort(data, offset);
offset += 2;
if (featureParams > 0) {
feat.featureParams = offset0 + featureParams;
}
var lookupCount = bin.readUshort(data, offset);
offset += 2;
feat.tab = [];
for (var i = 0; i < lookupCount; i++)
feat.tab.push(bin.readUshort(data, offset + 2 * i));
return feat;
};
Typr._lctf.readScriptList = function(data, offset) {
var bin = Typr._bin;
var offset0 = offset;
var obj = {};
var count = bin.readUshort(data, offset);
offset += 2;
for (var i = 0; i < count; i++) {
var tag = bin.readASCII(data, offset, 4);
offset += 4;
var noff = bin.readUshort(data, offset);
offset += 2;
obj[tag.trim()] = Typr._lctf.readScriptTable(data, offset0 + noff);
}
return obj;
};
Typr._lctf.readScriptTable = function(data, offset) {
var bin = Typr._bin;
var offset0 = offset;
var obj = {};
var defLangSysOff = bin.readUshort(data, offset);
offset += 2;
if (defLangSysOff > 0) {
obj["default"] = Typr._lctf.readLangSysTable(data, offset0 + defLangSysOff);
}
var langSysCount = bin.readUshort(data, offset);
offset += 2;
for (var i = 0; i < langSysCount; i++) {
var tag = bin.readASCII(data, offset, 4);
offset += 4;
var langSysOff = bin.readUshort(data, offset);
offset += 2;
obj[tag.trim()] = Typr._lctf.readLangSysTable(data, offset0 + langSysOff);
}
return obj;
};
Typr._lctf.readLangSysTable = function(data, offset) {
var bin = Typr._bin;
var obj = {};
bin.readUshort(data, offset);
offset += 2;
obj.reqFeature = bin.readUshort(data, offset);
offset += 2;
var featureCount = bin.readUshort(data, offset);
offset += 2;
obj.features = bin.readUshorts(data, offset, featureCount);
return obj;
};
Typr.CFF = {};
Typr.CFF.parse = function(data, offset, length) {
var bin = Typr._bin;
data = new Uint8Array(data.buffer, offset, length);
offset = 0;
data[offset];
offset++;
data[offset];
offset++;
data[offset];
offset++;
data[offset];
offset++;
var ninds = [];
offset = Typr.CFF.readIndex(data, offset, ninds);
var names = [];
for (var i = 0; i < ninds.length - 1; i++)
names.push(bin.readASCII(data, offset + ninds[i], ninds[i + 1] - ninds[i]));
offset += ninds[ninds.length - 1];
var tdinds = [];
offset = Typr.CFF.readIndex(data, offset, tdinds);
var topDicts = [];
for (var i = 0; i < tdinds.length - 1; i++)
topDicts.push(Typr.CFF.readDict(data, offset + tdinds[i], offset + tdinds[i + 1]));
offset += tdinds[tdinds.length - 1];
var topdict = topDicts[0];
var sinds = [];
offset = Typr.CFF.readIndex(data, offset, sinds);
var strings = [];
for (var i = 0; i < sinds.length - 1; i++)
strings.push(bin.readASCII(data, offset + sinds[i], sinds[i + 1] - sinds[i]));
offset += sinds[sinds.length - 1];
Typr.CFF.readSubrs(data, offset, topdict);
if (topdict.CharStrings) {
offset = topdict.CharStrings;
var sinds = [];
offset = Typr.CFF.readIndex(data, offset, sinds);
var cstr = [];
for (var i = 0; i < sinds.length - 1; i++)
cstr.push(bin.readBytes(data, offset + sinds[i], sinds[i + 1] - sinds[i]));
topdict.CharStrings = cstr;
}
if (topdict.ROS) {
offset = topdict.FDArray;
var fdind = [];
offset = Typr.CFF.readIndex(data, offset, fdind);
topdict.FDArray = [];
for (var i = 0; i < fdind.length - 1; i++) {
var dict = Typr.CFF.readDict(data, offset + fdind[i], offset + fdind[i + 1]);
Typr.CFF._readFDict(data, dict, strings);
topdict.FDArray.push(dict);
}
offset += fdind[fdind.length - 1];
offset = topdict.FDSelect;
topdict.FDSelect = [];
var fmt = data[offset];
offset++;
if (fmt == 3) {
var rns = bin.readUshort(data, offset);
offset += 2;
for (var i = 0; i < rns + 1; i++) {
topdict.FDSelect.push(bin.readUshort(data, offset), data[offset + 2]);
offset += 3;
}
} else
throw fmt;
}
if (topdict.Encoding)
topdict.Encoding = Typr.CFF.readEncoding(data, topdict.Encoding, topdict.CharStrings.length);
if (topdict.charset)
topdict.charset = Typr.CFF.readCharset(data, topdict.charset, topdict.CharStrings.length);
Typr.CFF._readFDict(data, topdict, strings);
return topdict;
};
Typr.CFF._readFDict = function(data, dict, ss) {
var offset;
if (dict.Private) {
offset = dict.Private[1];
dict.Private = Typr.CFF.readDict(data, offset, offset + dict.Private[0]);
if (dict.Private.Subrs)
Typr.CFF.readSubrs(data, offset + dict.Private.Subrs, dict.Private);
}
for (var p in dict)
if (["FamilyName", "FontName", "FullName", "Notice", "version", "Copyright"].indexOf(p) != -1)
dict[p] = ss[dict[p] - 426 + 35];
};
Typr.CFF.readSubrs = function(data, offset, obj) {
var bin = Typr._bin;
var gsubinds = [];
offset = Typr.CFF.readIndex(data, offset, gsubinds);
var bias, nSubrs = gsubinds.length;
if (nSubrs < 1240)
bias = 107;
else if (nSubrs < 33900)
bias = 1131;
else
bias = 32768;
obj.Bias = bias;
obj.Subrs = [];
for (var i = 0; i < gsubinds.length - 1; i++)
obj.Subrs.push(bin.readBytes(data, offset + gsubinds[i], gsubinds[i + 1] - gsubinds[i]));
};
Typr.CFF.tableSE = [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
91,
92,
93,
94,
95,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
96,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
109,
110,
0,
111,
112,
113,
114,
0,
115,
116,
117,
118,
119,
120,
121,
122,
0,
123,
0,
124,
125,
126,
127,
128,
129,
130,
131,
0,
132,
133,
0,
134,
135,
136,
137,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
138,
0,
139,
0,
0,
0,
0,
140,
141,
142,
143,
0,
0,
0,
0,
0,
144,
0,
0,
0,
145,
0,
0,
146,
147,
148,
149,
0,
0,
0,
0
];
Typr.CFF.glyphByUnicode = function(cff, code) {
for (var i = 0; i < cff.charset.length; i++)
if (cff.charset[i] == code)
return i;
return -1;
};
Typr.CFF.glyphBySE = function(cff, charcode) {
if (charcode < 0 || charcode > 255)
return -1;
return Typr.CFF.glyphByUnicode(cff, Typr.CFF.tableSE[charcode]);
};
Typr.CFF.readEncoding = function(data, offset, num) {
Typr._bin;
var array = [".notdef"];
var format = data[offset];
offset++;
if (format == 0) {
var nCodes = data[offset];
offset++;
for (var i = 0; i < nCodes; i++)
array.push(data[offset + i]);
} else
throw "error: unknown encoding format: " + format;
return array;
};
Typr.CFF.readCharset = function(data, offset, num) {
var bin = Typr._bin;
var charset = [".notdef"];
var format = data[offset];
offset++;
if (format == 0) {
for (var i = 0; i < num; i++) {
var first = bin.readUshort(data, offset);
offset += 2;
charset.push(first);
}
} else if (format == 1 || format == 2) {
while (charset.length < num) {
var first = bin.readUshort(data, offset);
offset += 2;
var nLeft = 0;
if (format == 1) {
nLeft = data[offset];
offset++;
} else {
nLeft = bin.readUshort(data, offset);
offset += 2;
}
for (var i = 0; i <= nLeft; i++) {
charset.push(first);
first++;
}
}
} else
throw "error: format: " + format;
return charset;
};
Typr.CFF.readIndex = function(data, offset, inds) {
var bin = Typr._bin;
var count = bin.readUshort(data, offset) + 1;
offset += 2;
var offsize = data[offset];
offset++;
if (offsize == 1)
for (var i = 0; i < count; i++)
inds.push(data[offset + i]);
else if (offsize == 2)
for (var i = 0; i < count; i++)
inds.push(bin.readUshort(data, offset + i * 2));
else if (offsize == 3)
for (var i = 0; i < count; i++)
inds.push(bin.readUint(data, offset + i * 3 - 1) & 16777215);
else if (count != 1)
throw "unsupported offset size: " + offsize + ", count: " + count;
offset += count * offsize;
return offset - 1;
};
Typr.CFF.getCharString = function(data, offset, o) {
var bin = Typr._bin;
var b0 = data[offset], b1 = data[offset + 1];
data[offset + 2];
data[offset + 3];
data[offset + 4];
var vs = 1;
var op = null, val = null;
if (b0 <= 20) {
op = b0;
vs = 1;
}
if (b0 == 12) {
op = b0 * 100 + b1;
vs = 2;
}
if (21 <= b0 && b0 <= 27) {
op = b0;
vs = 1;
}
if (b0 == 28) {
val = bin.readShort(data, offset + 1);
vs = 3;
}
if (29 <= b0 && b0 <= 31) {
op = b0;
vs = 1;
}
if (32 <= b0 && b0 <= 246) {
val = b0 - 139;
vs = 1;
}
if (247 <= b0 && b0 <= 250) {
val = (b0 - 247) * 256 + b1 + 108;
vs = 2;
}
if (251 <= b0 && b0 <= 254) {
val = -(b0 - 251) * 256 - b1 - 108;
vs = 2;
}
if (b0 == 255) {
val = bin.readInt(data, offset + 1) / 65535;
vs = 5;
}
o.val = val != null ? val : "o" + op;
o.size = vs;
};
Typr.CFF.readCharString = function(data, offset, length) {
var end = offset + length;
var bin = Typr._bin;
var arr = [];
while (offset < end) {
var b0 = data[offset], b1 = data[offset + 1];
data[offset + 2];
data[offset + 3];
data[offset + 4];
var vs = 1;
var op = null, val = null;
if (b0 <= 20) {
op = b0;
vs = 1;
}
if (b0 == 12) {
op = b0 * 100 + b1;
vs = 2;
}
if (b0 == 19 || b0 == 20) {
op = b0;
vs = 2;
}
if (21 <= b0 && b0 <= 27) {
op = b0;
vs = 1;
}
if (b0 == 28) {
val = bin.readShort(data, offset + 1);
vs = 3;
}
if (29 <= b0 && b0 <= 31) {
op = b0;
vs = 1;
}
if (32 <= b0 && b0 <= 246) {
val = b0 - 139;
vs = 1;
}
if (247 <= b0 && b0 <= 250) {
val = (b0 - 247) * 256 + b1 + 108;
vs = 2;
}
if (251 <= b0 && b0 <= 254) {
val = -(b0 - 251) * 256 - b1 - 108;
vs = 2;
}
if (b0 == 255) {
val = bin.readInt(data, offset + 1) / 65535;
vs = 5;
}
arr.push(val != null ? val : "o" + op);
offset += vs;
}
return arr;
};
Typr.CFF.readDict = function(data, offset, end) {
var bin = Typr._bin;
var dict = {};
var carr = [];
while (offset < end) {
var b0 = data[offset], b1 = data[offset + 1];
data[offset + 2];
data[offset + 3];
data[offset + 4];
var vs = 1;
var key = null, val = null;
if (b0 == 28) {
val = bin.readShort(data, offset + 1);
vs = 3;
}
if (b0 == 29) {
val = bin.readInt(data, offset + 1);
vs = 5;
}
if (32 <= b0 && b0 <= 246) {
val = b0 - 139;
vs = 1;
}
if (247 <= b0 && b0 <= 250) {
val = (b0 - 247) * 256 + b1 + 108;
vs = 2;
}
if (251 <= b0 && b0 <= 254) {
val = -(b0 - 251) * 256 - b1 - 108;
vs = 2;
}
if (b0 == 255) {
val = bin.readInt(data, offset + 1) / 65535;
vs = 5;
throw "unknown number";
}
if (b0 == 30) {
var nibs = [];
vs = 1;
while (true) {
var b = data[offset + vs];
vs++;
var nib0 = b >> 4, nib1 = b & 15;
if (nib0 != 15)
nibs.push(nib0);
if (nib1 != 15)
nibs.push(nib1);
if (nib1 == 15)
break;
}
var s = "";
var chars = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ".", "e", "e-", "reserved", "-", "endOfNumber"];
for (var i = 0; i < nibs.length; i++)
s += chars[nibs[i]];
val = parseFloat(s);
}
if (b0 <= 21) {
var keys = [
"version",
"Notice",
"FullName",
"FamilyName",
"Weight",
"FontBBox",
"BlueValues",
"OtherBlues",
"FamilyBlues",
"FamilyOtherBlues",
"StdHW",
"StdVW",
"escape",
"UniqueID",
"XUID",
"charset",
"Encoding",
"CharStrings",
"Private",
"Subrs",
"defaultWidthX",
"nominalWidthX"
];
key = keys[b0];
vs = 1;
if (b0 == 12) {
var keys = [
"Copyright",
"isFixedPitch",
"ItalicAngle",
"UnderlinePosition",
"UnderlineThickness",
"PaintType",
"CharstringType",
"FontMatrix",
"StrokeWidth",
"BlueScale",
"BlueShift",
"BlueFuzz",
"StemSnapH",
"StemSnapV",
"ForceBold",
0,
0,
"LanguageGroup",
"ExpansionFactor",
"initialRandomSeed",
"SyntheticBase",
"PostScript",
"BaseFontName",
"BaseFontBlend",
0,
0,
0,
0,
0,
0,
"ROS",
"CIDFontVersion",
"CIDFontRevision",
"CIDFontType",
"CIDCount",
"UIDBase",
"FDArray",
"FDSelect",
"FontName"
];
key = keys[b1];
vs = 2;
}
}
if (key != null) {
dict[key] = carr.length == 1 ? carr[0] : carr;
carr = [];
} else
carr.push(val);
offset += vs;
}
return dict;
};
Typr.cmap = {};
Typr.cmap.parse = function(data, offset, length) {
data = new Uint8Array(data.buffer, offset, length);
offset = 0;
var bin = Typr._bin;
var obj = {};
bin.readUshort(data, offset);
offset += 2;
var numTables = bin.readUshort(data, offset);
offset += 2;
var offs = [];
obj.tables = [];
for (var i = 0; i < numTables; i++) {
var platformID = bin.readUshort(data, offset);
offset += 2;
var encodingID = bin.readUshort(data, offset);
offset += 2;
var noffset = bin.readUint(data, offset);
offset += 4;
var id = "p" + platformID + "e" + encodingID;
var tind = offs.indexOf(noffset);
if (tind == -1) {
tind = obj.tables.length;
var subt;
offs.push(noffset);
var format = bin.readUshort(data, noffset);
if (format == 0)
subt = Typr.cmap.parse0(data, noffset);
else if (format == 4)
subt = Typr.cmap.parse4(data, noffset);
else if (format == 6)
subt = Typr.cmap.parse6(data, noffset);
else if (format == 12)
subt = Typr.cmap.parse12(data, noffset);
else
console.warn("unknown format: " + format, platformID, encodingID, noffset);
obj.tables.push(subt);
}
if (obj[id] != null)
throw "multiple tables for one platform+encoding";
obj[id] = tind;
}
return obj;
};
Typr.cmap.parse0 = function(data, offset) {
var bin = Typr._bin;
var obj = {};
obj.format = bin.readUshort(data, offset);
offset += 2;
var len = bin.readUshort(data, offset);
offset += 2;
bin.readUshort(data, offset);
offset += 2;
obj.map = [];
for (var i = 0; i < len - 6; i++)
obj.map.push(data[offset + i]);
return obj;
};
Typr.cmap.parse4 = function(data, offset) {
var bin = Typr._bin;
var offset0 = offset;
var obj = {};
obj.format = bin.readUshort(data, offset);
offset += 2;
var length = bin.readUshort(data, offset);
offset += 2;
bin.readUshort(data, offset);
offset += 2;
var segCountX2 = bin.readUshort(data, offset);
offset += 2;
var segCount = segCountX2 / 2;
obj.searchRange = bin.readUshort(data, offset);
offset += 2;
obj.entrySelector = bin.readUshort(data, offset);
offset += 2;
obj.rangeShift = bin.readUshort(data, offset);
offset += 2;
obj.endCount = bin.readUshorts(data, offset, segCount);
offset += segCount * 2;
offset += 2;
obj.startCount = bin.readUshorts(data, offset, segCount);
offset += segCount * 2;
obj.idDelta = [];
for (var i = 0; i < segCount; i++) {
obj.idDelta.push(bin.readShort(data, offset));
offset += 2;
}
obj.idRangeOffset = bin.readUshorts(data, offset, segCount);
offset += segCount * 2;
obj.glyphIdArray = [];
while (offset < offset0 + length) {
obj.glyphIdArray.push(bin.readUshort(data, offset));
offset += 2;
}
return obj;
};
Typr.cmap.parse6 = function(data, offset) {
var bin = Typr._bin;
var obj = {};
obj.format = bin.readUshort(data, offset);
offset += 2;
bin.readUshort(data, offset);
offset += 2;
bin.readUshort(data, offset);
offset += 2;
obj.firstCode = bin.readUshort(data, offset);
offset += 2;
var entryCount = bin.readUshort(data, offset);
offset += 2;
obj.glyphIdArray = [];
for (var i = 0; i < entryCount; i++) {
obj.glyphIdArray.push(bin.readUshort(data, offset));
offset += 2;
}
return obj;
};
Typr.cmap.parse12 = function(data, offset) {
var bin = Typr._bin;
var obj = {};
obj.format = bin.readUshort(data, offset);
offset += 2;
offset += 2;
bin.readUint(data, offset);
offset += 4;
bin.readUint(data, offset);
offset += 4;
var nGroups = bin.readUint(data, offset);
offset += 4;
obj.groups = [];
for (var i = 0; i < nGroups; i++) {
var off = offset + i * 12;
var startCharCode = bin.readUint(data, off + 0);
var endCharCode = bin.readUint(data, off + 4);
var startGlyphID = bin.readUint(data, off + 8);
obj.groups.push([startCharCode, endCharCode, startGlyphID]);
}
return obj;
};
Typr.glyf = {};
Typr.glyf.parse = function(data, offset, length, font) {
var obj = [];
for (var g = 0; g < font.maxp.numGlyphs; g++)
obj.push(null);
return obj;
};
Typr.glyf._parseGlyf = function(font, g) {
var bin = Typr._bin;
var data = font._data;
var offset = Typr._tabOffset(data, "glyf", font._offset) + font.loca[g];
if (font.loca[g] == font.loca[g + 1])
return null;
var gl = {};
gl.noc = bin.readShort(data, offset);
offset += 2;
gl.xMin = bin.readShort(data, offset);
offset += 2;
gl.yMin = bin.readShort(data, offset);
offset += 2;
gl.xMax = bin.readShort(data, offset);
offset += 2;
gl.yMax = bin.readShort(data, offset);
offset += 2;
if (gl.xMin >= gl.xMax || gl.yMin >= gl.yMax)
return null;
if (gl.noc > 0) {
gl.endPts = [];
for (var i = 0; i < gl.noc; i++) {
gl.endPts.push(bin.readUshort(data, offset));
offset += 2;
}
var instructionLength = bin.readUshort(data, offset);
offset += 2;
if (data.length - offset < instructionLength)
return null;
gl.instructions = bin.readBytes(data, offset, instructionLength);
offset += instructionLength;
var crdnum = gl.endPts[gl.noc - 1] + 1;
gl.flags = [];
for (var i = 0; i < crdnum; i++) {
var flag = data[offset];
offset++;
gl.flags.push(flag);
if ((flag & 8) != 0) {
var rep = data[offset];
offset++;
for (var j = 0; j < rep; j++) {
gl.flags.push(flag);
i++;
}
}
}
gl.xs = [];
for (var i = 0; i < crdnum; i++) {
var i8 = (gl.flags[i] & 2) != 0, same = (gl.flags[i] & 16) != 0;
if (i8) {
gl.xs.push(same ? data[offset] : -data[offset]);
offset++;
} else {
if (same)
gl.xs.push(0);
else {
gl.xs.push(bin.readShort(data, offset));
offset += 2;
}
}
}
gl.ys = [];
for (var i = 0; i < crdnum; i++) {
var i8 = (gl.flags[i] & 4) != 0, same = (gl.flags[i] & 32) != 0;
if (i8) {
gl.ys.push(same ? data[offset] : -data[offset]);
offset++;
} else {
if (same)
gl.ys.push(0);
else {
gl.ys.push(bin.readShort(data, offset));
offset += 2;
}
}
}
var x = 0, y = 0;
for (var i = 0; i < crdnum; i++) {
x += gl.xs[i];
y += gl.ys[i];
gl.xs[i] = x;
gl.ys[i] = y;
}
} else {
var ARG_1_AND_2_ARE_WORDS = 1 << 0;
var ARGS_ARE_XY_VALUES = 1 << 1;
var WE_HAVE_A_SCALE = 1 << 3;
var MORE_COMPONENTS = 1 << 5;
var WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6;
var WE_HAVE_A_TWO_BY_TWO = 1 << 7;
var WE_HAVE_INSTRUCTIONS = 1 << 8;
gl.parts = [];
var flags;
do {
flags = bin.readUshort(data, offset);
offset += 2;
var part = { m: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 }, p1: -1, p2: -1 };
gl.parts.push(part);
part.glyphIndex = bin.readUshort(data, offset);
offset += 2;
if (flags & ARG_1_AND_2_ARE_WORDS) {
var arg1 = bin.readShort(data, offset);
offset += 2;
var arg2 = bin.readShort(data, offset);
offset += 2;
} else {
var arg1 = bin.readInt8(data, offset);
offset++;
var arg2 = bin.readInt8(data, offset);
offset++;
}
if (flags & ARGS_ARE_XY_VALUES) {
part.m.tx = arg1;
part.m.ty = arg2;
} else {
part.p1 = arg1;
part.p2 = arg2;
}
if (flags & WE_HAVE_A_SCALE) {
part.m.a = part.m.d = bin.readF2dot14(data, offset);
offset += 2;
} else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
part.m.a = bin.readF2dot14(data, offset);
offset += 2;
part.m.d = bin.readF2dot14(data, offset);
offset += 2;
} else if (flags & WE_HAVE_A_TWO_BY_TWO) {
part.m.a = bin.readF2dot14(data, offset);
offset += 2;
part.m.b = bin.readF2dot14(data, offset);
offset += 2;
part.m.c = bin.readF2dot14(data, offset);
offset += 2;
part.m.d = bin.readF2dot14(data, offset);
offset += 2;
}
} while (flags & MORE_COMPONENTS);
if (flags & WE_HAVE_INSTRUCTIONS) {
var numInstr = bin.readUshort(data, offset);
offset += 2;
gl.instr = [];
for (var i = 0; i < numInstr; i++) {
gl.instr.push(data[offset]);
offset++;
}
}
}
return gl;
};
Typr.GPOS = {};
Typr.GPOS.parse = function(data, offset, length, font) {
return Typr._lctf.parse(data, offset, length, font, Typr.GPOS.subt);
};
Typr.GPOS.subt = function(data, ltype, offset, ltable) {
var bin = Typr._bin, offset0 = offset, tab = {};
tab.fmt = bin.readUshort(data, offset);
offset += 2;
if (ltype == 1 || ltype == 2 || ltype == 3 || ltype == 7 || ltype == 8 && tab.fmt <= 2) {
var covOff = bin.readUshort(data, offset);
offset += 2;
tab.coverage = Typr._lctf.readCoverage(data, covOff + offset0);
}
if (ltype == 1 && tab.fmt == 1) {
var valFmt1 = bin.readUshort(data, offset);
offset += 2;
var ones1 = Typr._lctf.numOfOnes(valFmt1);
if (valFmt1 != 0)
tab.pos = Typr.GPOS.readValueRecord(data, offset, valFmt1);
} else if (ltype == 2 && tab.fmt >= 1 && tab.fmt <= 2) {
var valFmt1 = bin.readUshort(data, offset);
offset += 2;
var valFmt2 = bin.readUshort(data, offset);
offset += 2;
var ones1 = Typr._lctf.numOfOnes(valFmt1);
var ones2 = Typr._lctf.numOfOnes(valFmt2);
if (tab.fmt == 1) {
tab.pairsets = [];
var psc = bin.readUshort(data, offset);
offset += 2;
for (var i = 0; i < psc; i++) {
var psoff = offset0 + bin.readUshort(data, offset);
offset += 2;
var pvc = bin.readUshort(data, psoff);
psoff += 2;
var arr = [];
for (var j = 0; j < pvc; j++) {
var gid2 = bin.readUshort(data, psoff);
psoff += 2;
var value1, value2;
if (valFmt1 != 0) {
value1 = Typr.GPOS.readValueRecord(data, psoff, valFmt1);
psoff += ones1 * 2;
}
if (valFmt2 != 0) {
value2 = Typr.GPOS.readValueRecord(data, psoff, valFmt2);
psoff += ones2 * 2;
}
arr.push({ gid2, val1: value1, val2: value2 });
}
tab.pairsets.push(arr);
}
}
if (tab.fmt == 2) {
var classDef1 = bin.readUshort(data, offset);
offset += 2;
var classDef2 = bin.readUshort(data, offset);
offset += 2;
var class1Count = bin.readUshort(data, offset);
offset += 2;
var class2Count = bin.readUshort(data, offset);
offset += 2;
tab.classDef1 = Typr._lctf.readClassDef(data, offset0 + classDef1);
tab.classDef2 = Typr._lctf.readClassDef(data, offset0 + classDef2);
tab.matrix = [];
for (var i = 0; i < class1Count; i++) {
var row = [];
for (var j = 0; j < class2Count; j++) {
var value1 = null, value2 = null;
if (valFmt1 != 0) {
value1 = Typr.GPOS.readValueRecord(data, offset, valFmt1);
offset += ones1 * 2;
}
if (valFmt2 != 0) {
value2 = Typr.GPOS.readValueRecord(data, offset, valFmt2);
offset += ones2 * 2;
}
row.push({ val1: value1, val2: value2 });
}
tab.matrix.push(row);
}
}
} else if (ltype == 9 && tab.fmt == 1) {
var extType = bin.readUshort(data, offset);
offset += 2;
var extOffset = bin.readUint(data, offset);
offset += 4;
if (ltable.ltype == 9) {
ltable.ltype = extType;
} else if (ltable.ltype != extType) {
throw "invalid extension substitution";
}
return Typr.GPOS.subt(data, ltable.ltype, offset0 + extOffset);
} else
console.warn("unsupported GPOS table LookupType", ltype, "format", tab.fmt);
return tab;
};
Typr.GPOS.readValueRecord = function(data, offset, valFmt) {
var bin = Typr._bin;
var arr = [];
arr.push(valFmt & 1 ? bin.readShort(data, offset) : 0);
offset += valFmt & 1 ? 2 : 0;
arr.push(valFmt & 2 ? bin.readShort(data, offset) : 0);
offset += valFmt & 2 ? 2 : 0;
arr.push(valFmt & 4 ? bin.readShort(data, offset) : 0);
offset += valFmt & 4 ? 2 : 0;
arr.push(valFmt & 8 ? bin.readShort(data, offset) : 0);
offset += valFmt & 8 ? 2 : 0;
return arr;
};
Typr.GSUB = {};
Typr.GSUB.parse = function(data, offset, length, font) {
return Typr._lctf.parse(data, offset, length, font, Typr.GSUB.subt);
};
Typr.GSUB.subt = function(data, ltype, offset, ltable) {
var bin = Typr._bin, offset0 = offset, tab = {};
tab.fmt = bin.readUshort(data, offset);
offset += 2;
if (ltype != 1 && ltype != 4 && ltype != 5 && ltype != 6)
return null;
if (ltype == 1 || ltype == 4 || ltype == 5 && tab.fmt <= 2 || ltype == 6 && tab.fmt <= 2) {
var covOff = bin.readUshort(data, offset);
offset += 2;
tab.coverage = Typr._lctf.readCoverage(data, offset0 + covOff);
}
if (ltype == 1 && tab.fmt >= 1 && tab.fmt <= 2) {
if (tab.fmt == 1) {
tab.delta = bin.readShort(data, offset);
offset += 2;
} else if (tab.fmt == 2) {
var cnt = bin.readUshort(data, offset);
offset += 2;
tab.newg = bin.readUshorts(data, offset, cnt);
offset += tab.newg.length * 2;
}
} else if (ltype == 4) {
tab.vals = [];
var cnt = bin.readUshort(data, offset);
offset += 2;
for (var i = 0; i < cnt; i++) {
var loff = bin.readUshort(data, offset);
offset += 2;
tab.vals.push(Typr.GSUB.readLigatureSet(data, offset0 + loff));
}
} else if (ltype == 5 && tab.fmt == 2) {
if (tab.fmt == 2) {
var cDefOffset = bin.readUshort(data, offset);
offset += 2;
tab.cDef = Typr._lctf.readClassDef(data, offset0 + cDefOffset);
tab.scset = [];
var subClassSetCount = bin.readUshort(data, offset);
offset += 2;
for (var i = 0; i < subClassSetCount; i++) {
var scsOff = bin.readUshort(data, offset);
offset += 2;
tab.scset.push(scsOff == 0 ? null : Typr.GSUB.readSubClassSet(data, offset0 + scsOff));
}
}
} else if (ltype == 6 && tab.fmt == 3) {
if (tab.fmt == 3) {
for (var i = 0; i < 3; i++) {
var cnt = bin.readUshort(data, offset);
offset += 2;
var cvgs = [];
for (var j = 0; j < cnt; j++)
cvgs.push(Typr._lctf.readCoverage(data, offset0 + bin.readUshort(data, offset + j * 2)));
offset += cnt * 2;
if (i == 0)
tab.backCvg = cvgs;
if (i == 1)
tab.inptCvg = cvgs;
if (i == 2)
tab.ahedCvg = cvgs;
}
var cnt = bin.readUshort(data, offset);
offset += 2;
tab.lookupRec = Typr.GSUB.readSubstLookupRecords(data, offset, cnt);
}
} else if (ltype == 7 && tab.fmt == 1) {
var extType = bin.readUshort(data, offset);
offset += 2;
var extOffset = bin.readUint(data, offset);
offset += 4;
if (ltable.ltype == 9) {
ltable.ltype = extType;
} else if (ltable.ltype != extType) {
throw "invalid extension substitution";
}
return Typr.GSUB.subt(data, ltable.ltype, offset0 + extOffset);
} else
console.warn("unsupported GSUB table LookupType", ltype, "format", tab.fmt);
return tab;
};
Typr.GSUB.readSubClassSet = function(data, offset) {
var rUs = Typr._bin.readUshort, offset0 = offset, lset = [];
var cnt = rUs(data, offset);
offset += 2;
for (var i = 0; i < cnt; i++) {
var loff = rUs(data, offset);
offset += 2;
lset.push(Typr.GSUB.readSubClassRule(data, offset0 + loff));
}
return lset;
};
Typr.GSUB.readSubClassRule = function(data, offset) {
var rUs = Typr._bin.readUshort, rule = {};
var gcount = rUs(data, offset);
offset += 2;
var scount = rUs(data, offset);
offset += 2;
rule.input = [];
for (var i = 0; i < gcount - 1; i++) {
rule.input.push(rUs(data, offset));
offset += 2;
}
rule.substLookupRecords = Typr.GSUB.readSubstLookupRecords(data, offset, scount);
return rule;
};
Typr.GSUB.readSubstLookupRecords = function(data, offset, cnt) {
var rUs = Typr._bin.readUshort;
var out = [];
for (var i = 0; i < cnt; i++) {
out.push(rUs(data, offset), rUs(data, offset + 2));
offset += 4;
}
return out;
};
Typr.GSUB.readChainSubClassSet = function(data, offset) {
var bin = Typr._bin, offset0 = offset, lset = [];
var cnt = bin.readUshort(data, offset);
offset += 2;
for (var i = 0; i < cnt; i++) {
var loff = bin.readUshort(data, offset);
offset += 2;
lset.push(Typr.GSUB.readChainSubClassRule(data, offset0 + loff));
}
return lset;
};
Typr.GSUB.readChainSubClassRule = function(data, offset) {
var bin = Typr._bin, rule = {};
var pps = ["backtrack", "input", "lookahead"];
for (var pi = 0; pi < pps.length; pi++) {
var cnt = bin.readUshort(data, offset);
offset += 2;
if (pi == 1)
cnt--;
rule[pps[pi]] = bin.readUshorts(data, offset, cnt);
offset += rule[pps[pi]].length * 2;
}
var cnt = bin.readUshort(data, offset);
offset += 2;
rule.subst = bin.readUshorts(data, offset, cnt * 2);
offset += rule.subst.length * 2;
return rule;
};
Typr.GSUB.readLigatureSet = function(data, offset) {
var bin = Typr._bin, offset0 = offset, lset = [];
var lcnt = bin.readUshort(data, offset);
offset += 2;
for (var j = 0; j < lcnt; j++) {
var loff = bin.readUshort(data, offset);
offset += 2;
lset.push(Typr.GSUB.readLigature(data, offset0 + loff));
}
return lset;
};
Typr.GSUB.readLigature = function(data, offset) {
var bin = Typr._bin, lig = { chain: [] };
lig.nglyph = bin.readUshort(data, offset);
offset += 2;
var ccnt = bin.readUshort(data, offset);
offset += 2;
for (var k = 0; k < ccnt - 1; k++) {
lig.chain.push(bin.readUshort(data, offset));
offset += 2;
}
return lig;
};
Typr.head = {};
Typr.head.parse = function(data, offset, length) {
var bin = Typr._bin;
var obj = {};
bin.readFixed(data, offset);
offset += 4;
obj.fontRevision = bin.readFixed(data, offset);
offset += 4;
bin.readUint(data, offset);
offset += 4;
bin.readUint(data, offset);
offset += 4;
obj.flags = bin.readUshort(data, offset);
offset += 2;
obj.unitsPerEm = bin.readUshort(data, offset);
offset += 2;
obj.created = bin.readUint64(data, offset);
offset += 8;
obj.modified = bin.readUint64(data, offset);
offset += 8;
obj.xMin = bin.readShort(data, offset);
offset += 2;
obj.yMin = bin.readShort(data, offset);
offset += 2;
obj.xMax = bin.readShort(data, offset);
offset += 2;
obj.yMax = bin.readShort(data, offset);
offset += 2;
obj.macStyle = bin.readUshort(data, offset);
offset += 2;
obj.lowestRecPPEM = bin.readUshort(data, offset);
offset += 2;
obj.fontDirectionHint = bin.readShort(data, offset);
offset += 2;
obj.indexToLocFormat = bin.readShort(data, offset);
offset += 2;
obj.glyphDataFormat = bin.readShort(data, offset);
offset += 2;
return obj;
};
Typr.hhea = {};
Typr.hhea.parse = function(data, offset, length) {
var bin = Typr._bin;
var obj = {};
bin.readFixed(data, offset);
offset += 4;
obj.ascender = bin.readShort(data, offset);
offset += 2;
obj.descender = bin.readShort(data, offset);
offset += 2;
obj.lineGap = bin.readShort(data, offset);
offset += 2;
obj.advanceWidthMax = bin.readUshort(data, offset);
offset += 2;
obj.minLeftSideBearing = bin.readShort(data, offset);
offset += 2;
obj.minRightSideBearing = bin.readShort(data, offset);
offset += 2;
obj.xMaxExtent = bin.readShort(data, offset);
offset += 2;
obj.caretSlopeRise = bin.readShort(data, offset);
offset += 2;
obj.caretSlopeRun = bin.readShort(data, offset);
offset += 2;
obj.caretOffset = bin.readShort(data, offset);
offset += 2;
offset += 4 * 2;
obj.metricDataFormat = bin.readShort(data, offset);
offset += 2;
obj.numberOfHMetrics = bin.readUshort(data, offset);
offset += 2;
return obj;
};
Typr.hmtx = {};
Typr.hmtx.parse = function(data, offset, length, font) {
var bin = Typr._bin;
var obj = {};
obj.aWidth = [];
obj.lsBearing = [];
var aw = 0, lsb = 0;
for (var i = 0; i < font.maxp.numGlyphs; i++) {
if (i < font.hhea.numberOfHMetrics) {
aw = bin.readUshort(data, offset);
offset += 2;
lsb = bin.readShort(data, offset);
offset += 2;
}
obj.aWidth.push(aw);
obj.lsBearing.push(lsb);
}
return obj;
};
Typr.kern = {};
Typr.kern.parse = function(data, offset, length, font) {
var bin = Typr._bin;
var version = bin.readUshort(data, offset);
offset += 2;
if (version == 1)
return Typr.kern.parseV1(data, offset - 2, length, font);
var nTables = bin.readUshort(data, offset);
offset += 2;
var map2 = { glyph1: [], rval: [] };
for (var i = 0; i < nTables; i++) {
offset += 2;
var length = bin.readUshort(data, offset);
offset += 2;
var coverage = bin.readUshort(data, offset);
offset += 2;
var format = coverage >>> 8;
format &= 15;
if (format == 0)
offset = Typr.kern.readFormat0(data, offset, map2);
else
throw "unknown kern table format: " + format;
}
return map2;
};
Typr.kern.parseV1 = function(data, offset, length, font) {
var bin = Typr._bin;
bin.readFixed(data, offset);
offset += 4;
var nTables = bin.readUint(data, offset);
offset += 4;
var map2 = { glyph1: [], rval: [] };
for (var i = 0; i < nTables; i++) {
bin.readUint(data, offset);
offset += 4;
var coverage = bin.readUshort(data, offset);
offset += 2;
bin.readUshort(data, offset);
offset += 2;
var format = coverage >>> 8;
format &= 15;
if (format == 0)
offset = Typr.kern.readFormat0(data, offset, map2);
else
throw "unknown kern table format: " + format;
}
return map2;
};
Typr.kern.readFormat0 = function(data, offset, map2) {
var bin = Typr._bin;
var pleft = -1;
var nPairs = bin.readUshort(data, offset);
offset += 2;
bin.readUshort(data, offset);
offset += 2;
bin.readUshort(data, offset);
offset += 2;
bin.readUshort(data, offset);
offset += 2;
for (var j = 0; j < nPairs; j++) {
var left = bin.readUshort(data, offset);
offset += 2;
var right = bin.readUshort(data, offset);
offset += 2;
var value = bin.readShort(data, offset);
offset += 2;
if (left != pleft) {
map2.glyph1.push(left);
map2.rval.push({ glyph2: [], vals: [] });
}
var rval = map2.rval[map2.rval.length - 1];
rval.glyph2.push(right);
rval.vals.push(value);
pleft = left;
}
return offset;
};
Typr.loca = {};
Typr.loca.parse = function(data, offset, length, font) {
var bin = Typr._bin;
var obj = [];
var ver = font.head.indexToLocFormat;
var len = font.maxp.numGlyphs + 1;
if (ver == 0)
for (var i = 0; i < len; i++)
obj.push(bin.readUshort(data, offset + (i << 1)) << 1);
if (ver == 1)
for (var i = 0; i < len; i++)
obj.push(bin.readUint(data, offset + (i << 2)));
return obj;
};
Typr.maxp = {};
Typr.maxp.parse = function(data, offset, length) {
var bin = Typr._bin;
var obj = {};
var ver = bin.readUint(data, offset);
offset += 4;
obj.numGlyphs = bin.readUshort(data, offset);
offset += 2;
if (ver == 65536) {
obj.maxPoints = bin.readUshort(data, offset);
offset += 2;
obj.maxContours = bin.readUshort(data, offset);
offset += 2;
obj.maxCompositePoints = bin.readUshort(data, offset);
offset += 2;
obj.maxCompositeContours = bin.readUshort(data, offset);
offset += 2;
obj.maxZones = bin.readUshort(data, offset);
offset += 2;
obj.maxTwilightPoints = bin.readUshort(data, offset);
offset += 2;
obj.maxStorage = bin.readUshort(data, offset);
offset += 2;
obj.maxFunctionDefs = bin.readUshort(data, offset);
offset += 2;
obj.maxInstructionDefs = bin.readUshort(data, offset);
offset += 2;
obj.maxStackElements = bin.readUshort(data, offset);
offset += 2;
obj.maxSizeOfInstructions = bin.readUshort(data, offset);
offset += 2;
obj.maxComponentElements = bin.readUshort(data, offset);
offset += 2;
obj.maxComponentDepth = bin.readUshort(data, offset);
offset += 2;
}
return obj;
};
Typr.name = {};
Typr.name.parse = function(data, offset, length) {
var bin = Typr._bin;
var obj = {};
bin.readUshort(data, offset);
offset += 2;
var count = bin.readUshort(data, offset);
offset += 2;
bin.readUshort(data, offset);
offset += 2;
var names = [
"copyright",
"fontFamily",
"fontSubfamily",
"ID",
"fullName",
"version",
"postScriptName",
"trademark",
"manufacturer",
"designer",
"description",
"urlVendor",
"urlDesigner",
"licence",
"licenceURL",
"---",
"typoFamilyName",
"typoSubfamilyName",
"compatibleFull",
"sampleText",
"postScriptCID",
"wwsFamilyName",
"wwsSubfamilyName",
"lightPalette",
"darkPalette"
];
var offset0 = offset;
for (var i = 0; i < count; i++) {
var platformID = bin.readUshort(data, offset);
offset += 2;
var encodingID = bin.readUshort(data, offset);
offset += 2;
var languageID = bin.readUshort(data, offset);
offset += 2;
var nameID = bin.readUshort(data, offset);
offset += 2;
var slen = bin.readUshort(data, offset);
offset += 2;
var noffset = bin.readUshort(data, offset);
offset += 2;
var cname = names[nameID];
var soff = offset0 + count * 12 + noffset;
var str;
if (platformID == 0)
str = bin.readUnicode(data, soff, slen / 2);
else if (platformID == 3 && encodingID == 0)
str = bin.readUnicode(data, soff, slen / 2);
else if (encodingID == 0)
str = bin.readASCII(data, soff, slen);
else if (encodingID == 1)
str = bin.readUnicode(data, soff, slen / 2);
else if (encodingID == 3)
str = bin.readUnicode(data, soff, slen / 2);
else if (platformID == 1) {
str = bin.readASCII(data, soff, slen);
console.warn("reading unknown MAC encoding " + encodingID + " as ASCII");
} else
throw "unknown encoding " + encodingID + ", platformID: " + platformID;
var tid = "p" + platformID + "," + languageID.toString(16);
if (obj[tid] == null)
obj[tid] = {};
obj[tid][cname !== void 0 ? cname : nameID] = str;
obj[tid]._lang = languageID;
}
for (var p in obj)
if (obj[p].postScriptName != null && obj[p]._lang == 1033)
return obj[p];
for (var p in obj)
if (obj[p].postScriptName != null && obj[p]._lang == 0)
return obj[p];
for (var p in obj)
if (obj[p].postScriptName != null && obj[p]._lang == 3084)
return obj[p];
for (var p in obj)
if (obj[p].postScriptName != null)
return obj[p];
var tname;
for (var p in obj) {
tname = p;
break;
}
console.warn("returning name table with languageID " + obj[tname]._lang);
return obj[tname];
};
Typr["OS/2"] = {};
Typr["OS/2"].parse = function(data, offset, length) {
var bin = Typr._bin;
var ver = bin.readUshort(data, offset);
offset += 2;
var obj = {};
if (ver == 0)
Typr["OS/2"].version0(data, offset, obj);
else if (ver == 1)
Typr["OS/2"].version1(data, offset, obj);
else if (ver == 2 || ver == 3 || ver == 4)
Typr["OS/2"].version2(data, offset, obj);
else if (ver == 5)
Typr["OS/2"].version5(data, offset, obj);
else
throw "unknown OS/2 table version: " + ver;
return obj;
};
Typr["OS/2"].version0 = function(data, offset, obj) {
var bin = Typr._bin;
obj.xAvgCharWidth = bin.readShort(data, offset);
offset += 2;
obj.usWeightClass = bin.readUshort(data, offset);
offset += 2;
obj.usWidthClass = bin.readUshort(data, offset);
offset += 2;
obj.fsType = bin.readUshort(data, offset);
offset += 2;
obj.ySubscriptXSize = bin.readShort(data, offset);
offset += 2;
obj.ySubscriptYSize = bin.readShort(data, offset);
offset += 2;
obj.ySubscriptXOffset = bin.readShort(data, offset);
offset += 2;
obj.ySubscriptYOffset = bin.readShort(data, offset);
offset += 2;
obj.ySuperscriptXSize = bin.readShort(data, offset);
offset += 2;
obj.ySuperscriptYSize = bin.readShort(data, offset);
offset += 2;
obj.ySuperscriptXOffset = bin.readShort(data, offset);
offset += 2;
obj.ySuperscriptYOffset = bin.readShort(data, offset);
offset += 2;
obj.yStrikeoutSize = bin.readShort(data, offset);
offset += 2;
obj.yStrikeoutPosition = bin.readShort(data, offset);
offset += 2;
obj.sFamilyClass = bin.readShort(data, offset);
offset += 2;
obj.panose = bin.readBytes(data, offset, 10);
offset += 10;
obj.ulUnicodeRange1 = bin.readUint(data, offset);
offset += 4;
obj.ulUnicodeRange2 = bin.readUint(data, offset);
offset += 4;
obj.ulUnicodeRange3 = bin.readUint(data, offset);
offset += 4;
obj.ulUnicodeRange4 = bin.readUint(data, offset);
offset += 4;
obj.achVendID = [bin.readInt8(data, offset), bin.readInt8(data, offset + 1), bin.readInt8(data, offset + 2), bin.readInt8(data, offset + 3)];
offset += 4;
obj.fsSelection = bin.readUshort(data, offset);
offset += 2;
obj.usFirstCharIndex = bin.readUshort(data, offset);
offset += 2;
obj.usLastCharIndex = bin.readUshort(data, offset);
offset += 2;
obj.sTypoAscender = bin.readShort(data, offset);
offset += 2;
obj.sTypoDescender = bin.readShort(data, offset);
offset += 2;
obj.sTypoLineGap = bin.readShort(data, offset);
offset += 2;
obj.usWinAscent = bin.readUshort(data, offset);
offset += 2;
obj.usWinDescent = bin.readUshort(data, offset);
offset += 2;
return offset;
};
Typr["OS/2"].version1 = function(data, offset, obj) {
var bin = Typr._bin;
offset = Typr["OS/2"].version0(data, offset, obj);
obj.ulCodePageRange1 = bin.readUint(data, offset);
offset += 4;
obj.ulCodePageRange2 = bin.readUint(data, offset);
offset += 4;
return offset;
};
Typr["OS/2"].version2 = function(data, offset, obj) {
var bin = Typr._bin;
offset = Typr["OS/2"].version1(data, offset, obj);
obj.sxHeight = bin.readShort(data, offset);
offset += 2;
obj.sCapHeight = bin.readShort(data, offset);
offset += 2;
obj.usDefault = bin.readUshort(data, offset);
offset += 2;
obj.usBreak = bin.readUshort(data, offset);
offset += 2;
obj.usMaxContext = bin.readUshort(data, offset);
offset += 2;
return offset;
};
Typr["OS/2"].version5 = function(data, offset, obj) {
var bin = Typr._bin;
offset = Typr["OS/2"].version2(data, offset, obj);
obj.usLowerOpticalPointSize = bin.readUshort(data, offset);
offset += 2;
obj.usUpperOpticalPointSize = bin.readUshort(data, offset);
offset += 2;
return offset;
};
Typr.post = {};
Typr.post.parse = function(data, offset, length) {
var bin = Typr._bin;
var obj = {};
obj.version = bin.readFixed(data, offset);
offset += 4;
obj.italicAngle = bin.readFixed(data, offset);
offset += 4;
obj.underlinePosition = bin.readShort(data, offset);
offset += 2;
obj.underlineThickness = bin.readShort(data, offset);
offset += 2;
return obj;
};
Typr.SVG = {};
Typr.SVG.parse = function(data, offset, length) {
var bin = Typr._bin;
var obj = { entries: [] };
var offset0 = offset;
bin.readUshort(data, offset);
offset += 2;
var svgDocIndexOffset = bin.readUint(data, offset);
offset += 4;
bin.readUint(data, offset);
offset += 4;
offset = svgDocIndexOffset + offset0;
var numEntries = bin.readUshort(data, offset);
offset += 2;
for (var i = 0; i < numEntries; i++) {
var startGlyphID = bin.readUshort(data, offset);
offset += 2;
var endGlyphID = bin.readUshort(data, offset);
offset += 2;
var svgDocOffset = bin.readUint(data, offset);
offset += 4;
var svgDocLength = bin.readUint(data, offset);
offset += 4;
var sbuf = new Uint8Array(data.buffer, offset0 + svgDocOffset + svgDocIndexOffset, svgDocLength);
var svg = bin.readUTF8(sbuf, 0, sbuf.length);
for (var f = startGlyphID; f <= endGlyphID; f++) {
obj.entries[f] = svg;
}
}
return obj;
};
Typr.SVG.toPath = function(str) {
var pth = { cmds: [], crds: [] };
if (str == null)
return pth;
var prsr = new DOMParser();
var doc = prsr["parseFromString"](str, "image/svg+xml");
var svg = doc.firstChild;
while (svg.tagName != "svg")
svg = svg.nextSibling;
var vb = svg.getAttribute("viewBox");
if (vb)
vb = vb.trim().split(" ").map(parseFloat);
else
vb = [0, 0, 1e3, 1e3];
Typr.SVG._toPath(svg.children, pth);
for (var i = 0; i < pth.crds.length; i += 2) {
var x = pth.crds[i], y = pth.crds[i + 1];
x -= vb[0];
y -= vb[1];
y = -y;
pth.crds[i] = x;
pth.crds[i + 1] = y;
}
return pth;
};
Typr.SVG._toPath = function(nds, pth, fill) {
for (var ni = 0; ni < nds.length; ni++) {
var nd = nds[ni], tn = nd.tagName;
var cfl = nd.getAttribute("fill");
if (cfl == null)
cfl = fill;
if (tn == "g")
Typr.SVG._toPath(nd.children, pth, cfl);
else if (tn == "path") {
pth.cmds.push(cfl ? cfl : "#000000");
var d = nd.getAttribute("d");
var toks = Typr.SVG._tokens(d);
Typr.SVG._toksToPath(toks, pth);
pth.cmds.push("X");
} else if (tn == "defs")
;
else
console.warn(tn, nd);
}
};
Typr.SVG._tokens = function(d) {
var ts = [], off = 0, rn = false, cn = "";
while (off < d.length) {
var cc = d.charCodeAt(off), ch = d.charAt(off);
off++;
var isNum = 48 <= cc && cc <= 57 || ch == "." || ch == "-";
if (rn) {
if (ch == "-") {
ts.push(parseFloat(cn));
cn = ch;
} else if (isNum)
cn += ch;
else {
ts.push(parseFloat(cn));
if (ch != "," && ch != " ")
ts.push(ch);
rn = false;
}
} else {
if (isNum) {
cn = ch;
rn = true;
} else if (ch != "," && ch != " ")
ts.push(ch);
}
}
if (rn)
ts.push(parseFloat(cn));
return ts;
};
Typr.SVG._toksToPath = function(ts, pth) {
var i = 0, x = 0, y = 0, ox = 0, oy = 0;
var pc = { "M": 2, "L": 2, "H": 1, "V": 1, "S": 4, "C": 6 };
var cmds = pth.cmds, crds = pth.crds;
while (i < ts.length) {
var cmd = ts[i];
i++;
if (cmd == "z") {
cmds.push("Z");
x = ox;
y = oy;
} else {
var cmu = cmd.toUpperCase();
var ps = pc[cmu], reps = Typr.SVG._reps(ts, i, ps);
for (var j = 0; j < reps; j++) {
var xi = 0, yi = 0;
if (cmd != cmu) {
xi = x;
yi = y;
}
if (cmu == "M") {
x = xi + ts[i++];
y = yi + ts[i++];
cmds.push("M");
crds.push(x, y);
ox = x;
oy = y;
} else if (cmu == "L") {
x = xi + ts[i++];
y = yi + ts[i++];
cmds.push("L");
crds.push(x, y);
} else if (cmu == "H") {
x = xi + ts[i++];
cmds.push("L");
crds.push(x, y);
} else if (cmu == "V") {
y = yi + ts[i++];
cmds.push("L");
crds.push(x, y);
} else if (cmu == "C") {
var x1 = xi + ts[i++], y1 = yi + ts[i++], x2 = xi + ts[i++], y2 = yi + ts[i++], x3 = xi + ts[i++], y3 = yi + ts[i++];
cmds.push("C");
crds.push(x1, y1, x2, y2, x3, y3);
x = x3;
y = y3;
} else if (cmu == "S") {
var co = Math.max(crds.length - 4, 0);
var x1 = x + x - crds[co], y1 = y + y - crds[co + 1];
var x2 = xi + ts[i++], y2 = yi + ts[i++], x3 = xi + ts[i++], y3 = yi + ts[i++];
cmds.push("C");
crds.push(x1, y1, x2, y2, x3, y3);
x = x3;
y = y3;
} else
console.warn("Unknown SVG command " + cmd);
}
}
}
};
Typr.SVG._reps = function(ts, off, ps) {
var i = off;
while (i < ts.length) {
if (typeof ts[i] == "string")
break;
i += ps;
}
return (i - off) / ps;
};
if (Typr == null)
Typr = {};
if (Typr.U == null)
Typr.U = {};
Typr.U.codeToGlyph = function(font, code) {
var cmap = font.cmap;
for (var _i = 0, _a = [cmap.p0e4, cmap.p3e1, cmap.p3e10, cmap.p0e3, cmap.p1e0]; _i < _a.length; _i++) {
var tind = _a[_i];
if (tind == null)
continue;
var tab = cmap.tables[tind];
if (tab.format == 0) {
if (code >= tab.map.length)
continue;
return tab.map[code];
} else if (tab.format == 4) {
var sind = -1;
for (var i = 0; i < tab.endCount.length; i++) {
if (code <= tab.endCount[i]) {
sind = i;
break;
}
}
if (sind == -1)
continue;
if (tab.startCount[sind] > code)
continue;
var gli = 0;
if (tab.idRangeOffset[sind] != 0) {
gli = tab.glyphIdArray[code - tab.startCount[sind] + (tab.idRangeOffset[sind] >> 1) - (tab.idRangeOffset.length - sind)];
} else {
gli = code + tab.idDelta[sind];
}
return gli & 65535;
} else if (tab.format == 12) {
if (code > tab.groups[tab.groups.length - 1][1])
continue;
for (var i = 0; i < tab.groups.length; i++) {
var grp = tab.groups[i];
if (grp[0] <= code && code <= grp[1])
return grp[2] + (code - grp[0]);
}
continue;
} else {
throw "unknown cmap table format " + tab.format;
}
}
return 0;
};
Typr.U.glyphToPath = function(font, gid) {
var path = { cmds: [], crds: [] };
if (font.SVG && font.SVG.entries[gid]) {
var p = font.SVG.entries[gid];
if (p == null)
return path;
if (typeof p == "string") {
p = Typr.SVG.toPath(p);
font.SVG.entries[gid] = p;
}
return p;
} else if (font.CFF) {
var state = { x: 0, y: 0, stack: [], nStems: 0, haveWidth: false, width: font.CFF.Private ? font.CFF.Private.defaultWidthX : 0, open: false };
var cff = font.CFF, pdct = font.CFF.Private;
if (cff.ROS) {
var gi = 0;
while (cff.FDSelect[gi + 2] <= gid)
gi += 2;
pdct = cff.FDArray[cff.FDSelect[gi + 1]].Private;
}
Typr.U._drawCFF(font.CFF.CharStrings[gid], state, cff, pdct, path);
} else if (font.glyf) {
Typr.U._drawGlyf(gid, font, path);
}
return path;
};
Typr.U._drawGlyf = function(gid, font, path) {
var gl = font.glyf[gid];
if (gl == null)
gl = font.glyf[gid] = Typr.glyf._parseGlyf(font, gid);
if (gl != null) {
if (gl.noc > -1) {
Typr.U._simpleGlyph(gl, path);
} else {
Typr.U._compoGlyph(gl, font, path);
}
}
};
Typr.U._simpleGlyph = function(gl, p) {
for (var c = 0; c < gl.noc; c++) {
var i0 = c == 0 ? 0 : gl.endPts[c - 1] + 1;
var il = gl.endPts[c];
for (var i = i0; i <= il; i++) {
var pr = i == i0 ? il : i - 1;
var nx = i == il ? i0 : i + 1;
var onCurve = gl.flags[i] & 1;
var prOnCurve = gl.flags[pr] & 1;
var nxOnCurve = gl.flags[nx] & 1;
var x = gl.xs[i], y = gl.ys[i];
if (i == i0) {
if (onCurve) {
if (prOnCurve) {
Typr.U.P.moveTo(p, gl.xs[pr], gl.ys[pr]);
} else {
Typr.U.P.moveTo(p, x, y);
continue;
}
} else {
if (prOnCurve) {
Typr.U.P.moveTo(p, gl.xs[pr], gl.ys[pr]);
} else {
Typr.U.P.moveTo(p, (gl.xs[pr] + x) / 2, (gl.ys[pr] + y) / 2);
}
}
}
if (onCurve) {
if (prOnCurve)
Typr.U.P.lineTo(p, x, y);
} else {
if (nxOnCurve) {
Typr.U.P.qcurveTo(p, x, y, gl.xs[nx], gl.ys[nx]);
} else {
Typr.U.P.qcurveTo(p, x, y, (x + gl.xs[nx]) / 2, (y + gl.ys[nx]) / 2);
}
}
}
Typr.U.P.closePath(p);
}
};
Typr.U._compoGlyph = function(gl, font, p) {
for (var j = 0; j < gl.parts.length; j++) {
var path = { cmds: [], crds: [] };
var prt = gl.parts[j];
Typr.U._drawGlyf(prt.glyphIndex, font, path);
var m = prt.m;
for (var i = 0; i < path.crds.length; i += 2) {
var x = path.crds[i], y = path.crds[i + 1];
p.crds.push(x * m.a + y * m.b + m.tx);
p.crds.push(x * m.c + y * m.d + m.ty);
}
for (var i = 0; i < path.cmds.length; i++) {
p.cmds.push(path.cmds[i]);
}
}
};
Typr.U._getGlyphClass = function(g, cd) {
var intr = Typr._lctf.getInterval(cd, g);
return intr == -1 ? 0 : cd[intr + 2];
};
Typr.U.getPairAdjustment = function(font, g1, g2) {
var hasGPOSkern = false;
if (font.GPOS) {
var gpos = font["GPOS"];
var llist = gpos.lookupList, flist = gpos.featureList;
var tused = [];
for (var i = 0; i < flist.length; i++) {
var fl = flist[i];
if (fl.tag != "kern")
continue;
hasGPOSkern = true;
for (var ti = 0; ti < fl.tab.length; ti++) {
if (tused[fl.tab[ti]])
continue;
tused[fl.tab[ti]] = true;
var tab = llist[fl.tab[ti]];
for (var j = 0; j < tab.tabs.length; j++) {
if (tab.tabs[j] == null)
continue;
var ltab = tab.tabs[j], ind;
if (ltab.coverage) {
ind = Typr._lctf.coverageIndex(ltab.coverage, g1);
if (ind == -1)
continue;
}
if (tab.ltype == 1)
;
else if (tab.ltype == 2) {
var adj = null;
if (ltab.fmt == 1) {
var right = ltab.pairsets[ind];
for (var i = 0; i < right.length; i++) {
if (right[i].gid2 == g2)
adj = right[i];
}
} else if (ltab.fmt == 2) {
var c1 = Typr.U._getGlyphClass(g1, ltab.classDef1);
var c2 = Typr.U._getGlyphClass(g2, ltab.classDef2);
adj = ltab.matrix[c1][c2];
}
if (adj) {
var offset = 0;
if (adj.val1 && adj.val1[2])
offset += adj.val1[2];
if (adj.val2 && adj.val2[0])
offset += adj.val2[0];
return offset;
}
}
}
}
}
}
if (font.kern && !hasGPOSkern) {
var ind1 = font.kern.glyph1.indexOf(g1);
if (ind1 != -1) {
var ind2 = font.kern.rval[ind1].glyph2.indexOf(g2);
if (ind2 != -1)
return font.kern.rval[ind1].vals[ind2];
}
}
return 0;
};
Typr.U.stringToGlyphs = function(font, str) {
var gls = [];
for (var i = 0; i < str.length; i++) {
var cc = str.codePointAt(i);
if (cc > 65535)
i++;
gls.push(Typr.U.codeToGlyph(font, cc));
}
for (var i = 0; i < str.length; i++) {
var cc = str.codePointAt(i);
if (cc == 2367) {
var t = gls[i - 1];
gls[i - 1] = gls[i];
gls[i] = t;
}
if (cc > 65535)
i++;
}
var gsub = font["GSUB"];
if (gsub == null)
return gls;
var llist = gsub.lookupList, flist = gsub.featureList;
var cligs = [
"rlig",
"liga",
"mset",
"isol",
"init",
"fina",
"medi",
"half",
"pres",
"blws"
/* Tibetan fonts like Himalaya.ttf */
];
var tused = [];
for (var fi = 0; fi < flist.length; fi++) {
var fl = flist[fi];
if (cligs.indexOf(fl.tag) == -1)
continue;
for (var ti = 0; ti < fl.tab.length; ti++) {
if (tused[fl.tab[ti]])
continue;
tused[fl.tab[ti]] = true;
var tab = llist[fl.tab[ti]];
for (var ci = 0; ci < gls.length; ci++) {
var feat = Typr.U._getWPfeature(str, ci);
if ("isol,init,fina,medi".indexOf(fl.tag) != -1 && fl.tag != feat)
continue;
Typr.U._applySubs(gls, ci, tab, llist);
}
}
}
return gls;
};
Typr.U._getWPfeature = function(str, ci) {
var wsep = '\n " ,.:;!?() ،';
var R = "آأؤإاةدذرزوٱٲٳٵٶٷڈډڊڋڌڍڎڏڐڑڒړڔڕږڗژڙۀۃۄۅۆۇۈۉۊۋۍۏےۓەۮۯܐܕܖܗܘܙܞܨܪܬܯݍݙݚݛݫݬݱݳݴݸݹࡀࡆࡇࡉࡔࡧࡩࡪࢪࢫࢬࢮࢱࢲࢹૅેૉૐૡ૯ஃஅஉஎஏனப";
var L = "ꡲ્";
var slft = ci == 0 || wsep.indexOf(str[ci - 1]) != -1;
var srgt = ci == str.length - 1 || wsep.indexOf(str[ci + 1]) != -1;
if (!slft && R.indexOf(str[ci - 1]) != -1)
slft = true;
if (!srgt && R.indexOf(str[ci]) != -1)
srgt = true;
if (!srgt && L.indexOf(str[ci + 1]) != -1)
srgt = true;
if (!slft && L.indexOf(str[ci]) != -1)
slft = true;
var feat = null;
if (slft) {
feat = srgt ? "isol" : "init";
} else {
feat = srgt ? "fina" : "medi";
}
return feat;
};
Typr.U._applySubs = function(gls, ci, tab, llist) {
var rlim = gls.length - ci - 1;
for (var j = 0; j < tab.tabs.length; j++) {
if (tab.tabs[j] == null)
continue;
var ltab = tab.tabs[j], ind;
if (ltab.coverage) {
ind = Typr._lctf.coverageIndex(ltab.coverage, gls[ci]);
if (ind == -1)
continue;
}
if (tab.ltype == 1) {
gls[ci];
if (ltab.fmt == 1)
gls[ci] = gls[ci] + ltab.delta;
else
gls[ci] = ltab.newg[ind];
} else if (tab.ltype == 4) {
var vals = ltab.vals[ind];
for (var k = 0; k < vals.length; k++) {
var lig = vals[k], rl = lig.chain.length;
if (rl > rlim)
continue;
var good = true, em1 = 0;
for (var l = 0; l < rl; l++) {
while (gls[ci + em1 + (1 + l)] == -1)
em1++;
if (lig.chain[l] != gls[ci + em1 + (1 + l)])
good = false;
}
if (!good)
continue;
gls[ci] = lig.nglyph;
for (var l = 0; l < rl + em1; l++)
gls[ci + l + 1] = -1;
break;
}
} else if (tab.ltype == 5 && ltab.fmt == 2) {
var cind = Typr._lctf.getInterval(ltab.cDef, gls[ci]);
var cls = ltab.cDef[cind + 2], scs = ltab.scset[cls];
for (var i = 0; i < scs.length; i++) {
var sc = scs[i], inp = sc.input;
if (inp.length > rlim)
continue;
var good = true;
for (var l = 0; l < inp.length; l++) {
var cind2 = Typr._lctf.getInterval(ltab.cDef, gls[ci + 1 + l]);
if (cind == -1 && ltab.cDef[cind2 + 2] != inp[l]) {
good = false;
break;
}
}
if (!good)
continue;
var lrs = sc.substLookupRecords;
for (var k = 0; k < lrs.length; k += 2) {
lrs[k];
lrs[k + 1];
}
}
} else if (tab.ltype == 6 && ltab.fmt == 3) {
if (!Typr.U._glsCovered(gls, ltab.backCvg, ci - ltab.backCvg.length))
continue;
if (!Typr.U._glsCovered(gls, ltab.inptCvg, ci))
continue;
if (!Typr.U._glsCovered(gls, ltab.ahedCvg, ci + ltab.inptCvg.length))
continue;
var lr = ltab.lookupRec;
for (var i = 0; i < lr.length; i += 2) {
var cind = lr[i], tab2 = llist[lr[i + 1]];
Typr.U._applySubs(gls, ci + cind, tab2, llist);
}
}
}
};
Typr.U._glsCovered = function(gls, cvgs, ci) {
for (var i = 0; i < cvgs.length; i++) {
var ind = Typr._lctf.coverageIndex(cvgs[i], gls[ci + i]);
if (ind == -1)
return false;
}
return true;
};
Typr.U.glyphsToPath = function(font, gls, clr) {
var tpath = { cmds: [], crds: [] };
var x = 0;
for (var i = 0; i < gls.length; i++) {
var gid = gls[i];
if (gid == -1)
continue;
var gid2 = i < gls.length - 1 && gls[i + 1] != -1 ? gls[i + 1] : 0;
var path = Typr.U.glyphToPath(font, gid);
for (var j = 0; j < path.crds.length; j += 2) {
tpath.crds.push(path.crds[j] + x);
tpath.crds.push(path.crds[j + 1]);
}
if (clr)
tpath.cmds.push(clr);
for (var j = 0; j < path.cmds.length; j++)
tpath.cmds.push(path.cmds[j]);
if (clr)
tpath.cmds.push("X");
x += font.hmtx.aWidth[gid];
if (i < gls.length - 1)
x += Typr.U.getPairAdjustment(font, gid, gid2);
}
return tpath;
};
Typr.U.pathToSVG = function(path, prec) {
if (prec == null)
prec = 5;
var out = [], co = 0, lmap = { "M": 2, "L": 2, "Q": 4, "C": 6 };
for (var i = 0; i < path.cmds.length; i++) {
var cmd = path.cmds[i], cn = co + (lmap[cmd] ? lmap[cmd] : 0);
out.push(cmd);
while (co < cn) {
var c = path.crds[co++];
out.push(parseFloat(c.toFixed(prec)) + (co == cn ? "" : " "));
}
}
return out.join("");
};
Typr.U.pathToContext = function(path, ctx) {
var c = 0, crds = path.crds;
for (var j = 0; j < path.cmds.length; j++) {
var cmd = path.cmds[j];
if (cmd == "M") {
ctx.moveTo(crds[c], crds[c + 1]);
c += 2;
} else if (cmd == "L") {
ctx.lineTo(crds[c], crds[c + 1]);
c += 2;
} else if (cmd == "C") {
ctx.bezierCurveTo(crds[c], crds[c + 1], crds[c + 2], crds[c + 3], crds[c + 4], crds[c + 5]);
c += 6;
} else if (cmd == "Q") {
ctx.quadraticCurveTo(crds[c], crds[c + 1], crds[c + 2], crds[c + 3]);
c += 4;
} else if (cmd.charAt(0) == "#") {
ctx.beginPath();
ctx.fillStyle = cmd;
} else if (cmd == "Z") {
ctx.closePath();
} else if (cmd == "X") {
ctx.fill();
}
}
};
Typr.U.P = {};
Typr.U.P.moveTo = function(p, x, y) {
p.cmds.push("M");
p.crds.push(x, y);
};
Typr.U.P.lineTo = function(p, x, y) {
p.cmds.push("L");
p.crds.push(x, y);
};
Typr.U.P.curveTo = function(p, a, b, c, d, e, f) {
p.cmds.push("C");
p.crds.push(a, b, c, d, e, f);
};
Typr.U.P.qcurveTo = function(p, a, b, c, d) {
p.cmds.push("Q");
p.crds.push(a, b, c, d);
};
Typr.U.P.closePath = function(p) {
p.cmds.push("Z");
};
Typr.U._drawCFF = function(cmds, state, font, pdct, p) {
var stack = state.stack;
var nStems = state.nStems, haveWidth = state.haveWidth, width = state.width, open = state.open;
var i = 0;
var x = state.x, y = state.y, c1x = 0, c1y = 0, c2x = 0, c2y = 0, c3x = 0, c3y = 0, c4x = 0, c4y = 0, jpx = 0, jpy = 0;
var o = { val: 0, size: 0 };
while (i < cmds.length) {
Typr.CFF.getCharString(cmds, i, o);
var v = o.val;
i += o.size;
if (v == "o1" || v == "o18") {
var hasWidthArg;
hasWidthArg = stack.length % 2 !== 0;
if (hasWidthArg && !haveWidth) {
width = stack.shift() + pdct.nominalWidthX;
}
nStems += stack.length >> 1;
stack.length = 0;
haveWidth = true;
} else if (v == "o3" || v == "o23") {
var hasWidthArg;
hasWidthArg = stack.length % 2 !== 0;
if (hasWidthArg && !haveWidth) {
width = stack.shift() + pdct.nominalWidthX;
}
nStems += stack.length >> 1;
stack.length = 0;
haveWidth = true;
} else if (v == "o4") {
if (stack.length > 1 && !haveWidth) {
width = stack.shift() + pdct.nominalWidthX;
haveWidth = true;
}
if (open)
Typr.U.P.closePath(p);
y += stack.pop();
Typr.U.P.moveTo(p, x, y);
open = true;
} else if (v == "o5") {
while (stack.length > 0) {
x += stack.shift();
y += stack.shift();
Typr.U.P.lineTo(p, x, y);
}
} else if (v == "o6" || v == "o7") {
var count = stack.length;
var isX = v == "o6";
for (var j = 0; j < count; j++) {
var sval = stack.shift();
if (isX) {
x += sval;
} else {
y += sval;
}
isX = !isX;
Typr.U.P.lineTo(p, x, y);
}
} else if (v == "o8" || v == "o24") {
var count = stack.length;
var index = 0;
while (index + 6 <= count) {
c1x = x + stack.shift();
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x + stack.shift();
y = c2y + stack.shift();
Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, x, y);
index += 6;
}
if (v == "o24") {
x += stack.shift();
y += stack.shift();
Typr.U.P.lineTo(p, x, y);
}
} else if (v == "o11") {
break;
} else if (v == "o1234" || v == "o1235" || v == "o1236" || v == "o1237") {
if (v == "o1234") {
c1x = x + stack.shift();
c1y = y;
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
jpx = c2x + stack.shift();
jpy = c2y;
c3x = jpx + stack.shift();
c3y = c2y;
c4x = c3x + stack.shift();
c4y = y;
x = c4x + stack.shift();
Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
Typr.U.P.curveTo(p, c3x, c3y, c4x, c4y, x, y);
}
if (v == "o1235") {
c1x = x + stack.shift();
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
jpx = c2x + stack.shift();
jpy = c2y + stack.shift();
c3x = jpx + stack.shift();
c3y = jpy + stack.shift();
c4x = c3x + stack.shift();
c4y = c3y + stack.shift();
x = c4x + stack.shift();
y = c4y + stack.shift();
stack.shift();
Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
Typr.U.P.curveTo(p, c3x, c3y, c4x, c4y, x, y);
}
if (v == "o1236") {
c1x = x + stack.shift();
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
jpx = c2x + stack.shift();
jpy = c2y;
c3x = jpx + stack.shift();
c3y = c2y;
c4x = c3x + stack.shift();
c4y = c3y + stack.shift();
x = c4x + stack.shift();
Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
Typr.U.P.curveTo(p, c3x, c3y, c4x, c4y, x, y);
}
if (v == "o1237") {
c1x = x + stack.shift();
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
jpx = c2x + stack.shift();
jpy = c2y + stack.shift();
c3x = jpx + stack.shift();
c3y = jpy + stack.shift();
c4x = c3x + stack.shift();
c4y = c3y + stack.shift();
if (Math.abs(c4x - x) > Math.abs(c4y - y)) {
x = c4x + stack.shift();
} else {
y = c4y + stack.shift();
}
Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
Typr.U.P.curveTo(p, c3x, c3y, c4x, c4y, x, y);
}
} else if (v == "o14") {
if (stack.length > 0 && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
haveWidth = true;
}
if (stack.length == 4) {
var adx = stack.shift();
var ady = stack.shift();
var bchar = stack.shift();
var achar = stack.shift();
var bind2 = Typr.CFF.glyphBySE(font, bchar);
var aind = Typr.CFF.glyphBySE(font, achar);
Typr.U._drawCFF(font.CharStrings[bind2], state, font, pdct, p);
state.x = adx;
state.y = ady;
Typr.U._drawCFF(font.CharStrings[aind], state, font, pdct, p);
}
if (open) {
Typr.U.P.closePath(p);
open = false;
}
} else if (v == "o19" || v == "o20") {
var hasWidthArg;
hasWidthArg = stack.length % 2 !== 0;
if (hasWidthArg && !haveWidth) {
width = stack.shift() + pdct.nominalWidthX;
}
nStems += stack.length >> 1;
stack.length = 0;
haveWidth = true;
i += nStems + 7 >> 3;
} else if (v == "o21") {
if (stack.length > 2 && !haveWidth) {
width = stack.shift() + pdct.nominalWidthX;
haveWidth = true;
}
y += stack.pop();
x += stack.pop();
if (open)
Typr.U.P.closePath(p);
Typr.U.P.moveTo(p, x, y);
open = true;
} else if (v == "o22") {
if (stack.length > 1 && !haveWidth) {
width = stack.shift() + pdct.nominalWidthX;
haveWidth = true;
}
x += stack.pop();
if (open)
Typr.U.P.closePath(p);
Typr.U.P.moveTo(p, x, y);
open = true;
} else if (v == "o25") {
while (stack.length > 6) {
x += stack.shift();
y += stack.shift();
Typr.U.P.lineTo(p, x, y);
}
c1x = x + stack.shift();
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x + stack.shift();
y = c2y + stack.shift();
Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, x, y);
} else if (v == "o26") {
if (stack.length % 2) {
x += stack.shift();
}
while (stack.length > 0) {
c1x = x;
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x;
y = c2y + stack.shift();
Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, x, y);
}
} else if (v == "o27") {
if (stack.length % 2) {
y += stack.shift();
}
while (stack.length > 0) {
c1x = x + stack.shift();
c1y = y;
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x + stack.shift();
y = c2y;
Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, x, y);
}
} else if (v == "o10" || v == "o29") {
var obj = v == "o10" ? pdct : font;
if (stack.length == 0) {
console.warn("error: empty stack");
} else {
var ind = stack.pop();
var subr = obj.Subrs[ind + obj.Bias];
state.x = x;
state.y = y;
state.nStems = nStems;
state.haveWidth = haveWidth;
state.width = width;
state.open = open;
Typr.U._drawCFF(subr, state, font, pdct, p);
x = state.x;
y = state.y;
nStems = state.nStems;
haveWidth = state.haveWidth;
width = state.width;
open = state.open;
}
} else if (v == "o30" || v == "o31") {
var count, count1 = stack.length;
var index = 0;
var alternate = v == "o31";
count = count1 & ~2;
index += count1 - count;
while (index < count) {
if (alternate) {
c1x = x + stack.shift();
c1y = y;
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
y = c2y + stack.shift();
if (count - index == 5) {
x = c2x + stack.shift();
index++;
} else {
x = c2x;
}
alternate = false;
} else {
c1x = x;
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x + stack.shift();
if (count - index == 5) {
y = c2y + stack.shift();
index++;
} else {
y = c2y;
}
alternate = true;
}
Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, x, y);
index += 4;
}
} else if ((v + "").charAt(0) == "o") {
console.warn("Unknown operation: " + v, cmds);
throw v;
} else
stack.push(v);
}
state.x = x;
state.y = y;
state.nStems = nStems;
state.haveWidth = haveWidth;
state.width = width;
state.open = open;
};
Typr$1.Typr = Typr;
var Typr_js_1 = Typr$1;
var friendlyTags = { "aalt": "Access All Alternates", "abvf": "Above-base Forms", "abvm": "Above - base Mark Positioning", "abvs": "Above - base Substitutions", "afrc": "Alternative Fractions", "akhn": "Akhands", "blwf": "Below - base Forms", "blwm": "Below - base Mark Positioning", "blws": "Below - base Substitutions", "calt": "Contextual Alternates", "case": "Case - Sensitive Forms", "ccmp": "Glyph Composition / Decomposition", "cfar": "Conjunct Form After Ro", "cjct": "Conjunct Forms", "clig": "Contextual Ligatures", "cpct": "Centered CJK Punctuation", "cpsp": "Capital Spacing", "cswh": "Contextual Swash", "curs": "Cursive Positioning", "c2pc": "Petite Capitals From Capitals", "c2sc": "Small Capitals From Capitals", "dist": "Distances", "dlig": "Discretionary Ligatures", "dnom": "Denominators", "dtls": "Dotless Forms", "expt": "Expert Forms", "falt": "Final Glyph on Line Alternates", "fin2": "Terminal Forms #2", "fin3": "Terminal Forms #3", "fina": "Terminal Forms", "flac": "Flattened accent forms", "frac": "Fractions", "fwid": "Full Widths", "half": "Half Forms", "haln": "Halant Forms", "halt": "Alternate Half Widths", "hist": "Historical Forms", "hkna": "Horizontal Kana Alternates", "hlig": "Historical Ligatures", "hngl": "Hangul", "hojo": "Hojo Kanji Forms(JIS X 0212 - 1990 Kanji Forms)", "hwid": "Half Widths", "init": "Initial Forms", "isol": "Isolated Forms", "ital": "Italics", "jalt": "Justification Alternates", "jp78": "JIS78 Forms", "jp83": "JIS83 Forms", "jp90": "JIS90 Forms", "jp04": "JIS2004 Forms", "kern": "Kerning", "lfbd": "Left Bounds", "liga": "Standard Ligatures", "ljmo": "Leading Jamo Forms", "lnum": "Lining Figures", "locl": "Localized Forms", "ltra": "Left - to - right alternates", "ltrm": "Left - to - right mirrored forms", "mark": "Mark Positioning", "med2": "Medial Forms #2", "medi": "Medial Forms", "mgrk": "Mathematical Greek", "mkmk": "Mark to Mark Positioning", "mset": "Mark Positioning via Substitution", "nalt": "Alternate Annotation Forms", "nlck": "NLC Kanji Forms", "nukt": "Nukta Forms", "numr": "Numerators", "onum": "Oldstyle Figures", "opbd": "Optical Bounds", "ordn": "Ordinals", "ornm": "Ornaments", "palt": "Proportional Alternate Widths", "pcap": "Petite Capitals", "pkna": "Proportional Kana", "pnum": "Proportional Figures", "pref": "Pre - Base Forms", "pres": "Pre - base Substitutions", "pstf": "Post - base Forms", "psts": "Post - base Substitutions", "pwid": "Proportional Widths", "qwid": "Quarter Widths", "rand": "Randomize", "rclt": "Required Contextual Alternates", "rkrf": "Rakar Forms", "rlig": "Required Ligatures", "rphf": "Reph Forms", "rtbd": "Right Bounds", "rtla": "Right - to - left alternates", "rtlm": "Right - to - left mirrored forms", "ruby": "Ruby Notation Forms", "rvrn": "Required Variation Alternates", "salt": "Stylistic Alternates", "sinf": "Scientific Inferiors", "size": "Optical size", "smcp": "Small Capitals", "smpl": "Simplified Forms", "ssty": "Math script style alternates", "stch": "Stretching Glyph Decomposition", "subs": "Subscript", "sups": "Superscript", "swsh": "Swash", "titl": "Titling", "tjmo": "Trailing Jamo Forms", "tnam": "Traditional Name Forms", "tnum": "Tabular Figures", "trad": "Traditional Forms", "twid": "Third Widths", "unic": "Unicase", "valt": "Alternate Vertical Metrics", "vatu": "Vattu Variants", "vert": "Vertical Writing", "vhal": "Alternate Vertical Half Metrics", "vjmo": "Vowel Jamo Forms", "vkna": "Vertical Kana Alternates", "vkrn": "Vertical Kerning", "vpal": "Proportional Alternate Vertical Metrics", "vrt2": "Vertical Alternates and Rotation", "vrtr": "Vertical Alternates for Rotation", "zero": "Slashed Zero" };
var Font = (
/** @class */
function() {
function Font2(data) {
var obj = Typr_js_1.Typr.parse(data);
if (!obj.length || typeof obj[0] !== "object" || typeof obj[0].hasOwnProperty !== "function") {
throw "unable to parse font";
}
for (var n in obj[0]) {
this[n] = obj[0][n];
}
this.enabledGSUB = {};
}
Font2.prototype.getFamilyName = function() {
return this.name && (this.name.typoFamilyName || this.name.fontFamily) || "";
};
Font2.prototype.getSubFamilyName = function() {
return this.name && (this.name.typoSubfamilyName || this.name.fontSubfamily) || "";
};
Font2.prototype.glyphToPath = function(gid) {
return Typr_js_1.Typr.U.glyphToPath(this, gid);
};
Font2.prototype.getPairAdjustment = function(gid1, gid2) {
return Typr_js_1.Typr.U.getPairAdjustment(this, gid1, gid2);
};
Font2.prototype.stringToGlyphs = function(str) {
return Typr_js_1.Typr.U.stringToGlyphs(this, str);
};
Font2.prototype.glyphsToPath = function(gls) {
return Typr_js_1.Typr.U.glyphsToPath(this, gls);
};
Font2.prototype.pathToSVG = function(path, prec) {
return Typr_js_1.Typr.U.pathToSVG(path, prec);
};
Font2.prototype.pathToContext = function(path, ctx) {
return Typr_js_1.Typr.U.pathToContext(path, ctx);
};
Font2.prototype.lookupFriendlyName = function(table, feature) {
if (this[table] !== void 0) {
var tbl = this[table];
var feat = tbl.featureList[feature];
return this.featureFriendlyName(feat);
}
return "";
};
Font2.prototype.featureFriendlyName = function(feature) {
if (friendlyTags[feature.tag]) {
return friendlyTags[feature.tag];
}
if (feature.tag.match(/ss[0-2][0-9]/)) {
var name_1 = "Stylistic Set " + Number(feature.tag.substr(2, 2)).toString();
if (feature.featureParams) {
var version = Typr_js_1.Typr._bin.readUshort(this._data, feature.featureParams);
if (version === 0) {
var nameID = Typr_js_1.Typr._bin.readUshort(this._data, feature.featureParams + 2);
if (this.name && this.name[nameID] !== void 0) {
return name_1 + " - " + this.name[nameID];
}
}
}
return name_1;
}
if (feature.tag.match(/cv[0-9][0-9]/)) {
return "Character Variant " + Number(feature.tag.substr(2, 2)).toString();
}
return "";
};
Font2.prototype.enableGSUB = function(featureNumber) {
if (this.GSUB) {
var feature = this.GSUB.featureList[featureNumber];
if (feature) {
for (var i = 0; i < feature.tab.length; ++i) {
this.enabledGSUB[feature.tab[i]] = (this.enabledGSUB[feature.tab[i]] || 0) + 1;
}
}
}
};
Font2.prototype.disableGSUB = function(featureNumber) {
if (this.GSUB) {
var feature = this.GSUB.featureList[featureNumber];
if (feature) {
for (var i = 0; i < feature.tab.length; ++i) {
if (this.enabledGSUB[feature.tab[i]] > 1) {
--this.enabledGSUB[feature.tab[i]];
} else {
delete this.enabledGSUB[feature.tab[i]];
}
}
}
}
};
Font2.prototype.codeToGlyph = function(code) {
var g = Typr_js_1.Typr.U.codeToGlyph(this, code);
if (this.GSUB) {
var gls = [g];
for (var n in this.enabledGSUB) {
var l = this.GSUB.lookupList[n];
Typr_js_1.Typr.U._applySubs(gls, 0, l, this.GSUB.lookupList);
}
if (gls.length === 1)
return gls[0];
}
return g;
};
return Font2;
}()
);
var Font_1 = Font;
function decrypt(iframeDocument) {
var _a, _b, _c;
const styles = iframeDocument.querySelectorAll("style");
let tip;
for (let i = 0; i < styles.length; i++) {
if ((_a = styles[i].textContent) == null ? void 0 : _a.includes("font-cxsecret")) {
tip = styles[i];
break;
}
}
if (!tip)
return;
const fontData = (_c = (_b = tip.textContent) == null ? void 0 : _b.match(/base64,([\w\W]+?)'/)) == null ? void 0 : _c[1];
if (!fontData)
return;
const fontBuffer = base64ToArrayBuffer(fontData);
const font = new Font_1(fontBuffer);
const table = JSON.parse(_GM_getResourceText("ttf"));
const match = {};
for (let i = 19968; i < 40870; i++) {
const glyph = font.codeToGlyph(i);
if (!glyph)
continue;
const path = font.glyphToPath(glyph);
const hash = md5(JSON.stringify(path)).slice(24);
match[i] = table[hash];
}
const elements = iframeDocument.querySelectorAll(".font-cxsecret");
for (let i = 0; i < elements.length; i++) {
const el = elements[i];
let html = el.innerHTML;
for (const key in match) {
const value = String.fromCharCode(match[key]);
const regExp = new RegExp(String.fromCharCode(Number(key)), "g");
html = html.replace(regExp, value);
}
el.innerHTML = html;
el.classList.remove("font-cxsecret");
}
function base64ToArrayBuffer(base64) {
const data = window.atob(base64);
const buffer = new ArrayBuffer(data.length);
const bytes = new Uint8Array(buffer);
for (let i = 0; i < data.length; ++i) {
bytes[i] = data.charCodeAt(i);
}
return buffer;
}
}
class BaseQuestionHandler {
constructor() {
__publicField(this, "_document", document);
__publicField(this, "_window", _unsafeWindow);
__publicField(this, "addLog");
__publicField(this, "addQuestion");
__publicField(this, "questions", []);
__publicField(this, "correctNum", 0);
__publicField(this, "parseHtml", () => {
throw new Error("请使用继承类的重写方法");
});
__publicField(this, "fillQuestion", (question) => {
throw new Error("请使用继承类的重写方法");
});
__publicField(this, "removeHtml", (html) => {
if (html == null) {
return "";
}
return html.replace(/<((?!img|sub|sup|br)[^>]+)>/g, "").replace(/ /g, " ").replace(/\s+/g, " ").replace(/
/g, "\n").replace(//g, '
').trim();
});
__publicField(this, "clean", (str) => {
return str.replace(/^【*?】\s*/, "").replace(/\s*(\d+\.\d+分)$/, "");
});
__publicField(this, "findMatchedOption", (options, answer) => {
const normalizedAnswer = this.normalizeOptionMatchText(String(answer));
if (!normalizedAnswer)
return null;
let bestElement = null;
let bestScore = -1;
for (const [optionText, optionElement] of Object.entries(options)) {
const score = this.calculateTextSimilarity(
this.normalizeOptionMatchText(optionText),
normalizedAnswer
);
if (score > bestScore) {
bestElement = optionElement;
bestScore = score;
}
}
if (!bestElement)
return null;
return bestScore >= this.getAnswerSimilarityThreshold() ? bestElement : null;
});
__publicField(this, "inferQuestionTypeByStructure", (questionElement, optionElements, optionTexts) => {
if (this.hasCheckboxControl(questionElement, optionElements))
return "1";
if (this.isJudgementQuestion(questionElement, optionElements, optionTexts))
return "3";
if (this.hasRadioControl(questionElement, optionElements))
return "0";
if (optionElements.length > 0)
return "0";
if (this.hasFillBlankControl(questionElement))
return "2";
if (this.hasLongTextControl(questionElement))
return "4";
return "8";
});
__publicField(this, "hasFillBlankControl", (questionElement) => {
const textInputs = Array.from(questionElement.querySelectorAll("input")).filter((input) => {
const type = (input.getAttribute("type") || "text").toLowerCase();
return ["", "text", "search", "number", "tel", "url", "email"].includes(type) && !input.disabled && !input.hidden;
});
const textareas = questionElement.querySelectorAll("textarea");
const editableElements = questionElement.querySelectorAll('[contenteditable="true"]');
const inputLikeElements = questionElement.querySelectorAll('input, textarea, [contenteditable="true"]');
const hasBlankCue = Array.from(inputLikeElements).some((element) => {
const attrs = [
element.getAttribute("class") || "",
element.getAttribute("name") || "",
element.getAttribute("placeholder") || ""
].join(" ").toLowerCase();
return attrs.includes("blank") || attrs.includes("填空");
});
const hasTextCue = /(?:___+|[{(\\[][^)}\\]]*[)}\\]])/.test(questionElement.textContent || "") && /填|空|blank/i.test(questionElement.textContent || "");
return textInputs.length > 0 || editableElements.length > 0 || textareas.length > 1 || hasBlankCue || textareas.length === 1 && hasTextCue;
});
__publicField(this, "hasLongTextControl", (questionElement) => {
return questionElement.querySelectorAll("textarea").length > 0;
});
__publicField(this, "hasCheckboxControl", (questionElement, optionElements) => {
if (questionElement.querySelector('input[type="checkbox"]'))
return true;
return optionElements.some((optionElement) => this.hasControlCue(optionElement, ["checkbox", "multi", "multiple"]));
});
__publicField(this, "isJudgementQuestion", (questionElement, optionElements, optionTexts) => {
if (optionTexts.length !== 2)
return false;
const judgementValues = optionTexts.map((optionText) => this.normalizeJudgementText(optionText));
return judgementValues.includes("true") && judgementValues.includes("false") && this.hasJudgementStructureCue(questionElement, optionElements);
});
__publicField(this, "hasJudgementStructureCue", (questionElement, optionElements) => {
const cueText = [
this.collectElementCueText(questionElement),
...optionElements.map((optionElement) => this.collectElementCueText(optionElement))
].join(" ").toLowerCase();
const judgementCues = [
"judge",
"judgement",
"judgment",
"truefalse",
"true-false",
"true_false",
"判断",
"对错"
];
if (judgementCues.some((cue) => cueText.includes(cue)))
return true;
const optionAriaLabels = optionElements.map((optionElement) => {
var _a;
return [
optionElement.getAttribute("aria-label"),
(_a = optionElement.querySelector("[aria-label]")) == null ? void 0 : _a.getAttribute("aria-label")
].filter(Boolean).join(" ");
}).join(" ");
return optionAriaLabels.includes("对选择") && optionAriaLabels.includes("错选择");
});
__publicField(this, "hasRadioControl", (questionElement, optionElements) => {
if (questionElement.querySelector('input[type="radio"]'))
return true;
return optionElements.some((optionElement) => this.hasControlCue(optionElement, ["radio"]));
});
__publicField(this, "hasControlCue", (optionElement, cues) => {
const cueText = this.collectElementCueText(optionElement).toLowerCase();
return cues.some((cue) => cueText.includes(cue));
});
__publicField(this, "collectElementCueText", (element) => {
const cueAttributes = [
"aria-label",
"class",
"data",
"data-qtype",
"data-question-type",
"data-type",
"name",
"role",
"type",
"value"
];
const cueElements = [
element,
...Array.from(element.querySelectorAll(
cueAttributes.map((attribute) => `[${attribute}]`).join(",")
))
];
return cueElements.flatMap((cueElement) => cueAttributes.map((attribute) => cueElement.getAttribute(attribute) || "")).filter(Boolean).join(" ");
});
__publicField(this, "getAnswerSimilarityThreshold", () => {
const configStore = useConfigStore();
const thresholdParam = configStore.otherParams.params.find((param) => param.name.includes("相似"));
const threshold = Number((thresholdParam == null ? void 0 : thresholdParam.value) ?? 85);
if (!Number.isFinite(threshold))
return 85;
return Math.min(100, Math.max(0, threshold));
});
__publicField(this, "normalizeOptionMatchText", (text) => {
return this.clean(this.removeHtml(text)).replace(/
\s]+).*?>/gi, "$1").replace(/&[a-z]+;/gi, "").replace(/^[a-z]\s*[..、:\]]\s*/i, "").replace(/[\s\u00a0]+/g, "").replace(/[,.,。、:;?!'""''「」【】《》()()\[\]{}【】<>]/g, "").toLowerCase();
});
__publicField(this, "calculateTextSimilarity", (left, right) => {
if (!left || !right)
return 0;
if (left === right)
return 100;
const leftChars = Array.from(left);
const rightChars = Array.from(right);
const distance = this.getLevenshteinDistance(leftChars, rightChars);
const maxLength = Math.max(leftChars.length, rightChars.length);
return Math.round((1 - distance / maxLength) * 100);
});
__publicField(this, "getLevenshteinDistance", (left, right) => {
const distances = Array.from({ length: left.length + 1 }, (_, index) => index);
right.forEach((rightChar, rightIndex) => {
let previous = distances[0];
distances[0] = rightIndex + 1;
left.forEach((leftChar, leftIndex) => {
const current = distances[leftIndex + 1];
distances[leftIndex + 1] = leftChar === rightChar ? previous : Math.min(previous, distances[leftIndex], current) + 1;
previous = current;
});
});
return distances[left.length];
});
__publicField(this, "normalizeJudgementText", (optionText) => {
const text = this.clean(optionText).replace(/[..、:\s]/g, "").toLowerCase();
if (/^(a)?(正确|是|对|√|t|true|right)$/.test(text))
return "true";
if (/^(b)?(错误|否|错|×|x|f|false|wrong)$/.test(text))
return "false";
return "";
});
const logStore = useLogStore();
const questionStore = useQuestionStore();
this.addLog = logStore.addLog;
this.addQuestion = questionStore.addQuestion;
}
}
class CxQuestionHandler extends BaseQuestionHandler {
constructor(type, iframe) {
super();
__publicField(this, "type");
__publicField(this, "init", async (isCurrent = () => true) => {
var _a, _b;
if (!isCurrent())
return 0;
this.questions = [];
this.correctNum = 0;
this.parseHtml();
if (this.questions.length) {
this.addLog(`成功解析到${this.questions.length}个题目`, "primary");
for (const [index, parsedQuestion] of this.questions.entries()) {
if (!isCurrent())
return 0;
const question = vue.reactive({ ...parsedQuestion, answerStatus: "searching" });
this.addQuestion(question);
const answerData = await getAnswer(question);
if (!isCurrent())
return 0;
const values = getAnswerValues(answerData, question);
if (values == null ? void 0 : values.length) {
question.answer = values;
question.answerStatus = "success";
this.fillQuestion(question);
this.addLog(`第${index + 1}道题搜索成功`, "success");
this.addLog(`剩余次数:${String(((_a = answerData.meta) == null ? void 0 : _a.credit_remaining) ?? "未知")}`, "primary");
this.correctNum += 1;
} else {
this.addLog(`第${index + 1}道题搜索失败,点击查看原因`, "danger");
question.answerStatus = "error";
question.answer = [((_b = answerData.error) == null ? void 0 : _b.message) || "未查询到答案"];
}
}
} else
this.addLog("未解析到题目,请进入正确页面", "danger");
return this.questions.length ? this.correctNum / this.questions.length * 100 : 0;
});
__publicField(this, "parseHtml", () => {
if (!this._document)
return [];
if (["zj"].includes(this.type)) {
const questionElements = this._document.querySelectorAll(".TiMu");
this.addQuestions(questionElements);
} else if (["zy", "ks"].includes(this.type)) {
const questionElements = this._document.querySelectorAll(".questionLi");
this.addQuestions(questionElements);
}
});
__publicField(this, "fillQuestion", (question) => {
var _a, _b;
if (!this._window)
return;
if (question.type === "0" || question.type === "1") {
question.answer.forEach((answer) => {
const optionElement = this.findMatchedOption(question.options, String(answer));
if (!optionElement)
return;
if (["zj", "zy"].includes(this.type)) {
if (optionElement.getAttribute("aria-checked") === "true")
return;
optionElement == null ? void 0 : optionElement.click();
} else if (["ks"].includes(this.type)) {
if (optionElement.querySelector(".check_answer"))
return;
if (optionElement.querySelector(".check_answer_dx"))
return;
optionElement == null ? void 0 : optionElement.click();
}
});
} else if (question.type === "2") {
const textareaElements = question.element.querySelectorAll("textarea");
if (textareaElements.length === 0)
return;
textareaElements.forEach((textareaElement, index) => {
try {
const ueditor = this._window.UE.getEditor(textareaElement.name);
ueditor.setContent(String(question.answer[index] ?? ""));
} catch (e) {
textareaElement.value = String(question.answer[index] ?? "");
}
});
} else if (question.type === "3") {
let answer = "true";
if (String(question.answer[0]).match(/(^|,)(正确|是|对|√|T|ri|right|true)(,|$)/)) {
answer = "true";
} else if (String(question.answer[0]).match(/(^|,)(错误|否|错|×|F|wr|wrong|false)(,|$)/)) {
answer = "false";
}
const trueOrFalse = {
"true": "对",
"false": "错"
};
for (const key in question.options) {
if (["zj", "zy"].includes(this.type)) {
if ((_a = question.options[key].getAttribute("aria-label")) == null ? void 0 : _a.includes(`${trueOrFalse[answer]}选择`)) {
if (question.options[key].getAttribute("aria-checked") === "true")
return;
(_b = question.options[key]) == null ? void 0 : _b.click();
}
} else if (["ks"].includes(this.type)) {
const optionElement = question.options[key].querySelector(`span[data='${answer}']`);
if (optionElement == null ? void 0 : optionElement.querySelector(".check_answer"))
return;
optionElement == null ? void 0 : optionElement.click();
}
}
} else if (question.type === "4" || question.type === "6") {
const textareaElement = question.element.querySelector("textarea");
if (!textareaElement)
return;
const ueditor = this._window.UE.getEditor(textareaElement.name);
ueditor.setContent(String(question.answer[0] ?? ""));
} else
;
});
this.type = type;
if (iframe) {
this._document = iframe.contentDocument;
this._window = iframe.contentWindow;
}
}
extractOptions(optionElements, optionSelector) {
const optionsObject = {};
const optionTexts = [];
optionElements.forEach((optionElement) => {
var _a;
const optionTextContent = this.removeHtml(((_a = optionElement.querySelector(optionSelector)) == null ? void 0 : _a.innerHTML) || "");
optionsObject[optionTextContent] = optionElement;
optionTexts.push(optionTextContent);
});
return [optionsObject, optionTexts];
}
addQuestions(questionElements) {
questionElements.forEach((questionElement) => {
var _a, _b, _c;
let questionTitle = "";
let optionElements = [];
let optionsObject = {};
let optionTexts = [];
let searchStem = "";
let searchOptions = [];
let questionKind;
if (["zy", "ks"].includes(this.type)) {
const titleElement = ((_a = questionElement == null ? void 0 : questionElement.querySelector("h3")) == null ? void 0 : _a.innerHTML) || "";
const colorShallowElement = ((_b = questionElement.querySelector(".colorShallow")) == null ? void 0 : _b.outerHTML) || "";
const titleSource = titleElement.split(colorShallowElement || "")[1] || "";
questionTitle = this.removeHtml(titleSource);
const searchTitleSource = colorShallowElement ? titleSource : titleElement;
searchStem = stripQuestionTypeLabel(
extractRawTextFromHtml(searchTitleSource, this._document)
);
const matchedOptionElements = questionElement.querySelectorAll(".answerBg");
optionElements = Array.from(matchedOptionElements);
[optionsObject, optionTexts] = this.extractOptions(matchedOptionElements, ".answer_p");
searchOptions = Array.from(matchedOptionElements).map((optionElement) => stripOptionLabel(extractRawTextFromElement(optionElement.querySelector(".answer_p"))));
} else if (["zj"].includes(this.type)) {
const titleSource = ((_c = questionElement.querySelector(".fontLabel")) == null ? void 0 : _c.innerHTML) || "";
questionTitle = this.removeHtml(titleSource);
searchStem = stripQuestionTypeLabel(
extractRawTextFromHtml(titleSource, this._document)
);
const matchedOptionElements = questionElement.querySelectorAll('[class*="before-after"]');
optionElements = Array.from(matchedOptionElements);
[optionsObject, optionTexts] = this.extractOptions(matchedOptionElements, ".fl.after");
searchOptions = Array.from(matchedOptionElements).map((optionElement) => stripOptionLabel(extractRawTextFromElement(optionElement.querySelector(".fl.after"))));
}
questionKind = this.inferQuestionTypeByStructure(questionElement, optionElements, optionTexts);
if (questionKind === "3") {
searchOptions = optionElements.map((optionElement, index) => extractJudgementToken(optionElement, searchOptions[index] || optionTexts[index]) || searchOptions[index]);
}
this.questions.push({
element: questionElement,
type: questionKind,
title: this.clean(questionTitle),
optionsText: optionTexts,
options: optionsObject,
searchText: { stem: searchStem, options: searchOptions },
answer: [],
workType: this.type,
refer: proxyLoc(this._window.location).href
});
});
}
}
const useCxChapterLogic = () => {
const logStore = useLogStore();
const questionStore = useQuestionStore();
const init = () => {
const currentUrl = proxyLoc(window.location).href;
if (!currentUrl.includes("&mooc2=1")) {
proxyLoc(window.location).href = currentUrl + "&mooc2=1";
}
questionStore.clearQuestion();
logStore.addLog(`检测到用户进入到章节学习页面`, "primary");
logStore.addLog(`正在解析任务点,请稍等5-10秒(如果长时间没有反应,请刷新页面)`, "warning");
};
const configStore = useConfigStore();
const HOME_TAB_NAME = "0";
const ANSWER_TAB_NAME2 = "1";
const PDF_COMPLETION_WAIT_SECONDS = 5;
let runningIframeDocumentTasks = /* @__PURE__ */ new WeakMap();
let processedIframeDocuments = /* @__PURE__ */ new WeakSet();
let currentPage;
let chapterController = new AbortController();
let currentWatchIframeTaskId = 0;
let disposed = false;
const isFinishedTask = (iframe) => {
var _a;
let element = iframe;
while (element) {
const className = typeof element.className === "string" ? element.className : "";
if (className.split(/\s+/).includes("ans-job-finished") || ((_a = element.classList) == null ? void 0 : _a.contains("ans-job-finished")))
return true;
element = element.parentElement;
}
return false;
};
const getChapterPage = () => {
const iframe = document.documentElement.querySelector("iframe");
return {
url: proxyLoc(window.location).href,
iframe,
src: (iframe == null ? void 0 : iframe.src) || "",
document: (iframe == null ? void 0 : iframe.contentDocument) || null
};
};
const isSamePage = (left, right) => left.url === right.url && left.iframe === right.iframe && left.src === right.src && left.document === right.document;
const onRootIframeLoad = () => processIframeTask(true);
const processIframeTask = (force = false) => {
var _a, _b;
if (disposed)
return;
const page = getChapterPage();
const changed = !currentPage || !isSamePage(currentPage, page);
if (!changed && !force)
return;
if (changed) {
(_a = currentPage == null ? void 0 : currentPage.iframe) == null ? void 0 : _a.removeEventListener("load", onRootIframeLoad);
chapterController.abort();
chapterController = new AbortController();
++currentWatchIframeTaskId;
runningIframeDocumentTasks = /* @__PURE__ */ new WeakMap();
processedIframeDocuments = /* @__PURE__ */ new WeakSet();
questionStore.clearQuestion();
configStore.menuIndex = HOME_TAB_NAME;
currentPage = page;
(_b = page.iframe) == null ? void 0 : _b.addEventListener("load", onRootIframeLoad);
}
if (!page.iframe || !page.document || page.document.readyState !== "complete" || page.document.URL === "about:blank")
return;
const signal = chapterController.signal;
watchIframe(document.documentElement, {
signal,
// 直接检查页面,避免在轮询发现切换之前,旧请求已返回。
isCurrent: () => !signal.aborted && isSamePage(page, getChapterPage())
});
};
const watchIframe = (documentElement, context) => {
const thisTaskId = ++currentWatchIframeTaskId;
IframeUtils.getAllNestedIframes(documentElement).subscribe((allIframes) => {
rxjs.from(allIframes).pipe(concatMap((iframe) => {
if (thisTaskId !== currentWatchIframeTaskId || !context.isCurrent())
return Promise.resolve();
return processIframe(iframe, context);
})).subscribe({
complete: async () => {
var _a;
if (thisTaskId === currentWatchIframeTaskId && context.isCurrent()) {
logStore.addLog(`本页任务点已全部完成,正前往下一章节`, "success");
if (configStore.platformParams.cx.parts[0].params[1].value) {
const nextBtn = documentElement.querySelector("#prevNextFocusNext");
if (!nextBtn || nextBtn.style.display === "none") {
logStore.addLog(`已经到达最后一章节,无法跳转`, "danger");
} else {
await sleep(configStore.otherParams.params[0].value);
if (thisTaskId !== currentWatchIframeTaskId || !context.isCurrent())
return;
(_a = document.querySelector(".jb_btn.jb_btn_92.fr.fs14.nextChapter")) == null ? void 0 : _a.click();
}
} else {
logStore.addLog(`已经关闭自动下一章节,在设置里可更改`, "danger");
}
}
},
error: (error) => {
if (context.isCurrent()) {
logStore.addLog(`任务处理失败:${String(error)}`, "danger");
}
}
});
});
};
const processMedia = async (mediaType, iframe, iframeDocument, context) => {
return new Promise((resolve, reject) => {
logStore.addLog(`发现一个${mediaType},正在解析`, "warning");
logStore.addLog(`正在尝试播放${mediaType},请稍等5s`, "primary");
let isExecuted = false;
let settled = false;
let mediaElement = null;
let quiz;
const MAX_PLAY_RETRIES = 3;
let playPending = false;
let playInterruptions = 0;
let waitingForManualPlay = false;
let playbackLogged = false;
let resumeTimer;
const clearResumeTimer = () => {
if (resumeTimer !== void 0)
clearTimeout(resumeTimer);
resumeTimer = void 0;
};
const cleanup = () => {
clearInterval(intervalId2);
clearResumeTimer();
quiz == null ? void 0 : quiz.dispose();
mediaElement == null ? void 0 : mediaElement.removeEventListener("pause", resume);
mediaElement == null ? void 0 : mediaElement.removeEventListener("ended", ended);
mediaElement == null ? void 0 : mediaElement.removeEventListener("playing", playing);
context.signal.removeEventListener("abort", finish);
};
const finish = () => {
if (settled)
return;
settled = true;
cleanup();
mediaElement == null ? void 0 : mediaElement.pause();
resolve();
};
const fail = (error) => {
if (settled)
return;
settled = true;
cleanup();
reject(error);
};
const ended = () => {
if (settled || (quiz == null ? void 0 : quiz.hasActiveQuiz()))
return;
if (context.isCurrent())
logStore.addLog(`${mediaType}已播放完成`, "success");
finish();
};
const canPlay = () => {
if (settled || !mediaElement)
return false;
if (!context.isCurrent() || isFinishedTask(iframe)) {
finish();
return false;
}
if (quiz == null ? void 0 : quiz.hasActiveQuiz())
return false;
if (mediaElement.ended) {
ended();
return false;
}
return true;
};
const playing = () => {
if (!canPlay() || (mediaElement == null ? void 0 : mediaElement.paused))
return;
clearResumeTimer();
playInterruptions = 0;
waitingForManualPlay = false;
if (!playbackLogged) {
playbackLogged = true;
logStore.addLog("播放成功", "success");
}
};
const resume = () => {
if (!canPlay() || playPending || waitingForManualPlay || resumeTimer !== void 0 || !(mediaElement == null ? void 0 : mediaElement.paused))
return;
const delay = Number(configStore.otherParams.params[0].value) * 1e3;
resumeTimer = setTimeout(() => {
resumeTimer = void 0;
void tryPlay();
}, Number.isFinite(delay) ? Math.max(1e3, delay) : 3e3);
};
const waitForManualPlay = (message) => {
waitingForManualPlay = true;
clearResumeTimer();
logStore.addLog(message, "warning");
};
const tryPlay = async () => {
if (!canPlay() || playPending || waitingForManualPlay || !(mediaElement == null ? void 0 : mediaElement.paused))
return;
playPending = true;
try {
await mediaElement.play();
playing();
} catch (error) {
if (settled)
return;
if (!context.isCurrent() || isFinishedTask(iframe))
return finish();
const name = typeof error === "object" && error !== null && "name" in error ? error.name : "";
if (name === "AbortError") {
if (!(quiz == null ? void 0 : quiz.hasActiveQuiz()) && mediaElement.paused && !mediaElement.ended) {
if (++playInterruptions > MAX_PLAY_RETRIES) {
waitForManualPlay("播放多次被暂停,请保持课程页可见并手动点击播放;播放后将继续处理任务。");
} else {
logStore.addLog(`播放被暂停打断,将稍后重试(${playInterruptions}/${MAX_PLAY_RETRIES})`, "warning");
}
}
} else if (name === "NotAllowedError") {
waitForManualPlay("浏览器暂不允许自动播放,请手动点击播放器的播放按钮;播放后将继续处理任务。");
} else {
fail(error);
}
} finally {
playPending = false;
resume();
}
};
const intervalId2 = setInterval(async () => {
if (settled)
return;
if (!context.isCurrent())
return finish();
if (!isExecuted)
mediaElement = iframeDocument.documentElement.querySelector(mediaType);
if (isFinishedTask(iframe)) {
logStore.addLog(`${mediaType}任务点已完成,跳过`, "success");
return finish();
}
if (mediaElement && !isExecuted) {
isExecuted = true;
try {
mediaElement.muted = true;
mediaElement.addEventListener("pause", resume);
mediaElement.addEventListener("ended", ended);
mediaElement.addEventListener("playing", playing);
if (mediaType === "video") {
quiz = watchVideoQuiz({
document: iframeDocument,
signal: context.signal,
isCurrent: () => !settled && context.isCurrent() && !isFinishedTask(iframe),
isEnabled: () => {
var _a;
return ((_a = configStore.platformParams.cx.parts[0].params.find((param) => param.name === VIDEO_QUIZ_SETTING)) == null ? void 0 : _a.value) !== false;
},
delayMs: () => Number(configStore.otherParams.params[0].value) * 1e3,
search: (question, searchContext) => getAnswer(question, { ...searchContext, skipDelay: true }),
similarityThreshold: () => {
var _a;
return Number(((_a = configStore.otherParams.params.find((param) => param.name.includes("相似"))) == null ? void 0 : _a.value) ?? 85);
},
publishQuestion: (question) => {
configStore.menuIndex = ANSWER_TAB_NAME2;
const row = vue.reactive(question);
questionStore.addQuestion(row);
return row;
},
onRelease: () => {
if (mediaElement == null ? void 0 : mediaElement.ended)
ended();
else
resume();
},
log: (message, type) => logStore.addLog(message, type)
});
}
await tryPlay();
} catch (error) {
fail(error);
}
}
}, 2500);
context.signal.addEventListener("abort", finish, { once: true });
if (!context.isCurrent())
finish();
});
};
const processWork = async (iframe, iframeDocument, iframeWindow, context) => {
if (!context.isCurrent())
return;
configStore.menuIndex = ANSWER_TAB_NAME2;
logStore.addLog("发现一个作业,正在解析", "warning");
if (iframeDocument.documentElement.innerText.includes("已完成") || iframeDocument.documentElement.innerText.includes("待批阅")) {
logStore.addLog("作业已经完成,跳过", "success");
return;
}
decrypt(iframeDocument);
logStore.addLog(`题目列表获取成功`, "primary");
const correctRate = await new CxQuestionHandler("zj", iframe).init(context.isCurrent);
if (!context.isCurrent())
return;
iframeWindow.alert = () => {
};
if (configStore.platformParams.cx.parts[0].params[0].value) {
logStore.addLog("自动提交已开启,尝试提交", "primary");
if (correctRate < Number(configStore.otherParams.params[1].value)) {
logStore.addLog(`正确率小于${configStore.otherParams.params[1].value}%,暂存`, "danger");
await iframeWindow.noSubmit();
} else {
logStore.addLog(`正确率大于${configStore.otherParams.params[1].value}%,提交`, "success");
await iframeWindow.btnBlueSubmit();
if (!context.isCurrent())
return;
await sleep(configStore.otherParams.params[0].value / 2);
if (!context.isCurrent())
return;
await iframeWindow.submitCheckTimes();
if (!context.isCurrent())
return;
logStore.addLog("提交成功", "success");
}
} else {
logStore.addLog("未开启自动提交,暂存", "primary");
await iframeWindow.noSubmit();
}
if (context.isCurrent())
logStore.addLog("作业已完成", "success");
};
const processPpt = async (iframeWindow, context) => {
logStore.addLog("发现一个文档,正在解析", "warning");
const document2 = iframeWindow.document;
const viewerFrame = document2.querySelector("#panView");
const scrollViewer = () => {
var _a, _b, _c;
const viewerDocument = (viewerFrame == null ? void 0 : viewerFrame.contentDocument) || document2;
const viewerWindow = (viewerFrame == null ? void 0 : viewerFrame.contentDocument) ? viewerFrame.contentWindow : iframeWindow;
const top = Math.max(
((_a = viewerDocument.body) == null ? void 0 : _a.scrollHeight) || 0,
((_b = viewerDocument.documentElement) == null ? void 0 : _b.scrollHeight) || 0,
((_c = viewerDocument.scrollingElement) == null ? void 0 : _c.scrollHeight) || 0
);
viewerWindow == null ? void 0 : viewerWindow.scrollTo({ top, behavior: "smooth" });
};
scrollViewer();
const configuredInterval = Number(configStore.otherParams.params[0].value);
await sleep(Math.max(
PDF_COMPLETION_WAIT_SECONDS,
Number.isFinite(configuredInterval) ? configuredInterval : 0
));
if (!context.isCurrent())
return;
scrollViewer();
logStore.addLog("阅读完成", "success");
};
const processBook = async (iframeWindow) => {
logStore.addLog("发现一个电子书,正在解析", "warning");
_unsafeWindow.top.onchangepage(iframeWindow.getFrameAttr("end"));
logStore.addLog("阅读完成", "success");
return Promise.resolve();
};
const runIframeDocumentTask = (iframeDocument, task) => {
const processed = processedIframeDocuments;
const running = runningIframeDocumentTasks;
if (processed.has(iframeDocument))
return Promise.resolve();
const runningTask = running.get(iframeDocument);
if (runningTask)
return runningTask;
const taskPromise = Promise.resolve().then(task).then(() => {
processed.add(iframeDocument);
}).finally(() => {
running.delete(iframeDocument);
});
running.set(iframeDocument, taskPromise);
return taskPromise;
};
const waitIframeLoad = async (iframe, context) => {
return new Promise((resolve) => {
const finish = () => {
clearInterval(intervalId2);
context.signal.removeEventListener("abort", finish);
resolve();
};
const check = () => {
var _a;
if (!context.isCurrent() || !iframe.isConnected || ((_a = iframe.contentDocument) == null ? void 0 : _a.readyState) === "complete")
finish();
};
const intervalId2 = setInterval(check, 500);
context.signal.addEventListener("abort", finish, { once: true });
check();
});
};
const processIframeBody = async (iframe, iframeSrc, iframeDocument, iframeWindow, context) => {
var _a, _b;
if (!context.isCurrent())
return;
if (iframeSrc.includes("javascript:"))
return Promise.resolve();
if (isFinishedTask(iframe)) {
configStore.menuIndex = iframeSrc.includes("api/work") ? ANSWER_TAB_NAME2 : HOME_TAB_NAME;
if (iframeSrc.includes("video") || iframeSrc.includes("audio")) {
(_a = iframeDocument.documentElement.querySelector("video, audio")) == null ? void 0 : _a.pause();
}
logStore.addLog("发现一个已完成任务点", "success");
} else {
if (iframeSrc.includes("api/work")) {
return processWork(iframe, iframeDocument, iframeWindow, context);
}
if (configStore.platformParams.cx.parts[0].params[2].value) {
logStore.addLog("只答题,不做其他已开启,可在设置里调整", "primary");
} else {
const ansJobIcon = (_b = iframe.parentElement) == null ? void 0 : _b.querySelector(".ans-job-icon");
if (ansJobIcon) {
configStore.menuIndex = HOME_TAB_NAME;
if (iframeSrc.includes("video")) {
return processMedia("video", iframe, iframeDocument, context);
} else if (iframeSrc.includes("audio")) {
return processMedia("audio", iframe, iframeDocument, context);
} else if (["ppt", "doc", "pptx", "docx", "pdf"].some((type) => iframeSrc.includes("modules/" + type))) {
return processPpt(iframeWindow, context);
} else if (["innerbook"].some((type) => iframeSrc.includes("modules/" + type))) {
return processBook(iframeWindow);
}
}
}
}
return Promise.resolve();
};
const processIframe = async (iframe, context) => {
if (iframe.src.includes("javascript:"))
return Promise.resolve();
if (!iframe.contentDocument || !iframe.contentWindow)
return Promise.resolve();
await waitIframeLoad(iframe, context);
if (!context.isCurrent() || !iframe.isConnected)
return;
const iframeSrc = iframe.src;
const iframeDocument = iframe.contentDocument;
const iframeWindow = iframe.contentWindow;
if (!iframeDocument || !iframeWindow)
return Promise.resolve();
const iframeContext = {
signal: context.signal,
isCurrent: () => context.isCurrent() && iframe.isConnected && iframe.contentDocument === iframeDocument && iframe.src === iframeSrc
};
return runIframeDocumentTask(iframeDocument, () => processIframeBody(
iframe,
iframeSrc,
iframeDocument,
iframeWindow,
iframeContext
));
};
init();
processIframeTask();
const intervalId = setInterval(processIframeTask, 500);
const observer = new MutationObserver(() => processIframeTask());
observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ["src"] });
vue.onBeforeUnmount(() => {
var _a;
disposed = true;
clearInterval(intervalId);
observer.disconnect();
(_a = currentPage == null ? void 0 : currentPage.iframe) == null ? void 0 : _a.removeEventListener("load", onRootIframeLoad);
chapterController.abort();
});
};
const useCxWorkLogic = async () => {
const logStore = useLogStore();
const questionStore = useQuestionStore();
questionStore.clearQuestion();
logStore.addLog(`进入新版作业页面,开始准备答题`, "primary");
logStore.addLog(`正在解析题目, 请等待5s`, "warning");
await new CxQuestionHandler("zy").init();
};
const NEXT_STEP_TEXT = "下一步";
const isVisibleElement = (element) => {
const style2 = window.getComputedStyle(element);
return style2.display !== "none" && style2.visibility !== "hidden" && style2.opacity !== "0" && element.getClientRects().length > 0;
};
const clickNextStepButton = () => {
const examDocument = _unsafeWindow.document || document;
const buttons = Array.from(examDocument.querySelectorAll(".nextDiv a"));
const nextStepButton = buttons.find((button) => {
var _a, _b;
return isVisibleElement(button) && (((_a = button.getAttribute("onclick")) == null ? void 0 : _a.includes("topreview")) || ((_b = button.textContent) == null ? void 0 : _b.trim()) === NEXT_STEP_TEXT);
});
if (!nextStepButton)
return false;
nextStepButton.click();
return true;
};
const useCxExamLogic = async () => {
const logStore = useLogStore();
const configStore = useConfigStore();
const questionStore = useQuestionStore();
questionStore.clearQuestion();
logStore.addLog("进入新版考试页面,开始准备答题", "primary");
logStore.addLog("正在解析题目,请稍等", "warning");
await new CxQuestionHandler("ks").init();
if (configStore.platformParams.cx.parts[1].params[0].value) {
logStore.addLog("自动切换已开启,正在前往下一题", "success");
await sleep(configStore.otherParams.params[0].value);
if (clickNextStepButton()) {
logStore.addLog("已经到最后一题,正在点击下一步", "success");
return;
}
_unsafeWindow.getTheNextQuestion(1);
} else {
logStore.addLog("已经关闭自动切换,在设置里可更改", "danger");
}
};
class ZhsQuestionHandler extends BaseQuestionHandler {
constructor() {
super();
__publicField(this, "init", async () => {
var _a, _b, _c, _d;
this.questions = [];
this.parseHtml();
if (this.questions.length) {
this.addLog(`成功解析到${this.questions.length}个题目`, "primary");
for (const [index, parsedQuestion] of this.questions.entries()) {
const question = vue.reactive({ ...parsedQuestion, answerStatus: "searching" });
this.addQuestion(question);
const answerData = await getAnswer(question);
const values = getAnswerValues(answerData, question);
if (values == null ? void 0 : values.length) {
question.answer = values;
question.answerStatus = "success";
this.fillQuestion(question);
this.addLog(`第${index + 1}道题搜索成功`, "success");
this.addLog(`剩余次数:${String(((_a = answerData.meta) == null ? void 0 : _a.credit_remaining) ?? "未知")}`, "primary");
} else {
this.addLog(`第${index + 1}道题搜索失败,点击查看原因`, "danger");
question.answerStatus = "error";
question.answer = [((_b = answerData.error) == null ? void 0 : _b.message) || "未查询到答案"];
}
await ((_d = (_c = this._document) == null ? void 0 : _c.querySelectorAll(".switch-btn-box > button")[1]) == null ? void 0 : _d.click());
}
} else
this.addLog("未解析到题目,请刷新重试或进入答题页面", "danger");
});
__publicField(this, "parseHtml", () => {
if (!this._document)
return [];
const questionElements = this._document.querySelectorAll(".examPaper_subject");
this.addQuestions(questionElements);
});
__publicField(this, "fillQuestion", (question) => {
if (!this._window)
return;
if (question.type === "单选题" || question.type === "多选题") {
question.answer.forEach((answer) => {
const optionElement = this.findMatchedOption(question.options, String(answer));
if (optionElement)
this.clickOptionIfNeeded(optionElement);
});
} else if (question.type === "判断题") {
let answer = "true";
if (String(question.answer[0]).match(/(^|,)(正确|是|对|√|T|ri|right|true)(,|$)/)) {
answer = "true";
} else if (String(question.answer[0]).match(/(^|,)(错误|否|错|×|F|wr|wrong|false)(,|$)/)) {
answer = "false";
}
for (const key in question.options) {
if (this.normalizeJudgementText(key) === answer) {
this.clickOptionIfNeeded(question.options[key]);
}
}
} else if (question.type === "填空题" || question.type === "简答题") {
this.fillTextQuestion(question);
} else
;
});
}
extractOptions(optionElements, optionSelector) {
const optionsObject = {};
const optionTexts = [];
optionElements.forEach((optionElement) => {
var _a;
const optionTextContent = this.removeHtml(((_a = optionElement.querySelector(optionSelector)) == null ? void 0 : _a.innerHTML) || "");
optionsObject[optionTextContent] = optionElement;
optionTexts.push(optionTextContent);
});
return [optionsObject, optionTexts];
}
addQuestions(questionElements) {
questionElements.forEach((questionElement) => {
var _a, _b;
const titleElement = questionElement == null ? void 0 : questionElement.querySelector(".subject_describe div,.smallStem_describe p");
const shadowDom = (_b = (_a = titleElement == null ? void 0 : titleElement.__Ivue__) == null ? void 0 : _a._data) == null ? void 0 : _b.shadowDom;
const questionTitle = (shadowDom == null ? void 0 : shadowDom.textContent) || (titleElement == null ? void 0 : titleElement.textContent) || "";
let searchStem = "";
if (shadowDom && typeof shadowDom === "object" && "nodeType" in shadowDom) {
searchStem = extractRawTextFromElement(shadowDom);
} else if (typeof shadowDom === "string") {
searchStem = extractRawTextFromHtml(shadowDom, this._document);
} else if (typeof (shadowDom == null ? void 0 : shadowDom.innerHTML) === "string") {
searchStem = extractRawTextFromHtml(shadowDom.innerHTML, this._document);
}
const optionElements = questionElement == null ? void 0 : questionElement.querySelectorAll(".label");
const [optionsObject, optionTexts] = this.extractOptions(optionElements, ".node_detail");
const questionKind = this.inferQuestionTypeByStructure(questionElement, Array.from(optionElements), optionTexts);
let searchOptions = Array.from(optionElements).map((optionElement) => stripOptionLabel(extractRawTextFromElement(optionElement.querySelector(".node_detail"))));
if (questionKind === "3") {
searchOptions = Array.from(optionElements).map((optionElement, index) => extractJudgementToken(optionElement, searchOptions[index] || optionTexts[index]) || searchOptions[index]);
}
const questionType = this.mapQuestionType(questionKind);
this.questions.push({
element: questionElement,
type: questionType,
title: questionTitle,
optionsText: optionTexts,
options: optionsObject,
searchText: { stem: searchStem, options: searchOptions },
answer: [],
workType: "zhs",
refer: proxyLoc(this._window.location).href
});
});
}
mapQuestionType(type) {
const typeMap = {
"0": "单选题",
"1": "多选题",
"2": "填空题",
"3": "判断题",
"4": "简答题",
"8": "其他"
};
return typeMap[type] || "其他";
}
clickOptionIfNeeded(optionElement) {
const input = optionElement.querySelector("input");
const className = optionElement.getAttribute("class") || "";
if (input == null ? void 0 : input.checked)
return;
if (optionElement.getAttribute("aria-checked") === "true")
return;
if (/\b(active|checked|selected|is-checked)\b/.test(className))
return;
if (optionElement.querySelector(".active, .checked, .selected, .is-checked"))
return;
optionElement.click();
}
fillTextQuestion(question) {
const fields = [
...Array.from(question.element.querySelectorAll("input")),
...Array.from(question.element.querySelectorAll("textarea")),
...Array.from(question.element.querySelectorAll('[contenteditable="true"]'))
].filter((field) => {
if (field instanceof HTMLInputElement) {
const type = (field.getAttribute("type") || "text").toLowerCase();
return ["", "text", "search", "number", "tel", "url", "email"].includes(type) && !field.disabled && !field.hidden;
}
return true;
});
fields.forEach((field, index) => {
const answer = String(question.answer[index] ?? question.answer[0] ?? "");
if (field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement) {
field.value = String(answer);
field.dispatchEvent(new Event("input", { bubbles: true }));
field.dispatchEvent(new Event("change", { bubbles: true }));
return;
}
field.textContent = answer;
field.dispatchEvent(new Event("input", { bubbles: true }));
});
}
}
const hookError = () => {
console.log("hookError");
const oldset = _unsafeWindow.setInterval;
const oldout = _unsafeWindow.setTimeout;
_unsafeWindow.setInterval = function(...args) {
const err = new Error();
if (err.stack && err.stack.indexOf("checkoutNotTrustScript") !== -1) {
return -1;
}
return oldset.call(this, ...args);
};
_unsafeWindow.setTimeout = function(...args) {
const err = new Error();
if (err.stack && err.stack.indexOf("checkoutNotTrustScript") !== -1) {
return -1;
}
return oldout.call(this, ...args);
};
};
class XMLHttpRequestInterceptor {
constructor(urlList, callback) {
__publicField(this, "xhr");
__publicField(this, "originalOpen");
__publicField(this, "originalSend");
__publicField(this, "callback");
this.xhr = new XMLHttpRequest();
this.originalOpen = this.xhr.open;
this.originalSend = this.xhr.send;
this.callback = callback;
this.intercept(urlList);
}
intercept(urlList) {
const self = this;
XMLHttpRequest.prototype.open = function(method, url2) {
self.originalOpen.apply(this, [method, url2]);
const shouldIntercept = urlList.some((urlItem) => url2.includes(urlItem));
if (shouldIntercept) {
self.callback(this.responseText);
}
};
}
}
const useZhsAnswerLogic = async () => {
hookError();
const logStore = useLogStore();
const questionStore = useQuestionStore();
questionStore.clearQuestion();
logStore.addLog(`进入答题页面,开始准备答题`, "primary");
logStore.addLog(`正在解析题目, 请等待5s`, "warning");
new XMLHttpRequestInterceptor(["gateway/t/v1/answer/hasAnswer"], async () => {
await sleep(1);
_unsafeWindow.document.getSelection = function() {
return {
removeAllRanges: function() {
}
};
};
_unsafeWindow.document.onselectstart = true;
_unsafeWindow.document.oncontextmenu = true;
_unsafeWindow.document.oncut = true;
_unsafeWindow.document.oncopy = true;
_unsafeWindow.document.onpaste = true;
await new ZhsQuestionHandler().init();
return true;
});
};
const DEFAULT_CARD_WIDTH = "310px";
const ANSWER_CARD_WIDTH = "630px";
const ANSWER_TAB_NAME = "1";
const _sfc_main$3 = /* @__PURE__ */ vue.defineComponent({
__name: "Index",
emits: ["customEvent"],
setup(__props, { emit: __emit }) {
var _a;
const isShow = vue.ref(false);
const configStore = useConfigStore();
const logStore = useLogStore();
const questionStore = useQuestionStore();
const emit = __emit;
const url2 = proxyLoc(window.location).href;
const cardWidth = vue.computed(() => configStore.menuIndex === ANSWER_TAB_NAME ? ANSWER_CARD_WIDTH : DEFAULT_CARD_WIDTH);
(_a = document.querySelector("li>a.experience:not([onclick])")) == null ? void 0 : _a.click();
logStore.addLog("用户悉知:使用脚本即为完全同意用户协议", "success");
logStore.addLog("脚本加载成功,正在解析网页", "primary");
logStore.addLog("请不要多个脚本同时使用,会有脚本冲突问题", "warning");
logStore.addLog("如果脚本出现异常,请用谷歌、火狐等浏览器", "warning");
const urlLogicPairs = [
{ keyword: "/mycourse/studentstudy", logic: useCxChapterLogic },
{ keyword: "/mooc2/work/dowork", logic: useCxWorkLogic },
{ keyword: "/exam-ans/exam", logic: useCxExamLogic },
{
keyword: "mycourse/stu?courseid",
logic: () => {
logStore.addLog("该页面无任务,请进入章节或答题页面使用", "error");
}
},
{ keyword: "/stuExamWeb.html", logic: useZhsAnswerLogic }
];
const updateVisible = (visible2) => {
isShow.value = visible2;
emit("customEvent", visible2);
};
const executeLogicByUrl = (currentUrl) => {
const matchedPair = urlLogicPairs.find(({ keyword }) => currentUrl.includes(keyword));
if (matchedPair) {
matchedPair.logic();
updateVisible(true);
return;
}
updateVisible(false);
};
executeLogicByUrl(url2);
const tabs = vue.computed(() => [
{
name: "0",
label: "首页",
component: _sfc_main$8,
props: { logList: logStore.logList }
},
{
name: ANSWER_TAB_NAME,
label: "答题",
component: _sfc_main$6,
props: { questionList: questionStore.questionList }
},
{
name: "2",
label: "设置",
component: _sfc_main$7,
props: { globalConfig: configStore }
},
{
name: "3",
label: "教程",
component: Tutorial
},
{
name: "4",
label: "协议",
component: ScriptTip
}
]);
return (_ctx, _cache) => {
const _component_el_tab_pane = vue.resolveComponent("el-tab-pane");
const _component_el_tabs = vue.resolveComponent("el-tabs");
return vue.openBlock(), vue.createElementBlock("div", {
style: vue.normalizeStyle({ width: cardWidth.value }),
class: "card_content"
}, [
vue.createVNode(_component_el_tabs, {
modelValue: vue.unref(configStore).menuIndex,
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => vue.unref(configStore).menuIndex = $event),
class: "demo-tabs"
}, {
default: vue.withCtx(() => [
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(tabs.value, (tab) => {
return vue.openBlock(), vue.createBlock(_component_el_tab_pane, {
key: tab.name,
name: tab.name,
label: tab.label
}, {
default: vue.withCtx(() => [
(vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent(tab.component), vue.mergeProps({ ref_for: true }, tab.props), null, 16))
]),
_: 2
}, 1032, ["name", "label"]);
}), 128))
]),
_: 1
}, 8, ["modelValue"])
], 4);
};
}
});
const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({
__name: "ZoomButtons",
emits: ["toggleZoom"],
setup(__props, { emit: __emit }) {
const emit = __emit;
const toggleZoom = (value) => {
emit("toggleZoom", value);
};
return (_ctx, _cache) => {
const _component_el_icon = vue.resolveComponent("el-icon");
return vue.openBlock(), vue.createElementBlock("div", {
onMousedown: _cache[2] || (_cache[2] = vue.withModifiers(() => {
}, ["stop"]))
}, [
vue.createVNode(_component_el_icon, {
onClick: _cache[0] || (_cache[0] = ($event) => toggleZoom(true)),
class: "zoom-icon",
size: "small"
}, {
default: vue.withCtx(() => [
vue.createVNode(vue.unref(minus_default))
]),
_: 1
}),
vue.createVNode(_component_el_icon, {
onClick: _cache[1] || (_cache[1] = ($event) => toggleZoom(false)),
class: "zoom-icon is-spaced",
size: "small"
}, {
default: vue.withCtx(() => [
vue.createVNode(vue.unref(full_screen_default))
]),
_: 1
})
], 32);
};
}
});
const _hoisted_1 = { class: "overlay" };
const _hoisted_2 = { class: "title" };
const _hoisted_3 = { class: "minus" };
const WINDOW_EDGE_OFFSET = 11;
const PANEL_WIDTH = 334;
const MINIMIZED_PANEL_HEIGHT = 35;
const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({
__name: "layout",
setup(__props) {
const isShow = vue.ref(false);
const configStore = useConfigStore();
const currentPlatformName = vue.computed(() => configStore.platformParams[configStore.platformName].name);
vue.watch(configStore, (newVal) => {
persistConfig(newVal);
}, { deep: true });
const isDragging = vue.ref(false);
const offsetX = vue.ref(0);
const offsetY = vue.ref(0);
const moveStyle = vue.ref({
left: configStore.position.x,
top: configStore.position.y
});
const handleVisibleChange = (visible2) => {
isShow.value = visible2;
};
const startDrag = (event) => {
isDragging.value = true;
const header = event.currentTarget;
offsetX.value = event.clientX - header.getBoundingClientRect().left;
offsetY.value = event.clientY - header.getBoundingClientRect().top;
document.addEventListener("mousemove", drag);
document.addEventListener("mouseup", endDrag);
};
const drag = (event) => {
if (!isDragging.value)
return;
const x = event.clientX - offsetX.value;
const y = event.clientY - offsetY.value;
const nextLeft = `${x - WINDOW_EDGE_OFFSET}px`;
const nextTop = `${y - WINDOW_EDGE_OFFSET}px`;
moveStyle.value = {
left: nextLeft,
top: nextTop
};
configStore.position.x = nextLeft;
configStore.position.y = nextTop;
if (x < 0) {
moveStyle.value.left = "0px";
configStore.position.x = "0px";
}
if (y < 0) {
moveStyle.value.top = "0px";
configStore.position.y = "0px";
}
if (x > window.innerWidth - PANEL_WIDTH) {
const maxLeft = `${window.innerWidth - PANEL_WIDTH}px`;
moveStyle.value.left = maxLeft;
configStore.position.x = maxLeft;
}
if (y > window.innerHeight - MINIMIZED_PANEL_HEIGHT) {
const maxTop = `${window.innerHeight - MINIMIZED_PANEL_HEIGHT}px`;
moveStyle.value.top = maxTop;
configStore.position.y = maxTop;
}
};
const endDrag = () => {
isDragging.value = false;
document.removeEventListener("mousemove", drag);
document.removeEventListener("mouseup", endDrag);
};
return (_ctx, _cache) => {
const _component_el_icon = vue.resolveComponent("el-icon");
const _component_el_tooltip = vue.resolveComponent("el-tooltip");
const _component_el_text = vue.resolveComponent("el-text");
const _component_el_divider = vue.resolveComponent("el-divider");
const _component_el_card = vue.resolveComponent("el-card");
return vue.withDirectives((vue.openBlock(), vue.createElementBlock("div", {
style: vue.normalizeStyle(moveStyle.value),
class: "main-page"
}, [
vue.withDirectives(vue.createElementVNode("div", _hoisted_1, null, 512), [
[vue.vShow, isDragging.value]
]),
vue.createVNode(_component_el_card, {
"close-on-click-modal": false,
"lock-scroll": false,
modal: false,
"show-close": false,
"modal-class": "modal"
}, {
header: vue.withCtx(() => [
vue.createElementVNode("div", {
class: "card-header",
onMousedown: startDrag
}, [
vue.createElementVNode("div", _hoisted_2, [
vue.createElementVNode("span", null, vue.toDisplayString(currentPlatformName.value), 1),
vue.createVNode(_component_el_tooltip, {
teleported: "",
effect: "dark",
placement: "top-start",
content: "注意事项:
请尽量使用新版,不要使用旧版。
",
"raw-content": ""
}, {
default: vue.withCtx(() => [
vue.createVNode(_component_el_icon, {
class: "warning-icon",
size: "small"
}, {
default: vue.withCtx(() => [
vue.createVNode(vue.unref(warning_default))
]),
_: 1
})
]),
_: 1
})
]),
vue.createVNode(_sfc_main$2, {
onToggleZoom: _cache[0] || (_cache[0] = ($event) => vue.unref(configStore).isMinus = $event)
})
], 32)
]),
default: vue.withCtx(() => [
vue.withDirectives(vue.createVNode(_sfc_main$3, { onCustomEvent: handleVisibleChange }, null, 512), [
[vue.vShow, !vue.unref(configStore).isMinus]
]),
vue.withDirectives(vue.createElementVNode("div", _hoisted_3, [
vue.createVNode(_component_el_text, {
type: "info",
size: "small"
}, {
default: vue.withCtx(() => [
vue.createTextVNode("已最小化,点击上方按钮恢复")
]),
_: 1
}),
vue.createVNode(_component_el_divider, {
class: "compact-divider",
"border-style": "dashed"
})
], 512), [
[vue.vShow, vue.unref(configStore).isMinus]
])
]),
_: 1
})
], 4)), [
[vue.vShow, isShow.value]
]);
};
}
});
const _sfc_main = /* @__PURE__ */ vue.defineComponent({
__name: "App",
setup(__props) {
const platformMatchers = [
{ keyword: "chaoxing", platformName: "cx" },
{ keyword: "zhihuishu", platformName: "zhs" }
];
const configStore = useConfigStore();
const url2 = proxyLoc(window.location).href;
const matchedPlatform = platformMatchers.find(({ keyword }) => url2.includes(keyword));
if (matchedPlatform) {
configStore.platformName = matchedPlatform.platformName;
}
return (_ctx, _cache) => {
return vue.openBlock(), vue.createBlock(_sfc_main$1);
};
}
});
const cssLoader = (e) => {
const t = GM_getResourceText(e);
return GM_addStyle(t), t;
};
cssLoader("ElementPlus");
const layoutCss = '.main-page .guide-page{box-sizing:border-box;max-height:min(400px,calc(100vh - 160px));max-height:min(400px,calc(100dvh - 160px));overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain;padding:2px 4px 2px 0;color:#4e5969;font-size:12px;line-height:1.7;scrollbar-width:thin;scrollbar-color:#c7d7eb transparent}.main-page .guide-page:focus-visible{outline:2px solid #176ae5;outline-offset:2px;border-radius:8px}.main-page .guide-header{margin:0 0 10px;padding:11px 12px;border:1px solid #d9e8fc;border-radius:9px;background:linear-gradient(120deg,#edf5ff 0%,#f8fbff 100%)}.main-page .guide-heading-row{display:flex;align-items:center;justify-content:space-between;gap:8px}.main-page .guide-title{margin:0;color:#174b94;font-size:13px;font-weight:600;line-height:1.6}.main-page .guide-tag{flex-shrink:0;padding:1px 7px;border:1px solid #d4e5fc;border-radius:20px;background-color:#fff;color:#2262b5;font-size:10px;line-height:18px}.main-page .guide-subtitle{margin:3px 0 0;color:#61758e;font-size:11px}.main-page .guide-list{display:grid;gap:8px;margin:0;padding:0;list-style:none}.main-page .guide-card{min-width:0;padding:10px;border:1px solid #e4eaf2;border-radius:8px;background-color:#fff}.main-page .guide-card-heading{display:flex;align-items:center;gap:8px;margin-bottom:6px}.main-page .guide-number{display:inline-flex;align-items:center;justify-content:center;flex:0 0 24px;height:24px;border-radius:7px;background-color:#eaf3ff;color:#176ae5;font-size:11px;font-weight:600;line-height:1;font-variant-numeric:tabular-nums}.main-page .guide-card-title{margin:0;color:#263a55;font-size:12px;font-weight:600;line-height:1.6}.main-page .guide-copy{margin:0;overflow-wrap:anywhere}.main-page .guide-flow{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px;margin:10px 0 0;padding:9px 4px 7px;border-radius:7px;background-color:#f3f7fd;list-style:none}.main-page .guide-flow-step{position:relative;display:flex;flex-direction:column;align-items:center;gap:4px;color:#3a5a83;font-size:10px;line-height:18px;text-align:center}.main-page .guide-flow-step+.guide-flow-step:before{position:absolute;top:8px;left:-5px;width:5px;height:5px;border-top:1px solid #9cb8da;border-right:1px solid #9cb8da;content:"";transform:rotate(45deg)}.main-page .guide-flow-number{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:1px solid #d5e4f8;border-radius:50%;background-color:#fff;color:#176ae5;font-size:11px;font-weight:600;line-height:1}.main-page{--app-font-family: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--el-font-family: var(--app-font-family);z-index:100003;position:fixed;color:#1f2329;font-family:var(--app-font-family)!important;font-size:14px;line-height:1.5715;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility}.main-page *,.main-page input,.main-page button,.main-page textarea{font-family:var(--app-font-family)!important;letter-spacing:0}.main-page .el-card,.main-page .el-tabs,.main-page .el-text,.main-page .el-button,.main-page .el-input,.main-page .el-input__inner,.main-page .el-input-number,.main-page .el-table{font-family:var(--app-font-family)!important}.main-page .overlay{position:fixed;top:0;left:0;right:0;bottom:0;z-index:1001}.main-page .el-card{border:0}.main-page .card-header{display:flex;justify-content:space-between;flex-direction:row;align-items:center;margin:0;padding:0;cursor:move}.main-page .card-header .title{font-size:14px;display:flex;align-items:center;justify-content:center;font-weight:500}.main-page .warning-icon{margin-left:5px}.main-page .zoom-icon{cursor:pointer}.main-page .zoom-icon.is-spaced{margin-left:8px}.main-page .minus{margin:5px 10px -10px 0}.main-page .compact-divider{margin:0}.main-page .demo-tabs{display:initial}.main-page .el-card__header{background-color:#1f71e0;color:#fff;padding:7px 10px 7px 16px;margin:0}.main-page .el-card__body{padding:0 16px 20px}.main-page .el-tabs__nav-wrap:after{height:1px}.main-page .el-tabs__active-bar{background-color:#176ae5}.main-page .el-tabs__item{font-size:13px;height:34px}.main-page .el-tabs__item.is-top{font-weight:400;color:#4e5969;padding:0 8px 0 12px}.main-page .el-tabs__item.is-active{font-weight:500;color:#176ae5;padding:0 8px 0 12px}.main-page .script-home{padding-top:2px}.main-page .announcement-board{box-sizing:border-box;margin:2px 0 10px;padding:8px 10px;border:1px solid #bae0ff;border-radius:6px;background-color:#e6f4ff}.main-page .announcement-heading{display:flex;align-items:center;gap:6px;margin-bottom:4px;color:#0958d9;font-size:12px;font-weight:600;line-height:20px}.main-page .announcement-heading:before{content:"";width:6px;height:6px;flex:0 0 auto;border-radius:50%;background-color:#1677ff}.main-page .announcement-list{display:grid;gap:3px;margin:0;padding:0;list-style:none}.main-page .announcement-item{color:#1f2329;font-size:12px;line-height:20px;word-break:break-word}.main-page .log .el-text{font-weight:400;white-space:normal}.main-page .log-time{font-weight:400}.main-page .log-action-link{color:#176ae5;cursor:pointer;text-decoration:none}.main-page .log-action-link:hover{color:#409eff;text-decoration:underline}.main-page .log-divider{margin:0}.main-page .token-input,.main-page .question-list{font-size:12px}.main-page .token-label{border-radius:0}.main-page .question_table{width:625px}.main-page .answer-legend{display:flex;flex-wrap:wrap;align-items:center;gap:6px 16px;padding:10px 2px 8px;font-size:11px;line-height:18px}.main-page .answer-legend>span{display:inline-flex;align-items:center;gap:5px}.main-page .answer-legend>span:before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}.main-page .answer-result{line-height:1.7;overflow-wrap:anywhere}.main-page .answer-result--success{color:#15803d}.main-page .answer-result--searching{color:#a15c08}.main-page .answer-result--pending{color:#697586}.main-page .answer-result--error{color:#c73e38}.main-page .setting{margin-top:-8px;font-size:14px}.main-page .setting-section-title{font-size:13px}.main-page .setting-checkbox{margin-bottom:6px}.main-page .setting-number{margin-top:6px}.main-page .setting .el-form-item{margin-bottom:0}\n';
const hookWebpack = () => {
let originCall = _unsafeWindow.Function.prototype.call;
_unsafeWindow.Function.prototype.call = function(...args) {
var _a, _b;
const result = originCall.apply(this, args);
if (((_b = (_a = args[0]) == null ? void 0 : _a.a) == null ? void 0 : _b.version) === "2.5.0") {
const install = args[1].exports.a.install;
args[1].exports.a.install = function(...installArgs) {
installArgs[0].mixin({
mounted: function() {
this.$el["__Ivue__"] = this;
}
});
return install.apply(this, installArgs);
};
return result;
}
return result;
};
};
const BOOTSTRAP_INTERVAL = 100;
const COMPLETE_READY_STATE = "complete";
const ELEMENT_PLUS_STYLE_RESOURCE = "ElementPlusStyle";
const url = proxyLoc(_unsafeWindow.location).href;
if (url.includes("zhihuishu.com")) {
hookWebpack();
hookError();
}
const createStyleSheet = (cssText) => {
const sheet = new CSSStyleSheet();
sheet.replaceSync(cssText);
return sheet;
};
const createShadowMountNode = () => {
const shadowHost = document.createElement("div");
const mountNode = document.createElement("div");
const shadowRoot = shadowHost.attachShadow({ mode: "closed" });
document.body.append(shadowHost);
shadowRoot.appendChild(mountNode);
shadowRoot.adoptedStyleSheets = [
createStyleSheet(_GM_getResourceText(ELEMENT_PLUS_STYLE_RESOURCE) ?? ""),
createStyleSheet(layoutCss)
];
return mountNode;
};
const mountApp = () => {
const app = vue.createApp(_sfc_main);
app.use(pinia.createPinia());
app.use(ElementPlus);
app.mount(createShadowMountNode());
};
const timer = setInterval(() => {
if (document.readyState !== COMPLETE_READY_STATE)
return;
clearInterval(timer);
mountApp();
}, BOOTSTRAP_INTERVAL);
})(Vue, Pinia, rxjs, md5, ElementPlus);
try { unsafeWindow.__cxhLegacy = 'ok'; } catch (e) {}
}
} catch (e) { window.__cxhFatal('原刷课脚本初始化失败', e); }
// ------------------------------------------------------------------
// 自检:课程卡片都出来了、面板还没出现 —— 说明面板没挂上,直接说出来
// ------------------------------------------------------------------
setTimeout(function () {
try {
var hasCards = !!document.querySelector('li[cid][clazzid]');
var hasPanel = !!document.getElementById('cxh-panel');
if (!hasPanel && window.top && window.top !== window) {
try { hasPanel = !!window.top.document.getElementById('cxh-panel'); } catch (e) {}
}
if (hasCards && !hasPanel) {
window.__cxhFatal('面板没有挂上', new Error('页面有课程卡片,但没有任何面板元素。URL=' + location.href));
}
} catch (e) {}
}, 15000);