1 changed files with 206 additions and 0 deletions
@ -0,0 +1,206 @@ |
|||||||
|
import type { NDKEvent } from "@nostr-dev-kit/ndk"; |
||||||
|
import { ndkInstance } from "../ndk"; |
||||||
|
import { get } from "svelte/store"; |
||||||
|
import { extractPubkeysFromEvents, batchFetchProfiles } from "./profileCache"; |
||||||
|
|
||||||
|
// Constants for publication event kinds
|
||||||
|
const INDEX_EVENT_KIND = 30040; |
||||||
|
const CONTENT_EVENT_KINDS = [30041, 30818]; |
||||||
|
|
||||||
|
/** |
||||||
|
* Interface for tag expansion fetch results |
||||||
|
*/ |
||||||
|
export interface TagExpansionResult { |
||||||
|
publications: NDKEvent[]; |
||||||
|
contentEvents: NDKEvent[]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Fetches publications and their content events from relays based on tags |
||||||
|
*
|
||||||
|
* This function handles the relay-based fetching portion of tag expansion: |
||||||
|
* 1. Fetches publication index events that have any of the specified tags |
||||||
|
* 2. Extracts content event references from those publications |
||||||
|
* 3. Fetches the referenced content events |
||||||
|
*
|
||||||
|
* @param tags Array of tags to search for in publications |
||||||
|
* @param existingEventIds Set of existing event IDs to avoid duplicates |
||||||
|
* @param baseEvents Array of base events to check for existing content |
||||||
|
* @param debug Optional debug function for logging |
||||||
|
* @returns Promise resolving to publications and content events |
||||||
|
*/ |
||||||
|
export async function fetchTaggedEventsFromRelays( |
||||||
|
tags: string[], |
||||||
|
existingEventIds: Set<string>, |
||||||
|
baseEvents: NDKEvent[], |
||||||
|
debug?: (...args: any[]) => void |
||||||
|
): Promise<TagExpansionResult> { |
||||||
|
const log = debug || console.debug; |
||||||
|
|
||||||
|
log("Fetching from relays for tags:", tags); |
||||||
|
|
||||||
|
// Fetch publications that have any of the specified tags
|
||||||
|
const ndk = get(ndkInstance); |
||||||
|
const taggedPublications = await ndk.fetchEvents({ |
||||||
|
kinds: [INDEX_EVENT_KIND], |
||||||
|
"#t": tags, // Match any of these tags
|
||||||
|
limit: 30 // Reasonable default limit
|
||||||
|
}); |
||||||
|
|
||||||
|
log("Found tagged publications from relays:", taggedPublications.size); |
||||||
|
|
||||||
|
// Filter to avoid duplicates
|
||||||
|
const newPublications = Array.from(taggedPublications).filter( |
||||||
|
(event: NDKEvent) => !existingEventIds.has(event.id) |
||||||
|
); |
||||||
|
|
||||||
|
// Extract content event d-tags from new publications
|
||||||
|
const contentEventDTags = new Set<string>(); |
||||||
|
const existingContentDTags = new Set( |
||||||
|
baseEvents |
||||||
|
.filter(e => e.kind !== undefined && CONTENT_EVENT_KINDS.includes(e.kind)) |
||||||
|
.map(e => e.tagValue("d")) |
||||||
|
.filter(d => d !== undefined) |
||||||
|
); |
||||||
|
|
||||||
|
newPublications.forEach((event: NDKEvent) => { |
||||||
|
const aTags = event.getMatchingTags("a"); |
||||||
|
aTags.forEach((tag: string[]) => { |
||||||
|
// Parse the 'a' tag identifier: kind:pubkey:d-tag
|
||||||
|
if (tag[1]) { |
||||||
|
const parts = tag[1].split(':'); |
||||||
|
if (parts.length >= 3) { |
||||||
|
const dTag = parts.slice(2).join(':'); // Handle d-tags with colons
|
||||||
|
if (!existingContentDTags.has(dTag)) { |
||||||
|
contentEventDTags.add(dTag); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
// Fetch the content events
|
||||||
|
let newContentEvents: NDKEvent[] = []; |
||||||
|
if (contentEventDTags.size > 0) { |
||||||
|
const contentEventsSet = await ndk.fetchEvents({ |
||||||
|
kinds: CONTENT_EVENT_KINDS, |
||||||
|
"#d": Array.from(contentEventDTags), // Use d-tag filter
|
||||||
|
}); |
||||||
|
newContentEvents = Array.from(contentEventsSet); |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
publications: newPublications, |
||||||
|
contentEvents: newContentEvents |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Searches through already fetched events for publications with specified tags |
||||||
|
*
|
||||||
|
* This function handles the local search portion of tag expansion: |
||||||
|
* 1. Searches through existing events for publications with matching tags |
||||||
|
* 2. Extracts content event references from those publications |
||||||
|
* 3. Finds the referenced content events in existing events |
||||||
|
*
|
||||||
|
* @param allEvents Array of all fetched events to search through |
||||||
|
* @param tags Array of tags to search for in publications |
||||||
|
* @param existingEventIds Set of existing event IDs to avoid duplicates |
||||||
|
* @param baseEvents Array of base events to check for existing content |
||||||
|
* @param debug Optional debug function for logging |
||||||
|
* @returns Promise resolving to publications and content events |
||||||
|
*/ |
||||||
|
export function findTaggedEventsInFetched( |
||||||
|
allEvents: NDKEvent[], |
||||||
|
tags: string[], |
||||||
|
existingEventIds: Set<string>, |
||||||
|
baseEvents: NDKEvent[], |
||||||
|
debug?: (...args: any[]) => void |
||||||
|
): TagExpansionResult { |
||||||
|
const log = debug || console.debug; |
||||||
|
|
||||||
|
log("Searching through already fetched events for tags:", tags); |
||||||
|
|
||||||
|
// Find publications in allEvents that have the specified tags
|
||||||
|
const taggedPublications = allEvents.filter(event => { |
||||||
|
if (event.kind !== INDEX_EVENT_KIND) return false; |
||||||
|
if (existingEventIds.has(event.id)) return false; // Skip base events
|
||||||
|
|
||||||
|
// Check if event has any of the specified tags
|
||||||
|
const eventTags = event.getMatchingTags("t").map(tag => tag[1]); |
||||||
|
return tags.some(tag => eventTags.includes(tag)); |
||||||
|
}); |
||||||
|
|
||||||
|
const newPublications = taggedPublications; |
||||||
|
log("Found", newPublications.length, "publications in fetched events"); |
||||||
|
|
||||||
|
// For content events, also search in allEvents
|
||||||
|
const existingContentDTags = new Set( |
||||||
|
baseEvents |
||||||
|
.filter(e => e.kind !== undefined && CONTENT_EVENT_KINDS.includes(e.kind)) |
||||||
|
.map(e => e.tagValue("d")) |
||||||
|
.filter(d => d !== undefined) |
||||||
|
); |
||||||
|
|
||||||
|
const contentEventDTags = new Set<string>(); |
||||||
|
newPublications.forEach((event: NDKEvent) => { |
||||||
|
const aTags = event.getMatchingTags("a"); |
||||||
|
aTags.forEach((tag: string[]) => { |
||||||
|
// Parse the 'a' tag identifier: kind:pubkey:d-tag
|
||||||
|
if (tag[1]) { |
||||||
|
const parts = tag[1].split(':'); |
||||||
|
if (parts.length >= 3) { |
||||||
|
const dTag = parts.slice(2).join(':'); // Handle d-tags with colons
|
||||||
|
if (!existingContentDTags.has(dTag)) { |
||||||
|
contentEventDTags.add(dTag); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
// Find content events in allEvents
|
||||||
|
const newContentEvents = allEvents.filter(event => { |
||||||
|
if (!CONTENT_EVENT_KINDS.includes(event.kind || 0)) return false; |
||||||
|
const dTag = event.tagValue("d"); |
||||||
|
return dTag !== undefined && contentEventDTags.has(dTag); |
||||||
|
}); |
||||||
|
|
||||||
|
return { |
||||||
|
publications: newPublications, |
||||||
|
contentEvents: newContentEvents |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Fetches profiles for new events and updates progress |
||||||
|
*
|
||||||
|
* @param newPublications Array of new publication events |
||||||
|
* @param newContentEvents Array of new content events |
||||||
|
* @param onProgressUpdate Callback to update progress state |
||||||
|
* @param debug Optional debug function for logging |
||||||
|
* @returns Promise that resolves when profile fetching is complete |
||||||
|
*/ |
||||||
|
export async function fetchProfilesForNewEvents( |
||||||
|
newPublications: NDKEvent[], |
||||||
|
newContentEvents: NDKEvent[], |
||||||
|
onProgressUpdate: (progress: { current: number; total: number } | null) => void, |
||||||
|
debug?: (...args: any[]) => void |
||||||
|
): Promise<void> { |
||||||
|
const log = debug || console.debug; |
||||||
|
|
||||||
|
// Extract pubkeys from new events
|
||||||
|
const newPubkeys = extractPubkeysFromEvents([...newPublications, ...newContentEvents]); |
||||||
|
|
||||||
|
if (newPubkeys.size > 0) { |
||||||
|
log("Fetching profiles for", newPubkeys.size, "new pubkeys from tag expansion"); |
||||||
|
|
||||||
|
onProgressUpdate({ current: 0, total: newPubkeys.size }); |
||||||
|
|
||||||
|
await batchFetchProfiles(Array.from(newPubkeys), (fetched, total) => { |
||||||
|
onProgressUpdate({ current: fetched, total }); |
||||||
|
}); |
||||||
|
|
||||||
|
onProgressUpdate(null); |
||||||
|
} |
||||||
|
}
|
||||||
Loading…
Reference in new issue