CSFD Notify Cleaner

Removes "watch later" notifications older than X days on CSFD (with progress bar)

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

You will need to install an extension such as Tampermonkey to install this script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Advertisement:

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

Advertisement:

// ==UserScript==
// @name         CSFD Notify Cleaner
// @name:cs      ČSFD "chci vidět" cleaner
// @namespace    cz.vodnikovo.csfd
// @version      2.3
// @license      CC-BY-NC-4.0
// @description  Removes "watch later" notifications older than X days on CSFD (with progress bar)
// @description:cs  Odstraňuje notifikace "chci vidět" starší než X dní na CSFD (s progress barem)
// @match        *://*.csfd.cz/soukrome/chci-videt*
// @grant        none
// @author       xxxvodnikxxx
// @icon         https://www.csfd.cz/favicon.ico
// @homepageURL  https://github.com/xxxvodnikxxx/vodnik-userscripts 
// ==/UserScript==

/*
    Last revision: 07.05.2026

    Primary implemented for TV notifications, probably will work for any kind of notification 
    Be aware there might be an issue with parsing date in another format than CZ
*/

(function () {
  "use strict";

  // Configuration - adjust as needed
  // ignore notifications newer than this many days
  const DAYS_LIMIT = 5;
  // delay between deletions to avoid server overload (in milliseconds)
  const DELAY_MS = 1500;
  // Configuration end - do not edit below unless you know what you are doing

  const sleep = (ms) => new Promise(r => setTimeout(r, ms));

  function createUI() {
    const panel = document.createElement("div");
    panel.style.position = "fixed";
    panel.style.top = "20px";
    panel.style.right = "20px";
    panel.style.zIndex = "999999";
    panel.style.background = "#222";
    panel.style.padding = "15px";
    panel.style.borderRadius = "8px";
    panel.style.boxShadow = "0 0 10px rgba(0,0,0,0.5)";
    panel.style.color = "white";
    panel.style.width = "220px";
    panel.id = "vodnik-cleaner-panel";

    const button = document.createElement("button");
    button.textContent = "Clean Notifications";
    button.style.width = "100%";
    button.style.padding = "8px";
    button.style.cursor = "pointer";
    button.style.marginBottom = "10px";

    const progress = document.createElement("div");
    progress.style.height = "8px";
    progress.style.background = "#444";
    progress.style.borderRadius = "5px";
    progress.style.overflow = "hidden";

    const bar = document.createElement("div");
    bar.style.height = "100%";
    bar.style.width = "0%";
    bar.style.background = "#4caf50";
    bar.id = "vodnik-progress-bar";

    progress.appendChild(bar);
    panel.appendChild(button);
    panel.appendChild(progress);
    document.body.appendChild(panel);

    button.addEventListener("click", cleanNotifications);

    printLoadedHeader();
  }

  function printLoadedHeader(){
      console.log("************************************");
      console.log("### ✅ Vodnik cleaner UI loaded ###");
      console.log("************************************");
  }

  function parseDate(str) {
    const [d, m, y] = str.split(".");
    return new Date(`${y}-${m}-${d}`);
  }

  function daysBetween(d1, d2) {
    return Math.floor((d2 - d1) / (1000 * 60 * 60 * 24));
  }

  async function cleanNotifications() {

  const rows = document.querySelectorAll(".watchlist-table-row");
  if (!rows.length) {
    alert("No watchlist rows found.");
    return;
  }

  const form = document.querySelector("#frm-reminderDismiss-form");
  if (!form) {
    alert("Delete form not found.");
    return;
  }

  const token = form.querySelector('input[name="_token_"]').value;
  const doValue = form.querySelector('input[name="_do"]').value;

  const now = new Date();
  let deletions = [];

  rows.forEach(row => {
    const movieTitleElement = row.querySelector("h3 a");
    const movieTitle = movieTitleElement ? movieTitleElement.textContent.trim() : "Unknown movie";

    const trs = row.querySelectorAll("table.modal-table-watchlist tr");

    trs.forEach(tr => {
      const text = tr.textContent.trim();

      const dateMatch = text.match(/(\d{2}\.\d{2}\.\d{4})/);
      if (!dateMatch) return;

      const timeMatch = text.match(/(\d{2}:\d{2})/);
      const channelMatch = text.match(/\b([A-Za-z0-9\s\+\-]+)\b(?=\s*\d{2}:\d{2})/);

      const notificationDate = parseDate(dateMatch[0]);
      const age = daysBetween(notificationDate, now);
      if (age <= DAYS_LIMIT) return;

      const link = tr.querySelector('a[href*="secureHandle"]');
      if (!link) return;

      const value = link.href.split("=").pop();

      deletions.push({
        value,
        movieTitle,
        date: dateMatch[0],
        time: timeMatch ? timeMatch[0] : "??:??",
        channel: channelMatch ? channelMatch[0].trim() : "Unknown",
        age
      });
    });
  });

  const total = deletions.length;
  if (!total) {
    alert("No old notifications found.");
    return;
  }

  const bar = document.getElementById("vodnik-progress-bar");

  console.log("🧹 Starting notification cleanup...");
  console.log("📊 Total to delete:", total);

  for (let i = 0; i < total; i++) {

    const item = deletions[i];

    console.groupCollapsed(`🗑 Deleting ${i + 1}/${total} — ${item.movieTitle}`);
    console.log("🎬 Movie:", item.movieTitle);
    console.log("📅 Date:", item.date);
    console.log("🕒 Time:", item.time);
    console.log("📺 Channel:", item.channel);
    console.log("⏳ Age (days):", item.age);
    console.log("🧾 _value_:", item.value);

    const formData = new URLSearchParams();
    formData.append("_token_", token);
    formData.append("_do", doValue);
    formData.append("_value_", item.value);

    const response = await fetch(form.action, {
      method: "POST",
      body: formData.toString(),
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      credentials: "include"
    });

    console.log("📡 Response status:", response.status);
    console.groupEnd();

    bar.style.width = `${((i + 1) / total) * 100}%`;

    await sleep(DELAY_MS);
  }

  console.log("✅ Cleaning finished.");
  alert("Cleaning finished.");
  location.reload();
}

  window.addEventListener("load", () => {
    setTimeout(createUI, 1000);
  });

})();