洛谷助手

优雅打印 | 显示难度

  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 && !flag) {
  90. difficulty = cfd + '/' + difficulty;
  91. flag = true;
  92. jQuery('.side a[href^="/problem/list?difficulty="] span').text(difficulty);
  93. }
  94. }
  95.  
  96. function getDeferredValue() {
  97. pnum = getTail(window.location.href);
  98. title = jQuery('.header h1 span').prop('title').replace(pnum, '').trim();
  99. problem = jQuery('.main .problem-card div').eq(1).html().trim();
  100. difficulty = jQuery('.side a[href^="/problem/list?difficulty="] span').text().trim();
  101. if (pnum.startsWith('CF')) showDifficulty();
  102. }
  103.  
  104. function onClickPrint() {
  105. jQuery('#app').remove();
  106.  
  107. let elem = '<h1 class="lfe-h1"><span title="' + title +
  108. '">LG' + pnum + '. ' + title +
  109. '</span></h1>';
  110. jQuery('body').append(elem);
  111.  
  112. elem = '<div><p>' + difficulty + '</p></div>';
  113. jQuery('body').append(elem);
  114.  
  115. elem = problem.replaceAll(/\sdata-v-\w+=""/g, '');
  116. jQuery('body').append(elem);
  117.  
  118. elem = '<div style="position: absolute; top: 2px; right: 2px; ' +
  119. 'font-family: Bahnschrift, Trebuchet MS, sans-serif; ' +
  120. 'font-weight: lighter; font-stretch: condensed; ' +
  121. 'font-size: 20px;">' + getToday() + '</div>';
  122. jQuery('body').append(elem);
  123.  
  124. jQuery('.lfe-h1').css({'font-size': '1.5em', 'font-weight': 'normal'});
  125. jQuery('.lfe-h2').css('margin-bottom', '0');
  126. jQuery('.lfe-h3').css('margin-bottom', '0');
  127. jQuery('.sample .input').css({'border': '1px solid #eee', 'margin': '0.25em 0', 'padding': '0.25em'});
  128. jQuery('.sample .output').css({'border': '1px solid #eee', 'margin': '0.25em 0', 'padding': '0.25em'});
  129. jQuery('.sample .copy-btn').css('display', 'none');
  130. jQuery('body').css({'zoom': '80%', 'padding': '2em 3em 2em 3em', 'font-size': '14px'});
  131. }
  132.  
  133. function onClickPurgeCache() {
  134. const curr = Date().now();
  135. let arr = new Array();
  136. for (const key of GM_listValues()) {
  137. const obj = JSON.parse(GM_getValue(key));
  138. if (curr - obj.time > CACHE_LIFESPAN) arr.push(key);
  139. }
  140. for (const key of arr) {
  141. GM_deleteValue(key);
  142. GM_log('Deleted key ' + key);
  143. }
  144. }
  145.  
  146. GM_registerMenuCommand('Prepare to print', onClickPrint);
  147. GM_registerMenuCommand('Purge cache', onClickPurgeCache);
  148.  
  149. const CACHE_LIFESPAN = 30 * 86400 * 1000;
  150. let pnum, title, problem, difficulty, flag = false;
  151. // Luogu load content lazily
  152. setTimeout(getDeferredValue, 1000);
  153. // update asynchronous result
  154. setTimeout(showDifficulty, 4000);
  155.  
  156. });