YouTube → Supabase Deduplicator & Blocker (with ID logging)

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

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

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

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

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

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

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.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==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();
    });
  })();
 
})();