洛谷通过题目比较器 - yyfcpp

比较你和其他用户在洛谷通过的题目

  1. // ==UserScript==
  2. // @name 洛谷通过题目比较器 - yyfcpp
  3. // @namespace http://tampermonkey.net/
  4. // @version 4.1
  5. // @description 比较你和其他用户在洛谷通过的题目
  6. // @author Anguei
  7. // @match https://www.luogu.com.cn/user/*
  8. // @match https://www.luogu.com.cn/record/list*
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // ==/UserScript==
  12.  
  13. var version = GM_getValue('version');
  14. if (version == undefined || version == 'undefined') {
  15. alert('感谢您长久以来的支持。洛谷 AC 比较器咕咕咕许久后,今日终于更新了。')
  16. }
  17. GM_setValue('version', '4.0');
  18.  
  19.  
  20. var myUid = GM_getValue('myUid');
  21. if (myUid == undefined || myUid == 'undefined') {
  22. while (1) {
  23. myUid = prompt(
  24. 'AC 比较器插件更新,请正确输入您的 uid(数字)以保障插件正常运行');
  25. if (prompt('请再次输入 uid') != myUid) {
  26. alert('两次输入数据不同,请重试');
  27. } else
  28. break;
  29. }
  30. }
  31. GM_setValue('myUid', myUid);
  32.  
  33. window.onload = function() {
  34. console.log('ACC: starting...');
  35.  
  36. function getJson(uid) {
  37. console.log('gettting json of ' + uid);
  38. var oReq = new XMLHttpRequest();
  39. oReq.open('GET', 'https://www.luogu.com.cn/user/' + uid, false);
  40. oReq.send();
  41. if (oReq.status != 200) throw ('AC 比较器:网络不稳定,终止运行。');
  42. var text = oReq.responseText;
  43. // console.log(text);
  44. var e = text.match(/JSON\.parse\(.*\)/)[0];
  45. var json = eval(e);
  46. return json;
  47. }
  48.  
  49. function getProblems(pJsons) {
  50. var arr = [];
  51. for (var i = 0; i < pJsons.length; ++i) arr.push(pJsons[i]['pid']);
  52. return arr;
  53. }
  54.  
  55.  
  56. if (window.location.href.match(/list/) == null) { // 个人主页
  57. var hisUid = window.location.href.match(/[0-9]+/)[0];
  58. console.log(myUid);
  59. console.log(hisUid);
  60. if (hisUid == myUid) {
  61. var myJson = getJson(myUid);
  62. var myAcCnt = myJson['currentData']['passedProblems'].length;
  63. setInterval(setAcCnt, 1000, myAcCnt);
  64. } else {
  65. var myJson = getJson(myUid);
  66. var hisJson = getJson(hisUid);
  67. console.log('got all jsons');
  68. var hisAcCnt = hisJson['currentData']['passedProblems'].length;
  69. setInterval(setAcCnt, 1000, hisAcCnt);
  70.  
  71. var myAced = getProblems(myJson['currentData']['passedProblems']);
  72. var myTried = getProblems(myJson['currentData']['submittedProblems']);
  73. var hisAced = getProblems(hisJson['currentData']['passedProblems']);
  74. var hisTried = getProblems(hisJson['currentData']['submittedProblems']);
  75.  
  76. setInterval(function() {
  77. if (window.location.href.match(/#practice/) == null) return; // 练习页面
  78. for (var i = 0; i < hisAced.length; ++i) {
  79. if (myAced.indexOf(hisAced[i]) != -1)
  80. deco(2, i, '#008000');
  81. else if (myTried.indexOf(hisAced[i]) != -1)
  82. deco(2, i, '#ff8c00');
  83. else
  84. deco(2, i, 'red')
  85. }
  86. for (var i = 0; i < hisTried.length; ++i) {
  87. if (myAced.indexOf(hisTried[i]) != -1)
  88. deco(1, i, '#008000');
  89. else if (myTried.indexOf(hisTried[i]) != -1)
  90. deco(1, i, '#ff8c00');
  91. else
  92. deco(1, i, 'red')
  93. }
  94. }, hisAcCnt);
  95. }
  96.  
  97.  
  98. function setAcCnt(x) {
  99. document
  100. .querySelector(
  101. '#app > div.main-container > main > div > div.card.user-header-container.padding-0 > div.user-header-bottom > div.user-stat-data.lfe-caption > div > div:nth-child(4) > a > span.value')
  102. .innerText = x;
  103. }
  104.  
  105. function deco(tryOrAc, idx, color) {
  106. // console.log(tryOrAc, idx, color);
  107. document
  108. .querySelector(
  109. '#app > div.main-container > main > div > div.full-container > section.main > div:nth-child(' +
  110. tryOrAc + ') > div.problems > span:nth-child(' + (idx + 1) +
  111. ') > a')
  112. .style = 'color: ' + color;
  113. }
  114. } else { // 评测记录
  115. var myJson = getJson(myUid);
  116. var myAcCnt = myJson['currentData']['passedProblems'].length;
  117. var myAced = getProblems(myJson['currentData']['passedProblems']);
  118. var myTried = getProblems(myJson['currentData']['submittedProblems']);
  119.  
  120. setInterval(function() {
  121. for (var i = 1; i <= 20; ++i) {
  122. var selector =
  123. '#app > div.main-container > main > div > div > div > div.border.table > div > div:nth-child(' +
  124. i + ') > div.problem > div > a';
  125. var problem = document.querySelector(selector).innerText;
  126. var problemId = problem.split(' ')[0];
  127.  
  128. if (myAced.indexOf(problemId) != -1)
  129. document.querySelector(selector).style = 'color: ' +
  130. '#008000';
  131. else if (myTried.indexOf(problemId) != -1)
  132. document.querySelector(selector).style = 'color: ' +
  133. '#ff8c00';
  134. else
  135. document.querySelector(selector).style = 'color: ' +
  136. 'red';
  137. }
  138. }, 300);
  139. }
  140. };
  141.  
  142.  
  143.  
  144. /*// 几个开关,可根据用户喜好自行定义
  145. var settings = {};
  146. var colored = 0;
  147.  
  148. // alert(document.body.innerHTML.match('uid=([0-9]+)')[0]);
  149. // console.log(document.body.innerHTML);
  150. var myUid = GM_getValue('myUid');
  151. if(myUid == undefined || myUid == 'undefined')
  152. myUid =
  153. prompt('比较器脚本更新,请正确输入您的 uid(数字)以保障插件正常运行');
  154. GM_setValue('myUid', myUid);
  155.  
  156.  
  157. function getAc(uid) {
  158. // 向指定的个人空间发送 get 请求,获取 AC 列表
  159. var xhr = new XMLHttpRequest();
  160. xhr.open(
  161. 'GET', 'https://' + window.location.host + '/space/show?uid=' + uid,
  162. false);
  163. xhr.send(null);
  164. console.log('got ' + uid + '\'s AC list: ' + xhr.status);
  165. if (xhr.status == 200) {
  166. return extractData(xhr.responseText); // 返回 AC 列表
  167. } else {
  168. return []; // 空列表
  169. }
  170.  
  171. function extractData(content) {
  172. // 如果你有一个问题打算用正则表达式来解决,那么就是两个问题了。
  173. // 所以窝还是用 split() 解决这一个问题吧!
  174. var acs = content.replace(
  175. /<span style=\"display:none\">\n.*?\n<\/span>/g,
  176. ''); // 把随机的干扰题号去除
  177. acs = acs.split(
  178. '[<a data-pjax href="/problemnew/show/'); // 使用 split()
  179. // 方法把通过的题目分割出来
  180. acs = clearData(acs); // 把分割好的数据清洁一下
  181. return acs;
  182.  
  183. function clearData(acs) {
  184. var res = new Array();
  185. res.push(new Array());
  186. res.push(new Array());
  187. var g = 0;
  188. for (var i = 1; i < acs.length;
  189. i++) { // 把每一行非题号字符删掉(从 1 开始循环为了避开 split
  190. // 之后产生的垃圾)
  191. var tmpStr = '';
  192. for (var j = 0; j < acs[i].length; j++) {
  193. if (acs[i][j] != '"') { // 引号后面的不是题号部分字符
  194. tmpStr = tmpStr.concat(acs[i][j]); // 拼接字符串
  195. } else
  196. break;
  197. }
  198. res[g].push(tmpStr);
  199. if (acs[i].length >
  200. 50) { // 这是最后一个题目 / 下一个是「尝试过的题目」
  201. g++;
  202. }
  203. }
  204. return res;
  205. }
  206. }
  207. }
  208.  
  209.  
  210. function compare_new(hisAc, myAc, myAttempt) {
  211. myAc.sort(); // 排序,用于二分查找
  212. myAttempt.sort();
  213. var tot = hisAc.length;
  214. for (var i = 0; i < hisAc.length; i++) {
  215. var meToo = false;
  216. if (binarySearch(hisAc[i], myAc)) {
  217. meToo = true;
  218. tot--;
  219. }
  220. if (++colored <= settings['limOfColoring']) { // 如果尚未超过阈值
  221. changeStyle(hisAc[i], meToo); // 改变题号颜色
  222. }
  223. if (!meToo) { // 没 AC 过,有没有尝试过?
  224. if (binarySearch(hisAc[i], myAttempt)) {
  225. meToo = true;
  226. }
  227. if (colored <= settings['limOfColoring']) {
  228. changeStyle2(hisAc[i], meToo);
  229. }
  230. }
  231. }
  232. if (settings['totDisplaying']) { // 如果打开显示未 AC 总数的开关
  233. displayTot(tot); // 显示 AC 总数
  234. }
  235.  
  236. function changeStyle(pid, meToo) // AC 过的题目
  237. {
  238. var cssSelector = 'a[href=\'/problemnew/show/' + pid + '\']';
  239. // 由于洛谷使用随机页面结构,导致了一点小问题,所以要
  240. // querySelectorAll,防止染色失败
  241. var elements = document.querySelectorAll(cssSelector);
  242. for (var i = 0; i < elements.length; i++) {
  243. elements[i].style.color = meToo ? '#008000' : 'red';
  244. }
  245. }
  246.  
  247. function changeStyle2(pid, meToo) // 尝试过的题目
  248. {
  249. var cssSelector = 'a[href=\'/problemnew/show/' + pid + '\']';
  250. var elements = document.querySelectorAll(cssSelector);
  251. for (var i = 0; i < elements.length; i++) {
  252. elements[i].style.color = meToo ? '#ff8c00' : 'red';
  253. }
  254. }
  255.  
  256. function displayTot(tot) {
  257. var cssSelector =
  258. '#app-body-new > div.am-g.lg-main-content > div.am-u-md-4.lg-right >
  259. div:nth-child(2) > h2'; document.querySelector(cssSelector).style.fontSize =
  260. '18px'; // 避免在一些低分辨率显示器上一行显示不开
  261. document.querySelector(cssSelector).textContent =
  262. '通过题目(其中有 ' + tot + ' 道题你尚未 AC)';
  263. }
  264. }
  265.  
  266.  
  267. function binarySearch(target, array) { // 使用二分查找算法进行比较
  268. var l = 0, r = array.length;
  269. while (l < r) {
  270. var mid = parseInt((l + r) / 2); // JavaScript 除法默认不是整数。。
  271. if (target == array[mid])
  272. return true;
  273. else if (target > array[mid])
  274. l = mid + 1;
  275. else
  276. r = mid;
  277. }
  278. return false;
  279. }
  280.  
  281.  
  282. function displayAcCnt(AcCnt) {
  283. for (var i = 2; i <= 3;
  284. i++) { // 解决页面结构不稳定导致的 AC 数无法正常显示问题
  285. var cssSelector =
  286. '#app-body-new > div.am-g.lg-main-content > div.am-u-md-4.lg-right >
  287. section > div > ul > li:nth-child(' + i +
  288. ') > ul > li:nth-child(2) > span.lg-bignum-num'; // 适配新的洛谷 UI
  289. if (document.querySelector(cssSelector) !=
  290. null) { // 确定了 AC 数的选择器
  291. document.querySelector(cssSelector).innerHTML =
  292. AcCnt + '<small></small>'; // 更新 AC 数
  293. if (settings['colorChanging']) { // 如果打开颜色变化的开关
  294. changeAcColor(cssSelector, AcCnt);
  295. }
  296. break;
  297. }
  298. }
  299.  
  300. function changeAcColor(cssSelector, AcCnt) {
  301. if (AcCnt >= 1275)
  302. document.querySelector(cssSelector).style = 'color:#FF0000;';
  303. else if (AcCnt >= 867)
  304. document.querySelector(cssSelector).style =
  305. 'color:rgb(255,' + ((1275 - AcCnt) / 2) + ',0);';
  306. else if (AcCnt >= 765)
  307. document.querySelector(cssSelector).style = 'color:rgb(' +
  308. ((AcCnt - 357) / 2) + ',' + ((1275 - AcCnt) / 2) + ',0);';
  309. else if (AcCnt >= 459)
  310. document.querySelector(cssSelector).style =
  311. 'color:rgb(' + ((AcCnt - 357) / 2) + ',255,0);';
  312. else if (AcCnt >= 357)
  313. document.querySelector(cssSelector).style =
  314. 'color:rgb(51,' + ((AcCnt + 51) / 2) + ',' + (459 - AcCnt) + ');';
  315. else if (AcCnt >= 204)
  316. document.querySelector(cssSelector).style =
  317. 'color:rgb(51,' + (AcCnt - 153) + ',' + (459 - AcCnt) + ');';
  318. else
  319. document.querySelector(cssSelector).style =
  320. 'color:rgb(51,51,' + (51 + AcCnt) + ');';
  321. }
  322. }
  323.  
  324.  
  325. function work() {
  326. var myAc = getAc(myUid);
  327. var hisAc = getAc(hisUid);
  328. // console.log(myAc);
  329. // console.log(hisAc);
  330. if (hisAc[0].length > 0) { // 对方没开完全隐私保护
  331. var start = new Date();
  332. compare_new(hisAc[0], myAc[0], myAc[1]);
  333. console.log('比较耗时:' + (new Date() - start) + 'ms');
  334. displayAcCnt(getAcCnt());
  335. } else {
  336. console.log('对方开启了完全隐私保护,无法比较。');
  337. }
  338. }
  339.  
  340.  
  341. function setSettings() {
  342. alert('首次运行新版比较器,请进行初步设置');
  343. if (GM_getValue('CompSettings') == undefined ||
  344. GM_getValue('CompSettings') == 'undefined') {
  345. settings = {};
  346. }
  347. settings['limOfColoring'] = prompt(
  348. '请设置红橙绿染色的题目上限(染色量太大会降低页面加载速度)(-1
  349. 表示不限量)') settings['totDisplaying'] = confirm('是否显示对方 AC 而您却未 AC
  350. 的题目总数?'); settings['colorChanging'] = confirm('是否根据用户 AC 数量显示 AC
  351. 数不同颜色?'); settings['repairAcCount'] = confirm( '是否实时更新 AC
  352. 数并把一千以上的 AC 数转化为精准数字?\n解释:洛谷个人空间的 AC
  353. 数非实时更新,开启该功能可实现实时更新'); if (settings['limOfColoring'] == '-1')
  354. { settings['limOfColoring'] = '99999';
  355. }
  356. GM_setValue('CompSettings', settings);
  357. alert('设置成功,您可以随时在任意用户的个人空间点击「更改」按钮修改设置。')
  358. }
  359.  
  360.  
  361. settings = GM_getValue('CompSettings');
  362. if(settings == undefined || settings == 'undefined') {
  363. setSettings();
  364. settings = GM_getValue('CompSettings');
  365. } else if(
  366. settings['records'] == undefined || settings['records'] == 'undefined') {
  367. // 这个 else 的内容下次更新可以删除
  368. settings['records'] = 1;
  369. GM_setValue('CompSettings', settings);
  370. // $.post("/api/discuss/reply/" + '60968', { content: 'records 版本报道',
  371. // verify: verify });
  372. alert('比较器更新,现在支持评测记录页面的比较!');
  373. }
  374.  
  375.  
  376. function getAcCnt() {
  377. var colors = document.getElementsByTagName('table')[0]
  378. .firstElementChild.innerText.match(/[0-9]+/g);
  379. var res = 0;
  380. for (var i = 0; i < colors.length; i++) {
  381. res += parseInt(colors[i]);
  382. }
  383. // console.log('acCnt = ' + res);
  384. return res;
  385. }
  386.  
  387.  
  388. if(window.location.href.match(/space/) != null) { // 个人空间页面
  389. $('#app-body-new > div.am-g.lg-main-content > div.am-u-md-4.lg-right > section
  390. > div > p') .append(
  391. '<button class="am-btn am-btn-sm am-btn-primary"
  392. id="changeComp">更改</button>')
  393. $('#changeComp').click(setSettings);
  394.  
  395. var hisUid = window.location.href.match(/uid=[0-9]+/)[0].substr(
  396. 4); // 获取当前所在个人空间主人的 UID
  397. if (document.getElementsByClassName('am-btn am-btn-sm am-btn-primary')[0]
  398. .attributes['href'] == undefined) { // 在自己的个人主页
  399. if (settings['repairAcCount']) {
  400. displayAcCnt(getAcCnt());
  401. }
  402. } else { // 在别人的主页
  403. // var myUid = document.getElementsByClassName('am-btn am-btn-sm
  404. // am-btn-primary')[0].attributes['href'].value.match(/[0-9]+/)[0]; //
  405. // 获取当前登录(不可用)账号的 uid(洛谷前端改版后)
  406. var myUid = GM_getValue('myUid');
  407. work();
  408. }
  409. } else if(window.location.href.match(/record/) != null) { // 评测记录页面
  410. var hisUidOrName = window.location.href.match(/user\=(.*)/g)[0].substr(
  411. 5); //
  412. 如果是一道题目的全部评测记录页面,这里会出现异常,直接退出,刚好不需要比较 var
  413. hisUid = ''; console.log(hisUidOrName);
  414. /*var hisUidOrName = '67013';
  415. $.get("/space/ajax_getuid?username=" + hisUidOrName, // 把用户名转化为 uid
  416. function (data) {
  417. hisUid = eval('(' + data + ')')['more']['uid'];
  418. });* /
  419. console.log(hisUid);
  420. var myUid = GM_getValue('myUid');
  421. console.log(hisUid);
  422. console.log(myUid);
  423. if (hisUid != myUid) {
  424. // console.log(myUid);
  425. recordsWork();
  426.  
  427. function recordsWork() {
  428. var myAc = getAc(myUid);
  429. var myAttempt = myAc[1];
  430. myAc = myAc[0];
  431. myAc.sort();
  432. myAttempt.sort();
  433. console.log(myAc);
  434. console.log(myAttempt);
  435. document.addEventListener('DOMContentLoaded', function(e) {
  436. var pageAcs = document.getElementsByClassName('part right-part');
  437. console.log(pageAcs);
  438. console.log(pageAcs.length);
  439.  
  440. for (var i = 0; i < pageAcs.length; i++) {
  441. var thisPid = pageAcs[i].innerText.split('\n')[0].split(' ')[0]
  442. console.log(thisPid)
  443. // var thisColor =
  444. // pageAcs[i].lastElementChild.firstElementChild.style.color;
  445. if (binarySearch(thisPid, myAc)) { // 也 AC
  446. pageAcs[i].firstElementChild.firstElementChild.style.color =
  447. '#008000';
  448. }
  449. else if (binarySearch(thisPid, myAttempt)) { // 尝试过
  450. pageAcs[i].firstElementChild.firstElementChild.style.color =
  451. '#ff8c00';
  452. }
  453. else { // 未 AC
  454. pageAcs[i].firstElementChild.firstElementChild.style.color =
  455. 'red';
  456. }
  457. }
  458. });
  459. }
  460. }
  461. }*/

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址