Advertisement
Md_Ahad

Untitled

May 24th, 2025 (edited)
25
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.16 KB | None | 0 0
  1. // ==UserScript==
  2. // @name Codeforces: Export All Problems to PDF
  3. // @description Export all Codeforces contest problems to a printable PDF, styled like CPC contests.
  4. // @match https://codeforces.com/group/*/contest/*
  5. // @match https://codeforces.com/gym/*
  6. // @match https://codeforces.com/contest/*
  7. // @grant none
  8. // @esversion 11
  9. // @version 1.0
  10. // @downloadURL https://update.greasyfork.org/scripts/536783/Codeforces%3A%20Export%20All%20Problems%20to%20PDF.user.js
  11. // @updateURL https://update.greasyfork.org/scripts/536783/Codeforces%3A%20Export%20All%20Problems%20to%20PDF.meta.js
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. if (!document.querySelector('table.datatable') && !document.querySelector('table.problems')) return;
  18.  
  19. const container = document.querySelector('div[style*="text-align: right"] a[href*="/problems"]')?.parentElement;
  20. if (!container) return;
  21. const originalHTML = document.body.innerHTML;
  22. const pdfLink = document.createElement('a');
  23. pdfLink.textContent = '💾 Save All as PDF';
  24. pdfLink.href = '#';
  25. pdfLink.style.marginLeft = '15px';
  26. pdfLink.style.color = '#0066cc';
  27. pdfLink.style.textDecoration = 'none';
  28. pdfLink.style.cursor = 'pointer';
  29. container.appendChild(pdfLink);
  30.  
  31. pdfLink.addEventListener('click', async (e) => {
  32. e.preventDefault();
  33. await exportPDF(pdfLink);
  34. });
  35.  
  36. async function exportPDF(link) {
  37. const originalText = link.textContent;
  38. link.style.pointerEvents = 'none';
  39. link.textContent = 'Fetching…';
  40.  
  41. const selectors = [
  42. 'table.datatable tr td.id a',
  43. 'table.datatable tr td.index a',
  44. 'table.problems tr td.id a',
  45. 'table.problems tr td.index a',
  46. 'table.datatable tr a[href*="/problem/"]',
  47. 'table.problems tr a[href*="/problem/"]'
  48. ];
  49.  
  50. const anchors = Array.from(document.querySelectorAll(selectors.join(',')));
  51. const seen = new Set(), links = [];
  52. anchors.forEach(a => {
  53. if (!seen.has(a.href)) {
  54. seen.add(a.href);
  55. links.push(a.href);
  56. }
  57. });
  58.  
  59. if (!links.length) return alert('No problem links found!');
  60.  
  61. const problemsHTML = await Promise.all(
  62. links.map(url => fetchProblem(url))
  63. );
  64.  
  65. document.body.innerHTML = problemsHTML
  66. .map((html, i) => html || `<div><h2>Problem ${links[i].split('/').pop()} failed to load.</h2></div>`);
  67.  
  68. document.querySelectorAll('.test-example-line-even, .test-example-line-odd').forEach(el => {
  69. el.classList.remove('test-example-line-even', 'test-example-line-odd');
  70. });
  71.  
  72. document.querySelectorAll('.problem-statement').forEach((prob, i, arr) => {
  73. const inputs = prob.querySelectorAll('.input');
  74. const outputs = prob.querySelectorAll('.output');
  75. if (inputs.length && outputs.length) {
  76. const table = document.createElement('table');
  77. table.className = 'samples-table';
  78. table.style.width = '100%';
  79. table.style.borderCollapse = 'collapse';
  80. table.style.marginBottom = '20px';
  81.  
  82. const header = table.insertRow();
  83. ['Standard Input', 'Standard Output'].forEach(text => {
  84. const th = document.createElement('th');
  85. th.textContent = text;
  86. th.style.border = '1px solid black';
  87. th.style.padding = '8px';
  88. th.style.textAlign = 'center';
  89. th.style.width = '50%';
  90. header.appendChild(th);
  91. });
  92.  
  93. for (let j = 0; j < Math.min(inputs.length, outputs.length); j++) {
  94. const row = table.insertRow();
  95. [inputs[j], outputs[j]].forEach(el => {
  96. const cell = row.insertCell();
  97. cell.style.border = '1px solid black';
  98. cell.style.padding = '8px';
  99. cell.style.verticalAlign = 'top';
  100. cell.appendChild(el);
  101. });
  102. }
  103.  
  104. const sample = prob.querySelector('.sample-tests') || prob.querySelector('.sample-test') || prob;
  105. sample?.parentNode?.insertBefore(table, sample.nextSibling);
  106. prob.querySelector('.sample-tests')?.remove();
  107. prob.querySelector('.sample-test')?.remove();
  108. }
  109.  
  110. if (i < arr.length - 1) {
  111. const pageBreak = document.createElement('div');
  112. pageBreak.style.pageBreakBefore = 'always';
  113. prob.parentNode.insertBefore(pageBreak, prob.nextSibling);
  114. }
  115. });
  116.  
  117. const style = document.createElement('style');
  118. style.textContent = `
  119. .samples-table th, .samples-table td {
  120. border: 1px solid black;
  121. padding: 8px;
  122. vertical-align: top;
  123. text-align: left;
  124. }
  125. .samples-table pre {
  126. white-space: pre-wrap;
  127. word-wrap: break-word;
  128. font-size: 12px;
  129. margin: 0;
  130. }
  131. @media print {
  132. .samples-table th, .samples-table td {
  133. border: 1px solid black;
  134. padding: 8px;
  135. }
  136. }
  137. `;
  138. document.head.appendChild(style);
  139.  
  140. ['#MathJax_Message', '.status-bar', '#status', '.print-hide']
  141. .forEach(sel => document.querySelectorAll(sel).forEach(el => el.style.display = 'none'));
  142.  
  143. const finalize = () => setTimeout(() => {
  144. document.getElementById('MathJax_Message')?.style.setProperty('display', 'none', 'important');
  145. window.print();
  146.  
  147. setTimeout(() => {
  148. document.body.innerHTML = originalHTML;
  149. link.style.pointerEvents = '';
  150. link.textContent = originalText;
  151. }, 500);
  152. }, 500);
  153.  
  154. if (window.MathJax) {
  155. try {
  156. if (MathJax.typesetPromise) {
  157. await MathJax.typesetPromise([document.body]);
  158. finalize();
  159. } else if (MathJax.Hub?.Queue) {
  160. MathJax.Hub.Queue(["Typeset", MathJax.Hub, document.body], finalize);
  161. } else finalize();
  162. } catch (e) {
  163. finalize();
  164. }
  165. } else finalize();
  166.  
  167. link.style.pointerEvents = '';
  168. link.textContent = originalText;
  169. }
  170.  
  171. async function fetchProblem(url, retries = 2) {
  172. try {
  173. const res = await fetch(url);
  174. const txt = await res.text();
  175. const doc = new DOMParser().parseFromString(txt, 'text/html');
  176. const prob = doc.querySelector('.problem-statement');
  177. if (!prob) throw new Error('Missing problem-statement');
  178.  
  179. prob.querySelectorAll('.input-output-copier').forEach(el => el.remove());
  180.  
  181. const header = doc.querySelector('.header');
  182. let title = 'Unknown Problem';
  183. let inputFile = 'standard input';
  184. let outputFile = 'standard output';
  185. let timeLimit = 'N/A';
  186. let memoryLimit = 'N/A';
  187.  
  188. if (header) {
  189. title = header.querySelector('.title')?.textContent.trim() || title;
  190. inputFile = header.querySelector('.input-file')?.textContent.replace('input', '').trim() || inputFile;
  191. outputFile = header.querySelector('.output-file')?.textContent.replace('output', '').trim() || outputFile;
  192. timeLimit = header.querySelector('.time-limit')?.textContent.replace('time limit per test', '').trim() || timeLimit;
  193. memoryLimit = header.querySelector('.memory-limit')?.textContent.replace('memory limit per test', '').trim() || memoryLimit;
  194. header.remove();
  195. }
  196.  
  197. const h2 = document.createElement('h2');
  198. h2.textContent = title;
  199. h2.style.marginBottom = '0.2em';
  200. h2.style.textAlign = 'left';
  201. prob.insertBefore(h2, prob.firstChild);
  202.  
  203. const metaDiv = document.createElement('div');
  204. metaDiv.className = 'problem-metadata';
  205. metaDiv.style.marginBottom = '1em';
  206. metaDiv.innerHTML = `
  207. <pre style="font-family: monospace; margin-left: 20px; font-size: 14px;">
  208. Input file:\t${inputFile}
  209. Output file:\t${outputFile}
  210. Time limit:\t${timeLimit}
  211. Memory limit:\t${memoryLimit}
  212. </pre>
  213. `;
  214.  
  215.  
  216. prob.insertBefore(metaDiv, prob.firstChild.nextSibling);
  217.  
  218. prob.querySelectorAll('.input .title, .output .title').forEach(el => el.remove());
  219. return prob.outerHTML;
  220.  
  221. } catch (err) {
  222. if (retries > 0) {
  223. await new Promise(r => setTimeout(r, 400));
  224. return fetchProblem(url, retries - 1);
  225. }
  226. return null;
  227. }
  228. }
  229. })();
  230.  
  231.  
  232.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement