// ==UserScript== // @name LeetCodeRating|显示力扣周赛难度分 // @namespace https://github.com/zhang-wangz // @version 3.0.3 // @license MIT // @description LeetCodeRating 力扣周赛分数显现和相关力扣小功能,目前浏览器更新规则,使用该插件前请手动打开浏览器开发者模式再食用~ // @author 小东是个阳光蛋(力扣名) // @leetcodehomepage https://leetcode.cn/u/runonline/ // @homepageURL https://github.com/zhang-wangz/LeetCodeRating // @contributionURL https://www.showdoc.com.cn/2069209189620830 // @run-at document-end // @match *://*leetcode.cn/* // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_openInTab // @grant GM_notification // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_getResourceText // @connect zerotrac.github.io // @connect raw.gitmirror.com // @connect hub.gitmirror.com // @connect raw.githubusercontents.com // @connect raw.githubusercontent.com // @require https://unpkg.com/jquery@3.5.1/dist/jquery.min.js // @require https://unpkg.com/layui@2.9.6/dist/layui.js // @grant unsafeWindow // @downloadURL https://update.greasyfork.icu/scripts/450890/LeetCodeRating%EF%BD%9C%E6%98%BE%E7%A4%BA%E5%8A%9B%E6%89%A3%E5%91%A8%E8%B5%9B%E9%9A%BE%E5%BA%A6%E5%88%86.user.js // @updateURL https://update.greasyfork.icu/scripts/450890/LeetCodeRating%EF%BD%9C%E6%98%BE%E7%A4%BA%E5%8A%9B%E6%89%A3%E5%91%A8%E8%B5%9B%E9%9A%BE%E5%BA%A6%E5%88%86.meta.js // ==/UserScript== (async function () { // 分离用户方法 function userScript() { 'use strict'; let version = '3.0.3'; let pbstatusVersion = 'version16'; // xhr劫持时使用,保留原始 const dummySend = XMLHttpRequest.prototype.send; const originalOpen = XMLHttpRequest.prototype.open; // 保留所有observe,每次触发都删除旧的 const observerMap = new WeakMap(); // css 渲染 $(document.body).append( `` ); // 页面相关url const allUrl = 'https://leetcode.cn/problemset/.*'; const pblistUrl = 'https://leetcode.cn/problem-list/.*'; const pbUrl = 'https://leetcode.{2,7}/problems/.*'; // 限定pbstatus使用, 不匹配题解链接 const pbSolutionUrl = 'https://leetcode.{2,7}/problems/.*/solution.*'; const searchUrl = 'https://leetcode.cn/search/.*'; const studyUrl = 'https://leetcode.cn/studyplan/.*'; const problemUrl = 'https://leetcode.cn/problemset'; const discussUrl = 'https://leetcode.cn/discuss/.*'; // req相关url const lcnojgo = 'https://leetcode.cn/graphql/noj-go/'; const lcgraphql = 'https://leetcode.cn/graphql/'; const chContestUrl = 'https://leetcode.cn/contest/'; const zhContestUrl = 'https://leetcode.com/contest/'; // 灵茶相关url const teaSheetUrl = 'https://docs.qq.com/sheet/DWGFoRGVZRmxNaXFz'; // 因为ui更新,暂时去除,没有位置存放当前位置了 // const lc0x3fsolveUrl = "https://huxulm.github.io/lc-rating/search" // 用于延时函数的通用id let id = ''; // 制片人url, 通过接口从version.json拿取 let papermanpic = ''; // rank 相关数据 let t2rate = JSON.parse(GM_getValue('t2ratedb', '{}').toString()); // pbstatus数据 let pbstatus = JSON.parse(GM_getValue('pbstatus', '{}').toString()); // 题目名称-id ContestID_zh-ID // 中文 let pbName2Id = JSON.parse(GM_getValue('pbName2Id', '{}').toString()); // 英文 let pbNamee2Id = JSON.parse(GM_getValue('pbNamee2Id', '{}').toString()); // preDate为更新分数使用,preDate1为更新版本使用 let preDate = GM_getValue('preDate', ''); let preDate1 = GM_getValue('preDate1', ''); // level数据 let levelData = JSON.parse(GM_getValue('levelData', '{}').toString()); // 中文 let levelTc2Id = JSON.parse(GM_getValue('levelTc2Id', '{}').toString()); // 英文 let levelTe2Id = JSON.parse(GM_getValue('levelTe2Id', '{}').toString()); // 是否使用动态布局 let localVal = localStorage.getItem('used-dynamic-layout'); let isDynamic = localVal != null ? localVal.includes('true') : false; // 判断observer是否已存在,如果存在,则断开重新创建 function observerReplace(item, newObserver) { const oldObserver = observerMap.get(item); if (oldObserver) { oldObserver.disconnect(); } observerMap.set(item, newObserver); } // ElementGetter依赖相关 let ElementGetter = (function () { const _jQuery = Symbol('jQuery'); const _window = Symbol('window'); const _matches = Symbol('matches'); const _MutationObs = Symbol('MutationObs'); const _listeners = Symbol('listeners'); const _addObserver = Symbol('addObserver'); const _addFilter = Symbol('addFilter'); const _removeFilter = Symbol('removeFilter'); const _query = Symbol('query'); const _getOne = Symbol('getOne'); const _getList = Symbol('getList'); class ElementGetter { [_addObserver](target, callback) { const observer = new this[_MutationObs](mutations => { for (const mutation of mutations) { if (mutation.type === 'attributes') { callback(mutation.target); if (observer.canceled) return; } for (const node of mutation.addedNodes) { if (node instanceof Element) callback(node); if (observer.canceled) return; } } }); observer.canceled = false; observer.observe(target, { childList: true, subtree: true, attributes: true }); return () => { observer.canceled = true; observer.disconnect(); }; } [_addFilter](target, filter) { let listener = this[_listeners].get(target); if (!listener) { listener = { filters: new Set(), remove: this[_addObserver](target, el => { listener.filters.forEach(f => f(el)); }) }; this[_listeners].set(target, listener); } listener.filters.add(filter); } [_removeFilter](target, filter) { const listener = this[_listeners].get(target); if (!listener) return; listener.filters.delete(filter); if (!listener.filters.size) { listener.remove(); this[_listeners].delete(target); } } [_query](all, selector, parent, includeParent) { const $ = this[_jQuery]; if ($) { let jNodes = includeParent ? $(parent) : $([]); jNodes = jNodes.add([...parent.querySelectorAll('*')]).filter(selector); if (all) { return $.map(jNodes, el => $(el)); } else { return jNodes.length ? $(jNodes.get(0)) : null; } } else { const checkParent = includeParent && this[_matches].call(parent, selector); if (all) { const result = checkParent ? [parent] : []; result.push(...parent.querySelectorAll(selector)); return result; } else { return checkParent ? parent : parent.querySelector(selector); } } } [_getOne](selector, parent, timeout) { return new Promise(resolve => { const node = this[_query](false, selector, parent, false); if (node) return resolve(node); let timer; const filter = el => { const node = this[_query](false, selector, el, true); if (node) { this[_removeFilter](parent, filter); timer && clearTimeout(timer); resolve(node); } }; this[_addFilter](parent, filter); if (timeout > 0) { timer = setTimeout(() => { this[_removeFilter](parent, filter); resolve(null); }, timeout); } }); } [_getList](selectorList, parent, timeout) { return Promise.all( selectorList.map(selector => this[_getOne](selector, parent, timeout)) ); } constructor(jQuery) { this[_jQuery] = jQuery && jQuery.fn && jQuery.fn.jquery ? jQuery : null; this[_window] = window.unsafeWindow || document.defaultView || window; const elProto = this[_window].Element.prototype; this[_matches] = elProto.matches || elProto.matchesSelector || elProto.mozMatchesSelector || elProto.oMatchesSelector || elProto.webkitMatchesSelector; this[_MutationObs] = this[_window].MutationObserver || this[_window].WebkitMutationObserver || this[_window].MozMutationObserver; this[_listeners] = new WeakMap(); } get(selector, ...args) { const parent = (typeof args[0] !== 'number' && args.shift()) || this[_window].document; const timeout = args[0] || 0; if (Array.isArray(selector)) { return this[_getList](selector, parent, timeout); } else { return this[_getOne](selector, parent, timeout); } } each(selector, ...args) { const parent = (typeof args[0] !== 'function' && args.shift()) || this[_window].document; const callback = args[0]; const refs = new WeakSet(); const nodes = this[_query](true, selector, parent, false); for (const node of nodes) { refs.add(this[_jQuery] ? node.get(0) : node); if (callback(node, false) === false) return; } const filter = el => { const nodes = this[_query](true, selector, el, true); for (const node of nodes) { const _el = this[_jQuery] ? node.get(0) : node; if (!refs.has(_el)) { refs.add(_el); if (callback(node, true) === false) { return this[_removeFilter](parent, filter); } } } }; this[_addFilter](parent, filter); } create(domString, parent) { const template = this[_window].document.createElement('template'); template.innerHTML = domString; const node = template.content.firstElementChild || template.content.firstChild; parent ? parent.appendChild(node) : node.remove(); return node; } } return ElementGetter; })(); // 监听相关, 监听之后提出变化并且重启插件 let debounceTimer = null; let isSelfChanging = false; const observedElements = new WeakMap(); function observeIfNeeded(target) { if (!target || !(target instanceof Node)) return; if (observedElements.has(target)) return; const observer = new MutationObserver(mutationsList => { if (isSelfChanging) return; if (debounceTimer) return; console.log('内容变化,执行 clearAndStart'); clearAndStart(location.href, 500, false); debounceTimer = setTimeout(() => { debounceTimer = null; }, 5000); // 连续变化时只触发一次 }); observer.observe(target, { childList: true, characterData: true, subtree: true }); observedElements.set(target, observer); } function getPbNameId(pbName) { pbName2Id = JSON.parse(GM_getValue('pbName2Id', '{}').toString()); pbNamee2Id = JSON.parse(GM_getValue('pbNamee2Id', '{}').toString()); let id = null; if (pbName2Id[pbName]) { id = pbName2Id[pbName]; } else if (pbNamee2Id[pbName]) { id = pbNamee2Id[pbName]; } return id; } function getLevelId(pbName) { levelTc2Id = JSON.parse(GM_getValue('levelTc2Id', '{}').toString()); levelTe2Id = JSON.parse(GM_getValue('levelTe2Id', '{}').toString()); if (levelTc2Id[pbName]) { return levelTc2Id[pbName]; } if (levelTe2Id[pbName]) { return levelTe2Id[pbName]; } return null; } // 同步函数 function waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector) { let targetNodes, btargetsFound; if (typeof iframeSelector == 'null') targetNodes = $(selectorTxt); else targetNodes = $(iframeSelector).contents().find(selectorTxt); if (targetNodes && targetNodes.length > 0) { btargetsFound = true; targetNodes.each(function () { let jThis = $(this); let alreadyFound = jThis.data('alreadyFound') || false; if (!alreadyFound) { let cancelFound = actionFunction(jThis); if (cancelFound) btargetsFound = false; else jThis.data('alreadyFound', true); } }); } else { btargetsFound = false; } let controlObj = waitForKeyElements.controlObj || {}; let controlKey = selectorTxt.replace(/[^\w]/g, '_'); let timeControl = controlObj[controlKey]; if (btargetsFound && bWaitOnce && timeControl) { clearInterval(timeControl); delete controlObj[controlKey]; } else { if (!timeControl) { timeControl = setInterval(function () { waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector); }, 300); controlObj[controlKey] = timeControl; } } waitForKeyElements.controlObj = controlObj; } let ajaxReq = (type, reqUrl, headers, data, successFuc, withCredentials = true) => { $.ajax({ // 请求方式 type: type, // 请求的媒体类型 contentType: 'application/json;charset=UTF-8', // 请求地址 url: reqUrl, // 数据,json字符串 data: data != null ? JSON.stringify(data) : null, // 同步方式 async: false, xhrFields: { withCredentials: true }, headers: headers, // 请求成功 success: function (result) { successFuc(result); }, // 请求失败,包含具体的错误信息 error: function (e) { console.log(e.status); console.log(e.responseText); } }); }; // 刷新菜单 script_setting(); // 注册urlchange事件 initUrlChange()(); // 常量数据 const regDiss = '.*//leetcode.cn/problems/.*/discussion/.*'; const regSovle = '.*//leetcode.cn/problems/.*/solutions/.*'; const regPbSubmission = '.*//leetcode.cn/problems/.*/submissions/.*'; // 监听urlchange事件定义 function initUrlChange() { let isLoad = false; const load = () => { if (isLoad) return; isLoad = true; const oldPushState = history.pushState; const oldReplaceState = history.replaceState; history.pushState = function pushState(...args) { const res = oldPushState.apply(this, args); window.dispatchEvent(new Event('urlchange')); return res; }; history.replaceState = function replaceState(...args) { const res = oldReplaceState.apply(this, args); window.dispatchEvent(new Event('urlchange')); return res; }; window.addEventListener('popstate', () => { window.dispatchEvent(new Event('urlchange')); }); }; return load; } let isVpn = !GM_getValue('switchvpn'); // 访问相关url let versionUrl, sciptUrl, rakingUrl, levelUrl; if (isVpn) { versionUrl = 'https://raw.githubusercontent.com/zhang-wangz/LeetCodeRating/main/version.json'; sciptUrl = 'https://raw.githubusercontent.com/zhang-wangz/LeetCodeRating/main/leetcodeRating_greasyfork.user.js'; rakingUrl = 'https://zerotrac.github.io/leetcode_problem_rating/data.json'; levelUrl = 'https://raw.githubusercontent.com/zhang-wangz/LeetCodeRating/main/stormlevel/data.json'; } else { versionUrl = 'https://raw.gitmirror.com/zhang-wangz/LeetCodeRating/main/version.json'; sciptUrl = 'https://raw.gitmirror.com/zhang-wangz/LeetCodeRating/main/leetcodeRating_greasyfork.user.js'; rakingUrl = 'https://raw.gitmirror.com/zerotrac/leetcode_problem_rating/main/data.json'; levelUrl = 'https://raw.gitmirror.com/zhang-wangz/LeetCodeRating/main/stormlevel/data.json'; } // 菜单方法定义 function script_setting() { let menu_ALL = [ ['switchvpn', 'vpn', '是否使用cdn访问数据', false, false], ['switchupdate', 'switchupdate', '是否每天最多只更新一次', true, true], ['switchTea', '0x3f tea', '题库页灵茶信息显示', true, true], ['switchpbRepo', 'pbRepo function', '题库页周赛难度评分(不包括灵茶)', true, false], ['switchpbscore', 'pb function', '题目页周赛难度评分', true, true], ['switchcopyright', 'pb function', '题解复制去除版权信息', true, true], ['switchpbside', 'switchpbside function', '题目页侧边栏分数显示', true, true], ['switchpbsearch', 'switchpbsearch function', '题目页题目搜索框', true, true], ['switchsearch', 'search function', '题目搜索页周赛难度评分', true, false], [ 'switchpblist', 'pbList function', '题单页周赛难度评分(包含自定义和官方题单)', true, false ], ['switchpblistRateDisplay', 'pbList function', '题单页一直显示通过率', true, false], ['switchstudy', 'studyplan function', '学习计划周赛难度评分', true, false], ['switchcontestpage', 'contestpage function', '竞赛页面双栏布局', true, false], [ 'switchlevel', 'studyplan level function', '算术评级(显示题库/题单/题目/学习计划页)', true, false ], [ 'switchrealoj', 'delvip function', '模拟oj环境(去除通过率,难度,周赛Qidx等)', false, true ], ['switchdark', 'dark function', '自动切换白天黑夜模式(早8晚8切换制)', false, true], ['switchpbstatus', 'pbstatus function', '讨论区和题目页显示题目完成状态', true, true], [ 'switchpbstatusscoredefault', 'pbstatusscore function', '题目完成状态增加难度分和会员题状态', false, true ], [ 'switchpbstatusLocationRight', 'switchpbstatusLocation function', '题目显示完成状态(位置改为右方)', false, true ], [ 'switchpbstatusBtn', 'pbstatusBtn function', '讨论区和题目页添加同步题目状态按钮', true, true ], ['switchperson', 'person function', '纸片人', false, true] ], menu_ID = [], menu_ID_Content = []; for (const element of menu_ALL) { // 如果读取到的值为 null 就写入默认值 if (GM_getValue(element[0]) == null) { GM_setValue(element[0], element[3]); } } registerMenuCommand(); // 注册脚本菜单 function registerMenuCommand() { if (menu_ID.length > menu_ALL.length) { // 如果菜单ID数组多于菜单数组,说明不是首次添加菜单,需要卸载所有脚本菜单 for (const element of menu_ID) { GM_unregisterMenuCommand(element); } } for (let i = 0; i < menu_ALL.length; i++) { // 循环注册脚本菜单 menu_ALL[i][3] = GM_getValue(menu_ALL[i][0]); let content = `${menu_ALL[i][3] ? '✅' : '❎'} ${menu_ALL[i][2]}`; menu_ID[i] = GM_registerMenuCommand(content, function () { menu_switch( `${menu_ALL[i][0]}`, `${menu_ALL[i][1]}`, `${menu_ALL[i][2]}`, `${menu_ALL[i][3]}` ); }); menu_ID_Content[i] = content; } menu_ID[menu_ID.length] = GM_registerMenuCommand(`🏁 当前版本 ${version}`, function () {}); menu_ID_Content[menu_ID_Content.length] = `🏁 当前版本 ${version}`; menu_ID[menu_ID.length + 1] = GM_registerMenuCommand( `🏁 企鹅群号 654726006`, function () {} ); menu_ID_Content[menu_ID_Content.length + 1] = `🏁 654726006`; } //切换选项 function menu_switch(name, ename, cname, value) { if (value == 'false') { GM_setValue(`${name}`, true); registerMenuCommand(); // 重新注册脚本菜单 location.reload(); // 刷新网页 GM_notification({ text: `「${cname}」已开启\n`, timeout: 3500 }); // 提示消息 } else { GM_setValue(`${name}`, false); registerMenuCommand(); // 重新注册脚本菜单 location.reload(); // 刷新网页 GM_notification({ text: `「${cname}」已关闭\n`, timeout: 3500 }); // 提示消息 } registerMenuCommand(); // 重新注册脚本菜单 } } function copyNoRight() { new ElementGetter().each('.WRmCx > div:has(code)', document, item => { addCopy(item); let observer = new MutationObserver(function (mutationsList, observer) { // 检查每个变化 mutationsList.forEach(function (mutation) { addCopy(item); }); }); observerReplace(item, observer); // 配置 MutationObserver 监听的内容和选项 let config = { attributes: false, childList: true, subtree: false }; observer.observe(item, config); }); function addCopy(item) { let nowShow = item.querySelector('div:not(.hidden) > div.group.relative > pre > code'); // console.log(nowShow); let copyNode = nowShow.parentElement.nextElementSibling.cloneNode(true); nowShow.parentElement.nextElementSibling.setAttribute('hidden', true); copyNode.classList.add('copyNode'); copyNode.onclick = function () { let nowShow = item.querySelector('div:not(.hidden) > div.group.relative > pre > code'); navigator.clipboard.writeText(nowShow.textContent).then(() => { layer.msg('复制成功'); }); }; nowShow.parentNode.parentNode.appendChild(copyNode); } } // lc 基础req let baseReq = (type, reqUrl, query, variables, successFuc) => { //请求参数 let list = { query: query, variables: variables }; // ajaxReq(type, reqUrl, null, list, successFuc); }; // post请求 let postReq = (reqUrl, query, variables, successFuc) => { baseReq('POST', reqUrl, query, variables, successFuc); }; // 基础函数休眠 async function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } let lcTheme = mode => { let headers = { accept: '*/*', 'accept-language': 'zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7', 'content-type': 'application/json' }; let body = { operationName: 'setTheme', query: '\n mutation setTheme($darkMode: String!) {\n setDarkSide(darkMode: $darkMode)\n}\n ', variables: { darkMode: mode } }; ajaxReq('POST', lcnojgo, headers, body, () => {}); }; if (GM_getValue('switchdark')) { let h = new Date().getHours(); if (h >= 8 && h < 20) { lcTheme('light'); localStorage.setItem('lc-dark-side', 'light'); console.log('修改至light mode...'); } else { lcTheme('dark'); localStorage.setItem('lc-dark-side', 'dark'); console.log('修改至dark mode...'); } } function allPbPostData(skip, limit) { let reqs = { query: `query problemsetQuestionList($categorySlug: String, $limit: Int, $skip: Int, $filters: QuestionListFilterInput) { problemsetQuestionList( categorySlug: $categorySlug limit: $limit skip: $skip filters: $filters ) { hasMore total questions { acRate difficulty freqBar frontendQuestionId isFavor paidOnly solutionNum status title titleCn titleSlug topicTags { name nameTranslated id slug } extra { hasVideoSolution topCompanyTags { imgUrl slug numSubscribed } } } } }`, variables: { categorySlug: 'all-code-essentials', skip: skip, limit: limit, filters: {} } }; reqs.key = 'LeetcodeRating'; return reqs; } function getpbCnt() { let total = 0; let headers = { 'Content-Type': 'application/json' }; ajaxReq('POST', lcgraphql, headers, allPbPostData(0, 0), res => { total = res.data.problemsetQuestionList.total; }); return total; } // 从题目链接提取slug // 在这之前需要匹配出所有符合条件的a标签链接 function getSlug(problemUrl) { let preUrl = 'https://leetcode-cn.com/problems/'; let nowurl = 'https://leetcode.cn/problems/'; if (problemUrl.startsWith(preUrl)) return problemUrl.replace(preUrl, '').split('/')[0]; else if (problemUrl.startsWith(nowurl)) return problemUrl.replace(nowurl, '').split('/')[0]; return null; } // 获取题目相关内容 function getpbRelation(pburl) { let pbstatus = JSON.parse(GM_getValue('pbstatus', '{}').toString()); let titleSlug = getSlug(pburl); if (!titleSlug) return [null, null, null]; let status = pbstatus[titleSlug] == null ? 'NOT_STARTED' : pbstatus[titleSlug]['status']; // 获取分数 let score; let idExist = pbstatus[titleSlug] != null && t2rate[pbstatus[titleSlug]['id']] != null; if (idExist) { score = t2rate[pbstatus[titleSlug]['id']]['Rating']; } let paid = pbstatus[titleSlug] == null ? null : pbstatus[titleSlug]['paidOnly']; return [status, score, paid]; } // 1 ac 2 tried 3 not_started function getPbstatusIcon(code, score, paid) { let value; switch (code) { case 1: value = ` `; break; case 2: value = ` `; break; // code3 的时候需要调整style,所以设置了class,调整在css中 case 3: value = ` `; break; default: value = ''; break; } // [难度分 1980] (会员题) if (GM_getValue('switchpbstatusscoredefault')) { if (score) { value += ` [难度分 ${score}] `; } if (paid != null && paid != false) { value += ` (会员题) `; } } return value; } function handleLink(link) { // 每日一题或者是标签icon内容,不做更改直接跳过 // no-underline是标题 // rounded排除每日一题的火花和题目侧边栏,火花一开始刷新时候href为空,直到lc请求接口之后才显示每日一题链接,所以有一瞬间的时间会错误识别 if ( link.href.includes('daily-question') || link.getAttribute('class')?.includes('rounded') || link.getAttribute('data-state') || link.getAttribute('class')?.includes('no-underline') ) { link.setAttribute('linkId', 'leetcodeRating'); return; } // console.log(link.href) // console.log(link) let linkId = link.getAttribute('linkId'); if (linkId != null && linkId == 'leetcodeRating') { console.log(getSlug(link.href) + '已经替换..., 略过'); return; } let [status, score, paid] = getpbRelation(link.href); if (!status) { link.setAttribute('linkId', 'leetcodeRating'); return; } // console.log(status); // 1 ac 2 tried 3 not_started let code = status == 'NOT_STARTED' ? 3 : status == 'AC' ? 1 : 2; // console.log(code); let iconStr = getPbstatusIcon(code, score, paid); let iconEle = document.createElement('span'); iconEle.innerHTML = iconStr; // console.log(iconEle); // 获取元素的父节点 link.setAttribute('linkId', 'leetcodeRating'); const parent = link.parentNode; // 改变方位 // 功能不开启的时候移动到左边-历史遗留问题 if (!GM_getValue('switchpbstatusLocationRight')) { parent.insertBefore(iconEle, link); } else { if (link.nextSibling) { parent.insertBefore(iconEle, link.nextSibling); } else { parent.appendChild(iconEle); } } } async function createstatusBtn() { // console.log("start...") if (document.querySelector('#statusBtn')) return; let span = document.createElement('span'); span.setAttribute('data-small-spacing', 'true'); span.setAttribute('id', 'statusBtn'); // 判断同步按钮 if (GM_getValue('switchpbstatusBtn')) { // console.log(levelData[id]) span.innerHTML = ` 同步题目状态`; span.onclick = function (e) { layer.open({ type: 1, content: `${pbstatusContent}`, title: '同步所有题目状态', area: ['550px', '250px'], shade: 0.6 }); }; // 使用layui的渲染 layuiload(); } new ElementGetter().each('.flex-wrap.items-center', document, userinfo => { if (userinfo?.lastChild?.textContent?.includes('发布于')) { // console.log(userinfo) span.setAttribute('class', userinfo.lastChild.getAttribute('class')); span.setAttribute('class', span.getAttribute('class') + ' hover:text-blue-s'); span.setAttribute('style', 'cursor:pointer'); userinfo.appendChild(span); } }); // console.log("end...") } // 竞赛页面双栏布局 // 来源 better contest page / author ExplodingKonjac // 竞赛页面影响不大,会reload,所以不放到initfunction中,url变化重启流程 let switchcontestpage = GM_getValue('switchcontestpage'); if (location.href.match('https://leetcode.cn/contest/.*/problems/.*') && switchcontestpage) { const CSS = ` body { display: flex; flex-direction: column; } body .content-wrapper { height: 0; min-height: 0 !important; flex: 1; display: flex; flex-direction: column; padding-bottom: 0 !important; } .content-wrapper #base_content { display: flex; overflow: hidden; height: 0; flex: 1; } .content-wrapper #base_content > .container { width: 40%; overflow: scroll; } .content-wrapper #base_content > .container .question-content { overflow: unset !important; } .content-wrapper #base_content > .container .question-content > pre { white-space: break-spaces; } .content-wrapper #base_content > .editor-container { flex: 1; overflow: scroll; } .content-wrapper #base_content > .editor-container .container { width: 100% !important; } .content-wrapper #base_content > .custom-resize { width: 4px; height: 100%; background: #eee; cursor: ew-resize; margin: 0 2px; } .content-wrapper #base_content > .custom-resize:hover { background: #1a90ff; } `; const storageKey = '--previous-editor-size'; (function () { const $css = document.createElement('style'); $css.innerHTML = CSS; document.head.append($css); const $problem = document.querySelector('.content-wrapper #base_content > .container'); const $editor = document.querySelector( '.content-wrapper #base_content > .editor-container' ); const $resize = document.createElement('div'); if (localStorage.getItem(storageKey)) { $problem.style.width = localStorage.getItem(storageKey); } $editor.parentElement.insertBefore($resize, $editor); $resize.classList.add('custom-resize'); let currentSize, startX, resizing = false; $resize.addEventListener('mousedown', e => { currentSize = $problem.getBoundingClientRect().width; startX = e.clientX; resizing = true; $resize.style.background = '#1a90ff'; }); window.addEventListener('mousemove', e => { if (!resizing) return; const deltaX = e.clientX - startX; const newSize = Math.max(450, Math.min(1200, currentSize + deltaX)); $problem.style.width = `${newSize}px`; e.preventDefault(); }); window.addEventListener('mouseup', e => { if (!resizing) return; e.preventDefault(); resizing = false; $resize.style.background = ''; localStorage.setItem(storageKey, $problem.style.width); }); })(); } // 监听变化 // 改变大小 // style监听影响不大,所以不放到initfunction中,url变化重启流程 let whetherSolution = location.href.match(pbUrl); if (whetherSolution) { // 左边 console.log('执行插入题目显示按钮style...'); if (!GM_getValue('switchpbstatusLocationRight')) { GM_addStyle(` circle.mycircle { cx: 9; cy: 9; r: 7; } `); } else { // 右边 GM_addStyle(` circle.mycircle { cx: 13; cy: 9; r: 7; } `); } } else { // 左边 if (!GM_getValue('switchpbstatusLocationRight')) { GM_addStyle(` circle.mycircle { cx: 8; cy: 9; r: 7; } `); } else { // 右边 GM_addStyle(` circle.mycircle { cx: 13; cy: 10; r: 7; } `); } } function realOpr() { // 只有讨论区才制作同步按钮,题解区不做更改 if (window.location.href.match(discussUrl)) { createstatusBtn(); } // 只有讨论区和题目页进行a标签制作 if (window.location.href.match(discussUrl) || window.location.href.match(pbUrl)) { // 获取所有的标签 let links = document.querySelectorAll('a'); // 过滤出符合条件的标签 let matchingLinks = Array.from(links).filter(link => { return ( !link.getAttribute('linkId') && link.href.match(pbUrl) && !link.href.match(pbSolutionUrl) ); }); // console.log(matchingLinks); // 符合条件的标签 matchingLinks.forEach(link => { handleLink(link); }); } } // 创建题目状态icon function waitOprpbStatus() { if (GM_getValue('switchpbstatus')) { if (window.location.href.match(discussUrl) || window.location.href.match(pbUrl)) { let css_flag = ''; if (window.location.href.match(discussUrl)) { // css_flag = ".css-qciawt-Wrapper"; css_flag = '.relative.flex'; } else { css_flag = '#qd-content'; } new ElementGetter().each(css_flag, document, item => { if (window.location.href.match(discussUrl)) realOpr(); let observer = new MutationObserver(function (mutationsList, observer) { // 检查变化 mutationsList.forEach(function (mutation) { realOpr(); }); }); observerReplace(item, observer); // 配置 MutationObserver 监听的内容和选项 let config = { attributes: false, childList: true, subtree: true }; observer.observe(item, config); }); } } } function pbsubmitListen() { var originalFetch = fetch; window.unsafeWindow.fetch = function () { return originalFetch.apply(this, arguments).then(function (response) { let checkUrl = 'https://leetcode.cn/submissions/detail/[0-9]*/check/.*'; let clonedResponse = response.clone(); clonedResponse.text().then(function (bodyText) { if ( clonedResponse.url.match(checkUrl) && clonedResponse.status == 200 && clonedResponse.ok ) { console.log('HTTP请求完成:', arguments[0]); let resp = JSON.parse(bodyText); console.log('响应数据:', resp); if (resp?.status_msg?.includes('Accepted')) { let pbstatus = JSON.parse(GM_getValue('pbstatus', '{}').toString()); let slug = getSlug(location.href); if (!pbstatus[slug]) pbstatus[slug] = {}; pbstatus[slug]['status'] = 'AC'; GM_setValue('pbstatus', JSON.stringify(pbstatus)); console.log('提交成功,当前题目状态已更新'); } else if (resp?.status_msg && !resp.status_msg.includes('Accepted')) { let pbstatus = JSON.parse(GM_getValue('pbstatus', '{}').toString()); let slug = getSlug(location.href); // 同步一下之前的记录是什么状态 let query = '\n query userQuestionStatus($titleSlug: String!) {\n question(titleSlug: $titleSlug) {\n status\n }\n}\n '; let headers = { 'Content-Type': 'application/json' }; let postdata = { query: query, variables: { titleSlug: slug }, operationName: 'userQuestionStatus' }; let status; ajaxReq('POST', lcgraphql, headers, postdata, response => { status = response.data.question.status; }); // 如果之前为ac状态,那么停止更新,直接返回 if (status && status == 'ac') { if (!pbstatus[slug]) pbstatus[slug] = {}; pbstatus[slug]['status'] = 'AC'; GM_setValue('pbstatus', JSON.stringify(pbstatus)); console.log('提交失败,但是之前已经ac过该题,所以状态为ac'); } else { // 之前没有提交过或者提交过但是没有ac的状态,那么仍然更新为提交失败状态 if (!pbstatus[slug]) pbstatus[slug] = {}; pbstatus[slug]['status'] = 'TRIED'; GM_setValue('pbstatus', JSON.stringify(pbstatus)); console.log('提交失败, 当前题目状态已更新'); } } } }); return response; }); }; } // 获取数字 function getcontestNumber(url) { return parseInt(url.substr(15)); } // 获取时间 function getCurrentDate(format) { let now = new Date(); let year = now.getFullYear(); //得到年份 let month = now.getMonth(); //得到月份 let date = now.getDate(); //得到日期 let hour = now.getHours(); //得到小时 let minu = now.getMinutes(); //得到分钟 let sec = now.getSeconds(); //得到秒 month = month + 1; if (month < 10) month = '0' + month; if (date < 10) date = '0' + date; if (hour < 10) hour = '0' + hour; if (minu < 10) minu = '0' + minu; if (sec < 10) sec = '0' + sec; let time = ''; // 精确到天 if (format == 1) { time = year + '年' + month + '月' + date + '日'; } // 精确到分 else if (format == 2) { time = year + '-' + month + '-' + date + ' ' + hour + ':' + minu + ':' + sec; } else if (format == 3) { time = year + '/' + month + '/' + date; } return time; } GM_addStyle(` .containerlingtea { background: rgba(233, 183, 33, 0.2); white-space: pre-wrap; word-wrap: break-word; display: block; } `); // 因为力扣未捕获错误信息,所以重写一下removechild方法 const removeChildFn = Node.prototype.removeChild; Node.prototype.removeChild = function (n) { let err = null; try { err = removeChildFn.call(this, n); // 正常删除 } catch (error) { if (!error.toString().includes('NotFoundError')) console.log('力扣api发生错误: ', error.toString().substr(0, 150)); } return err; }; function createProblemCard({ title, pburl, difficulty, rate, parentNodeList }) { const $a = $('', { class: 'group flex flex-col rounded-[8px] duration-300', id: Date.now(), // 随便给个唯一id target: '_blank', href: pburl // 跳转链接 }); const $div1 = $('
', { class: 'flex h-[44px] w-full items-center space-x-3 px-4' }); const $wrapper = $('
', { style: 'transform: translateX(-3px);' }); // 嵌套的小结构 const $inner1 = $('
', { class: 'flex items-center justify-center w-[20px] h-[20px]' }).append( $('', { xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 576 512', fill: 'currentColor', class: 'w-4 h-4 text-yellow-400', // 大小4×4,黄色 html: ` ` }) ); // 第二块内容 const $inner2 = $('
', { class: 'relative flex h-full w-full cursor-pointer items-center' }).append( $('
', { class: 'flex w-0 flex-1 items-center space-x-2' }).append( $('
', { class: 'text-body text-sd-foreground max-w-[90%] font-medium' }).append( $('
', { class: 'ellipsis line-clamp-1' }).text(title) ) ), $('
', { class: 'text-sd-muted-foreground flex w-[70px] items-center justify-center text-sm opacity-0 group-hover:opacity-100 lc-xl:opacity-100', 'data-state': 'closed' }).text(rate), $('

', { class: 'mx-0 text-[14px] lc-xl:mx-4' }).text(difficulty) ); // 第三个部分 小竖条 const $inner3 = $('

', { 'data-state': 'closed' }).append( $('
', { class: 'flex gap-0.5 px-1' }).append( Array.from({ length: 8 }).map(() => $('
', { class: 'h-2 w-0.5 rounded bg-sd-foreground opacity-20' }) ) ) ); // 收藏 const $inner4 = $('
', { class: 'hover:bg-sd-accent flex h-7 w-7 items-center justify-center rounded opacity-0', type: 'button', 'aria-haspopup': 'dialog', 'aria-expanded': 'false', 'aria-controls': 'xxx', 'data-state': 'closed' }).append( $('
', { class: 'relative text-[14px] leading-[normal] p-[1px] before:block before:h-3.5 before:w-3.5 text-sd-muted-foreground', html: '' }) ); $div1.append($inner1, $inner2, $inner3, $inner4); $wrapper.append($div1); $a.append($wrapper); // 插入到第一个父元素的最前面 if (parentNodeList && parentNodeList.childNodes.length > 0) { const firstChild = parentNodeList.childNodes[0]; if (firstChild) { parentNodeList.insertBefore($a[0], firstChild); } else { parentNodeList.appendChild($a[0]); } } } let lcCnt = 0; let pbSetCnt = 0; function getData() { let switchpbRepo = GM_getValue('switchpbRepo'); let switchTea = GM_getValue('switchTea'); let switchrealoj = GM_getValue('switchrealoj'); let switchlevel = GM_getValue('switchlevel'); let arr = document.querySelector('[class*="pb-[80px]"]'); let everydatpbidx = 0; // pb页面加载时直接返回 if (arr == null) { return; } observeIfNeeded(arr); isSelfChanging = true; try { if (pbSetCnt && pbSetCnt == arr.childNodes.length) { console.log('第' + lcCnt + '次刷新插件...'); // 到达次数之后删除定时防止卡顿 if (lcCnt == shortCnt) { console.log('到达当前功能指定刷新次数, 检测暂时无更新, 暂停刷新...'); clearId('all'); } lcCnt += 1; return; } t2rate = JSON.parse(GM_getValue('t2ratedb', '{}').toString()); // 灵茶题目渲染 if (switchTea) { let first = arr.firstChild; if (!first.textContent.includes('灵茶题集')) { createProblemCard({ title: '灵茶题集' + '-' + getCurrentDate(3), pburl: teaSheetUrl, difficulty: '暂无', rate: '暂无', parentNodeList: arr }); } // 经过灵茶之后,无论如何数量都会变成1 everydatpbidx = 1; } if (switchpbRepo) { let childs = arr.childNodes; let idx = switchTea ? 1 : 0; let childLength = childs.length; for (; idx < childLength; idx++) { let v = childs[idx]; // 如果元素第一个就不存在或undifined就直接返回 if (!v) return; let t = v.textContent; let data = t.split('.'); let id = data[0].trim(); let $item = $(v); let difficulty = $item.find('.text-sd-medium, .text-sd-easy, .text-sd-hard').first(); let passRate = difficulty.siblings('div.text-sd-muted-foreground').first(); // 如果没有难度和通过率属性,则跳过步骤 if (difficulty.length <= 0 || passRate.length <= 0) continue; if (switchrealoj) { // 难度修改为隐藏 if (difficulty.length > 0) { difficulty.text('隐藏'); difficulty.removeClass('text-sd-easy text-sd-medium text-sd-hard'); } // 通过率修改为隐藏 if (passRate.length > 0) { passRate.text('隐藏'); } continue; } // 因为lc请求是有缓存的,所以多次刷新的时候同一个位置会是不同的题目,这时候需要还原 if (t2rate[id] != null) { let ndScore = t2rate[id]['Rating']; difficulty.text(ndScore); // 修改尺寸使得数字分数和文字比如(困难)保持在同一行 passRate.removeClass('w-[70px]'); passRate.addClass('w-[55px]'); } else { let nd2ch = { 'mx-0 text-[14px] text-sd-easy lc-xl:mx-4': '简单', 'mx-0 text-[14px] text-sd-medium lc-xl:mx-4': '中等', 'mx-0 text-[14px] text-sd-hard lc-xl:mx-4': '困难' }; difficulty.text(nd2ch[difficulty.attr('class')]); // 恢复原有大小尺寸 passRate.removeClass('w-[55px]'); passRate.addClass('w-[70px]'); } // 增加算术评级插入操作 if (switchlevel) { let level = levelData[id]; let levelText = level ? '算术评级: ' + level['Level'] : ''; let $existingLevel = passRate.siblings('.arithmetic-level'); // 如果已经操作过 if ($existingLevel.length > 0) { // 如果含有算术评级则更新文本,如果没有则删除原来插入的数据 if (level) { $existingLevel.text(levelText); } else { $existingLevel.remove(); } } else if (level) { // 如果没有操作过 // 如果含有算术评级则插入,如果没有算术评级,则不做任何操作 // 构造新的算术等级元素(保持结构一致) const $level = $('
') .addClass(passRate.attr('class')) // 复用样式 .addClass('arithmetic-level') // 自定义类作为标记 .text(levelText); // 去除灰色颜色和尺寸限制 $level .removeClass('w-[70px] w-[55px] text-sd-muted-foreground') .addClass('min-w-[100px]'); // 如果插入的为每日一题位置,需要修改尺寸,左移8px if (idx == everydatpbidx) { $level.css('transform', 'translateX(-8px)'); } // 插入到通过率前面 passRate.before($level); } } } console.log('has refreshed problemlist...'); } pbSetCnt = arr.childNodes.length; } finally { isSelfChanging = false; } } // pblist插件刷新次数 let pbListCnt = 0; // pblist当前刷新之后列表所含题目数量 let pbListpbCnt = 0; function getPblistData() { if (!GM_getValue('switchpblist')) return; let switchrealoj = GM_getValue('switchrealoj'); let switchlevel = GM_getValue('switchlevel'); let switchpblistRateDisplay = GM_getValue('switchpblistRateDisplay'); let pre = document.querySelector('.w-full .pb-20'); let arr = pre?.childNodes[0]?.lastChild?.childNodes[0]; if (!arr) return; // 设置监听官方渲染,并标记当前自己修改不被监听 observeIfNeeded(arr); isSelfChanging = true; try { // console.log(arr) // console.log(pbListpbCnt) // console.log(arr.childNodes.length) if (pbListpbCnt && pbListpbCnt == arr.childNodes.length) { console.log('第' + pbListCnt + '次刷新插件...'); // 到达次数之后删除定时防止卡顿 if (pbListCnt == shortCnt) { console.log('到达当前功能指定刷新次数, 检测暂时无更新, 暂停刷新...'); console.log('清理标记'); clearId('pblist'); } pbListCnt += 1; return; } t2rate = JSON.parse(GM_getValue('t2ratedb', '{}').toString()); let childs = arr.childNodes; let childLength = childs.length; for (let idx = 0; idx < childLength; idx++) { let v = childs[idx]; if (!v) return; let t = v.textContent; let data = t.split('.'); let id = data[0].trim(); // console.log(id) // 如果不是a标签,说明是自定义题单,需要多进一层 let $item = $(v); let difficulty = $item.find('.text-sd-medium, .text-sd-easy, .text-sd-hard').first(); let passRate = difficulty.siblings('div.text-sd-muted-foreground').first(); if (switchpblistRateDisplay) passRate.removeClass('opacity-0').addClass('opacity-100'); // 如果没有难度属性,则跳过步骤 if (difficulty.length <= 0 || passRate.length <= 0) continue; if (switchrealoj) { // 难度修改为隐藏 if (difficulty.length > 0) { difficulty.text('隐藏'); difficulty.removeClass('text-sd-easy text-sd-medium text-sd-hard'); } // 通过率修改为隐藏 if (passRate.length > 0) { passRate.text('隐藏'); } continue; } // 插入竞赛分数 if (t2rate[id] != null) { let ndScore = t2rate[id]['Rating']; difficulty.text(ndScore); // 修改尺寸使得数字分数和文字比如(困难)保持在同一行 passRate.removeClass('w-[70px]'); passRate.addClass('w-[55px]'); } else { let nd2ch = { 'mx-0 text-[14px] text-sd-easy lc-xl:mx-4': '简单', 'mx-0 text-[14px] text-sd-medium lc-xl:mx-4': '中等', 'mx-0 text-[14px] text-sd-hard lc-xl:mx-4': '困难' }; difficulty.text(nd2ch[difficulty.attr('class')]); // 恢复原有大小尺寸 passRate.removeClass('w-[55px]'); passRate.addClass('w-[70px]'); } // 增加算术评级插入操作 if (switchlevel) { let level = levelData[id]; let levelText = level ? '算术评级: ' + level['Level'] : ''; let $existingLevel = passRate.siblings('.arithmetic-level'); // 如果已经操作过 if ($existingLevel.length > 0) { // 如果含有算术评级则更新文本,如果没有则删除原来插入的数据 if (level) { $existingLevel.text(levelText); } else { $existingLevel.remove(); } } else if (level) { // 如果没有操作过 // 如果含有算术评级则插入,如果没有算术评级,则不做任何操作 // 构造新的算术等级元素(保持结构一致) const $level = $('
') .addClass(passRate.attr('class')) // 复用样式 .addClass('arithmetic-level') // 自定义类作为标记 .text(levelText); // 去除灰色颜色和尺寸限制 $level .removeClass('opacity-0 w-[70px] w-[55px] text-sd-muted-foreground') .addClass('min-w-[100px] opacity-100'); // 插入到通过率前面 passRate.before($level); } } } console.log('has refreshed...'); pbListpbCnt = arr.childNodes.length; } finally { isSelfChanging = false; } } function getSearch() { if (!GM_getValue('switchsearch')) return; let arr = $("div[role='table']"); if (arr.length == 0) return; arr = arr[0].childNodes[1]; let head = document.querySelector("div[role='row']"); if (!head) rerurn; // 确认难度序列 let rateRefresh = false; let headndidx; for (let i = 0; i < head.childNodes.length; i++) { let headEle = head.childNodes[i]; if (headEle.textContent.includes('难度')) { headndidx = i; } if (headEle.textContent.includes('题目评分')) { rateRefresh = true; } } if (!arr) return; let childs = arr.childNodes; for (const element of childs) { let v = element; if (!v.childNodes[1]) return; let t = v.childNodes[1].textContent; let data = t.split('.'); let id = data[0].trim(); let nd = v.childNodes[headndidx].childNodes[0].innerHTML; if (t2rate[id] != null && !rateRefresh) { nd = t2rate[id]['Rating']; v.childNodes[headndidx].childNodes[0].innerHTML = nd; } else { let nd2ch = { 'text-green-s': '简单', 'text-yellow': '中等', 'text-red-s': '困难' }; let clr = v.childNodes[headndidx].childNodes[0].getAttribute('class'); v.childNodes[headndidx].childNodes[0].innerHTML = nd2ch[clr]; } } } /** * 渲染 rating * @param {HTMLElement} nd 要操作的节点 * @param {string | undefined} ndRate rating * @param {Record} lightn2c 亮模式难度列表 * @param {Record} darkn2c 暗模式难度列表 * @returns {boolean} 是否命中 */ function renderRating(nd, ndRate, lightn2c, darkn2c) { if (ndRate) { nd.textContent = ndRate; return true; } let clr = nd.classList; if (clr.length === 0) return false; for (const [className, text] of Object.entries({ ...lightn2c, ...darkn2c })) { if (clr.contains(className)) { nd.innerText = text; return true; } } return false; } /** * 渲染 level * @param {HTMLElement} nd 要操作的节点 * @param {string | undefined} level 评级 * @param {DOMTokenList} cls class 列表 * @param {boolean} hit 是否命中 * @param {number} padding 单位: px, 默认80 */ function renderLevel(nd, level, cls, hit, padding = 80) { if (level && GM_getValue('switchlevel')) { let text = document.createElement('span'); text.classList.add(...cls); text.innerHTML = '算术评级: ' + level; text.style = nd.getAttribute('style'); text.style.paddingRight = `${hit ? padding - 5 : padding}px`; // 命中之后宽度不一样 nd.parentNode.insertBefore(text, nd); } } /** * 修正侧边栏高亮题目的样式 * @param {HTMLElement} listNode 侧边栏列表节点 * @param {string} cssSelector 子节点选择器 */ function fixSiderbarProblemHighlight(listNode, cssSelector) { // console.log("修正侧边栏高亮题目样式"); const pbList = listNode.querySelectorAll(cssSelector); pbList.forEach(div => { const levelSpan = div.querySelector(':scope > span'); const pbDiv = div.querySelector(':scope > div > div'); if (!levelSpan) return; if (pbDiv.className !== levelSpan.className) { // 如果className不一致,说明是高亮状态不一致 levelSpan.className = pbDiv.className; } }); } // 确认之后不再刷新 let studyf; let studyCnt = 0; function getStudyData(css_selector) { if (!GM_getValue('switchstudy')) return; levelData = JSON.parse(GM_getValue('levelData', '{}').toString()); let totArr = null; // 如果传入的是已经找到的node元素, 就不再搜索 if (css_selector instanceof Element) { totArr = css_selector; } else { totArr = document.querySelector(css_selector); } if (totArr == null) return; let first = totArr.firstChild?.childNodes[1]?.textContent; if (studyf && first && studyf == first) { // 到达次数之后删除定时防止卡顿 if (studyCnt == shortCnt) { clearId('study'); } studyCnt += 1; return; } let childs = totArr.childNodes; for (const arr of childs) { for (let pbidx = 1; pbidx < arr.childNodes.length; pbidx++) { let pb = arr.childNodes[pbidx]; let pbNameLabel = pb.querySelector('.truncate'); if (pbNameLabel == null) continue; let pbName = pbNameLabel.textContent; let nd = pb.childNodes[0].childNodes[1].childNodes[1]; let pbhtml = pb?.childNodes[0]?.childNodes[1]?.childNodes[0]?.childNodes[0]; pbName = pbName.trim(); // 保证 nd 存在 if (nd == null || nd.classList.length === 0) { // console.log(nd) continue; } let levelId = getLevelId(pbName); let id = getPbNameId(pbName); // console.log(pbName, level) let darkn2c = { 'text-lc-green-60': '简单', 'text-lc-yellow-60': '中等', 'text-lc-red-60': '困难' }; let lightn2c = { 'text-lc-green-60': '简单', 'text-lc-yellow-60': '中等', 'text-lc-red-60': '困难' }; // render rating let hit = renderRating(nd, t2rate?.[id]?.Rating, lightn2c, darkn2c); // render level renderLevel(nd, levelData[levelId]?.Level?.toString(), pbhtml.classList, hit, 130); } } if (totArr.firstChild?.childNodes[1]) studyf = totArr.firstChild?.childNodes[1]?.textContent; console.log('has refreshed...'); } let pbsidef; let pbsidee; function getpbside(css_selector) { let totArr = null; // 如果传入的是已经找到的node元素, 就不再搜索 if (css_selector instanceof Element) { totArr = css_selector; } else { totArr = document.querySelector(css_selector); } if (totArr == null) return; if (totArr.firstChild == null) return; let first = totArr.firstChild?.childNodes[0]?.textContent; let last = totArr.lastChild?.childNodes[0]?.textContent; if (first && pbsidef && pbsidef == first && last && pbsidee && pbsidee == last) { if (pbsideCnt == normalCnt) clearId('pbside'); // TODO: 没想到什么好的办法来确切的监听源站前端对题目列表的更新,只能大概等一个延时 if (pbsideCnt === 1) { // 在此处检查高亮状态是否改变,并修正 fixSiderbarProblemHighlight(totArr, ':scope > div > div[id] > div > :nth-child(2)'); } pbsideCnt += 1; return; } let childs = totArr.childNodes; for (const arr of childs) { // 特殊判定, 如果大于30则是每日一题列表 let pbidx = 1; if (arr.childNodes.length >= 30) pbidx = 0; for (; pbidx < arr.childNodes.length; pbidx++) { let pb = arr.childNodes[pbidx]; let pbName = pb.childNodes[0].childNodes[1].childNodes[0].textContent; let nd = pb.childNodes[0].childNodes[1].childNodes[1]; let pbhtml = pb?.childNodes[0]?.childNodes[1]?.childNodes[0]?.childNodes[0]; // 保证 nd 存在 if (nd == null || nd.classList.length === 0) { // console.log(nd) continue; } // console.log(pbName) let data = pbName.split('.'); let id = data[0]; let darkn2c = { 'text-lc-green-60': '简单', 'text-lc-yellow-60': '中等', 'text-lc-red-60': '困难' }; let lightn2c = { 'text-lc-green-60': '简单', 'text-lc-yellow-60': '中等', 'text-lc-red-60': '困难' }; // render rating let hit = renderRating(nd, t2rate?.[id]?.Rating, lightn2c, darkn2c); // render level renderLevel(nd, levelData[id]?.Level?.toString(), pbhtml.classList, hit); } } if (totArr.firstChild?.childNodes[0]) pbsidef = totArr.firstChild.childNodes[0].textContent; if (totArr.lastChild?.childNodes[0]) pbsidee = totArr.lastChild.childNodes[0].textContent; // console.log(pbsidef, pbsidee) console.log('已经刷新侧边栏envType分数...'); } let pbsideCnt = 0; function getpbsideData() { // 左侧栏分数显示 let searchParams = location.search; levelData = JSON.parse(GM_getValue('levelData', '{}').toString()); // ?envType=study-plan-v2&envId=leetcode-75 // 类似学习计划的展开栏 if ( searchParams.includes('envType') && !searchParams.includes('daily-question') && !searchParams.includes('problem-list') ) { let overflow = document.querySelector('.overflow-auto.p-5'); if (overflow == null) return; let studyplan = overflow.childNodes[0].childNodes[1]; if (!studyplan) studyf = null; if (GM_getValue('switchstudy') && studyplan) { getpbside(studyplan); } } else { // 普通展开栏 let overflow = document.querySelector('.overflow-auto.p-4'); if (overflow == null) return; let pbarr = overflow?.childNodes[0]?.childNodes[1]; if (pbarr == null) return; if (pbarr.firstChild == null) return; if (pbarr.lastChild == null) return; if (pbsidef == pbarr.firstChild?.textContent && pbsidee == pbarr.lastChild?.textContent) { if (pbsideCnt == normalCnt) clearId('pbside'); // TODO: 没想到什么好的办法来确切的监听源站前端对题目列表的更新,只能大概等一个延时 // 根据列表的大小不同,更新耗时可能不同,故直接对快慢两种情况运行两次修正 if (pbsideCnt === 4 || pbsideCnt === 1) { // 在此处检查高亮状态是否改变,并修正 fixSiderbarProblemHighlight(pbarr, ':scope > .group > :first-child > :nth-child(2)'); } pbsideCnt += 1; return; } if (pbarr != null) { for (const onepb of pbarr.childNodes) { let tp = onepb.childNodes[0]?.childNodes[1]; if (!tp) { // console.log(tp) continue; } let pbName = tp.childNodes[0]?.textContent; if (pbName == null) { continue; // pbName = tp.childNodes[0]?.textContent // console.log(pbName) } let nd = tp.childNodes[1]; let pbhtml = tp.childNodes[0]?.childNodes[0]; // 保证 nd 存在 if (nd == null || nd.classList.length === 0) { // console.log(nd) continue; } // 如果为算术,说明当前已被替换过 if (nd.textContent.includes('算术')) continue; // console.log(pbName) let data = pbName.split('.'); let id = data[0]; let darkn2c = { 'text-sd-easy': '简单', 'text-sd-medium': '中等', 'text-sd-hard': '困难' }; let lightn2c = { 'text-sd-easy': '简单', 'text-sd-medium': '中等', 'text-sd-hard': '困难' }; // render rating let hit = renderRating(nd, t2rate?.[id]?.Rating, lightn2c, darkn2c); // render level renderLevel(nd, levelData[id]?.Level?.toString(), pbhtml.classList, hit); } pbsidef = pbarr.firstChild.textContent; pbsidee = pbarr.lastChild.textContent; // console.log(pbsidef, pbsidee) console.log('已经刷新侧边栏题库分数...'); } } } function createSearchBtn() { if (!GM_getValue('switchpbsearch')) return; if (document.querySelector('#id-dropdown') == null) { // 做个搜索框 let div = document.createElement('div'); div.setAttribute('class', 'layui-inline'); // 适配黑色主题 div.classList.add('leetcodeRating-search'); div.innerHTML += ``; let center = document.querySelector('.flex.justify-between'); center = center?.childNodes[0]?.childNodes[0]?.childNodes[0]; if (center == null) return; if (center.childNodes.length > 0) center.insertBefore(div, center.childNodes[1]); else center.appendChild(div); layui.use(function () { let dropdown = layui.dropdown; let $ = layui.$; let inst = dropdown.render({ elem: '#id-dropdown', data: [], click: function (obj) { this.elem.val(obj.title); this.elem.attr('data-id', obj.id); } }); let elemInput = $(inst.config.elem); let lastQueryTime = ''; let timer; elemInput.on('input propertychange', function (event) { clearTimeout(timer); timer = setTimeout(function () { let currentTime = Date.now(); if (currentTime - lastQueryTime >= 800) { let elem = $(inst.config.elem); let value = elem.val().trim(); elem.removeAttr('data-id'); let dataNew = findData(value); dropdown.reloadData(inst.config.id, { data: dataNew }); lastQueryTime = currentTime; } }, 800); }); $(inst.config.elem).on('blur', function () { let elem = $(this); let dataId = elem.attr('data-id'); if (!dataId) { elem.val(''); } }); function findData(value) { return getsearch(value); } function getsearch(search) { let queryT = ` query problemsetQuestions($in: ProblemsetQuestionsInput!) { problemsetQuestions(in: $in) { hasMore questions { titleCn titleSlug title frontendId acRate solutionNum difficulty userQuestionStatus } } } `; let list = { query: queryT, operationName: 'problemsetQuestions', variables: { in: { query: search, limit: 10, offset: 0 } } }; let resLst = []; $.ajax({ type: 'POST', url: lcnojgo, data: JSON.stringify(list), success: function (res) { let data = res.data.problemsetQuestions.questions; for (let idx = 0; idx < data.length; idx++) { let resp = data[idx]; let item = {}; item.id = idx; item.title = resp.frontendId + '.' + resp.titleCn; item.href = 'https://leetcode.cn/problems/' + resp.titleSlug; item.target = '_self'; resLst.push(item); } }, async: false, xhrFields: { withCredentials: true }, contentType: 'application/json;charset=UTF-8' }); return resLst; } }); } } // 因为字符显示问题,暂时去除 // 0% let pbstatusContent = `
希望有大佬可以美化这丑丑的界面~ =v=

`; let levelContent = ` 1 无算法要求 2 知道常用数据结构和算法并简单使用 3 理解常用数据结构和算法 4 掌握常用数据结构和算法 5 熟练掌握常用数据结构和算法,初步了解高级数据结构 6 深入理解并灵活应用数据结构和算法,理解高级数据结构 7 结合多方面的数据结构和算法,处理较复杂问题 8 掌握不同的数据结构与算法之间的关联性,处理复杂问题,掌握高级数据结构 9 处理复杂问题,对时间复杂度的要求更严格 10 非常复杂的问题,非常高深的数据结构和算法(例如线段树、树状数组) 11 竞赛内容,知识点超出面试范围 `; async function layuiload() { // 使用layui的渲染 layui.use(function () { let element = layui.element; let util = layui.util; let pbstatus = JSON.parse(GM_getValue('pbstatus', '{}').toString()); // 普通事件 util.on('lay-on', { // loading loading: function (othis) { let DISABLED = 'layui-btn-disabled'; if (othis.hasClass(DISABLED)) return; othis.addClass(DISABLED); let cnt = Math.trunc((getpbCnt() + 99) / 100); let headers = { 'Content-Type': 'application/json' }; let skip = 0; let timer = setInterval( async function () { ajaxReq('POST', lcgraphql, headers, allPbPostData(skip, 100), res => { let questions = res.data.problemsetQuestionList.questions; for (let pb of questions) { pbstatus[pb.titleSlug] = { titleSlug: pb.titleSlug, id: pb.frontendQuestionId, status: pb.status, title: pb.title, titleCn: pb.titleCn, difficulty: pb.difficulty, paidOnly: pb.paidOnly }; } }); skip += 100; // skip / 100 是当前已经进行的次数 let showval = Math.trunc((skip / 100 / cnt) * 100); if (skip / 100 >= cnt) { showval = 100; clearInterval(timer); } element.progress('demo-filter-progress', showval + '%'); if (showval == 100) { pbstatus[pbstatusVersion] = {}; GM_setValue('pbstatus', JSON.stringify(pbstatus)); console.log('同步所有题目状态完成...'); await sleep(1000); layer.msg('同步所有题目状态完成!'); await sleep(1000); layer.closeAll(); } }, 300 + Math.random() * 1000 ); } }); }); } let t1; // pb let pbCnt = 0; let pbCnt2 = 0; function getpb() { let switchrealoj = GM_getValue('switchrealoj'); // 搜索功能 if (GM_getValue('switchpbsearch')) createSearchBtn(); // 题目页面 let curUrl = location.href; // 只有描述页才进行加载 let isDescript = !curUrl.match(regDiss) && !curUrl.match(regSovle) && !curUrl.match(regPbSubmission); // 如果持续10次都不在描述页面, 则关闭pb定时 if (!isDescript) { // 非des清除定时 if (pbCnt == shortCnt) clearId('pb'); pbCnt += 1; return; } // 流动布局逻辑 if (isDynamic) { // pb其他页面时刷新多次后也直接关闭 let t = document.querySelector('.text-title-large'); if (t == null) { t1 = 'unknown'; pbCnt = 0; if (pbCnt2 == shortCnt) clearId('pb'); pbCnt2 += 1; return; } // console.log(t1, t.textContent) if (t1 != null && t1 == t.textContent) { // des清除定时 if (pbCnt == shortCnt) clearId('pb'); pbCnt += 1; return; } let data = t.textContent.split('.'); let id = data[0].trim(); let colorA = ['.text-difficulty-hard', '.text-difficulty-easy', '.text-difficulty-medium']; let colorSpan; for (const color of colorA) { colorSpan = document.querySelector(color); if (colorSpan) break; } if (!colorSpan) { if (switchrealoj) return; console.log('color ele not found'); return; } // 统计难度分数并且修改 let nd = colorSpan.getAttribute('class'); let nd2ch = { 'text-difficulty-easy': '简单', 'text-difficulty-medium': '中等', 'text-difficulty-hard': '困难' }; if (switchrealoj || (t2rate[id] != null && GM_getValue('switchpbscore'))) { if (switchrealoj) colorSpan.remove(); else if (t2rate[id] != null) colorSpan.innerHTML = t2rate[id]['Rating']; } else { for (let item in nd2ch) { if (nd.toString().includes(item)) { colorSpan.innerHTML = nd2ch[item]; break; } } } // 逻辑,准备做周赛链接,如果已经不存在组件就执行操作 let url = chContestUrl; let zhUrl = zhContestUrl; let tips = colorSpan?.parentNode; if (tips == null) return; let tipsPa = tips?.parentNode; // tips 一栏的父亲节点第一子元素的位置, 插入后变成竞赛信息位置 let tipsChildone = tipsPa.childNodes[1]; // 题目内容, 插入后变成原tips栏目 let pbDescription = tipsPa.childNodes[2]; if (pbDescription?.childNodes[0]?.getAttribute('data-track-load') != null) { let divTips = document.createElement('div'); divTips.setAttribute('class', 'flex gap-1'); let abody = document.createElement('a'); abody.setAttribute('data-small-spacing', 'true'); abody.setAttribute('class', 'css-nabodd-Button e167268t1 hover:text-blue-s'); let abody2 = document.createElement('a'); abody2.setAttribute('data-small-spacing', 'true'); abody2.setAttribute('class', 'css-nabodd-Button e167268t1 hover:text-blue-s'); let abody3 = document.createElement('a'); abody3.setAttribute('data-small-spacing', 'true'); abody3.setAttribute('class', 'css-nabodd-Button e167268t1 hover:text-blue-s'); let abody4 = document.createElement('p'); abody4.setAttribute('data-small-spacing', 'true'); abody4.setAttribute('class', 'css-nabodd-Button e167268t1 hover:text-blue-s'); let span = document.createElement('span'); let span2 = document.createElement('span'); let span3 = document.createElement('span'); let span4 = document.createElement('span'); // 判断同步按钮 if (GM_getValue('switchpbstatusBtn')) { // console.log(levelData[id]) span4.innerHTML = ` 同步题目状态`; span4.onclick = function (e) { layer.open({ type: 1, content: `${pbstatusContent}`, title: '同步所有题目状态', area: ['550px', '250px'], shade: 0.6 }); }; span4.setAttribute('style', 'cursor:pointer;'); // 使用layui的渲染 layuiload(); abody4.removeAttribute('hidden'); } else { span4.innerText = '未知按钮'; abody4.setAttribute('hidden', 'true'); } abody4.setAttribute('style', 'padding-left: 10px;'); levelData = JSON.parse(GM_getValue('levelData', '{}').toString()); if (levelData[id] != null) { // console.log(levelData[id]) let des = '算术评级: ' + levelData[id]['Level'].toString(); span3.innerText = des; span3.onclick = function (e) { e.preventDefault(); layer.open({ type: 1, // Page 层类型 area: ['700px', '450px'], title: '算术评级说明', shade: 0.6, // 遮罩透明度 maxmin: true, // 允许全屏最小化 anim: 5, // 0-6的动画形式,-1不开启 content: `

${levelContent}

` }); }; abody3.removeAttribute('hidden'); } else { span3.innerText = '未知评级'; abody3.setAttribute('hidden', 'true'); } abody3.setAttribute('href', '/xxx'); abody3.setAttribute('style', 'padding-right: 10px;'); abody3.setAttribute('target', '_blank'); if (t2rate[id] != null) { let contestUrl; let num = getcontestNumber(t2rate[id]['ContestSlug']); if (num < 83) { contestUrl = zhUrl; } else { contestUrl = url; } span.innerText = t2rate[id]['ContestID_zh']; span2.innerText = t2rate[id]['ProblemIndex']; abody.setAttribute('href', contestUrl + t2rate[id]['ContestSlug']); abody.setAttribute('target', '_blank'); abody.removeAttribute('hidden'); abody2.setAttribute( 'href', contestUrl + t2rate[id]['ContestSlug'] + '/problems/' + t2rate[id]['TitleSlug'] ); abody2.setAttribute('target', '_blank'); if (switchrealoj) abody2.setAttribute('hidden', true); else abody2.removeAttribute('hidden'); } else { span.innerText = '对应周赛未知'; abody.setAttribute('href', '/xxx'); abody.setAttribute('target', '_self'); abody.setAttribute('hidden', 'true'); span2.innerText = '未知'; abody2.setAttribute('href', '/xxx'); abody2.setAttribute('target', '_self'); abody2.setAttribute('hidden', 'true'); } abody.setAttribute('style', 'padding-right: 10px;'); // abody2.setAttribute("style", "padding-top: 1.5px;") abody.appendChild(span); abody2.appendChild(span2); abody3.appendChild(span3); abody4.appendChild(span4); divTips.appendChild(abody3); divTips.appendChild(abody); divTips.appendChild(abody2); divTips.appendChild(abody4); tipsPa.insertBefore(divTips, tips); } else if ( tipsChildone.childNodes != null && tipsChildone.childNodes.length >= 2 && (tipsChildone.childNodes[2].textContent.includes('Q') || tipsChildone.childNodes[2].textContent.includes('未知')) ) { let pa = tipsChildone; let le = pa.childNodes.length; // 判断同步按钮 if (GM_getValue('switchpbstatusBtn')) { // 使用layui的渲染, 前面已经添加渲染按钮,所以这里不用重新添加 pa.childNodes[le - 1].removeAttribute('hidden'); } else { pa.childNodes[le - 1].childNodes[0].innerText = '未知按钮'; pa.childNodes[le - 1].setAttribute('hidden', 'true'); } // 存在就直接替换 let levelData = JSON.parse(GM_getValue('levelData', '{}').toString()); if (levelData[id] != null) { let des = '算术评级: ' + levelData[id]['Level'].toString(); pa.childNodes[le - 4].childNodes[0].innerText = des; pa.childNodes[le - 4].childNodes[0].onclick = function (e) { e.preventDefault(); layer.open({ type: 1, // Page 层类型 area: ['700px', '450px'], title: '算术评级说明', shade: 0.6, // 遮罩透明度 maxmin: true, // 允许全屏最小化 anim: 5, // 0-6的动画形式,-1不开启 content: `

${levelContent}

` }); }; pa.childNodes[le - 4].removeAttribute('hidden'); } else { pa.childNodes[le - 4].childNodes[0].innerText = '未知评级'; pa.childNodes[le - 4].setAttribute('hidden', 'true'); pa.childNodes[le - 4].setAttribute('href', '/xxx'); } // ContestID_zh ContestSlug if (t2rate[id] != null) { let contestUrl; let num = getcontestNumber(t2rate[id]['ContestSlug']); if (num < 83) { contestUrl = zhUrl; } else { contestUrl = url; } pa.childNodes[le - 3].childNodes[0].innerText = t2rate[id]['ContestID_zh']; pa.childNodes[le - 3].setAttribute('href', contestUrl + t2rate[id]['ContestSlug']); pa.childNodes[le - 3].setAttribute('target', '_blank'); pa.childNodes[le - 3].removeAttribute('hidden'); pa.childNodes[le - 2].childNodes[0].innerText = t2rate[id]['ProblemIndex']; pa.childNodes[le - 2].setAttribute( 'href', contestUrl + t2rate[id]['ContestSlug'] + '/problems/' + t2rate[id]['TitleSlug'] ); pa.childNodes[le - 2].setAttribute('target', '_blank'); if (switchrealoj) pa.childNodes[le - 2].setAttribute('hidden', 'true'); else pa.childNodes[le - 2].removeAttribute('hidden'); } else { pa.childNodes[le - 3].childNodes[0].innerText = '对应周赛未知'; // 不填写的话默认为当前url pa.childNodes[le - 3].setAttribute('href', '/xxx'); pa.childNodes[le - 3].setAttribute('target', '_self'); pa.childNodes[le - 3].setAttribute('hidden', 'true'); pa.childNodes[le - 2].childNodes[0].innerText = '未知'; pa.childNodes[le - 2].setAttribute('href', '/xxx'); pa.childNodes[le - 2].setAttribute('target', '_self'); pa.childNodes[le - 2].setAttribute('hidden', 'true'); } } t1 = t.textContent; } } function clearId(name) { // 'all', 'tag', 'pb', 'company', 'pblist', 'search', 'study' let tmp = GM_getValue(name, -1); clearInterval(tmp); console.log('clear ' + name + ' ' + id + ' success'); } let shortCnt = 3; let normalCnt = 5; function initCnt() { // 卡顿问题页面修复 // 搜索页面为自下拉,所以需要无限刷新,无法更改,这一点不会造成卡顿,所以剔除计划 // 题库页 ✅ lcCnt = 0; pbSetCnt = 0; // 题目页 pbCnt = 0; // ✅ pbCnt2 = 0; // ✅ // 题单页 ✅ pbsideCnt = 0; pbListpbCnt = 0; pbListCnt = 0; // ✅ studyCnt = 0; // ✅ } // 初始化一些lc切换网页但是没有reload,需要执行的方法 function initfunction() { // 添加题目页面复制按钮 console.log('当前页面url: ' + location.href); if (GM_getValue('switchcopyright') && location.href.match(pbUrl)) { console.log('当前处于题目页,已开始添加复制按钮....'); copyNoRight(); } // 创建题目状态icon,题目页和讨论区刷新 waitOprpbStatus(); if (GM_getValue('switchpbstatus') && location.href.match(pbUrl)) { console.log('当前处于题目页,已开启题目提交监听....'); pbsubmitListen(); } } function clearAndStart(url, timeout, isAddEvent) { initCnt(); initfunction(); let start = ''; let targetIdx = -1; let pageLst = ['all', 'pb', 'pblist', 'search', 'study']; let urlLst = [allUrl, pbUrl, pblistUrl, searchUrl, studyUrl]; let funcLst = [getData, getpb, getPblistData, getSearch, getStudyData]; for (let index = 0; index < urlLst.length; index++) { const element = urlLst[index]; if (url.match(element)) { targetIdx = index; } else if (!url.match(element)) { // 清理其他的 let tmp = GM_getValue(pageLst[index], -1); clearInterval(tmp); } } if (targetIdx != -1) start = pageLst[targetIdx]; if (start != '') { // 清理重复运行 let preId = GM_getValue(start); if (preId != null) { clearInterval(preId); } let css_selector = 'div.relative.flex.w-full.flex-col > .flex.w-full.flex-col.gap-4'; if (start == 'study') { id = setInterval(getStudyData, timeout, css_selector); } else if (start == 'pb') { id = setInterval(getpb, timeout); if (GM_getValue('switchpbside')) { let pbsideId = setInterval(getpbsideData, timeout); GM_setValue('pbside', pbsideId); } } else { id = setInterval(funcLst[targetIdx], timeout); } GM_setValue(start, id); } if (isAddEvent) { // 只需要定位urlchange变更 window.addEventListener('urlchange', () => { console.log('urlchange/event/happened'); let newUrl = location.href; clearAndStart(newUrl, 1000, false); }); } } // 获取界面所需数据, 需要在菜单页面刷新前进行更新 function getNeedData() { // 更新分数数据 async function getScore() { let now = getCurrentDate(1); preDate = GM_getValue('preDate', ''); if (t2rate['tagVersion9'] == null || preDate == '' || preDate != now) { // 每天重置为空 GM_setValue('pbSubmissionInfo', '{}'); let res = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'get', url: rakingUrl + '?timeStamp=' + new Date().getTime(), headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, onload: function (res) { resolve(res); }, onerror: function (err) { console.log('error'); console.log(err); } }); }); if (res.status === 200) { // 保留唯一标识 t2rate = {}; pbName2Id = {}; pbNamee2Id = {}; let dataStr = res.response; let json = eval(dataStr); for (const element of json) { t2rate[element.ID] = element; t2rate[element.ID]['Rating'] = Number.parseInt( Number.parseFloat(element['Rating']) + 0.5 ); pbName2Id[element.TitleZH] = element.ID; pbNamee2Id[element.Title] = element.ID; } t2rate['tagVersion9'] = {}; console.log('everyday getdata once...'); preDate = now; GM_setValue('preDate', preDate); GM_setValue('t2ratedb', JSON.stringify(t2rate)); GM_setValue('pbName2Id', JSON.stringify(pbName2Id)); GM_setValue('pbNamee2Id', JSON.stringify(pbNamee2Id)); } } } getScore(); // 更新level数据 async function getPromiseLevel() { let week = new Date().getDay(); if (levelData['tagVersion24'] == null || week == 1) { let res = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'get', url: levelUrl + '?timeStamp=' + new Date().getTime(), headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, onload: function (res) { resolve(res); }, onerror: function (err) { console.log('error'); console.log(err); } }); }); if (res.status === 200) { levelData = {}; levelTc2Id = {}; levelTe2Id = {}; let dataStr = res.response; let json = eval(dataStr); for (const element of json) { if (typeof element.TitleCn == 'string') { let titlec = element.TitleCn; let title = element.Title; levelData[element.ID] = element; levelTc2Id[titlec] = element.ID; levelTe2Id[title] = element.ID; } } levelData['tagVersion24'] = {}; console.log('every Monday get level once...'); GM_setValue('levelData', JSON.stringify(levelData)); GM_setValue('levelTc2Id', JSON.stringify(levelTc2Id)); GM_setValue('levelTe2Id', JSON.stringify(levelTe2Id)); } } } getPromiseLevel(); // 版本更新机制 let now = getCurrentDate(1); preDate1 = GM_getValue('preDate1', ''); let checkVersionLayer = GM_getValue('switchupdate') ? preDate1 == '' || preDate1 != now : true; GM_xmlhttpRequest({ method: 'get', url: versionUrl + '?timeStamp=' + new Date().getTime(), headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, onload: function (res) { if (res.status === 200) { console.log('check version success...'); let dataStr = res.response; let json = JSON.parse(dataStr); let v = json['version']; let upcontent = json['content']; // 更新纸片人地址 papermanpic = json['papermanpic']; // 通过更新 CSS 变量来更新纸片人 document.documentElement.style.setProperty('--mumu-img', `url(${papermanpic})`); console.log(papermanpic); if (v != version) { if (checkVersionLayer) { console.log('弹窗更新栏一次..'); layer.open({ area: ['500px', '300px'], content: '
更新通知: 
leetcodeRating有新的版本' + v + '啦,请前往更新~
' + '更新内容:
' + upcontent + '
', yes: function (index, layer0) { let c = window.open(sciptUrl + '?timeStamp=' + new Date().getTime()); // c.close() layer.close(index); preDate1 = now; GM_setValue('preDate1', preDate1); console.log('update preDate1 success'); } }); } else { console.log('有新的版本,但是已经弹窗过且开启了最多只更新一次功能,等待明天弹窗..'); } } else { console.log('leetcodeRating难度分插件当前已经是最新版本~'); } } }, onerror: function (err) { console.log('error'); console.log(err); } }); } // 获取必须获取的数据 getNeedData(); // 如果pbstatus数据开关已打开且需要更新 if (GM_getValue('switchpbstatus')) { (function () { let pbstatus = JSON.parse(GM_getValue('pbstatus', '{}').toString()); if (pbstatus[pbstatusVersion]) { console.log('已经同步过初始题目状态数据...'); return; } let syncLayer = layer.confirm( '
检测本地没有题目数据状态,即将开始初始化进行所有题目状态,是否开始同步?
tips:(该检测和开启讨论区展示题目状态功能有关)
', { icon: 3 }, function () { layer.close(syncLayer); layer.open({ type: 1, content: `${pbstatusContent}`, title: '同步所有题目状态', area: ['550px', '250px'], shade: 0.6 }); layuiload(); }, function () { // do nothong } ); })(); } // 定时启动函数程序 clearAndStart(location.href, 1000, true); GM_addStyle(` .versioncontent { white-space: pre-wrap; word-wrap: break-word; display: block; } `); // TODO 分割 // spig js 纸片人相关 if (GM_getValue('switchperson')) { const isindex = true; const visitor = '主人'; let msgs = []; // 求等级用的数据 let userTag = null; let level = 0; let score = 0; const queryProcess = '\n query userQuestionProgress($userSlug: String!) {\n userProfileUserQuestionProgress(userSlug: $userSlug) {\n numAcceptedQuestions {\n difficulty\n count\n }\n numFailedQuestions {\n difficulty\n count\n }\n numUntouchedQuestions {\n difficulty\n count\n }\n }\n}\n '; const queryUser = '\n query globalData {\n userStatus {\n isSignedIn\n isPremium\n username\n realName\n avatar\n userSlug\n isAdmin\n checkedInToday\n useTranslation\n premiumExpiredAt\n isTranslator\n isSuperuser\n isPhoneVerified\n isVerified\n }\n jobsMyCompany {\n nameSlug\n }\n commonNojPermissionTypes\n}\n '; GM_addStyle(` :root { --mumu-img: url(${papermanpic}); } .spig { display:block; width:154px; height:190px; position:absolute; top: -150px; left: 160px; z-index:9999; } #message { line-height:170%; color :#191919; border: 1px solid #c4c4c4; background:#ddd; -moz-border-radius:5px; -webkit-border-radius:5px; border-radius:5px; min-height:1em; padding:5px; top:-30px; position:absolute; text-align:center; width:auto !important; z-index:10000; -moz-box-shadow:0 0 15px #eeeeee; -webkit-box-shadow:0 0 15px #eeeeee; border-color:#eeeeee; box-shadow:0 0 15px #eeeeee; outline:none; opacity: 0.75 !important; } .mumu { width:154px; height:190px; cursor: move; background:var(--mumu-img) no-repeat; } #level { text-align:center; z-index:9999; color :#191919; } `); const spig = `