From 0373859ad3793dceadf441bbf296e395c2c2a547 Mon Sep 17 00:00:00 2001 From: Keep Creating Online <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sun, 23 Feb 2025 11:40:07 -0500 Subject: [PATCH 001/101] added ~Bordut-Nodlex to whitelist --- js/lists.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/lists.js b/js/lists.js index ee7274a1..2abc968e 100644 --- a/js/lists.js +++ b/js/lists.js @@ -13,6 +13,7 @@ const npubs = [ "npub1m5s9w4t03znyetxswhgq0ud7fq8ef8y3l4kscn2e8wkvmv42hh3qujgjl3", // mister_monster "npub13qexjtmajssuhz8gdchgx65dwsnr705drse294zz5vt4e78ya2vqzyg8lv", // SatoshiSignal "npub1da7m2ksdj24995hm8afv88pjpvzt6t9vh70mg8t52yjwtxza3vjszyar58", // GoblinBox + "npub196rl3tls3c4y79pc3ptrrj2430z7p5uwetfhukhtkt69hph0fvwq08l43q", // ~Bordut-Nodlex ]; console.log("DEBUG: lists.js loaded, npubs:", npubs); From c4289161e11c3524fdba2200ac23902ac82d6a75 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 22 Sep 2025 09:41:07 -0400 Subject: [PATCH 002/101] Load disclaimer modal dynamically --- js/app.js | 4 +--- js/disclaimer.js | 24 ++++++++++++++++++------ js/index.js | 6 ++++++ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/js/app.js b/js/app.js index 5d45bc03..51997718 100644 --- a/js/app.js +++ b/js/app.js @@ -4,7 +4,6 @@ import { loadView } from "./viewManager.js"; import { nostrClient } from "./nostr.js"; import { torrentClient } from "./webtorrent.js"; import { isDevMode } from "./config.js"; -import disclaimerModal from "./disclaimer.js"; import { isWhitelistEnabled } from "./config.js"; import { initialWhitelist, @@ -208,9 +207,8 @@ class bitvidApp { } } - // 5. Setup general event listeners, show disclaimers + // 5. Setup general event listeners this.setupEventListeners(); - disclaimerModal.show(); // 6) Load the default view ONLY if there's no #view= already if (!window.location.hash || !window.location.hash.startsWith("#view=")) { diff --git a/js/disclaimer.js b/js/disclaimer.js index e1a13980..bdfa4454 100644 --- a/js/disclaimer.js +++ b/js/disclaimer.js @@ -2,17 +2,29 @@ class DisclaimerModal { constructor() { - // Initialize elements when the disclaimer HTML is in the DOM. - this.init(); + this.modal = null; + this.acceptButton = null; + this.acceptHandler = null; } init() { this.modal = document.getElementById("disclaimerModal"); - this.acceptButton = document.getElementById("acceptDisclaimer"); - if (this.acceptButton) { - this.acceptButton.addEventListener("click", () => { + const nextAcceptButton = document.getElementById("acceptDisclaimer"); + + if (!this.acceptHandler) { + this.acceptHandler = () => { this.hide(); - }); + }; + } + + if (this.acceptButton && this.acceptHandler) { + this.acceptButton.removeEventListener("click", this.acceptHandler); + } + + this.acceptButton = nextAcceptButton || null; + + if (this.acceptButton && this.acceptHandler) { + this.acceptButton.addEventListener("click", this.acceptHandler); } } diff --git a/js/index.js b/js/index.js index ce3b6db5..64d41275 100644 --- a/js/index.js +++ b/js/index.js @@ -171,6 +171,12 @@ Promise.all([ // Also run once on initial load handleHashChange(); + + return import("./disclaimer.js"); + }) + .then(({ default: disclaimerModal }) => { + disclaimerModal.init(); + disclaimerModal.show(); }); /* ------------------------------------------- From fe0245e30cc5328335088a7c81971a3571697ec1 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 22 Sep 2025 09:54:19 -0400 Subject: [PATCH 003/101] Improve access control list initialization --- js/accessControl.js | 110 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 92 insertions(+), 18 deletions(-) diff --git a/js/accessControl.js b/js/accessControl.js index 15d68bff..4cc051ba 100644 --- a/js/accessControl.js +++ b/js/accessControl.js @@ -7,44 +7,118 @@ class AccessControl { constructor() { // Debug logging for initialization console.log("DEBUG: AccessControl constructor called"); - console.log("DEBUG: initialWhitelist from import:", initialWhitelist); - console.log("DEBUG: typeof initialWhitelist:", typeof initialWhitelist); - console.log("DEBUG: initialWhitelist length:", initialWhitelist.length); - // Initialize empty sets - this.whitelist = new Set(initialWhitelist); - this.blacklist = new Set(initialBlacklist.filter((x) => x)); // Filter out empty strings + const { data: storedWhitelist, status: whitelistStatus } = this.loadWhitelist(); + const { data: storedBlacklist, status: blacklistStatus } = this.loadBlacklist(); + + if (storedWhitelist !== null) { + this.whitelist = new Set(storedWhitelist); + } else { + this.whitelist = new Set(initialWhitelist); + this.saveWhitelist(); + if (whitelistStatus && whitelistStatus !== "missing") { + console.warn( + `Whitelist storage ${whitelistStatus}. Falling back to initial whitelist.` + ); + } + } + + if (storedBlacklist !== null) { + this.blacklist = new Set(storedBlacklist); + } else { + this.blacklist = new Set(initialBlacklist.filter((x) => x)); // Filter out empty strings + this.saveBlacklist(); + if (blacklistStatus && blacklistStatus !== "missing") { + console.warn( + `Blacklist storage ${blacklistStatus}. Falling back to initial blacklist.` + ); + } + } // Debug the sets - console.log("DEBUG: Whitelist after Set creation:", [...this.whitelist]); - console.log("DEBUG: Blacklist after Set creation:", [...this.blacklist]); - - // Save to localStorage - this.saveWhitelist(); - this.saveBlacklist(); + console.log("DEBUG: Whitelist after initialization:", [...this.whitelist]); + console.log("DEBUG: Blacklist after initialization:", [...this.blacklist]); } // Rest of the class remains the same... loadWhitelist() { try { const stored = localStorage.getItem("bitvid_whitelist"); - return stored ? JSON.parse(stored) : []; + if (!stored) { + if (isDevMode) console.log("No stored whitelist found in localStorage."); + return { data: null, status: "missing" }; + } + + const parsed = JSON.parse(stored); + const sanitized = this.sanitizeList(parsed, "whitelist"); + return { + data: sanitized, + status: sanitized === null ? "invalid" : "ok", + }; } catch (error) { - console.error("Error loading whitelist:", error); - return []; + console.error("Error loading whitelist, using defaults:", error); + return { data: null, status: "error" }; } } loadBlacklist() { try { const stored = localStorage.getItem("bitvid_blacklist"); - return stored ? JSON.parse(stored) : []; + if (!stored) { + if (isDevMode) console.log("No stored blacklist found in localStorage."); + return { data: null, status: "missing" }; + } + + const parsed = JSON.parse(stored); + const sanitized = this.sanitizeList(parsed, "blacklist"); + return { + data: sanitized, + status: sanitized === null ? "invalid" : "ok", + }; } catch (error) { - console.error("Error loading blacklist:", error); - return []; + console.error("Error loading blacklist, using defaults:", error); + return { data: null, status: "error" }; } } + sanitizeList(list, listName) { + if (!Array.isArray(list)) { + console.warn( + `Stored ${listName} is not an array. Received:`, + list + ); + return null; + } + + const sanitized = []; + let invalidEntries = 0; + + list.forEach((value) => { + if (typeof value !== "string") { + invalidEntries += 1; + return; + } + + const trimmed = value.trim(); + if (trimmed) { + sanitized.push(trimmed); + } else { + invalidEntries += 1; + } + }); + + if (invalidEntries > 0) { + console.warn( + `Stored ${listName} contained ${invalidEntries} invalid entr${ + invalidEntries === 1 ? "y" : "ies" + }. Sanitized list:`, + sanitized + ); + } + + return sanitized; + } + saveWhitelist() { try { localStorage.setItem( From b51b692f31a3fc21595f04c3899e9b511e45b1ef Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 22 Sep 2025 09:57:19 -0400 Subject: [PATCH 004/101] Added Agents.md --- AGENTS.md | 229 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..b06fd708 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,229 @@ +# Bitvid — AI Agent Guide (AGENTS.md) + +This document tells AI agents how Bitvid works **now** and how to extend it going forward. It focuses on the newly adopted **URL‑first playback with WebTorrent fallback** strategy, minimal code touchpoints, and reliable interop with the Nostr ecosystem. + +--- + +## 1) Mission & Product Direction + +**Goal:** Stream videos reliably and cheaply. + +* **Primary transport:** a **hosted video URL** (MP4/WebM/HLS/DASH) that plays directly in the browser. +* **Secondary transport (fallback & cost control):** a **WebTorrent magnet** enhanced with **`ws=` (web seed)** and **`xs=` (.torrent URL)**. +* **Behavior:** Try `url` first. If it’s missing or unreachable, **fallback to WebTorrent** using the same post’s magnet. If the magnet includes `ws`/`xs`, WebTorrent can bootstrap via HTTP and/or peers. +* **Nostr interop:** Keep existing kind `30078` events for Bitvid’s app data; optionally **mirror a NIP‑94 kind `1063`** event so other clients can discover the same hosted video URL. +* **Resilience:** Light link‑health probing to hide/flag dead URLs and avoid broken playback. + +--- + +## 2) High‑Level Architecture + +* **Frontend app** (vanilla JS + HTML/CSS) + + * **Upload modal** accepts: Title (required), **URL or Magnet** (at least one required), Thumbnail, Description. Optional fields for **`ws`** and **`xs`** help P2P bootstrap. + * **Feed / Cards** render video posts, each carrying both `data-play-url` and `data-play-magnet` attributes. + * **Player** uses `playVideoWithFallback({ url, magnet })`: probe `url`; if OK, play via `