// ==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/[email protected]/dist/jquery.min.js
// @require https://unpkg.com/[email protected]/dist/layui.js
// @grant unsafeWindow
// ==/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(
`<link href="https://unpkg.com/[email protected]/index.min.css" rel="stylesheet">`
);
// 页面相关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 = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="1em" height="1em" fill="currentColor" class="myiconsvg h-[18px] w-[18px] text-green-s dark:text-dark-green-s"><path fill-rule="evenodd" d="M20 12.005v-.828a1 1 0 112 0v.829a10 10 0 11-5.93-9.14 1 1 0 01-.814 1.826A8 8 0 1020 12.005zM8.593 10.852a1 1 0 011.414 0L12 12.844l8.293-8.3a1 1 0 011.415 1.413l-9 9.009a1 1 0 01-1.415 0l-2.7-2.7a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg> `;
break;
case 2:
value = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="1.6 0 12.5 14" width="1.2em" height="1.2em" fill="currentColor" class="myiconsvg text-message-warning dark:text-message-warning"><path d="M6.998 7v-.6a.6.6 0 00-.6.6h.6zm.05 0h.6a.6.6 0 00-.6-.6V7zm0 .045v.6a.6.6 0 00.6-.6h-.6zm-.05 0h-.6a.6.6 0 00.6.6v-.6zm5-.045a5 5 0 01-5 5v1.2a6.2 6.2 0 006.2-6.2h-1.2zm-5 5a5 5 0 01-5-5h-1.2a6.2 6.2 0 006.2 6.2V12zm-5-5a5 5 0 015-5V.8A6.2 6.2 0 00.798 7h1.2zm5-5a5 5 0 015 5h1.2a6.2 6.2 0 00-6.2-6.2V2zm2.2 5a2.2 2.2 0 01-2.2 2.2v1.2a3.4 3.4 0 003.4-3.4h-1.2zm-2.2 2.2a2.2 2.2 0 01-2.2-2.2h-1.2a3.4 3.4 0 003.4 3.4V9.2zM4.798 7a2.2 2.2 0 012.2-2.2V3.6a3.4 3.4 0 00-3.4 3.4h1.2zm2.2-2.2a2.2 2.2 0 012.2 2.2h1.2a3.4 3.4 0 00-3.4-3.4v1.2zm0 2.8h.05V6.4h-.05v1.2zm-.55-.6v.045h1.2V7h-1.2zm.6-.555h-.05v1.2h.05v-1.2zm.55.6V7h-1.2v.045h1.2z"></path></svg> `;
break;
// code3 的时候需要调整style,所以设置了class,调整在css中
case 3:
value = `<svg class="myiconsvg" width="21" height="20">
<circle class="mycircle" stroke="black" stroke-width="2" fill="white"></circle>
</svg> `;
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 = `<i style="font-size:12px;" class="layui-icon layui-icon-refresh"></i> 同步题目状态`;
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)) {
// 获取所有的<a>标签
let links = document.querySelectorAll('a');
// 过滤出符合条件的<a>标签
let matchingLinks = Array.from(links).filter(link => {
return (
!link.getAttribute('linkId') &&
link.href.match(pbUrl) &&
!link.href.match(pbSolutionUrl)
);
});
// console.log(matchingLinks);
// 符合条件的<a>标签
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 = $('<a>', {
class: 'group flex flex-col rounded-[8px] duration-300',
id: Date.now(), // 随便给个唯一id
target: '_blank',
href: pburl // 跳转链接
});
const $div1 = $('<div>', {
class: 'flex h-[44px] w-full items-center space-x-3 px-4'
});
const $wrapper = $('<div>', {
style: 'transform: translateX(-3px);'
});
// 嵌套的小结构
const $inner1 = $('<div>', {
class: 'flex items-center justify-center w-[20px] h-[20px]'
}).append(
$('<svg>', {
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: `
<path d="M287.9 0c9.2 0 17.6 5.2 21.6 13.5l68.6 141.3
153.2 22.6c9 1.3 16.5 7.6 19.3 16.3s.5 18.1-5.9 24.5L433.6
328.4l26.2 155.6c1.5 9-2.2 18.1-9.6 23.5s-17.3 6-25.3
1.7l-137-73.2-137 73.2c-8.1 4.3-17.9 3.7-25.3-1.7s-11.2-14.5-9.7-23.5
l26.2-155.6-111-108.2c-6.5-6.4-8.7-15.9-5.9-24.5s10.3-14.9
19.3-16.3l153.2-22.6 68.6-141.3C270.4 5.2 278.7 0 287.9 0z"/>
`
})
);
// 第二块内容
const $inner2 = $('<div>', {
class: 'relative flex h-full w-full cursor-pointer items-center'
}).append(
$('<div>', { class: 'flex w-0 flex-1 items-center space-x-2' }).append(
$('<div>', { class: 'text-body text-sd-foreground max-w-[90%] font-medium' }).append(
$('<div>', { class: 'ellipsis line-clamp-1' }).text(title)
)
),
$('<div>', {
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),
$('<p>', { class: 'mx-0 text-[14px] lc-xl:mx-4' }).text(difficulty)
);
// 第三个部分 小竖条
const $inner3 = $('<div>', { 'data-state': 'closed' }).append(
$('<div>', { class: 'flex gap-0.5 px-1' }).append(
Array.from({ length: 8 }).map(() =>
$('<div>', { class: 'h-2 w-0.5 rounded bg-sd-foreground opacity-20' })
)
)
);
// 收藏
const $inner4 = $('<div>', {
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(
$('<div>', {
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 = $('<div></div>')
.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 = $('<div></div>')
.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<string, string>} lightn2c 亮模式难度列表
* @param {Record<string, string>} 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 += `<input name="" placeholder="请输入题号或关键字" class="lcr layui-input" id="id-dropdown">`;
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;
}
});
}
}
// 因为字符显示问题,暂时去除
// <span class="layui-progress-text myfont">0%</span>
let pbstatusContent = `
<div style="text-align: center;">
<strong class="myfont"> 希望有大佬可以美化这丑丑的界面~ =v= <strong>
<p style="padding-top: 10px;"></p>
<div class="layui-progress layui-progress-big" lay-showpercent="true" lay-filter="demo-filter-progress">
<div class="layui-progress-bar" lay-percent="0%">
</div>
</div>
<p style="padding-top: 20px;"></p>
<div class="layui-btn-container" style="">
<button id="statusasyc" class="layui-btn layui-btn-radius" lay-on="loading">同步所有问题状态按钮</button>
</div>
</div>
`;
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 = `<i style="font-size:12px" class="layui-icon layui-icon-refresh"></i> 同步题目状态`;
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: `<p class="containerlingtea" style="padding:10px;color:#000;">${levelContent}</p>`
});
};
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: `<p class="containerlingtea" style="padding:10px;color:#000;">${levelContent}</p>`
});
};
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:
'<pre class="versioncontent" style="color:#000">更新通知: <br/>leetcodeRating有新的版本' +
v +
'啦,请前往更新~ <br/>' +
'更新内容: <br/>' +
upcontent +
'</pre>',
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(
'<div class="myfont">检测本地没有题目数据状态,即将开始初始化进行所有题目状态,是否开始同步? <br/> tips:(该检测和开启讨论区展示题目状态功能有关)</div>',
{ 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 = `<div id="spig" class="spig" hidden>
<div id="message">正在加载中……</div>
<div style="height=80px"/>
<div id="mumu" class="mumu"></div>
<div id="level">level loading...</div>
</div>`;
const hitokoto = `<span class="hitokoto" id="hitokoto" style="display:none">Loading...</span>`;
$('body').append(spig, hitokoto);
// 消息函数
let showMessage = (a, b) => {
if (b == null) b = 10000;
$('#mumu').css({ opacity: '0.5 !important' });
$('#message').hide().stop();
$('#message').html(a);
$('#message').fadeIn();
$('#message').fadeTo('1', 1);
$('#message').fadeOut(b);
$('#mumu').css({ opacity: '1 !important' });
};
// 右键菜单
jQuery(document).ready(function ($) {
$('#spig').mousedown(function (e) {
if (e.which == 3) {
showMessage(`秘密通道:<br/> <a href="${problemUrl}" title="题库">题库</a>`, 10000);
}
});
$('#spig').bind('contextmenu', function (e) {
return false;
});
});
function getscore(userTag) {
let list = { query: queryProcess, variables: { userSlug: userTag } };
$.ajax({
type: 'POST',
url: lcgraphql,
data: JSON.stringify(list),
success: function (res) {
let levelData = res.data.userProfileUserQuestionProgress.numAcceptedQuestions;
levelData.forEach(e => {
if (e.difficulty == 'EASY') score += e.count * 10;
else if (e.difficulty == 'MEDIUM') score += e.count * 20;
else if (e.difficulty == 'HARD') score += e.count * 100;
});
level = score / 1000;
$('#level').text('level: ' + Math.trunc(level).toString());
console.log('目前纸片人的等级是: ' + Math.trunc(level).toString());
},
async: false,
xhrFields: { withCredentials: true },
contentType: 'application/json;charset=UTF-8'
});
}
$.ajax({
type: 'POST',
url: lcgraphql,
data: JSON.stringify({ query: queryUser, variables: {} }),
success: function (res) {
userTag = res.data.userStatus.userSlug;
// console.log(userTag)
},
async: false,
xhrFields: { withCredentials: true },
contentType: 'application/json;charset=UTF-8'
});
if (userTag != null) {
getscore(userTag);
} else {
// console.log(userTag)
$('#level').text('请登录后再尝试获取level');
}
// 监听分数提交
let addListener2 = () => {
let checkUrl = 'https://leetcode.cn/submissions/detail/[0-9]*/check/.*';
XMLHttpRequest.prototype.send = function (str) {
const _onreadystatechange = this.onreadystatechange;
this.onreadystatechange = (...args) => {
if (this.readyState == this.DONE && this.responseURL.match(checkUrl)) {
let resp = JSON.parse(this.response);
// console.log(resp)
if (resp && resp.status_msg && resp.status_msg.includes('Accepted')) {
showMessage(
'恭喜主人成功提交, 当前分数为: ' +
score +
', 当前等级为: ' +
Math.trunc(level).toString()
);
console.log(
'恭喜主人成功提交, 当前分数为: ' +
score +
', 当前等级为: ' +
Math.trunc(level).toString()
);
} else if (resp && resp.status_msg && !resp.status_msg.includes('Accepted')) {
showMessage(
'很遗憾,主人提交失败,不过也不要气馁呀,加油! <br/> 当前分数为: ' +
score +
', 当前等级为: ' +
Math.trunc(level).toString()
);
console.log(
'很遗憾,主人提交失败,不过也不要气馁呀,加油! 当前分数为: ' +
score +
', 当前等级为: ' +
Math.trunc(level).toString()
);
}
}
if (_onreadystatechange) {
_onreadystatechange.apply(this, args);
}
};
return dummySend.call(this, str);
};
};
addListener2();
// 鼠标在消息上时
jQuery(document).ready(function ($) {
$('#message').hover(function () {
$('#message').fadeTo('100', 1);
});
});
// 鼠标在上方时
jQuery(document).ready(function ($) {
$('.mumu').mouseover(function () {
$('.mumu').fadeTo('300', 0.3);
msgs = [
'我隐身了,你看不到我',
'我会隐身哦!嘿嘿!',
'别动手动脚的,把手拿开!',
'把手拿开我才出来!'
];
let i = Math.floor(Math.random() * msgs.length);
showMessage(msgs[i]);
});
$('.mumu').mouseout(function () {
$('.mumu').fadeTo('300', 1);
});
});
function msgPageWelcome(url, isAddEvent) {
let urlLst = [allUrl, pbUrl, pblistUrl, searchUrl];
let msgShow = [
'欢迎来到题库页, 美好的一天从做每日一题开始~',
'欢迎来到做题页面,让我看看是谁光看不做?🐰',
'欢迎来到题单页面~',
'欢迎来到搜索页,在这里你能搜到一切你想做的题!'
];
for (let index = 0; index < urlLst.length; index++) {
const element = urlLst[index];
if (url.match(element)) {
// console.log(msgShow[index])
showMessage(msgShow[index]);
}
}
if (isAddEvent) {
window.addEventListener('urlchange', () => {
let newUrl = location.href;
msgPageWelcome(newUrl, false);
});
}
}
// 开始
jQuery(document).ready(function ($) {
if (isindex) {
// 如果是主页
let now = new Date().getHours();
if (now > 0 && now <= 6) {
showMessage(visitor + ' 你是夜猫子呀?还不睡觉,明天起的来么你?', 6000);
} else if (now > 6 && now <= 11) {
showMessage(
visitor + ' 早上好,早起的鸟儿有虫吃噢!早起的虫儿被鸟吃,你是鸟儿还是虫儿?嘻嘻!',
6000
);
} else if (now > 11 && now <= 14) {
showMessage(visitor + ' 中午了,吃饭了么?不要饿着了,饿死了谁来挺我呀!', 6000);
} else if (now > 14 && now <= 18) {
showMessage(visitor + ' 中午的时光真难熬!还好有你在!', 6000);
} else {
showMessage(visitor + ' 快来逗我玩吧!', 6000);
}
msgPageWelcome(location.href, true);
} else {
showMessage('力扣欢迎你~', 6000);
}
let top = $('#spig').offset().top + 150;
let left = document.body.offsetWidth - 160;
if (location.href.match(pbUrl)) {
top = $('#spig').offset().top + 200;
}
$('#spig').attr('hidden', false);
$('#spig').css({ top: top, left: left });
});
// 随滚动条移动
jQuery(document).ready(function ($) {
let f = $('.spig').offset().top;
$(window).scroll(function () {
$('.spig').animate(
{
top: $(window).scrollTop() + f + 150
},
{
queue: false,
duration: 1000
}
);
});
});
// 鼠标点击时
jQuery(document).ready(function ($) {
let stat_click = 0;
let i = 0;
$('.mumu').click(function () {
if (!ismove) {
stat_click++;
if (stat_click > 4) {
msgs = [
'你有完没完呀?',
'你已经摸我' + stat_click + '次了',
'非礼呀!救命!OH,My ladygaga'
];
i = Math.floor(Math.random() * msgs.length);
showMessage(msgs[i]);
} else {
msgs = [
'筋斗云!~我飞!',
'我跑呀跑呀跑!~~',
'别摸我,有什么好摸的!',
'惹不起你,我还躲不起你么?',
'不要摸我了,我会告诉你老婆来打你的!',
'干嘛动我呀!小心我咬你!'
];
i = Math.floor(Math.random() * msgs.length);
showMessage(msgs[i]);
}
let s = [
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.75, -0.1, -0.2, -0.3, -0.4, -0.5, -0.6, -0.7,
-0.75
];
let i1 = Math.floor(Math.random() * s.length);
let i2 = Math.floor(Math.random() * s.length);
$('.spig').animate(
{
left: (document.body.offsetWidth / 2) * (1 + s[i1]),
top: (document.body.offsetHeight / 2) * (1 + s[i2])
},
{
duration: 500,
complete: showMessage(msgs[i])
}
);
} else {
ismove = false;
}
});
});
// 拖动
let _move = false;
let ismove = false; // 移动标记
let _x, _y; // 鼠标离控件左上角的相对位置
jQuery(document).ready(function ($) {
$('#spig').mousedown(function (e) {
_move = true;
_x = e.pageX - parseInt($('#spig').css('left'));
_y = e.pageY - parseInt($('#spig').css('top'));
});
$(document)
.mousemove(function (e) {
if (_move) {
let x = e.pageX - _x;
let y = e.pageY - _y;
let wx = $(window).width() - $('#spig').width();
let dy = $(document).height() - $('#spig').height();
if (x >= 0 && x <= wx && y > 0 && y <= dy) {
$('#spig').css({
top: y,
left: x
}); //控件新位置
ismove = true;
}
}
})
.mouseup(function () {
_move = false;
});
});
// 纸片人一言api
// $("#spig").attr("hidden", false)
let hitokotohtml = function () {
let msgShow = [$('#hitokoto').text()];
showMessage(msgShow[0]);
setTimeout(hitokotohtml, 15000);
};
setTimeout(hitokotohtml, 6000);
function getkoto() {
$.get('https://v1.hitokoto.cn/?c=j&encode=json')
.then(res => {
echokoto(res);
})
.catch(xhr => xhr);
setTimeout(getkoto, 6000);
}
function echokoto(result) {
let hc = eval(result);
document.getElementById('hitokoto').textContent = hc.hitokoto;
// console.log(hc.content)
}
setTimeout(getkoto, 5000);
}
}
userScript();
})();