Luogu Helper

Print elegantly | Show difficulty

目前為 2023-02-17 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Luogu Helper
  3. // @name:zh-CN 洛谷助手
  4. // @description Print elegantly | Show difficulty
  5. // @description:zh-CN 优雅打印 | 显示难度
  6. // @namespace work.pythoner
  7. // @match *://*.luogu.com.cn/*
  8. // @require https://cdn.jsdelivr.net/npm/jquery@3/dist/jquery.min.js
  9. // @run-at document-end
  10. // @grant GM_registerMenuCommand
  11. // @grant GM_xmlhttpRequest
  12. // @grant GM_listValues
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @grant GM_deleteValue
  16. // @grant GM_log
  17. // @version 1.0
  18. // @author Hanson Hu
  19. // @homepage https://blog.pythoner.work
  20. // @icon https://blog.pythoner.work/favicon.ico
  21. // @license MIT
  22. // ==/UserScript==
  23.  
  24. $.noConflict();
  25. jQuery(document).ready(function() {
  26.  
  27. function getToday() {
  28. // local tz
  29. let ret = new Date().toLocaleString('sv').slice(0, 10);
  30. return ret;
  31. }
  32.  
  33. function getTail(str) {
  34. let index = str.lastIndexOf('\/');
  35. return str.substring(index + 1, str.length);
  36. }
  37.  
  38. function getCache(key) {
  39. const value = GM_getValue(key);
  40. if (value === undefined) return false;
  41. const obj = JSON.parse(value);
  42. const curr = Date.now();
  43. if (curr - obj.time > CACHE_LIFESPAN) {
  44. GM_deleteValue(key);
  45. GM_log('Deleted key ' + key);
  46. return false;
  47. } else {
  48. return obj.data;
  49. }
  50. }
  51.  
  52. function setCache(key, value) {
  53. const curr = Date.now();
  54. GM_setValue(key, JSON.stringify({data: value, time: curr}));
  55. GM_log('Added key ' + key);
  56. }
  57.  
  58. function getCFDifficulty() {
  59. let ret = getCache(pnum);
  60. if (ret) return ret;
  61. const re = /CF(\d+)(\w+)/;
  62. let t1 = re.exec(pnum)[1],
  63. t2 = re.exec(pnum)[2];
  64. GM_xmlhttpRequest({
  65. method: 'GET',
  66. url: 'https://codeforces.com/problemset/problem/' +
  67. t1 + '/' + t2,
  68. // synchronous is not supported
  69. synchronous: true,
  70. timeout: 10000,
  71. onload: function(response) {
  72. const re = /<span.+? title="Difficulty">\s*([*0-9]+)\s*<\/span>/,
  73. html = response.responseText;
  74. ret = re.exec(html)[1];
  75. setCache(pnum, ret);
  76. },
  77. onerror: function(e) {
  78. console.error('Access CodeForces error ', e);
  79. },
  80. ontimeout: function(e) {
  81. console.error('Access CodeForces timeout ', e);
  82. }
  83. });
  84. return ret;
  85. }
  86.  
  87. function showDifficulty() {
  88. let cfd = getCFDifficulty();
  89. if (cfd) difficulty = cfd + '/' + difficulty;
  90. jQuery('.side a[href^="/problem/list?difficulty="] span').text(difficulty);
  91. }
  92.  
  93. function getDeferredValue() {
  94. pnum = getTail(window.location.href);
  95. title = jQuery('.header h1 span').prop('title').replace(pnum, '').trim();
  96. problem = jQuery('.main .problem-card div').eq(1).html().trim();
  97. difficulty = jQuery('.side a[href^="/problem/list?difficulty="] span').text().trim();
  98. if (pnum.startsWith('CF')) showDifficulty();
  99. }
  100.  
  101. function onClickPrint() {
  102. jQuery('#app').remove();
  103.  
  104. let elem = '<h1 class="lfe-h1"><span title="' + title +
  105. '">LG' + pnum + '. ' + title +
  106. '</span></h1>';
  107. jQuery('body').append(elem);
  108.  
  109. elem = '<div><p>' + difficulty + '</p></div>';
  110. jQuery('body').append(elem);
  111.  
  112. elem = problem.replaceAll(/\sdata-v-\w+=""/g, '');
  113. jQuery('body').append(elem);
  114.  
  115. elem = '<div style="position: absolute; top: 2px; right: 2px; ' +
  116. 'font-family: Bahnschrift, Trebuchet MS, sans-serif; ' +
  117. 'font-weight: lighter; font-stretch: condensed; ' +
  118. 'font-size: 20px;">' + getToday() + '</div>';
  119. jQuery('body').append(elem);
  120.  
  121. jQuery('.lfe-h1').css({'font-size': '1.5em', 'font-weight': 'normal'});
  122. jQuery('.lfe-h2').css('margin-bottom', '0');
  123. jQuery('.lfe-h3').css('margin-bottom', '0');
  124. jQuery('.sample .input').css({'border': '1px solid #eee', 'margin': '0.25em 0', 'padding': '0.25em'});
  125. jQuery('.sample .output').css({'border': '1px solid #eee', 'margin': '0.25em 0', 'padding': '0.25em'});
  126. jQuery('.sample .copy-btn').css('display', 'none');
  127. jQuery('body').css({'zoom': '80%', 'padding': '2em 3em 2em 3em', 'font-size': '14px'});
  128. }
  129.  
  130. function onClickPurgeCache() {
  131. const curr = Date().now();
  132. let arr = new Array();
  133. for (const key of GM_listValues()) {
  134. const obj = JSON.parse(GM_getValue(key));
  135. if (curr - obj.time > CACHE_LIFESPAN) arr.push(key);
  136. }
  137. for (const key of arr) {
  138. GM_deleteValue(key);
  139. GM_log('Deleted key ' + key);
  140. }
  141. }
  142.  
  143. GM_registerMenuCommand('Prepare to print', onClickPrint);
  144. GM_registerMenuCommand('Purge cache', onClickPurgeCache);
  145.  
  146. const CACHE_LIFESPAN = 30 * 86400 * 1000;
  147. let pnum, title, problem, difficulty;
  148. // Luogu load content lazily
  149. setTimeout(getDeferredValue, 1000);
  150. // update asynchronous result
  151. setTimeout(showDifficulty, 4000);
  152.  
  153. });