YouTube → Supabase Deduplicator & Blocker (with ID logging)

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

  1. // ==UserScript==
  2. // @name YouTube → Supabase Deduplicator & Blocker (with ID logging)
  3. // @description Hide any recommended videos whose IDs already exist in your Supabase table, and log those IDs
  4. // @match https://www.youtube.com/*
  5. // @run-at document-end
  6. // @version 0.0.1.20250510181837
  7. // @namespace https://gf.qytechs.cn/users/1435046
  8. // ==/UserScript==
  9. (function () {
  10. 'use strict';
  11. // 1. Supabase configuration
  12. const SUPABASE_URL = 'https://haughsijawbsqwumuryg.supabase.co';
  13. const SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImhhdWdoc2lqYXdic3F3dW11cnlnIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzM0ODE3MjYsImV4cCI6MjA0OTA1NzcyNn0.stESUMuJEs4CNBWGtxZr1XNp2XpnQeXmKkq3fNaVE-c';
  14. const TABLE = 'youtube_recommended_videos_table';
  15. const ID_COLUMN = 'video_id_column';
  16. // 2. Skip search/results or channel pages
  17. if (location.pathname.startsWith('/results') || location.pathname.startsWith('/@')) {
  18. return;
  19. }
  20. // 3. Data structures & selectors
  21. const existingIds = new Set();
  22. const ITEM_SELECTOR = 'ytd-rich-item-renderer'; /*, ytd-rich-grid-media, ytd-video-renderer*/
  23. const TITLE_LINK_SEL = 'a#video-title-link';
  24. // 4. Utility to extract ?v= from a link
  25. function getVideoId(href) {
  26. try {
  27. const url = new URL(href);
  28. return url.searchParams.get('v');
  29. } catch {
  30. return null;
  31. }
  32. }
  33. // 5. Hide one item if its ID is in existingIds
  34. function filterItem(item) {
  35. const linkEl = item.querySelector(TITLE_LINK_SEL);
  36. if (!linkEl) return;
  37. const id = getVideoId(linkEl.href);
  38. if (id && existingIds.has(id)) {
  39. item.style.display = 'none';
  40. //item.textContent = 'This video was blocked because it was found in the Supabase database.';
  41. //item.style.color = 'gray';
  42. }
  43. }
  44. // 6. Scan all current items
  45. function scanPage() {
  46. document.querySelectorAll(ITEM_SELECTOR).forEach(filterItem);
  47. }
  48. // 7. Fetch existing IDs from Supabase and log them
  49. async function fetchExistingIds() {
  50. const url = `${SUPABASE_URL}/rest/v1/${TABLE}?select=${ID_COLUMN}`;
  51. const res = await fetch(url, {
  52. headers: {
  53. 'apikey': SUPABASE_KEY,
  54. 'Authorization': `Bearer ${SUPABASE_KEY}`
  55. }
  56. });
  57. if (!res.ok) {
  58. console.error('Failed to fetch existing IDs:', res.status, res.statusText);
  59. return;
  60. }
  61. const rows = await res.json();
  62. for (const row of rows) {
  63. if (row[ID_COLUMN]) {
  64. existingIds.add(row[ID_COLUMN]);
  65. }
  66. }
  67. // Log the imported IDs
  68. console.log('Imported video IDs from Supabase:', Array.from(existingIds));
  69. }
  70. // 8. Initialization
  71. (async () => {
  72. await fetchExistingIds();
  73. scanPage();
  74. // 9. Watch for newly added nodes (infinite scroll, dynamic loading)
  75. new MutationObserver(mutations => {
  76. const path = location.pathname;
  77. if (path.startsWith('/results') || path.startsWith('/@')) return;
  78. for (const { addedNodes } of mutations) {
  79. for (const node of addedNodes) {
  80. if (!(node instanceof HTMLElement)) continue;
  81. if (node.matches(ITEM_SELECTOR)) {
  82. filterItem(node);
  83. } else if (node.querySelectorAll) {
  84. node.querySelectorAll(ITEM_SELECTOR).forEach(filterItem);
  85. }
  86. }
  87. }
  88. }).observe(document.body, { childList: true, subtree: true });
  89. // 10. Also re-scan after YouTube navigation
  90. window.addEventListener('yt-navigate-finish', () => {
  91. const path = location.pathname;
  92. if (path.startsWith('/results') || path.startsWith('/@')) return;
  93. scanPage();
  94. });
  95. })();
  96. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址