Guest User

Untitled

a guest
Jul 8th, 2025
29
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. javascript:(function() {
  2.  
  3.   const CODE_NAME = "LinkArchiver";
  4.   console.log(`${CODE_NAME} Session started at ${new Date().toISOString()}`);
  5.  
  6.  
  7.   const style = document.createElement('style');
  8.   style.innerHTML = `
  9.     #bookmarklet-ui {
  10.       position: fixed;
  11.       top: 50%;
  12.       left: 50%;
  13.       transform: translate(-50%, -50%);
  14.       background: rgba(255, 255, 255, 0.95);
  15.       padding: 20px;
  16.       border: 1px solid #333;
  17.       border-radius: 8px;
  18.       box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  19.       z-index: 9999;
  20.       font-family: Arial, sans-serif;
  21.       max-width: 600px;
  22.       width: 90%;
  23.     }
  24.     #bookmarklet-ui h3 {
  25.       margin: 0 0 10px;
  26.       color: #333;
  27.     }
  28.     #bookmarklet-ui label {
  29.       display: block;
  30.       margin-bottom: 5px;
  31.       color: #333;
  32.     }
  33.     #pattern, #pasted-links, .replace-rule input {
  34.       width: 100%;
  35.       padding: 8px;
  36.       margin-bottom: 10px;
  37.       border: 1px solid #ccc;
  38.       border-radius: 4px;
  39.       box-sizing: border-box;
  40.     }
  41.     #pasted-links {
  42.       resize: vertical;
  43.       min-height: 100px;
  44.       max-height: 300px;
  45.     }
  46.     #suggestions {
  47.       max-height: 200px;
  48.       overflow-y: auto;
  49.       margin-bottom: 10px;
  50.       border: 1px solid #ccc;
  51.       border-radius: 4px;
  52.       padding: 5px;
  53.       background: #f9f9f9;
  54.     }
  55.     #suggestions div {
  56.       padding: 5px;
  57.       cursor: pointer;
  58.       color: #333;
  59.     }
  60.     #suggestions personally {
  61.       background: #e0e0e0;
  62.     }
  63.     #start, #close, #pause-resume, #add-rule {
  64.       padding: 8px 16px;
  65.       margin-right: 10px;
  66.       border: none;
  67.       border-radius: 4px;
  68.       cursor: pointer;
  69.     }
  70.     #start {
  71.       background: #007bff;
  72.       color: white;
  73.     }
  74.     #start:hover {
  75.       background: #0056b3;
  76.     }
  77.     #close {
  78.       background: #dc3545;
  79.       color: white;
  80.     }
  81.     #close:hover {
  82.       background: #c82333;
  83.     }
  84.     #pause-resume {
  85.       background: #ffc107;
  86.       color: #333;
  87.     }
  88.     #pause-resume:hover {
  89.       background: #e0a800;
  90.     }
  91.     #add-rule {
  92.       background: #28a745;
  93.       color: white;
  94.     }
  95.     #add-rule:hover {
  96.       background: #218838;
  97.     }
  98.     #toast {
  99.       position: fixed;
  100.       bottom: 20px;
  101.       right: 20px;
  102.       background: rgba(51, 51, 51, 0.9);
  103.       color: white;
  104.       padding: 10px 20px;
  105.       border-radius: 5px;
  106.       display: none;
  107.       z-index: 10000;
  108.       font-family: Arial, sans-serif;
  109.     }
  110.     #mode-toggle {
  111.       margin-bottom: 10px;
  112.     }
  113.     #rate-limit-warning {
  114.       color: #d33;
  115.       font-size: 0.9em;
  116.       margin-top: 10px;
  117.     }
  118.     #replace-rules {
  119.       margin-bottom: 10px;
  120.     }
  121.     .replace-rule {
  122.       display: flex;
  123.       gap: 10px;
  124.       margin-bottom: 10px;
  125.     }
  126.     .replace-rule select {
  127.       padding: 8px;
  128.       border: 1px solid #ccc;
  129.       border-radius: 4px;
  130.     }
  131.     @media (prefers-color-scheme: dark) {
  132.       #bookmarklet-ui {
  133.         background: rgba(30, 30, 30, 0.95);
  134.         border-color: #666;
  135.       }
  136.       #bookmarklet-ui h3, #bookmarklet-ui label {
  137.         color: #ddd;
  138.       }
  139.       #pattern, #pasted-links, .replace-rule input, .replace-rule select {
  140.         background: #333;
  141.         color: #ddd;
  142.         border-color: #666;
  143.       }
  144.       #suggestions {
  145.         background: #2a2a2a;
  146.         border-color: #666;
  147.       }
  148.       #suggestions div {
  149.         color: #ddd;
  150.       }
  151.       #suggestions div:hover {
  152.         background: #444;
  153.       }
  154.     }
  155.   `;
  156.   try {
  157.     document.head.appendChild(style);
  158.   } catch (e) {
  159.     console.error(`${CODE_NAME}: Failed to inject styles: ${e.message}`);
  160.     return;
  161.   }
  162.  
  163.  
  164.   const existingUi = document.getElementById('bookmarklet-ui');
  165.   if (existingUi) {
  166.     existingUi.remove();
  167.   }
  168.  
  169.  
  170.   const ui = document.createElement('div');
  171.   ui.id = 'bookmarklet-ui';
  172.   ui.innerHTML = `
  173.     <h3>${CODE_NAME}</h3>
  174.     <div id="mode-toggle">
  175.       <label><input type="radio" name="mode" value="scrape" checked> Scrape from current page</label>
  176.       <label><input type="radio" name="mode" value="paste"> Use pasted links</label>
  177.     </div>
  178.     <div id="pasted-links-container" style="display: none;">
  179.       <label for="pasted-links">Paste your links (one per line):</label>
  180.       <textarea id="pasted-links" placeholder="https://example.com\nhttps://another.com"></textarea>
  181.     </div>
  182.     <label for="pattern">Regex Pattern (optional):</label>
  183.     <input type="text" id="pattern" placeholder="e.g., .*example\\.com.*">
  184.     <div id="replace-rules">
  185.       <label>Replace Rules (optional):</label>
  186.       <div class="replace-rule">
  187.         <select class="replace-type">
  188.           <option value="domain">Domain</option>
  189.           <option value="path">Path</option>
  190.         </select>
  191.         <input type="text" class="replace-what" placeholder="e.g., olddomain.com or /path">
  192.         <input type="text" class="replace-with" placeholder="e.g., newdomain.com or /newpath">
  193.       </div>
  194.     </div>
  195.     <button id="add-rule">+ Add Rule</button>
  196.     <div id="suggestions"></div>
  197.     <div>
  198.       <button id="start">Start Archiving</button>
  199.       <button id="pause-resume">Pause</button>
  200.       <button id="close">Close</button>
  201.     </div>
  202.     <div id="rate-limit-warning" style="display: none;">
  203.       Note: The Wayback Machine allows up to 100 saves per day for anonymous users. For more than 100 links, run this multiple times on different days.
  204.     </div>
  205.   `;
  206.   try {
  207.     document.body.appendChild(ui);
  208.   } catch (e) {
  209.     console.error(`${CODE_NAME}: Failed to inject UI: ${e.message}`);
  210.     return;
  211.   }
  212.  
  213.  
  214.   setTimeout(() => {
  215.     const patternInput = document.getElementById('pattern');
  216.     if (patternInput) {
  217.       patternInput.focus();
  218.     } else {
  219.       console.error(`${CODE_NAME}: Cannot find #pattern input. UI may not have rendered correctly.`);
  220.     }
  221.   }, 100);
  222.  
  223.  
  224.   const toast = document.createElement('div');
  225.   toast.id = 'toast';
  226.   try {
  227.     document.body.appendChild(toast);
  228.   } catch (e) {
  229.     console.error(`${CODE_NAME}: Failed to inject toast: ${e.message}`);
  230.     return;
  231.   }
  232.  
  233.  
  234.   let allLinks = Array.from(document.getElementsByTagName('a'))
  235.     .map(a => a.href)
  236.     .filter(href => href && href.includes('://'));
  237.  
  238.  
  239.   let isPaused = false;
  240.   let archivingPromise = null;
  241.  
  242.  
  243.   const scrapeRadio = document.querySelector('input[value="scrape"]');
  244.   const pasteRadio = document.querySelector('input[value="paste"]');
  245.   const pastedLinksContainer = document.getElementById('pasted-links-container');
  246.   const patternInput = document.getElementById('pattern');
  247.   const suggestionsDiv = document.getElementById('suggestions');
  248.   const rateLimitWarning = document.getElementById('rate-limit-warning');
  249.   const addRuleButton = document.getElementById('add-rule');
  250.   const pauseResumeButton = document.getElementById('pause-resume');
  251.   const closeButton = document.getElementById('close');
  252.  
  253.   if (!scrapeRadio || !pasteRadio || !pastedLinksContainer || !patternInput || !suggestionsDiv || !rateLimitWarning || !addRuleButton || !pauseResumeButton || !closeButton) {
  254.     console.error(`${CODE_NAME}: One or more UI elements missing. Check webpage for CSP or DOM restrictions.`);
  255.     return;
  256.   }
  257.  
  258.   function toggleMode() {
  259.     pastedLinksContainer.style.display = pasteRadio.checked ? 'block' : 'none';
  260.    
  261.     allLinks = Array.from(document.getElementsByTagName('a'))
  262.       .map(a => a.href)
  263.       .filter(href => href && href.includes('://'));
  264.     generateSuggestions();
  265.   }
  266.  
  267.   scrapeRadio.addEventListener('change', toggleMode);
  268.   pasteRadio.addEventListener('change', toggleMode);
  269.  
  270.  
  271.   addRuleButton.addEventListener('click', () => {
  272.     const ruleDiv = document.createElement('div');
  273.     ruleDiv.className = 'replace-rule';
  274.     ruleDiv.innerHTML = `
  275.       <select class="replace-type">
  276.         <option value="domain">Domain</option>
  277.         <option value="path">Path</option>
  278.       </select>
  279.       <input type="text" class="replace-what" placeholder="e.g., olddomain.com or /path">
  280.       <input type="text" class="replace-with" placeholder="e.g., newdomain.com or /newpath">
  281.     `;
  282.     document.getElementById('replace-rules').appendChild(ruleDiv);
  283.   });
  284.  
  285.  
  286.   function isValidReplaceValue(type, value) {
  287.     if (type === 'domain') {
  288.       return /^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value);
  289.     }
  290.     return value.length > 0;
  291.   }
  292.  
  293.  
  294.   function generateSuggestions() {
  295.     suggestionsDiv.innerHTML = '';
  296.     const links = pasteRadio.checked ? getPastedLinks() : allLinks;
  297.     try {
  298.       const domains = [...new Set(links.map(url => {
  299.         try {
  300.           return new URL(url).hostname;
  301.         } catch (e) {
  302.           return null;
  303.         }
  304.       }).filter(Boolean))];
  305.       domains.forEach(domain => {
  306.         const regexPattern = `^https?://(www\\.)?${domain.replace('.', '\\.')}/.*`;
  307.         const suggestion = document.createElement('div');
  308.         suggestion.textContent = regexPattern;
  309.         suggestion.style.cursor = 'pointer';
  310.         suggestion.onclick = () => {
  311.           patternInput.value = regexPattern;
  312.           patternInput.dispatchEvent(new Event('input', { bubbles: true }));
  313.         };
  314.         suggestionsDiv.appendChild(suggestion);
  315.       });
  316.     } catch (e) {
  317.       console.error(`${CODE_NAME}: Error generating suggestions: ${e.message}`);
  318.       showToast('Error generating suggestions');
  319.     }
  320.   }
  321.  
  322.   generateSuggestions();
  323.  
  324.  
  325.   function getPastedLinks() {
  326.     const pastedText = document.getElementById('pasted-links').value;
  327.     return pastedText.split('\n').map(url => url.trim()).filter(url => url && url.includes('://'));
  328.   }
  329.  
  330.  
  331.   patternInput.addEventListener('input', () => {
  332.     const pattern = patternInput.value;
  333.     suggestionsDiv.innerHTML = '';
  334.     const links = pasteRadio.checked ? getPastedLinks() : allLinks;
  335.     if (pattern) {
  336.       try {
  337.         const regex = new RegExp(pattern, 'i');
  338.         const matchingLinks = links.filter(url => regex.test(url));
  339.         matchingLinks.slice(0, 20).forEach(url => {
  340.           const match = document.createElement('div');
  341.           match.textContent = url;
  342.           match.style.cursor = 'pointer';
  343.           match.onclick = () => {
  344.             patternInput.value = url;
  345.             patternInput.dispatchEvent(new Event('input', { bubbles: true }));
  346.           };
  347.           suggestionsDiv.appendChild(match);
  348.         });
  349.       } catch (e) {
  350.         showToast('Invalid regex pattern');
  351.         console.error(`${CODE_NAME}: Invalid regex pattern: ${e.message}`);
  352.       }
  353.     } else {
  354.       generateSuggestions();
  355.     }
  356.   });
  357.  
  358.  
  359.   function applyReplaceRules(url, rules) {
  360.     let modifiedUrl = url;
  361.     for (const rule of rules) {
  362.       try {
  363.         const parsedUrl = new URL(modifiedUrl);
  364.         if (rule.type === 'domain' && parsedUrl.hostname.toLowerCase() === rule.replaceWhat.toLowerCase()) {
  365.           if (isValidReplaceValue('domain', rule.replaceWith)) {
  366.             parsedUrl.hostname = rule.replaceWith;
  367.             modifiedUrl = parsedUrl.toString();
  368.             console.log(`${CODE_NAME}: Replaced domain ${ruled.replaceWhat} with ${rule.replaceWith} for ${url}`);
  369.           } else {
  370.             console.error(`${CODE_NAME}: Invalid replacement domain: ${rule.replaceWith}`);
  371.           }
  372.         } else if (rule.type === 'path' && parsedUrl.pathname.toLowerCase().includes(rule.replaceWhat.toLowerCase())) {
  373.           if (isValidReplaceValue('path', rule.replaceWith)) {
  374.             parsedUrl.pathname = parsedUrl.pathname.replace(new RegExp(rule.replaceWhat.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'), rule.replaceWith);
  375.             modifiedUrl = parsedUrl.toString();
  376.             console.log(`${CODE_NAME}: Replaced path ${rule.replaceWhat} with ${rule.replaceWith} for ${url}`);
  377.           } else {
  378.             console.error(`${CODE_NAME}: Invalid replacement path: ${rule.replaceWith}`);
  379.           }
  380.         }
  381.       } catch (e) {
  382.         console.error(`${CODE_NAME}: Error applying rule to ${url}: ${e.message}`);
  383.       }
  384.     }
  385.     return modifiedUrl;
  386.   }
  387.  
  388.  
  389.   document.getElementById('start').addEventListener('click', () => {
  390.     const pattern = patternInput.value;
  391.     const replaceRules = Array.from(document.querySelectorAll('.replace-rule')).map(ruleDiv => ({
  392.       type: ruleDiv.querySelector('.replace-type').value,
  393.       replaceWhat: ruleDiv.querySelector('.replace-what').value.trim(),
  394.       replaceWith: ruleDiv.querySelector('.replace-with').value.trim()
  395.     })).filter(rule => rule.replaceWhat && rule.replaceWith);
  396.     const links = pasteRadio.checked ? getPastedLinks() : allLinks;
  397.     let filteredLinks = links;
  398.  
  399.    
  400.     if (pattern) {
  401.       try {
  402.         const regex = new RegExp(pattern, 'i');
  403.         filteredLinks = links.filter(url => regex.test(url));
  404.       } catch (e) {
  405.         showToast('Invalid regex pattern');
  406.         console.error(`${CODE_NAME}: Invalid regex pattern: ${e.message}`);
  407.         return;
  408.       }
  409.     }
  410.  
  411.    
  412.     const linksToArchive = filteredLinks.map(url => applyReplaceRules(url, replaceRules));
  413.  
  414.     if (linksToArchive.length === 0) {
  415.       showToast('No links to archive');
  416.       console.log(`${CODE_NAME}: No links to archive`);
  417.       return;
  418.     }
  419.  
  420.     if (linksToArchive.length > 100) {
  421.       rateLimitWarning.style.display = 'block';
  422.     }
  423.  
  424.     console.log(`${CODE_NAME}: About to archive ${linksToArchive.length} URLs`);
  425.     document.getElementById('bookmarklet-ui').style.display = 'none';
  426.     archivingPromise = archiveLinks(linksToArchive);
  427.   });
  428.  
  429.  
  430.   pauseResumeButton.addEventListener('click', () => {
  431.     isPaused = !isPaused;
  432.     pauseResumeButton.textContent = isPaused ? 'Resume' : 'Pause';
  433.     showToast(isPaused ? 'Archiving paused' : 'Archiving resumed');
  434.     console.log(`${CODE_NAME}: Archiving ${isPaused ? 'paused' : 'resumed'}`);
  435.   });
  436.  
  437.  
  438.   closeButton.addEventListener('click', () => {
  439.     isPaused = true;
  440.     document.getElementById('bookmarklet-ui').remove();
  441.     showToast('Bookmarklet closed');
  442.     console.log(`${CODE_NAME}: Bookmarklet closed`);
  443.   });
  444.  
  445.  
  446.   function showToast(message) {
  447.     const toast = document.getElementById('toast');
  448.     toast.textContent = message;
  449.     toast.style.display = 'block';
  450.     setTimeout(() => {
  451.       toast.style.display = 'none';
  452.     }, 3000);
  453.   }
  454.  
  455.  
  456.   async function archiveLinks(links) {
  457.     let failedLinks = [];
  458.     let consecutiveErrors = 0;
  459.     const maxRetries = 3;
  460.     let retryCount = 0;
  461.  
  462.     async function attemptArchive(url, attempt = 1) {
  463.       if (isPaused) {
  464.         await new Promise(resolve => {
  465.           const checkResume = setInterval(() => {
  466.             if (!isPaused) {
  467.               clearInterval(checkResume);
  468.               resolve();
  469.             }
  470.           }, 100);
  471.         });
  472.       }
  473.       try {
  474.         showToast(`Archiving ${links.indexOf(url) + 1}/${links.length}: ${url}`);
  475.         console.log(`${CODE_NAME}: Archiving ${links.indexOf(url) + 1}/${links.length}: ${url}`);
  476.         await fetch(`https://web.archive.org/save/${encodeURIComponent(url)}`, { mode: 'no-cors' });
  477.         console.log(`${CODE_NAME}: Save attempted for ${url}. Check https://web.archive.org/web/*/${encodeURIComponent(url)} for the snapshot`);
  478.         consecutiveErrors = 0;
  479.         return true;
  480.       } catch (e) {
  481.         console.error(`${CODE_NAME}: Error archiving ${url} (Attempt ${attempt}): ${e.message}`);
  482.         showToast(`Error archiving ${url}`);
  483.         consecutiveErrors++;
  484.         return false;
  485.       }
  486.     }
  487.  
  488.     while (retryCount <= maxRetries) {
  489.       const currentFailed = [];
  490.       for (const url of links) {
  491.         if (isPaused) {
  492.           await new Promise(resolve => {
  493.             const checkResume = setInterval(() => {
  494.               if (!isPaused) {
  495.                 clearInterval(checkResume);
  496.                 resolve();
  497.               }
  498.             }, 100);
  499.           });
  500.         }
  501.         const success = await attemptArchive(url, retryCount + 1);
  502.         if (!success) {
  503.           currentFailed.push(url);
  504.         }
  505.         await new Promise(resolve => setTimeout(resolve, 1000));
  506.       }
  507.       if (currentFailed.length === 0) break;
  508.       if (consecutiveErrors >= 3 && retryCount < maxRetries) {
  509.         showToast(`Multiple errors detected. Waiting 5 minutes before retrying...`);
  510.         console.log(`${CODE_NAME}: Multiple consecutive errors. Waiting 5 minutes before retry ${retryCount + 2}`);
  511.         await new Promise(resolve => setTimeout(resolve, 5 * 60 * 1000));
  512.         consecutiveErrors = 0;
  513.       }
  514.       links = currentFailed;
  515.       failedLinks = currentFailed;
  516.       retryCount++;
  517.     }
  518.  
  519.     if (failedLinks.length > 0) {
  520.       console.log(`${CODE_NAME}: The following links failed to archive after ${maxRetries} retries:`);
  521.       failedLinks.forEach(url => console.log(url));
  522.       showToast(`Archiving completed with ${failedLinks.length} failures. Check console for details.`);
  523.     } else {
  524.       showToast('Archiving completed successfully');
  525.       console.log(`${CODE_NAME}: Archiving completed successfully`);
  526.     }
  527.     document.getElementById('bookmarklet-ui').remove();
  528.   }
  529. })();
Advertisement
Add Comment
Please, Sign In to add comment