您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
乐学脚本儿大礼包
// ==UserScript== // @name [北京理工大学 BIT] 乐学增强脚本整合包 // @namespace http://tampermonkey.net/ // @version 0.2.3 // @description 乐学脚本儿大礼包 // @license GPL-3.0-or-later // @supportURL https://github.com/windlandneko/ // @author Charlie, Y.D.X. // @match https://lexue.bit.edu.cn/* // @grant none // ==/UserScript== // document.querySelector('.header-main > .container').remove() if (location.href.includes('submit.php')) // submit.php 自动点击 查看结果 按钮 onload = () => document.querySelector('a[href^=result]').click() ;(() => { // * 删除姓名中的空格 const selectors = [ "a[href*='/user/view.php'], #page-navbar a[href*='/user/profile.php']", // 通用 '.usertext', // 通用:header 中头像的左边 '.fullname', // 首页 - 已登录用户 'author-info > .text-truncate', // forum/view.php '#page-header h1, head title', // user/profile.php '#page-content .userprofile .page-header-headings > h2', // user/view.php ] function format(str) { return str.match(/[a-zA-Z]/) ? str : str.replaceAll(' ', '') } // assign/view.php // 整理评分人名字这一格的格式。 if (document.querySelector('.feedback table.generaltable')) { const cell_gradedBy = document.querySelector( '.feedback table.generaltable > tbody > tr:last-child > td:last-child' ) const user_url = cell_gradedBy.querySelector('a').href const user_name = cell_gradedBy.textContent cell_gradedBy.innerHTML = `<a href="${user_url}">${user_name}</a >` } // grade/report/(overview|user)/index.php if ( location.pathname.match(/^\/grade\/report\/(overview|user)\/index\.php$/) ) { const headline = document.querySelector('#maincontent + h2') if (headline) { headline.textContent = headline.textContent.replace( /^(..报表) - (.+)$/, (_, prefix, name) => prefix + ' - ' + format(name) ) } } document.querySelectorAll(selectors.join(', ')).forEach(el => { el.textContent = format(el.textContent) }) })() ;(() => { if (!location.href.includes('result.php')) return // result.php 结果页自动刷新 addEventListener('load', () => { const titles = document.querySelectorAll('#region-main h3') if ( titles.length <= 1 || !titles[titles.length - 1].textContent.includes('测试结果') ) setTimeout(() => location.reload(), 1000) }) })() ;(() => { if (!location.href.includes('result.php')) return // result.php 设置测试点颜色 for (const row of document.querySelectorAll( '#test-result-detail > table > tbody > tr' )) { const result = row.querySelector('.cell.c12').textContent let color if (result.includes('RE:')) color = 'LightBlue' if (result.includes('FPE:')) color = 'BlanchedAlmond' if (result.includes('TLE:')) color = 'Tomato' if (result.includes('KS:')) color = 'Violet' if (color) for (const column of row.querySelectorAll('.cell')) column.style.backgroundColor = color } })() ;(() => { if (!location.href.includes('view.php')) return // view.php 自动折叠公告 function add_style_sheet() { const style = document.createElement('style') style.textContent = `\ [role=main] > .course-content > .collapse-content { height: 15em; overflow: auto; } .hider { margin-top: -4em; background: linear-gradient(#0000, lightgray); height: 4em; display: grid; place-content: center; } #show-all { z-index: 2; } ` document.head.appendChild(style) } const course_content = document.querySelector('[role=main] > .course-content') if (!course_content) return const front_content = course_content.querySelector( '.course-content > ul:first-child' ) const single_section = course_content.querySelector('.single-section') const collapse = 'collapse-content' // moodle 已经占用了 .collapse:not(.show) if (front_content.clientHeight > (1 / 3) * window.innerHeight) { add_style_sheet() front_content.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(h => { h.textContent = h.textContent.trim() }) front_content.classList.add(collapse) const div = document.createElement('div') div.classList.add('hider') div.innerHTML = '<button id="show-all">▼展开</button>' course_content.insertBefore(div, single_section) div.querySelector('#show-all').addEventListener('click', () => { front_content.classList.remove(collapse) div.hidden = true }) } })() ;(() => { if (!location.href.includes('view.php')) return // view.php 题目状态颜色显示 function add_style_sheet() { const sheet = document.createElement('style') sheet.innerHTML = `\ .problem-state-true, .quiz-state-true { color: #52C41A; } .problem-state-null, .quiz-state-false .problem-state-false { font-weight: bold; color: #E74C3C; } .problem-state-undefined { color: purple; } .state-pending { animation: pending 2s infinite ease; } .state-fetcherror { border: 3px solid red; } @keyframes pending { 0% { background-color: #eee; } 50% { background-color: #ddd; } 100% { background-color: #eee; } } ` document.head.appendChild(sheet) } async function check_all_problems() { for await (const e of document.querySelectorAll( "#region-main a[href*='lexue.bit.edu.cn/mod/programming/view']" )) { e.classList.add('state-pending') const result = await get_problem_state( new URL(e.href).searchParams.get('id') ).catch(err => e.classList.add('state-fetcherror')) e.classList.remove('state-pending') e.classList.add('quiz-state-' + String(result)) } } async function check_all_quizzes() { for await (const e of document.querySelectorAll( "#region-main a[href*='lexue.bit.edu.cn/mod/quiz/view']" )) { e.classList.add('state-pending') const result = await get_quiz_state(e.href).catch(err => e.classList.add('state-fetcherror') ) e.classList.remove('state-pending') e.classList.add('problem-state-' + String(result)) } } const parser = new DOMParser() /** * 获取编程题的情况 * @param {String} problem_id 编程题的 id * @returns null 尚未提交 * @returns undefined 正在排队或正在编译 * @returns true 全部通过 * @returns false 已提交但尚未全部通过 */ async function get_problem_state(problem_id) { const response = await fetch( document.location.origin + `/mod/programming/result.php?id=${problem_id}` ).then(e => e.text()) const html = parser.parseFromString(response, 'text/html') const headings = html.querySelectorAll('#region-main h3') if (headings.length <= 1) return null if (headings[headings.length - 1].textContent !== '测试结果') return undefined const result = html.querySelector( '#test-result-detail > p:first-child' ).textContent const data = result.match( /测试结果:共 (?<total>\d+) 个测试用例,您的程序通过了其中的 (?<accepted>\d+) 个,未能通过的有 (?<rejected>\d+) 个。/ ) return data.groups.rejected == 0 } /** * 获取测验的情况 * @param {String} href 测验的 URL * @returns true 已完成 * @returns false 未完成 */ async function get_quiz_state(href) { const response = await fetch(href).then(e => e.text()) const heading = parser .parseFromString(response, 'text/html') .querySelector('[role=main] > h3') return heading && heading.textContent == '您上次尝试的概要' } add_style_sheet() check_all_problems() check_all_quizzes() })() ;(async () => { if (!location.href.includes('mod/programming')) return // mod/programming 代码展示增强 const style = document.createElement('style') style.textContent = `\ .path-mod-programming td.programming-io li::marker { content: ''; } .path-mod-programming td.programming-io li { font-family: 'Fira Code', 'Courier New', Courier, monospace; line-height: normal; } pre.custom-pre { margin: 0.5em 0; padding: 0.3em 0.5em; border: #ddd solid 1px; background: #f8f8f8; border-radius: 3px; overflow: auto; font-size: 0.875em; font-family: monospace; } pre.custom-pre > div { overflow: hidden !important; height: unset !important; width: unset !important; } pre.custom-pre .ret { background: none !important; user-select: none !important; opacity: 0.3; } textarea#id_code { width: inherit; font-family: monospace; } .dp-highlighter ol { counter-reset: count; } .dp-highlighter ol li { line-height: unset; counter-increment: count; background-color: #f8f8f8 !important; } .dp-highlighter ol li::marker { content: counter(count, decimal-leading-zero) " "; } .path-mod-programming #codeview { overflow: auto; } button.copy-button { background-color: #4285F4; color: #ffffff; border-radius: 4px; margin: 0.2em 0.8em; padding: 0.2em 0.5em; font-size: 0.8em; font-family: initial; min-height: auto; line-height: normal; transition: background-color 150ms ease; } button.copy-button:hover { background-color: #669DF6; } button.copy-button:focus { background-color: #4285F4; } button.copy-button:active { background-color: #2A4D80; } .testcase-table tbody tr { display: flex; } .testcase-table tbody tr > td { flex: 1; background-color: #ffffff !important; } ` document.head.appendChild(style) // IndexedDB Cache const dbName = 'lexueCache' const storeName = 'sampleCodeCache' async function dropDB() { return new Promise((res, rej) => { const request = indexedDB.deleteDatabase(dbName) request.onsuccess = res request.onerror = rej }) } const version = localStorage.getItem('lexue-patch-version') if (parseInt(version) < 1) await dropDB() localStorage.setItem('lexue-patch-version', 1) async function openDB() { // drop the database first return new Promise((res, rej) => { const request = indexedDB.open(dbName) request.onsuccess = event => res(event.target.result) request.onerror = event => rej(event.target.error) request.onupgradeneeded = event => { const db = event.target.result if (!db.objectStoreNames.contains(storeName)) db.createObjectStore(storeName, { keyPath: 'key' }) } }) } async function getCache(key) { return new Promise(async (res, rej) => { const db = await openDB() const store = db.transaction(storeName, 'readonly').objectStore(storeName) const request = store.get(key) request.onsuccess = event => res(event.target.result ? event.target.result.code : null) request.onerror = event => rej(event.target.error) }) } async function setCache(key, code) { return new Promise(async (res, rej) => { const db = await openDB() const store = db .transaction(storeName, 'readwrite') .objectStore(storeName) const request = store.put({ key, code }) request.onsuccess = () => res() request.onerror = event => rej(event.target.error) }) } if (document.querySelector('.testcase-table')) { document.querySelectorAll('.testcase-table .c0').forEach(e => e.remove()) document.querySelector('.testcase-table thead').style.display = 'none' } const is3column = document.querySelector('#test-result-detail') document .querySelectorAll("td.programming-io > a[href*='download=0']") .forEach(async (a, id) => { // Description const description = document.createElement('span') description.textContent = is3column ? `${['输入', '答案', '输出'][id % 3]} #${~~(id / 3) + 1}` : `${['输入', '输出'][id % 2]} #${~~(id / 2) + 1}` description.style.fontWeight = 'bold' description.style.backgroundColor = 'unset' // Copy button const copy_button = document.createElement('button') copy_button.textContent = '复制' copy_button.classList.add('copy-button') // Code block const pre = document.createElement('pre') pre.classList.add('custom-pre') pre.style.color = '#bbbbbb' pre.textContent = '(获取中)' a.parentNode.prepend(description, copy_button) a.parentNode.replaceChild(pre, a.parentNode.lastChild) a.remove() const key = new URL(a.href).search let str = await getCache(key) if (!str) { const data = await fetch(a.href).then(res => res.text()) await setCache(key, data) str = data } if (str.length == 0) pre.textContent = '(输入为空)' else pre.textContent = '', pre.style.color = 'inherit' str = str.replace(/\r/g, '') let hasTrailingLineBreak = str[str.length - 1] == '\n' str.split('\n').forEach((line, lineNo, lineArray) => { // 末尾有换行,且在最后一行,那么省略渲染最后一个空行 if(hasTrailingLineBreak && lineNo == lineArray.length - 1) return const div = document.createElement('div') pre.appendChild(div) const code = document.createElement('code') code.textContent = line div.append(code) // 末尾无换行,且在最后一行,那么不渲染换行符 if(!hasTrailingLineBreak && lineNo == lineArray.length - 1) return const linebreak = document.createElement('span') linebreak.textContent = '↵' linebreak.classList.add('ret') div.append(linebreak) }) copy_button.addEventListener('click', () => navigator.clipboard.writeText(str).then( () => { if (copy_button.textContent == '复制') setTimeout(() => (copy_button.textContent = '复制'), 1000) copy_button.textContent = '复制成功' }, err => { if (copy_button.textContent == '复制') setTimeout(() => (copy_button.textContent = '复制'), 2000) copy_button.textContent = '复制失败' } ) ) }) })() ;(() => { if (!location.href.includes('result.php')) return // result.php 显示优化 if (!document.querySelector('#test-result-detail')) return if(document.querySelector('.compilemessage')) document.querySelector('.compilemessage').style.backgroundColor = '#ffe4e4' const el = document.querySelector('#test-result-detail').previousSibling el.style.fontWeight = 'bold' el.style.backgroundColor = '#ddd' el.style.margin = '0.5em 0' el.style.padding = '0.2em 0.3em' el.style.borderRadius = '4px' const text = document.querySelector( '#test-result-detail > p:first-child' ).textContent const data = text.match( /测试结果:共 (?<total>\d+) 个测试用例,您的程序通过了其中的 (?<accepted>\d+) 个,未能通过的有 (?<rejected>\d+) 个。/ ) if (data) { const { total, accepted } = data.groups el.textContent += ` (${accepted} / ${total})` if (accepted == total) { el.textContent += ' Accepted!' el.style.color = '#fff' el.style.backgroundColor = '#42ab0e' } else { el.textContent += ' Wrong Answer.' el.style.backgroundColor = '#ffe4e4' el.style.color = '#ff0424' } } document.querySelectorAll('#test-result-detail > p').forEach(e => e.remove()) })() ;(() => { if (!location.href.includes('history.php')) return // history.php 显示优化 const update = () => { const parser = new DOMParser() const el = document.querySelector('.dp-highlighter') const code = parser.parseFromString( `<div>${el.highlighter.originalCode}</div>`, 'text/html' ).body.textContent const copy_button = document.createElement('button') copy_button.textContent = '复制' copy_button.classList.add('copy-button') document.querySelector('.dp-highlighter .tools').replaceWith(copy_button) copy_button.addEventListener('click', () => navigator.clipboard.writeText(code).then( () => { if (copy_button.textContent == '复制') setTimeout(() => (copy_button.textContent = '复制'), 1000) copy_button.textContent = '复制成功' }, err => { if (copy_button.textContent == '复制') setTimeout(() => (copy_button.textContent = '复制'), 2000) copy_button.textContent = '复制失败' } ) ) } document .querySelectorAll('#submitlist') .forEach(el => el.addEventListener('click', () => setTimeout(update, 100))) addEventListener('load', update) })()