// js/channelProfile.js import { nostrClient } from "./nostr.js"; import { app } from "./app.js"; import { initialBlacklist, initialWhitelist } from "./lists.js"; import { isWhitelistEnabled } from "./config.js"; /** * Initialize the channel profile view. * Called when #view=channel-profile is active. */ export async function initChannelProfileView() { // 1) Get npub from hash (e.g. #view=channel-profile&npub=...) const hashParams = new URLSearchParams(window.location.hash.slice(1)); const npub = hashParams.get("npub"); if (!npub) { console.error( "No npub found in hash. Example: #view=channel-profile&npub=npub1..." ); return; } // 2) Decode npub => hex pubkey let hexPub; try { const decoded = window.NostrTools.nip19.decode(npub); if (decoded.type === "npub" && decoded.data) { hexPub = decoded.data; } else { throw new Error("Invalid npub decoding result."); } } catch (err) { console.error("Error decoding npub:", err); return; } // 3) Load user’s profile (banner, avatar, etc.) await loadUserProfile(hexPub); // 4) Load user’s videos (filtered + rendered like the home feed) await loadUserVideos(hexPub); } /** * Fetches and displays the user’s metadata (kind=0). */ async function loadUserProfile(pubkey) { try { const events = await nostrClient.pool.list(nostrClient.relays, [ { kinds: [0], authors: [pubkey], limit: 1 }, ]); if (events.length && events[0].content) { const meta = JSON.parse(events[0].content); // Banner const bannerEl = document.getElementById("channelBanner"); if (bannerEl) { bannerEl.src = meta.banner || "assets/jpg/default-banner.jpg"; } // Avatar const avatarEl = document.getElementById("channelAvatar"); if (avatarEl) { avatarEl.src = meta.picture || "assets/svg/default-profile.svg"; } // Channel Name const nameEl = document.getElementById("channelName"); if (nameEl) { nameEl.textContent = meta.display_name || meta.name || "Unknown User"; } // Channel npub const channelNpubEl = document.getElementById("channelNpub"); if (channelNpubEl) { const userNpub = window.NostrTools.nip19.npubEncode(pubkey); channelNpubEl.textContent = userNpub; } // About/Description const aboutEl = document.getElementById("channelAbout"); if (aboutEl) { aboutEl.textContent = meta.about || ""; } // Website const websiteEl = document.getElementById("channelWebsite"); if (websiteEl) { if (meta.website) { websiteEl.href = meta.website; websiteEl.textContent = meta.website; } else { websiteEl.textContent = ""; websiteEl.removeAttribute("href"); } } // Lightning Address const lnEl = document.getElementById("channelLightning"); if (lnEl) { lnEl.textContent = meta.lud16 || meta.lud06 || "No lightning address found."; } } else { console.warn("No metadata found for this user."); } } catch (err) { console.error("Failed to fetch user profile data:", err); } } /** * Fetches and displays this user's videos (kind=30078), * filtering out older overshadowed notes, blacklisted, non‐whitelisted, etc. */ async function loadUserVideos(pubkey) { try { // 1) Build filter for videos from this pubkey const filter = { kinds: [30078], authors: [pubkey], "#t": ["video"], limit: 200, }; // 2) Collect raw events from all relays const events = []; for (const url of nostrClient.relays) { try { const result = await nostrClient.pool.list([url], [filter]); events.push(...result); } catch (relayErr) { console.error(`Relay error (${url}):`, relayErr); } } // 3) Convert to "video" objects let videos = []; for (const evt of events) { const vid = localConvertEventToVideo(evt); if (!vid.invalid && !vid.deleted) { videos.push(vid); } } // 4) Deduplicate older overshadowed versions => newest only videos = dedupeToNewestByRoot(videos); // 5) Filter out blacklisted IDs / authors videos = videos.filter((video) => { // Event-level blacklisting if (app.blacklistedEventIds.has(video.id)) return false; // Author-level const authorNpub = app.safeEncodeNpub(video.pubkey) || video.pubkey; if (initialBlacklist.includes(authorNpub)) return false; if (isWhitelistEnabled && !initialWhitelist.includes(authorNpub)) { return false; } return true; }); // 6) Sort newest first videos.sort((a, b) => b.created_at - a.created_at); // 7) Render them const container = document.getElementById("channelVideoList"); if (!container) { console.warn("channelVideoList element not found in DOM."); return; } container.innerHTML = ""; if (!videos.length) { container.innerHTML = `
No videos to display.
`; return; } const fragment = document.createDocumentFragment(); const channelVideos = videos; // We'll need all known events for revert-check const allKnownEventsArray = Array.from(nostrClient.allEvents.values()); channelVideos.forEach((video, index) => { // Private => decrypt if owned by the user if ( video.isPrivate && video.pubkey === nostrClient.pubkey && !video.alreadyDecrypted ) { video.magnet = fakeDecrypt(video.magnet); video.alreadyDecrypted = true; } // Check if user can edit const canEdit = video.pubkey === app.pubkey; let hasOlder = false; if (canEdit && video.videoRootId) { // Use the same hasOlderVersion approach as home feed hasOlder = app.hasOlderVersion(video, allKnownEventsArray); } // If there's an older overshadowed version, show revert const revertButton = hasOlder ? ` ` : ""; // Gear menu let gearMenu = ""; if (canEdit) { gearMenu = `${new Date(video.created_at * 1000).toLocaleString()}