// 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 and rendered like 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 events and blacklisted/non‐whitelisted entries. * Renders the video cards using the same `.ratio-16-9` approach as the home view. */ 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 events 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 event IDs and authors videos = videos.filter((video) => { // Event-level blacklisting if (app.blacklistedEventIds.has(video.id)) { return false; } // Author-level checks 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 videos by newest first videos.sort((a, b) => b.created_at - a.created_at); // 7) Render the videos in #channelVideoList 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(); // We'll store them in a local array so gear handlers match the indexes const channelVideos = videos; channelVideos.forEach((video, index) => { // If private + the user is the owner => decrypt if ( video.isPrivate && video.pubkey === nostrClient.pubkey && !video.alreadyDecrypted ) { video.magnet = fakeDecrypt(video.magnet); video.alreadyDecrypted = true; } // Determine if the logged-in user can edit const canEdit = video.pubkey === app.pubkey; let gearMenu = ""; if (canEdit) { gearMenu = `${new Date(video.created_at * 1000).toLocaleString()}