CSFD Notify Cleaner

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

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Advertisement:

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

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);
  });

})();