Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- javascript:(function() {
- const CODE_NAME = "LinkArchiver";
- console.log(`${CODE_NAME} Session started at ${new Date().toISOString()}`);
- const style = document.createElement('style');
- style.innerHTML = `
- #bookmarklet-ui {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: rgba(255, 255, 255, 0.95);
- padding: 20px;
- border: 1px solid #333;
- border-radius: 8px;
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
- z-index: 9999;
- font-family: Arial, sans-serif;
- max-width: 600px;
- width: 90%;
- }
- #bookmarklet-ui h3 {
- margin: 0 0 10px;
- color: #333;
- }
- #bookmarklet-ui label {
- display: block;
- margin-bottom: 5px;
- color: #333;
- }
- #pattern, #pasted-links, .replace-rule input {
- width: 100%;
- padding: 8px;
- margin-bottom: 10px;
- border: 1px solid #ccc;
- border-radius: 4px;
- box-sizing: border-box;
- }
- #pasted-links {
- resize: vertical;
- min-height: 100px;
- max-height: 300px;
- }
- #suggestions {
- max-height: 200px;
- overflow-y: auto;
- margin-bottom: 10px;
- border: 1px solid #ccc;
- border-radius: 4px;
- padding: 5px;
- background: #f9f9f9;
- }
- #suggestions div {
- padding: 5px;
- cursor: pointer;
- color: #333;
- }
- #suggestions personally {
- background: #e0e0e0;
- }
- #start, #close, #pause-resume, #add-rule {
- padding: 8px 16px;
- margin-right: 10px;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- }
- #start {
- background: #007bff;
- color: white;
- }
- #start:hover {
- background: #0056b3;
- }
- #close {
- background: #dc3545;
- color: white;
- }
- #close:hover {
- background: #c82333;
- }
- #pause-resume {
- background: #ffc107;
- color: #333;
- }
- #pause-resume:hover {
- background: #e0a800;
- }
- #add-rule {
- background: #28a745;
- color: white;
- }
- #add-rule:hover {
- background: #218838;
- }
- #toast {
- position: fixed;
- bottom: 20px;
- right: 20px;
- background: rgba(51, 51, 51, 0.9);
- color: white;
- padding: 10px 20px;
- border-radius: 5px;
- display: none;
- z-index: 10000;
- font-family: Arial, sans-serif;
- }
- #mode-toggle {
- margin-bottom: 10px;
- }
- #rate-limit-warning {
- color: #d33;
- font-size: 0.9em;
- margin-top: 10px;
- }
- #replace-rules {
- margin-bottom: 10px;
- }
- .replace-rule {
- display: flex;
- gap: 10px;
- margin-bottom: 10px;
- }
- .replace-rule select {
- padding: 8px;
- border: 1px solid #ccc;
- border-radius: 4px;
- }
- @media (prefers-color-scheme: dark) {
- #bookmarklet-ui {
- background: rgba(30, 30, 30, 0.95);
- border-color: #666;
- }
- #bookmarklet-ui h3, #bookmarklet-ui label {
- color: #ddd;
- }
- #pattern, #pasted-links, .replace-rule input, .replace-rule select {
- background: #333;
- color: #ddd;
- border-color: #666;
- }
- #suggestions {
- background: #2a2a2a;
- border-color: #666;
- }
- #suggestions div {
- color: #ddd;
- }
- #suggestions div:hover {
- background: #444;
- }
- }
- `;
- try {
- document.head.appendChild(style);
- } catch (e) {
- console.error(`${CODE_NAME}: Failed to inject styles: ${e.message}`);
- return;
- }
- const existingUi = document.getElementById('bookmarklet-ui');
- if (existingUi) {
- existingUi.remove();
- }
- const ui = document.createElement('div');
- ui.id = 'bookmarklet-ui';
- ui.innerHTML = `
- <h3>${CODE_NAME}</h3>
- <div id="mode-toggle">
- <label><input type="radio" name="mode" value="scrape" checked> Scrape from current page</label>
- <label><input type="radio" name="mode" value="paste"> Use pasted links</label>
- </div>
- <div id="pasted-links-container" style="display: none;">
- <label for="pasted-links">Paste your links (one per line):</label>
- <textarea id="pasted-links" placeholder="https://example.com\nhttps://another.com"></textarea>
- </div>
- <label for="pattern">Regex Pattern (optional):</label>
- <input type="text" id="pattern" placeholder="e.g., .*example\\.com.*">
- <div id="replace-rules">
- <label>Replace Rules (optional):</label>
- <div class="replace-rule">
- <select class="replace-type">
- <option value="domain">Domain</option>
- <option value="path">Path</option>
- </select>
- <input type="text" class="replace-what" placeholder="e.g., olddomain.com or /path">
- <input type="text" class="replace-with" placeholder="e.g., newdomain.com or /newpath">
- </div>
- </div>
- <button id="add-rule">+ Add Rule</button>
- <div id="suggestions"></div>
- <div>
- <button id="start">Start Archiving</button>
- <button id="pause-resume">Pause</button>
- <button id="close">Close</button>
- </div>
- <div id="rate-limit-warning" style="display: none;">
- 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.
- </div>
- `;
- try {
- document.body.appendChild(ui);
- } catch (e) {
- console.error(`${CODE_NAME}: Failed to inject UI: ${e.message}`);
- return;
- }
- setTimeout(() => {
- const patternInput = document.getElementById('pattern');
- if (patternInput) {
- patternInput.focus();
- } else {
- console.error(`${CODE_NAME}: Cannot find #pattern input. UI may not have rendered correctly.`);
- }
- }, 100);
- const toast = document.createElement('div');
- toast.id = 'toast';
- try {
- document.body.appendChild(toast);
- } catch (e) {
- console.error(`${CODE_NAME}: Failed to inject toast: ${e.message}`);
- return;
- }
- let allLinks = Array.from(document.getElementsByTagName('a'))
- .map(a => a.href)
- .filter(href => href && href.includes('://'));
- let isPaused = false;
- let archivingPromise = null;
- const scrapeRadio = document.querySelector('input[value="scrape"]');
- const pasteRadio = document.querySelector('input[value="paste"]');
- const pastedLinksContainer = document.getElementById('pasted-links-container');
- const patternInput = document.getElementById('pattern');
- const suggestionsDiv = document.getElementById('suggestions');
- const rateLimitWarning = document.getElementById('rate-limit-warning');
- const addRuleButton = document.getElementById('add-rule');
- const pauseResumeButton = document.getElementById('pause-resume');
- const closeButton = document.getElementById('close');
- if (!scrapeRadio || !pasteRadio || !pastedLinksContainer || !patternInput || !suggestionsDiv || !rateLimitWarning || !addRuleButton || !pauseResumeButton || !closeButton) {
- console.error(`${CODE_NAME}: One or more UI elements missing. Check webpage for CSP or DOM restrictions.`);
- return;
- }
- function toggleMode() {
- pastedLinksContainer.style.display = pasteRadio.checked ? 'block' : 'none';
- allLinks = Array.from(document.getElementsByTagName('a'))
- .map(a => a.href)
- .filter(href => href && href.includes('://'));
- generateSuggestions();
- }
- scrapeRadio.addEventListener('change', toggleMode);
- pasteRadio.addEventListener('change', toggleMode);
- addRuleButton.addEventListener('click', () => {
- const ruleDiv = document.createElement('div');
- ruleDiv.className = 'replace-rule';
- ruleDiv.innerHTML = `
- <select class="replace-type">
- <option value="domain">Domain</option>
- <option value="path">Path</option>
- </select>
- <input type="text" class="replace-what" placeholder="e.g., olddomain.com or /path">
- <input type="text" class="replace-with" placeholder="e.g., newdomain.com or /newpath">
- `;
- document.getElementById('replace-rules').appendChild(ruleDiv);
- });
- function isValidReplaceValue(type, value) {
- if (type === 'domain') {
- return /^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value);
- }
- return value.length > 0;
- }
- function generateSuggestions() {
- suggestionsDiv.innerHTML = '';
- const links = pasteRadio.checked ? getPastedLinks() : allLinks;
- try {
- const domains = [...new Set(links.map(url => {
- try {
- return new URL(url).hostname;
- } catch (e) {
- return null;
- }
- }).filter(Boolean))];
- domains.forEach(domain => {
- const regexPattern = `^https?://(www\\.)?${domain.replace('.', '\\.')}/.*`;
- const suggestion = document.createElement('div');
- suggestion.textContent = regexPattern;
- suggestion.style.cursor = 'pointer';
- suggestion.onclick = () => {
- patternInput.value = regexPattern;
- patternInput.dispatchEvent(new Event('input', { bubbles: true }));
- };
- suggestionsDiv.appendChild(suggestion);
- });
- } catch (e) {
- console.error(`${CODE_NAME}: Error generating suggestions: ${e.message}`);
- showToast('Error generating suggestions');
- }
- }
- generateSuggestions();
- function getPastedLinks() {
- const pastedText = document.getElementById('pasted-links').value;
- return pastedText.split('\n').map(url => url.trim()).filter(url => url && url.includes('://'));
- }
- patternInput.addEventListener('input', () => {
- const pattern = patternInput.value;
- suggestionsDiv.innerHTML = '';
- const links = pasteRadio.checked ? getPastedLinks() : allLinks;
- if (pattern) {
- try {
- const regex = new RegExp(pattern, 'i');
- const matchingLinks = links.filter(url => regex.test(url));
- matchingLinks.slice(0, 20).forEach(url => {
- const match = document.createElement('div');
- match.textContent = url;
- match.style.cursor = 'pointer';
- match.onclick = () => {
- patternInput.value = url;
- patternInput.dispatchEvent(new Event('input', { bubbles: true }));
- };
- suggestionsDiv.appendChild(match);
- });
- } catch (e) {
- showToast('Invalid regex pattern');
- console.error(`${CODE_NAME}: Invalid regex pattern: ${e.message}`);
- }
- } else {
- generateSuggestions();
- }
- });
- function applyReplaceRules(url, rules) {
- let modifiedUrl = url;
- for (const rule of rules) {
- try {
- const parsedUrl = new URL(modifiedUrl);
- if (rule.type === 'domain' && parsedUrl.hostname.toLowerCase() === rule.replaceWhat.toLowerCase()) {
- if (isValidReplaceValue('domain', rule.replaceWith)) {
- parsedUrl.hostname = rule.replaceWith;
- modifiedUrl = parsedUrl.toString();
- console.log(`${CODE_NAME}: Replaced domain ${ruled.replaceWhat} with ${rule.replaceWith} for ${url}`);
- } else {
- console.error(`${CODE_NAME}: Invalid replacement domain: ${rule.replaceWith}`);
- }
- } else if (rule.type === 'path' && parsedUrl.pathname.toLowerCase().includes(rule.replaceWhat.toLowerCase())) {
- if (isValidReplaceValue('path', rule.replaceWith)) {
- parsedUrl.pathname = parsedUrl.pathname.replace(new RegExp(rule.replaceWhat.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'), rule.replaceWith);
- modifiedUrl = parsedUrl.toString();
- console.log(`${CODE_NAME}: Replaced path ${rule.replaceWhat} with ${rule.replaceWith} for ${url}`);
- } else {
- console.error(`${CODE_NAME}: Invalid replacement path: ${rule.replaceWith}`);
- }
- }
- } catch (e) {
- console.error(`${CODE_NAME}: Error applying rule to ${url}: ${e.message}`);
- }
- }
- return modifiedUrl;
- }
- document.getElementById('start').addEventListener('click', () => {
- const pattern = patternInput.value;
- const replaceRules = Array.from(document.querySelectorAll('.replace-rule')).map(ruleDiv => ({
- type: ruleDiv.querySelector('.replace-type').value,
- replaceWhat: ruleDiv.querySelector('.replace-what').value.trim(),
- replaceWith: ruleDiv.querySelector('.replace-with').value.trim()
- })).filter(rule => rule.replaceWhat && rule.replaceWith);
- const links = pasteRadio.checked ? getPastedLinks() : allLinks;
- let filteredLinks = links;
- if (pattern) {
- try {
- const regex = new RegExp(pattern, 'i');
- filteredLinks = links.filter(url => regex.test(url));
- } catch (e) {
- showToast('Invalid regex pattern');
- console.error(`${CODE_NAME}: Invalid regex pattern: ${e.message}`);
- return;
- }
- }
- const linksToArchive = filteredLinks.map(url => applyReplaceRules(url, replaceRules));
- if (linksToArchive.length === 0) {
- showToast('No links to archive');
- console.log(`${CODE_NAME}: No links to archive`);
- return;
- }
- if (linksToArchive.length > 100) {
- rateLimitWarning.style.display = 'block';
- }
- console.log(`${CODE_NAME}: About to archive ${linksToArchive.length} URLs`);
- document.getElementById('bookmarklet-ui').style.display = 'none';
- archivingPromise = archiveLinks(linksToArchive);
- });
- pauseResumeButton.addEventListener('click', () => {
- isPaused = !isPaused;
- pauseResumeButton.textContent = isPaused ? 'Resume' : 'Pause';
- showToast(isPaused ? 'Archiving paused' : 'Archiving resumed');
- console.log(`${CODE_NAME}: Archiving ${isPaused ? 'paused' : 'resumed'}`);
- });
- closeButton.addEventListener('click', () => {
- isPaused = true;
- document.getElementById('bookmarklet-ui').remove();
- showToast('Bookmarklet closed');
- console.log(`${CODE_NAME}: Bookmarklet closed`);
- });
- function showToast(message) {
- const toast = document.getElementById('toast');
- toast.textContent = message;
- toast.style.display = 'block';
- setTimeout(() => {
- toast.style.display = 'none';
- }, 3000);
- }
- async function archiveLinks(links) {
- let failedLinks = [];
- let consecutiveErrors = 0;
- const maxRetries = 3;
- let retryCount = 0;
- async function attemptArchive(url, attempt = 1) {
- if (isPaused) {
- await new Promise(resolve => {
- const checkResume = setInterval(() => {
- if (!isPaused) {
- clearInterval(checkResume);
- resolve();
- }
- }, 100);
- });
- }
- try {
- showToast(`Archiving ${links.indexOf(url) + 1}/${links.length}: ${url}`);
- console.log(`${CODE_NAME}: Archiving ${links.indexOf(url) + 1}/${links.length}: ${url}`);
- await fetch(`https://web.archive.org/save/${encodeURIComponent(url)}`, { mode: 'no-cors' });
- console.log(`${CODE_NAME}: Save attempted for ${url}. Check https://web.archive.org/web/*/${encodeURIComponent(url)} for the snapshot`);
- consecutiveErrors = 0;
- return true;
- } catch (e) {
- console.error(`${CODE_NAME}: Error archiving ${url} (Attempt ${attempt}): ${e.message}`);
- showToast(`Error archiving ${url}`);
- consecutiveErrors++;
- return false;
- }
- }
- while (retryCount <= maxRetries) {
- const currentFailed = [];
- for (const url of links) {
- if (isPaused) {
- await new Promise(resolve => {
- const checkResume = setInterval(() => {
- if (!isPaused) {
- clearInterval(checkResume);
- resolve();
- }
- }, 100);
- });
- }
- const success = await attemptArchive(url, retryCount + 1);
- if (!success) {
- currentFailed.push(url);
- }
- await new Promise(resolve => setTimeout(resolve, 1000));
- }
- if (currentFailed.length === 0) break;
- if (consecutiveErrors >= 3 && retryCount < maxRetries) {
- showToast(`Multiple errors detected. Waiting 5 minutes before retrying...`);
- console.log(`${CODE_NAME}: Multiple consecutive errors. Waiting 5 minutes before retry ${retryCount + 2}`);
- await new Promise(resolve => setTimeout(resolve, 5 * 60 * 1000));
- consecutiveErrors = 0;
- }
- links = currentFailed;
- failedLinks = currentFailed;
- retryCount++;
- }
- if (failedLinks.length > 0) {
- console.log(`${CODE_NAME}: The following links failed to archive after ${maxRetries} retries:`);
- failedLinks.forEach(url => console.log(url));
- showToast(`Archiving completed with ${failedLinks.length} failures. Check console for details.`);
- } else {
- showToast('Archiving completed successfully');
- console.log(`${CODE_NAME}: Archiving completed successfully`);
- }
- document.getElementById('bookmarklet-ui').remove();
- }
- })();
Advertisement
Add Comment
Please, Sign In to add comment