diff --git a/nostr/commit-signatures.jsonl b/nostr/commit-signatures.jsonl index d256f57..9a49cef 100644 --- a/nostr/commit-signatures.jsonl +++ b/nostr/commit-signatures.jsonl @@ -127,3 +127,4 @@ {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772299137,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","more-muted replyt-to"]],"content":"Signed commit: more-muted replyt-to","id":"fc0a91b526083b640d8116592fcac064fcf3cec9625b48dbd41c3877b2fe5444","sig":"998273d70d827ffbb939b4c149ff88e11c9f3aae3c5ddee78d860710f7fbff42c5ceed9433367b530bdc2869f9d382eb449537f813cf745c49f1a87a36926502"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772299733,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","make relay timeouts more efficient\nsuppress diff on initial commit"]],"content":"Signed commit: make relay timeouts more efficient\nsuppress diff on initial commit","id":"5fbc2dfb13acab011df5a394a022267e69bbe908e696c4389e0c07ba83d58a0d","sig":"daf46d563c413e2481be2cbd2b00d3015cf601e19fe0a191ffbb18c2c07508b17e34ebda5c903a1391914f991cecd7a7a4e809fcba45e1f14ebab674117eb53c"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772300935,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"3618c494d594408165ebf461e290676817ab6cc8b0b076ccc02b35a487ae8da1","sig":"d9106ce1318e703df1c366835be69c0cab12fba3a9be0fed944b17e55fd3b44f3fc9d45b32366d1d237452f099ab3b07f8ad6199660972ce571ec23ae264e873"} +{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772302842,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"5c4b680a04363718d8de6aa05b824d30417221a9095be57bb9a7c2cf01c5af59","sig":"51ffa554e83a6a3c4ca97cffc7eca67e770ca822e43e9e78692bafcd63401c4df84e1fe030592e63982b509d3cfa8bfbd57c6b4257661b0f43adedef335c7575"} diff --git a/src/routes/users/[npub]/+page.svelte b/src/routes/users/[npub]/+page.svelte index 51fd51f..77282ff 100644 --- a/src/routes/users/[npub]/+page.svelte +++ b/src/routes/users/[npub]/+page.svelte @@ -13,7 +13,7 @@ import { userStore } from '$lib/stores/user-store.js'; import { fetchUserProfile, extractProfileData } from '$lib/utils/user-profile.js'; import { combineRelays } from '$lib/config.js'; - import { KIND, isEphemeralKind, isReplaceableKind } from '$lib/types/nostr.js'; + import { KIND, isEphemeralKind, isReplaceableKind, isAddressableKind } from '$lib/types/nostr.js'; import { hasUnlimitedAccess } from '$lib/utils/user-access.js'; import { eventCache } from '$lib/services/nostr/event-cache.js'; @@ -633,6 +633,19 @@ i * return true; } + // Exclude addressable events (30000-39999) that are not repo-related + // Only allow known repo-related addressable events: REPO_ANNOUNCEMENT, REPO_STATE, BRANCH_PROTECTION + if (isAddressableKind(event.kind)) { + const allowedAddressableKinds: number[] = [ + KIND.REPO_ANNOUNCEMENT, + KIND.REPO_STATE, + KIND.BRANCH_PROTECTION + ]; + if (!allowedAddressableKinds.includes(event.kind)) { + return true; + } + } + // Exclude specific regular kinds that are not repo-related: // Kind 1: Keep this one in, just for the user's convenience @@ -944,41 +957,99 @@ i * return date.toLocaleDateString(); } + // Helper function to check if an event is repo-related + function isRepoRelatedEvent(event: NostrEvent): boolean { + const repoRelatedKinds: number[] = [ + KIND.REPO_ANNOUNCEMENT, + KIND.REPO_STATE, + KIND.PATCH, + KIND.PULL_REQUEST, + KIND.PULL_REQUEST_UPDATE, + KIND.ISSUE, + KIND.STATUS_OPEN, + KIND.STATUS_APPLIED, + KIND.STATUS_CLOSED, + KIND.STATUS_DRAFT, + KIND.COMMIT_SIGNATURE, + KIND.OWNERSHIP_TRANSFER, + KIND.RELEASE, + KIND.COMMENT, + KIND.THREAD, + KIND.BRANCH_PROTECTION, + KIND.HIGHLIGHT + ]; + return repoRelatedKinds.includes(event.kind); + } + + // Helper function to check if an event references a repo via a-tag + function eventReferencesRepo(event: NostrEvent, repoATags: Set): boolean { + const aTags = event.tags.filter(t => t[0] === 'a' && t[1]); + for (const aTag of aTags) { + if (aTag[1] && repoATags.has(aTag[1])) { + return true; + } + } + return false; + } + + // Helper function to check if an event has p-tag or q-tag referencing the user + function eventReferencesUser(event: NostrEvent, userPubkey: string): boolean { + // Check p-tags + const pTags = event.tags.filter(t => t[0] === 'p' && t[1]); + for (const pTag of pTags) { + if (pTag[1] && pTag[1].toLowerCase() === userPubkey.toLowerCase()) { + return true; + } + } + + // Check q-tags + const qTags = event.tags.filter(t => t[0] === 'q' && t[1]); + for (const qTag of qTags) { + if (qTag[1] && qTag[1].toLowerCase() === userPubkey.toLowerCase()) { + return true; + } + } + + return false; + } + async function loadActivity() { if (!profileOwnerPubkeyHex || loadingActivity || activityLoaded) return; const userPubkey = profileOwnerPubkeyHex; // Store in local variable for type safety loadingActivity = true; try { - // Step 1: Fetch repo announcements in parallel (reduced limit) + // Step 1: Fetch all repo announcements where user is owner or maintainer const [repoAnnouncements, allAnnouncements] = await Promise.all([ nostrClient.fetchEvents([ { kinds: [KIND.REPO_ANNOUNCEMENT], authors: [userPubkey], - limit: 50 // Reduced from 100 + limit: 100 } ]), nostrClient.fetchEvents([ { kinds: [KIND.REPO_ANNOUNCEMENT], '#p': [userPubkey], - limit: 50 // Reduced from 100 + limit: 100 } ]) ]); - // Step 2: Extract a-tags from repo announcements - const aTags = new Set(); + // Step 2: Extract a-tags from repos where user is owner or maintainer + const repoATags = new Set(); + + // Add a-tags from repos owned by user for (const announcement of repoAnnouncements) { const dTag = announcement.tags.find(t => t[0] === 'd')?.[1]; if (dTag) { const aTag = `${KIND.REPO_ANNOUNCEMENT}:${announcement.pubkey}:${dTag}`; - aTags.add(aTag); + repoATags.add(aTag); } } - // Step 3: Check for repos where user is a maintainer + // Add a-tags from repos where user is a maintainer for (const announcement of allAnnouncements) { const maintainersTag = announcement.tags.find(t => t[0] === 'maintainers'); if (maintainersTag) { @@ -998,43 +1069,95 @@ i * const dTag = announcement.tags.find(t => t[0] === 'd')?.[1]; if (dTag) { const aTag = `${KIND.REPO_ANNOUNCEMENT}:${announcement.pubkey}:${dTag}`; - aTags.add(aTag); + repoATags.add(aTag); } } } } - // Step 4: Fetch events that reference the user or their repos (reduced limits) + // If user has no repos, return empty activity + if (repoATags.size === 0) { + activityEvents = []; + activityLoaded = true; + loadingActivity = false; + return; + } + + // Step 3: Define repo-related event kinds + const repoRelatedKinds = [ + KIND.REPO_ANNOUNCEMENT, + KIND.REPO_STATE, + KIND.PATCH, + KIND.PULL_REQUEST, + KIND.PULL_REQUEST_UPDATE, + KIND.ISSUE, + KIND.STATUS_OPEN, + KIND.STATUS_APPLIED, + KIND.STATUS_CLOSED, + KIND.STATUS_DRAFT, + KIND.COMMIT_SIGNATURE, + KIND.OWNERSHIP_TRANSFER, + KIND.RELEASE, + KIND.COMMENT, + KIND.THREAD, + KIND.BRANCH_PROTECTION, + KIND.HIGHLIGHT + ]; + + // Step 4: Fetch events that: + // - Have p-tags or q-tags referencing the user + // - AND reference repos where user is owner/maintainer (via a-tags) + // - AND are repo-related kinds const filters: any[] = []; - // Events with user in p-tag + // Events with user in p-tag AND repo a-tags AND repo-related kinds filters.push({ '#p': [userPubkey], - limit: 100 // Reduced from 200 + '#a': Array.from(repoATags), + kinds: repoRelatedKinds, + limit: 200 }); - // Events with user in q-tag + // Events with user in q-tag AND repo a-tags AND repo-related kinds filters.push({ '#q': [userPubkey], - limit: 100 // Reduced from 200 + '#a': Array.from(repoATags), + kinds: repoRelatedKinds, + limit: 200 }); - // Events with repo a-tags - if (aTags.size > 0) { - filters.push({ - '#a': Array.from(aTags), - limit: 100 // Reduced from 200 - }); - } - const allActivityEvents = await Promise.race([ nostrClient.fetchEvents(filters), new Promise((resolve) => setTimeout(() => resolve([]), 15000)) // 15s timeout ]); - // Step 5: Deduplicate, filter, and sort by created_at (newest first) + // Step 5: Additional filtering to ensure events: + // - Reference the user via p-tag or q-tag + // - Reference a repo the user owns/maintains via a-tag + // - Are repo-related kinds const eventMap = new Map(); for (const event of allActivityEvents) { + // Skip user's own events + if (event.pubkey === userPubkey) { + continue; + } + + // Must be repo-related + if (!isRepoRelatedEvent(event)) { + continue; + } + + // Must reference the user via p-tag or q-tag + if (!eventReferencesUser(event, userPubkey)) { + continue; + } + + // Must reference a repo the user owns/maintains + if (!eventReferencesRepo(event, repoATags)) { + continue; + } + + // Apply standard exclusions if (shouldExcludeEvent(event, userPubkey, true)) { continue; } @@ -1045,7 +1168,7 @@ i * } } - // Sort by created_at descending and limit to 50 (reduced from 200) + // Sort by created_at descending and limit to 50 activityEvents = Array.from(eventMap.values()) .sort((a, b) => b.created_at - a.created_at) .slice(0, 50);