Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name 4chan Deleted Post Viewer
- // @namespace http://tampermonkey.net/
- // @version 10.1
- // @description Fixes the "Deleted: 0" bug by updating all Top/Bottom buttons simultaneously.
- // @author Anonymous
- // @match *://boards.4chan.org/*/thread/*
- // @match *://boards.4channel.org/*/thread/*
- // @connect *
- // @grant GM_openInTab
- // @grant GM_xmlhttpRequest
- // ==/UserScript==
- (function() {
- 'use strict';
- // Class name to identify our buttons
- const BTN_CLASS = 'deleted-tracker-btn';
- function init() {
- const navLinks = document.querySelectorAll('.navLinks');
- navLinks.forEach(nav => {
- // Create a button for THIS nav container
- const localBtn = document.createElement('a');
- localBtn.href = 'javascript:;';
- localBtn.textContent = 'Deleted: 0';
- localBtn.className = BTN_CLASS; // Tag it so we can find it later
- localBtn.style.fontWeight = 'bold';
- localBtn.style.color = '#888';
- localBtn.addEventListener('click', startProcess);
- nav.appendChild(document.createTextNode(' ['));
- nav.appendChild(localBtn);
- nav.appendChild(document.createTextNode(']'));
- });
- // Initial Count
- updateButtonCount();
- // Live Monitoring
- const observer = new MutationObserver(debounce(updateButtonCount, 200));
- observer.observe(document.body, {
- childList: true,
- subtree: true,
- attributes: true,
- attributeFilter: ['style', 'class', 'hidden']
- });
- }
- function debounce(func, wait) {
- let timeout;
- return function() {
- clearTimeout(timeout);
- timeout = setTimeout(() => func.apply(this, arguments), wait);
- };
- }
- function updateButtonCount() {
- // Find ALL buttons (Top, Bottom, Mobile)
- const buttons = document.querySelectorAll('.' + BTN_CLASS);
- if (buttons.length === 0) return;
- // Perform the scan
- const allPosts = document.querySelectorAll('.post');
- let visibleCount = 0;
- let hiddenCount = 0;
- allPosts.forEach(post => {
- const txt = (post.textContent || "").toLowerCase();
- // Logic: Has tag AND (is visible OR is hidden)
- if (txt.includes('[deleted]') || txt.includes('restored from external')) {
- // Check if visible on screen (height > 0)
- if (post.offsetWidth > 0 || post.getClientRects().length > 0) {
- visibleCount++;
- } else {
- hiddenCount++;
- }
- }
- });
- // Determine Text & Color
- let labelText = `Deleted: ${visibleCount}`;
- if (hiddenCount > 0) labelText += ` (+${hiddenCount} Hidden)`;
- const color = (visibleCount > 0 || hiddenCount > 0) ? (hiddenCount > 0 ? 'orange' : '#d00') : '#888';
- // Update EVERY button
- buttons.forEach(btn => {
- btn.textContent = labelText;
- btn.style.color = color;
- });
- }
- // --- Report Generator (unchanged) ---
- function fetchImageAsBase64(url) {
- return new Promise((resolve) => {
- if (url.startsWith('//')) url = 'https:' + url;
- if (url.startsWith('/')) url = window.location.origin + url;
- GM_xmlhttpRequest({
- method: "GET",
- url: url,
- responseType: "blob",
- onload: function(response) {
- if (response.status === 200) {
- const reader = new FileReader();
- reader.onloadend = () => resolve(reader.result);
- reader.readAsDataURL(response.response);
- } else { resolve(null); }
- },
- onerror: function() { resolve(null); }
- });
- });
- }
- async function startProcess() {
- const status = document.createElement('div');
- Object.assign(status.style, {
- position: 'fixed', top: '10px', right: '10px', background: 'rgba(0,0,0,0.9)',
- color: '#fff', padding: '15px', zIndex: '999999', borderRadius: '5px',
- fontFamily: 'sans-serif', fontSize: '14px'
- });
- document.body.appendChild(status);
- function updateStatus(msg) { status.innerText = msg; }
- updateStatus('Scanning visible posts...');
- const allPosts = document.querySelectorAll('.post');
- // Only report VISIBLE posts
- const targets = Array.from(allPosts).filter(post => {
- const txt = (post.textContent || "").toLowerCase();
- const isDel = txt.includes('[deleted]') || txt.includes('restored from external');
- const isVisible = post.offsetWidth > 0 || post.getClientRects().length > 0;
- return isDel && isVisible;
- });
- if (targets.length === 0) {
- alert('No visible deleted posts found.\n(Hidden posts are excluded from the report image)');
- status.remove();
- return;
- }
- updateStatus(`Found ${targets.length} visible posts. Preparing...`);
- const cleanPosts = [];
- const imageJobs = [];
- targets.forEach(post => {
- const clone = post.cloneNode(true);
- const removeSelectors = [
- '.sauce', '.backlink', '.menu-button', '.download-button',
- 'input[type="checkbox"]', '.postInfo > span.container',
- '.sideArrows', '.mobile', '.expanded-thumb'
- ];
- clone.querySelectorAll(removeSelectors.join(',')).forEach(el => el.remove());
- const warning = clone.querySelector('.warning');
- if (warning) {
- warning.style.cssText = "font-size: inherit; visibility: visible; display: inline; opacity: 1; color: red; font-weight: bold;";
- warning.textContent = '[Deleted]';
- }
- const fileThumb = clone.querySelector('.fileThumb img');
- if (fileThumb) {
- const src = fileThumb.getAttribute('src') || fileThumb.src;
- imageJobs.push({ element: fileThumb, url: src });
- fileThumb.style.cssText = "width: auto; height: auto; max-width: 250px; max-height: 250px;";
- fileThumb.removeAttribute('loading');
- }
- clone.style.cssText = "position: static; display: block; margin: 0; border: none; box-shadow: none; max-width: 100%; opacity: 1;";
- cleanPosts.push(clone);
- });
- if (imageJobs.length > 0) {
- updateStatus(`Downloading ${imageJobs.length} thumbnails...`);
- const downloads = imageJobs.map(job => fetchImageAsBase64(job.url));
- const results = await Promise.all(downloads);
- results.forEach((base64Data, index) => {
- if (base64Data) imageJobs[index].element.src = base64Data;
- });
- }
- updateStatus('Generating HTML...');
- let styles = '';
- document.querySelectorAll('link[rel="stylesheet"], style').forEach(style => {
- styles += style.outerHTML;
- });
- const htmlContent = `
- <!DOCTYPE html>
- <html>
- <head>
- <title>Deleted Posts Report</title>
- <meta charset="utf-8">
- <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
- ${styles}
- <style>
- body { padding: 10px; margin: 0; background-color: #eef2ff; }
- body.dark { background-color: #222; color: #aaa; }
- #header-bar { display: flex; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #ccc; }
- h1 { font-family: sans-serif; font-size: 20px; margin: 0; flex-grow: 1; }
- #save-btn { background: #d00; color: white; border: none; padding: 8px 15px; font-size: 14px; font-weight: bold; cursor: pointer; border-radius: 4px; margin-left: 20px; }
- #save-btn:hover { background: #b00; }
- #save-btn:disabled { background: #777; cursor: wait; }
- #container { column-width: 300px; column-gap: 5px; width: 100%; }
- .post-wrapper { break-inside: avoid; page-break-inside: avoid; margin-bottom: 5px; padding: 4px; display: inline-block; width: 100%; box-sizing: border-box; }
- .post { display: block !important; margin: 0 !important; width: 100% !important; opacity: 1 !important; }
- blockquote { margin: 4px 0 0 0; }
- s, .spoiler { background-color: #000 !important; color: #fff !important; text-decoration: none !important; padding: 0 2px; }
- </style>
- </head>
- <body class="${document.body.className}">
- <div id="header-bar">
- <h1>Deleted Posts Report (${targets.length})</h1>
- <button id="save-btn" onclick="saveAsImage()">Save as PNG</button>
- </div>
- <div id="container">
- ${cleanPosts.map(p => `<div class="post-wrapper">${p.outerHTML}</div>`).join('')}
- </div>
- <script>
- function saveAsImage() {
- const btn = document.getElementById('save-btn');
- const originalText = btn.innerText;
- btn.disabled = true; btn.innerText = 'Rendering...';
- window.scrollTo(0,0);
- html2canvas(document.body, {
- scale: 1, ignoreElements: (element) => element.id === 'header-bar', backgroundColor: null
- }).then(canvas => {
- canvas.toBlob(function(blob) {
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a'); a.href = url; a.download = '4chan_deleted_collage.png';
- document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url);
- btn.disabled = false; btn.innerText = originalText;
- });
- }).catch(err => {
- alert('Error: ' + err.message);
- btn.disabled = false; btn.innerText = originalText;
- });
- }
- </script>
- </body>
- </html>
- `;
- updateStatus('Opening Report...');
- const blob = new Blob([htmlContent], { type: 'text/html' });
- const url = URL.createObjectURL(blob);
- window.open(url, '_blank');
- setTimeout(() => status.remove(), 1000);
- }
- init();
- })();
Add Comment
Please, Sign In to add comment