Batch Unsave Reddit Posts

Unsave all saved posts on old.reddit.com via Tampermonkey menu

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         Batch Unsave Reddit Posts
// @namespace    https://github.com/YangHgRi
// @version      1.0
// @description  Unsave all saved posts on old.reddit.com via Tampermonkey menu
// @author       YangHgRi
// @match        https://old.reddit.com/user/*/saved*
// @grant        GM_registerMenuCommand
// @grant        GM_notification
// @license MIT
// ==/UserScript==

(function () {
  const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
  const BATCH_SIZE = 50;
  const BATCH_BREAK = 60000;
  const DELAY_MIN = 2000;
  const DELAY_MAX = 5000;

  const originalTitle = document.title;
  let totalUnsaved = 0;
  let totalFound = 0;

  // ── Toast UI ──
  function createToast() {
    const el = document.createElement("div");
    Object.assign(el.style, {
      position: "fixed",
      bottom: "20px",
      right: "20px",
      zIndex: "99999",
      padding: "12px 20px",
      minWidth: "220px",
      background: "#1a1a2e",
      color: "#eee",
      fontSize: "13px",
      borderRadius: "8px",
      boxShadow: "0 4px 16px rgba(0,0,0,.4)",
      fontFamily: "system-ui, sans-serif",
      transition: "opacity .4s",
      lineHeight: "1.6",
    });
    document.body.appendChild(el);
    return el;
  }

  let toast = null;

  function updateProgress(msg) {
    if (!toast) toast = createToast();
    toast.innerHTML = msg;
    document.title = `[${totalUnsaved}/${totalFound}] Unsaving… — Reddit`;
  }

  function dismissToast(delay = 4000) {
    document.title = originalTitle;
    if (!toast) return;
    setTimeout(() => {
      toast.style.opacity = "0";
    }, delay);
    setTimeout(() => {
      toast.remove();
      toast = null;
    }, delay + 500);
  }

  // ── Core ──
  async function getUnsaveLinks() {
    return $("a")
      .filter(function () {
        return $(this).text().toLowerCase() === "unsave";
      })
      .toArray();
  }

  async function processLinks(links) {
    for (let i = 0; i < links.length; i++) {
      $(links[i]).click();
      totalUnsaved++;
      updateProgress(`⏳ Unsaving <b>${totalUnsaved}</b> / ${totalFound}`);

      if (totalUnsaved % BATCH_SIZE === 0) {
        updateProgress(
          `⏸ Processed ${totalUnsaved} items, cooling down ${BATCH_BREAK / 1000}s…`,
        );
        await wait(BATCH_BREAK);
      } else {
        const delay =
          Math.floor(Math.random() * (DELAY_MAX - DELAY_MIN + 1)) + DELAY_MIN;
        await wait(delay);
      }
    }
  }

  async function scrollAndUnsave() {
    let previousHeight = 0;
    let sameHeightCount = 0;
    updateProgress("🔍 Scanning saved list…");

    while (true) {
      const links = await getUnsaveLinks();
      if (links.length === 0) break;

      totalFound += links.length;
      await processLinks(links);

      window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
      updateProgress("🔍 Scrolling for more…");
      await wait(2000);

      const currentHeight = document.body.scrollHeight;
      if (currentHeight === previousHeight) {
        sameHeightCount++;
        if (sameHeightCount >= 3) break;
      } else {
        sameHeightCount = 0;
        previousHeight = currentHeight;
      }
    }

    updateProgress(`✅ Done! Unsaved <b>${totalUnsaved}</b> posts`);
    GM_notification({
      title: "Reddit Unsave",
      text: `Done! Unsaved ${totalUnsaved} posts`,
      timeout: 5000,
    });
    dismissToast(3000);
    setTimeout(() => location.reload(), 4000);
  }

  GM_registerMenuCommand("Unsave All", () => {
    if (typeof $ === "undefined") {
      const s = document.createElement("script");
      s.src = "https://code.jquery.com/jquery-3.6.0.min.js";
      s.onload = () => scrollAndUnsave();
      document.head.appendChild(s);
    } else {
      scrollAndUnsave();
    }
  });
})();