YouTube → Supabase Deduplicator & Blocker (with ID logging)

Hide any recommended videos whose IDs already exist in your Supabase table, and log those IDs

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        YouTube → Supabase Deduplicator & Blocker (with ID logging)
// @description Hide any recommended videos whose IDs already exist in your Supabase table, and log those IDs
// @match       https://www.youtube.com/*
// @run-at      document-end
// @version 0.0.1.20250510181837
// @namespace https://greasyfork.org/users/1435046
// ==/UserScript==
 
(function () {
  'use strict';
 
  // 1. Supabase configuration
  const SUPABASE_URL = 'https://haughsijawbsqwumuryg.supabase.co';
  const SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImhhdWdoc2lqYXdic3F3dW11cnlnIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzM0ODE3MjYsImV4cCI6MjA0OTA1NzcyNn0.stESUMuJEs4CNBWGtxZr1XNp2XpnQeXmKkq3fNaVE-c';
  const TABLE = 'youtube_recommended_videos_table';
  const ID_COLUMN = 'video_id_column';
 
  // 2. Skip search/results or channel pages
  if (location.pathname.startsWith('/results') || location.pathname.startsWith('/@')) {
    return;
  }
 
  // 3. Data structures & selectors
  const existingIds = new Set();
  const ITEM_SELECTOR = 'ytd-rich-item-renderer'; /*, ytd-rich-grid-media, ytd-video-renderer*/
  const TITLE_LINK_SEL = 'a#video-title-link';
 
  // 4. Utility to extract ?v= from a link
  function getVideoId(href) {
    try {
      const url = new URL(href);
      return url.searchParams.get('v');
    } catch {
      return null;
    }
  }
 
  // 5. Hide one item if its ID is in existingIds
  function filterItem(item) {
    const linkEl = item.querySelector(TITLE_LINK_SEL);
    if (!linkEl) return;
 
    const id = getVideoId(linkEl.href);
    if (id && existingIds.has(id)) {
      item.style.display = 'none';
      //item.textContent = 'This video was blocked because it was found in the Supabase database.';
      //item.style.color = 'gray';
 
    }
  }
 
  // 6. Scan all current items
  function scanPage() {
    document.querySelectorAll(ITEM_SELECTOR).forEach(filterItem);
  }
 
  // 7. Fetch existing IDs from Supabase and log them
  async function fetchExistingIds() {
    const url = `${SUPABASE_URL}/rest/v1/${TABLE}?select=${ID_COLUMN}`;
    const res = await fetch(url, {
      headers: {
        'apikey': SUPABASE_KEY,
        'Authorization': `Bearer ${SUPABASE_KEY}`
      }
    });
    if (!res.ok) {
      console.error('Failed to fetch existing IDs:', res.status, res.statusText);
      return;
    }
    const rows = await res.json();
    for (const row of rows) {
      if (row[ID_COLUMN]) {
        existingIds.add(row[ID_COLUMN]);
      }
    }
    // Log the imported IDs
    console.log('Imported video IDs from Supabase:', Array.from(existingIds));
  }
 
  // 8. Initialization
  (async () => {
    await fetchExistingIds();
    scanPage();
 
    // 9. Watch for newly added nodes (infinite scroll, dynamic loading)
    new MutationObserver(mutations => {
      const path = location.pathname;
      if (path.startsWith('/results') || path.startsWith('/@')) return;
      for (const { addedNodes } of mutations) {
        for (const node of addedNodes) {
          if (!(node instanceof HTMLElement)) continue;
          if (node.matches(ITEM_SELECTOR)) {
            filterItem(node);
          } else if (node.querySelectorAll) {
            node.querySelectorAll(ITEM_SELECTOR).forEach(filterItem);
          }
        }
      }
    }).observe(document.body, { childList: true, subtree: true });
 
    // 10. Also re-scan after YouTube navigation
    window.addEventListener('yt-navigate-finish', () => {
      const path = location.pathname;
      if (path.startsWith('/results') || path.startsWith('/@')) return;
      scanPage();
    });
  })();
 
})();