The 4chan Cleaner

Release 2026 Build 1

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name        The 4chan Cleaner
// @description Release 2026 Build 1
// @author      BoKu
// @version     2026.1
// @namespace   https://greasyfork.org/scripts/T4C - The 4chan Cleaner
// @icon        https://i.imgur.com/jsBhOii.gif
// @license 	The 4chan Cleaner is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
// @match       *://*.4chan.org/*
// @exclude     *://p.4chan.org/*
// @noframes
// @run-at      document-body
// @resource    bootstrapCss https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css#sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB
// @require     https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js#sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI
// @grant       GM.setValue
// @grant       GM.getValue
// @grant       GM.openInTab
// @grant       GM_getResourceText
// @grant       GM_addStyle
// @grant       GM_info
// @grant       unsafeWindow
// @grant       GM_download
// @connect     i.4cdn.org
// ==/UserScript==
'use strict';
const bootstrapCss = GM_getResourceText("bootstrapCss"),
      scriptVersion = GM_info.script.version,
      scriptDesc = GM_info.script.description,
      c = unsafeWindow,
      logo = "https://s.4cdn.org/image/fp/logo-transparent.png",
      h = c.document.querySelector("html"),
      s = c.document.querySelectorAll('head script, head style, link[rel*="stylesheet"]'),
      b = c.document.querySelector('body'),
      d = c.document.getElementById('doc'),
      f = c.document.getElementById('ft'),
      bt = c.document.querySelector("body > div.boardBanner > div.boardTitle")?.textContent || "4chan";
h.setAttribute('data-bs-theme','dark');
GM_addStyle(bootstrapCss);
h.style.background = '#212529';
b.style.background = '#212529';
f?.remove();

const b1 = c.document.querySelector("#delform > div.bottomCtrl.desktop > span.deleteform > input[type=submit]:nth-child(5)");
if (b1) {
    b1.classList.add("btn", "btn-danger");
    b1.style.margin = ".25rem .125rem";
}
const b2 = c.document.querySelector("#bottomReportBtn");
if (b2) {
    b2.classList.add("btn", "btn-warning");
    b2.style.margin = ".25rem .125rem";
}
const i1 = c.document.querySelector("#delform > div.bottomCtrl.desktop > span.deleteform > input[type=text]:nth-child(1)");
if (i1) {
    i1.classList.add("form-control");
}
const i2 = c.document.querySelector("#delform > div.bottomCtrl.desktop > span.deleteform > input[type=text]:nth-child(2)");
if (i2) {
    i2.classList.add("form-control");
}
const i3 = c.document.querySelector("#delPassword");
if (i3) {
    i3.classList.add("form-control");
}
const c1 = c.document.querySelector('#delform .bottomCtrl.desktop .deleteform input[type="checkbox"]:nth-child(3)');
if (c1) {
    c1.classList.add("form-check-input");
    Object.assign(c1.style, {
        minHeight: `calc(1em + 1rem + 2px)`,
        padding: `.5rem 1rem`,
        fontSize: `1.25rem`,
        marginTop: 'unset'
    })
}
const s1 = c.document.querySelector("#styleSelector");
if (s1) {
    s1.classList.add("form-select");
}

function whenReady(fn) {
    if (c.document.readyState !== "loading") {
        fn();
    } else {
        c.document.addEventListener("DOMContentLoaded", fn);
    }
}
function downloadAllImages() {
    const imageLinks = c.document.querySelectorAll('a.fileThumb');
    if (imageLinks.length === 0) {
        console.debug('No images found to download!');
        return;
    }
    const confirmationMessage = `Are you sure you want to download all the ${imageLinks.length} images?`;
    if (!c.confirm(confirmationMessage)) {
        return;
    }
    imageLinks.forEach((link, index) => {
    const imageUrl = link.href;
    // Cleans the URL path down to the pure filename string
    const fileName = imageUrl.substring(imageUrl.lastIndexOf('/') + 1);

    // Staggers requests by 150ms intervals to prevent file streaming bottlenecks
    setTimeout(() => {
      GM_download({
        url: imageUrl,
        name: fileName,
        onload: () => {
          console.log(`Successfully saved: ${fileName}`);
        },
        onerror: (error) => {
          console.error(`Download failed for ${fileName}:`, error.error);

          // Fallback Strategy: Triggers if your browser blocks native extension file generation
          if (error.error === 'not_enabled' || error.error === 'permission_denied') {
             const fallbackAnchor = c.document.createElement('a');
             fallbackAnchor.href = imageUrl;
             fallbackAnchor.download = fileName;
             c.document.body.appendChild(fallbackAnchor);
             fallbackAnchor.click();
             fallbackAnchor.remove();
          }
        }
      });
    }, index * 150);
  });
}
whenReady(() => {
    const nlD = c.document.querySelector("body > div.navLinks.desktop");
    if (!nlD) return;

    setTimeout(() => {
        if (nlD.childElementCount === 7) {
            try {
                const modalWrap = c.document.createElement("div");
                modalWrap.innerHTML = `
                <div class="modal fade" id="T4C_viewImages_modal" tabindex="-1" aria-hidden="true">
                  <div class="modal-dialog modal-dialog-centered modal-lg modal-dialog-scrollable" style="width:90vw!important;min-width:90vw!important;max-width:90vw!important;">
                    <div class="modal-content bg-dark text-light">
                      <div class="modal-header">
                        <h5 class="modal-title">View All Images</h5>
                        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
                      </div>
                      <div class="modal-body" id="T4C_viewImages_modal_body"></div>
                      <div class="modal-footer" id="T4C_viewImages_modal_footer"><small>Doesn't support <b>webm</b> yet.</small></div>
                    </div>
                  </div>
                </div>`;
                c.document.body.appendChild(modalWrap.firstElementChild);

                const modalEl = c.document.getElementById("T4C_viewImages_modal"),
                      modalBody = c.document.getElementById("T4C_viewImages_modal_body");
                function buildThumbs() {
                    modalBody.replaceChildren();
                    const wrap = c.document.createElement("div");
                    wrap.className = "d-flex flex-wrap gap-2";
                    c.document.querySelectorAll("a.fileThumb").forEach((thumb) => {
                        const href = thumb.getAttribute("href") || "";
                        if (href.includes("webm")) return;
                        thumb.querySelectorAll(":scope > img").forEach((img) => {
                            img.classList.add("t4c-thumb");
                        });
                        wrap.appendChild(thumb.cloneNode(true));
                    });
                    modalBody.appendChild(wrap);
                }
                function clearThumbs() {
                    modalBody.replaceChildren();
                }
                nlD.appendChild(c.document.createTextNode("["));
                const vai = c.document.createElement("a");
                vai.id = "T4C_viewImages";
                vai.textContent = "View All Images";
                vai.style.cursor = "pointer";
                vai.dataset.bsBackdrop="static";
                vai.addEventListener("click", (event) => {
                    if (!modalEl) return;
                    const modal = bootstrap.Modal.getOrCreateInstance(modalEl, {
                        backdrop: true,
                        keyboard: true
                    });
                    modal.show();
                })
                nlD.appendChild(vai);
                modalEl.addEventListener("shown.bs.modal", buildThumbs);
                modalEl.addEventListener("hidden.bs.modal", clearThumbs);
                nlD.appendChild(c.document.createTextNode("] ["));
                const dai = c.document.createElement("a");
                dai.id = "T4C_downImages";
                dai.textContent = "Download All Images";
                dai.style.cursor = "pointer";
                dai.addEventListener("click", (event) => {
                    event.preventDefault();
                    downloadAllImages();
                })
                nlD.appendChild(dai);
                nlD.appendChild(c.document.createTextNode("]"));
            } catch (ex) {
                console.debug(ex);
            }
        }
    }, 500);
});

GM_addStyle(`
  * {
    scrollbar-color: rgba(255, 70, 87, 1) black;
  }
  html {
    font-size:75%!important;
  }
  #doc, #doc2, #doc3, #doc4 {
    display:flex;
    flex-direction:column;
    width:unset;
  }
  .boxcontent,
  .box-inner,
  .box-outer,
  #announce,
  #entries,
  #disclaimer-dialog,
  div.reply{
    background:#343a40;
    color:#f8f9fa;
  }
  div.reply{
    border: 1px solid #212529;
    border-left: none;
    border-top: none;
  }
  .boxbar,
  #entries th{
    background:#1a1d20;
    color:#dee2e6;
  }
  div#filter-btn,
  div#opts-btn {
    display:none;
  }
  .boxcontent {
    display:flex;
    flex-wrap:nowrap;
    justify-content:space-around;
    align-items:flex-start;
  }
  #boards .column {
    float:unset;
    width:100%;
  }
  span.warning sup {
    vertical-align:unset!important;
    top:unset;
  }
  div[class="box-outer top-box"],
  #announce{
    border:1px solid #212529;
  }

  /* Normal */
  a, a:visited,
  #boards a,
  div#boardNavDesktop a,
  a.replylink, a.replylink:not(:hover), div#absbot a:not(:hover),
  .quoteLink, .quotelink, .deadlink, .button
  {
    color: rgba(255, 70, 87, 1) !important;
  }
  /* Hover */
  a:hover,
  #boards a:hover,
  div#boardNavDesktop a:hover,
  a.replylink:hover, div.post div.postInfo span.postNum a:hover, .posteruid .hand:hover,
  a.quoteLink:hover, a.quotelink:hover, .button:hover
  {
    color: rgba(234, 134, 143, 1) !important;
  }

  .c-thread img {
    border: 1px solid rgba(220, 53, 69, 1) !important;
  }
  ol, ul {
    padding-left: unset;
  }
  #c-threads{
    display: flex;
    flex-wrap: nowrap;
    justify-content: space-evenly;
    flex-direction: row;
  }
  .c-thread{
    width:100%;
  }
  div#bannerCnt,
  hr.aboveMidAd,
  div.middlead,
  .adl,
  body > hr:nth-child(13),
  footer,
  #absbot,
  hr,
  #delform > div.bottomCtrl.desktop > span.stylechanger,
  #blotter{
    display:none!important;
  }
  hr{
    width:100%!important;
  }
  div.post div.postInfo span.subject {
    color: rgba(13, 202, 240, 1);
  }
  div.post div.postInfo span.nameBlock span.name {
    color: rgba(37, 200, 125, 1);
  }
  .nameBlock.capcodeMod span.name, span.capcodeMod a span.name, span.capcodeMod span.postertrip, span.capcodeMod strong.capcode {
    color: rgba(255, 126, 255,1) !important;
  }
  #delform > div.bottomCtrl.desktop{
    display: flex !important;
    width:100%;
    justify-content: flex-end;
    align-items: center;
  }

  #delform > div.bottomCtrl.desktop > span.deleteform
  {
    display: flex !important;
    align-items: center;
    justify-content: flex-end;
    gap: .5rem;
  }
  .form-check-input:focus {
    border-color: rgba(255, 70, 87, 1);
    outline: 0;
    box-shadow: 0 0 0 .25rem rgba(234, 134, 143, .25) !important;
  }
  .form-check-input:checked {
    background-color: rgba(255, 70, 87, 1);
    border-color: rgba(255, 70, 87, 1);
  }
  div.thread{
    border-bottom:2px solid rgba(255,255,255,.25);
    margin:1rem 0;
  }
  body > div.navLinks.desktop > div > span.ts-replies::before{
    content: "Replies: ";
  }body > div.navLinks.desktop > div > span.ts-images::before{
    content: "Images: ";
  }
  body > div.navLinks.desktop > div > span.ts-page::before{
    content: "Pages: ";
  }
  .fileThumb img,
  #T4C_viewImages_modal_body .t4c-thumb {
    max-height: 125px!important;
    width: auto !important;
  }
`);