clone of repo on github
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

397 lines
12 KiB

import { ndkInstance } from "$lib/ndk";
import { getUserMetadata, getNpubFromNip05 } from "$lib/utils/nostrUtils";
import { NDKRelaySet, NDKEvent } from "@nostr-dev-kit/ndk";
import { searchCache } from "$lib/utils/searchCache";
import { standardRelays, fallbackRelays } from "$lib/consts";
import { get } from "svelte/store";
import type { NostrProfile, ProfileSearchResult } from "./search_types";
import {
fieldMatches,
nip05Matches,
normalizeSearchTerm,
COMMON_DOMAINS,
createProfileFromEvent,
} from "./search_utils";
import { checkCommunityStatus } from "./community_checker";
import { TIMEOUTS } from "./search_constants";
/**
* Search for profiles by various criteria (display name, name, NIP-05, npub)
*/
export async function searchProfiles(
searchTerm: string,
): Promise<ProfileSearchResult> {
const normalizedSearchTerm = normalizeSearchTerm(searchTerm);
console.log(
"searchProfiles called with:",
searchTerm,
"normalized:",
normalizedSearchTerm,
);
// Check cache first
const cachedResult = searchCache.get("profile", normalizedSearchTerm);
if (cachedResult) {
console.log("Found cached result for:", normalizedSearchTerm);
const profiles = cachedResult.events
.map((event) => {
try {
const profileData = JSON.parse(event.content);
return createProfileFromEvent(event, profileData);
} catch {
return null;
}
})
.filter(Boolean) as NostrProfile[];
console.log("Cached profiles found:", profiles.length);
return { profiles, Status: {} };
}
const ndk = get(ndkInstance);
if (!ndk) {
console.error("NDK not initialized");
throw new Error("NDK not initialized");
}
console.log("NDK initialized, starting search logic");
let foundProfiles: NostrProfile[] = [];
try {
// Check if it's a valid npub/nprofile first
if (
normalizedSearchTerm.startsWith("npub") ||
normalizedSearchTerm.startsWith("nprofile")
) {
try {
const metadata = await getUserMetadata(normalizedSearchTerm);
if (metadata) {
foundProfiles = [metadata];
}
} catch (error) {
console.error("Error fetching metadata for npub:", error);
}
} else if (normalizedSearchTerm.includes("@")) {
// Check if it's a NIP-05 address - normalize it properly
const normalizedNip05 = normalizedSearchTerm.toLowerCase();
try {
const npub = await getNpubFromNip05(normalizedNip05);
if (npub) {
const metadata = await getUserMetadata(npub);
const profile: NostrProfile = {
...metadata,
pubkey: npub,
};
foundProfiles = [profile];
}
} catch (e) {
console.error("[Search] NIP-05 lookup failed:", e);
}
} else {
// Try NIP-05 search first (faster than relay search)
console.log("Starting NIP-05 search for:", normalizedSearchTerm);
foundProfiles = await searchNip05Domains(normalizedSearchTerm, ndk);
console.log(
"NIP-05 search completed, found:",
foundProfiles.length,
"profiles",
);
// If no NIP-05 results, try quick relay search
if (foundProfiles.length === 0) {
console.log("No NIP-05 results, trying quick relay search");
foundProfiles = await quickRelaySearch(normalizedSearchTerm, ndk);
console.log(
"Quick relay search completed, found:",
foundProfiles.length,
"profiles",
);
}
}
// Cache the results
if (foundProfiles.length > 0) {
const events = foundProfiles.map((profile) => {
const event = new NDKEvent(ndk);
event.content = JSON.stringify(profile);
event.pubkey = profile.pubkey || "";
return event;
});
const result = {
events,
secondOrder: [],
tTagEvents: [],
eventIds: new Set<string>(),
addresses: new Set<string>(),
searchType: "profile",
searchTerm: normalizedSearchTerm,
};
searchCache.set("profile", normalizedSearchTerm, result);
}
console.log("Search completed, found profiles:", foundProfiles.length);
return { profiles: foundProfiles, Status: {} };
} catch (error) {
console.error("Error searching profiles:", error);
return { profiles: [], Status: {} };
}
}
/**
* Search for NIP-05 addresses across common domains
*/
async function searchNip05Domains(
searchTerm: string,
ndk: any,
): Promise<NostrProfile[]> {
const foundProfiles: NostrProfile[] = [];
// Enhanced list of common domains for NIP-05 lookups
// Prioritize gitcitadel.com since we know it has profiles
const commonDomains = [
"gitcitadel.com", // Prioritize this domain
"theforest.nostr1.com",
"nostr1.com",
"nostr.land",
"sovbit.host",
"damus.io",
"snort.social",
"iris.to",
"coracle.social",
"nostr.band",
"nostr.wine",
"purplepag.es",
"relay.noswhere.com",
"aggr.nostr.land",
"nostr.sovbit.host",
"freelay.sovbit.host",
"nostr21.com",
"greensoul.space",
"relay.damus.io",
"relay.nostr.band",
];
// Normalize the search term for NIP-05 lookup
const normalizedSearchTerm = searchTerm.toLowerCase().trim();
console.log("NIP-05 search: normalized search term:", normalizedSearchTerm);
// Try gitcitadel.com first with extra debugging
const gitcitadelAddress = `${normalizedSearchTerm}@gitcitadel.com`;
console.log("NIP-05 search: trying gitcitadel.com first:", gitcitadelAddress);
try {
const npub = await getNpubFromNip05(gitcitadelAddress);
if (npub) {
console.log(
"NIP-05 search: SUCCESS! found npub for gitcitadel.com:",
npub,
);
const metadata = await getUserMetadata(npub);
const profile: NostrProfile = {
...metadata,
pubkey: npub,
};
console.log(
"NIP-05 search: created profile for gitcitadel.com:",
profile,
);
foundProfiles.push(profile);
return foundProfiles; // Return immediately if we found it on gitcitadel.com
} else {
console.log("NIP-05 search: no npub found for gitcitadel.com");
}
} catch (e) {
console.log("NIP-05 search: error for gitcitadel.com:", e);
}
// If gitcitadel.com didn't work, try other domains
console.log("NIP-05 search: gitcitadel.com failed, trying other domains...");
const otherDomains = commonDomains.filter(
(domain) => domain !== "gitcitadel.com",
);
// Search all other domains in parallel with timeout
const searchPromises = otherDomains.map(async (domain) => {
const nip05Address = `${normalizedSearchTerm}@${domain}`;
console.log("NIP-05 search: trying address:", nip05Address);
try {
const npub = await getNpubFromNip05(nip05Address);
if (npub) {
console.log("NIP-05 search: found npub for", nip05Address, ":", npub);
const metadata = await getUserMetadata(npub);
const profile: NostrProfile = {
...metadata,
pubkey: npub,
};
console.log(
"NIP-05 search: created profile for",
nip05Address,
":",
profile,
);
return profile;
} else {
console.log("NIP-05 search: no npub found for", nip05Address);
}
} catch (e) {
console.log("NIP-05 search: error for", nip05Address, ":", e);
// Continue to next domain
}
return null;
});
// Wait for all searches with timeout
const results = await Promise.allSettled(searchPromises);
for (const result of results) {
if (result.status === "fulfilled" && result.value) {
foundProfiles.push(result.value);
}
}
console.log("NIP-05 search: total profiles found:", foundProfiles.length);
return foundProfiles;
}
/**
* Quick relay search with short timeout
*/
async function quickRelaySearch(
searchTerm: string,
ndk: any,
): Promise<NostrProfile[]> {
console.log("quickRelaySearch called with:", searchTerm);
const foundProfiles: NostrProfile[] = [];
// Normalize the search term for relay search
const normalizedSearchTerm = normalizeSearchTerm(searchTerm);
console.log("Normalized search term for relay search:", normalizedSearchTerm);
// Use all profile relays for better coverage
const quickRelayUrls = [...standardRelays, ...fallbackRelays]; // Use all available relays
console.log("Using all relays for search:", quickRelayUrls);
// Create relay sets for parallel search
const relaySets = quickRelayUrls
.map((url) => {
try {
return NDKRelaySet.fromRelayUrls([url], ndk);
} catch (e) {
console.warn(`Failed to create relay set for ${url}:`, e);
return null;
}
})
.filter(Boolean);
// Search all relays in parallel with short timeout
const searchPromises = relaySets.map(async (relaySet, index) => {
if (!relaySet) return [];
return new Promise<NostrProfile[]>((resolve) => {
const foundInRelay: NostrProfile[] = [];
let eventCount = 0;
console.log(
`Starting search on relay ${index + 1}: ${quickRelayUrls[index]}`,
);
const sub = ndk.subscribe(
{ kinds: [0] },
{ closeOnEose: true, relaySet },
);
sub.on("event", (event: NDKEvent) => {
eventCount++;
try {
if (!event.content) return;
const profileData = JSON.parse(event.content);
const displayName =
profileData.displayName || profileData.display_name || "";
const display_name = profileData.display_name || "";
const name = profileData.name || "";
const nip05 = profileData.nip05 || "";
const about = profileData.about || "";
// Check if any field matches the search term using normalized comparison
const matchesDisplayName = fieldMatches(
displayName,
normalizedSearchTerm,
);
const matchesDisplay_name = fieldMatches(
display_name,
normalizedSearchTerm,
);
const matchesName = fieldMatches(name, normalizedSearchTerm);
const matchesNip05 = nip05Matches(nip05, normalizedSearchTerm);
const matchesAbout = fieldMatches(about, normalizedSearchTerm);
if (
matchesDisplayName ||
matchesDisplay_name ||
matchesName ||
matchesNip05 ||
matchesAbout
) {
console.log(`Found matching profile on relay ${index + 1}:`, {
name: profileData.name,
display_name: profileData.display_name,
nip05: profileData.nip05,
pubkey: event.pubkey,
searchTerm: normalizedSearchTerm,
});
const profile = createProfileFromEvent(event, profileData);
// Check if we already have this profile in this relay
const existingIndex = foundInRelay.findIndex(
(p) => p.pubkey === event.pubkey,
);
if (existingIndex === -1) {
foundInRelay.push(profile);
}
}
} catch (e) {
// Invalid JSON or other error, skip
}
});
sub.on("eose", () => {
console.log(
`Relay ${index + 1} (${quickRelayUrls[index]}) search completed, processed ${eventCount} events, found ${foundInRelay.length} matches`,
);
resolve(foundInRelay);
});
// Short timeout for quick search
setTimeout(() => {
console.log(
`Relay ${index + 1} (${quickRelayUrls[index]}) search timed out after 1.5s, processed ${eventCount} events, found ${foundInRelay.length} matches`,
);
sub.stop();
resolve(foundInRelay);
}, 1500); // 1.5 second timeout per relay
});
});
// Wait for all searches to complete
const results = await Promise.allSettled(searchPromises);
// Combine and deduplicate results
const allProfiles: Record<string, NostrProfile> = {};
for (const result of results) {
if (result.status === "fulfilled") {
for (const profile of result.value) {
if (profile.pubkey) {
allProfiles[profile.pubkey] = profile;
}
}
}
}
console.log(
`Total unique profiles found: ${Object.keys(allProfiles).length}`,
);
return Object.values(allProfiles);
}