feat(nostr): NIP-50 search via relay.nostr.band and nostr.wine (M11.7)

Search input sends REQ with search field to NIP-50 supporting relays.
Results shown as note cards with author, content, timestamp. Filter
by content type. NIP-50 button triggers relay search on Enter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-04 00:25:24 +00:00
co-authored by Claude Opus 4.6
parent 49c9f71aa2
commit ffc6524286
2 changed files with 122 additions and 7 deletions
+82
View File
@@ -282,6 +282,85 @@ function importNIP65Relays(event: NostrEvent) {
}
}
const NIP50_RELAYS = [
'wss://relay.nostr.band',
'wss://nostr.wine',
]
const searchResults = shallowRef<NostrNote[]>([])
const isSearching = ref(false)
function searchNostr(query: string, kinds?: number[]): void {
if (!query.trim()) {
searchResults.value = []
return
}
isSearching.value = true
searchResults.value = []
const subId = generateSubId()
const filter: Record<string, unknown> = { search: query, limit: 50 }
if (kinds && kinds.length > 0) filter.kinds = kinds
const results: NostrNote[] = []
let closedCount = 0
for (const relayUrl of NIP50_RELAYS) {
try {
const ws = new WebSocket(relayUrl)
const timer = setTimeout(() => {
ws.close()
}, 8000)
ws.onopen = () => {
ws.send(JSON.stringify(['REQ', subId, filter]))
}
ws.onmessage = (msg) => {
try {
const data = JSON.parse(msg.data)
if (Array.isArray(data) && data[0] === 'EVENT' && data[1] === subId && data[2]) {
const evt = data[2] as NostrEvent
if (!results.find(r => r.id === evt.id)) {
results.push({
id: evt.id,
pubkey: evt.pubkey,
authorName: truncatePubkey(evt.pubkey),
kind: evt.kind,
content: evt.content,
created_at: evt.created_at,
tags: evt.tags ?? [],
})
searchResults.value = [...results].sort((a, b) => b.created_at - a.created_at)
}
}
if (Array.isArray(data) && data[0] === 'EOSE' && data[1] === subId) {
clearTimeout(timer)
ws.close()
}
} catch { /* skip */ }
}
ws.onclose = () => {
closedCount++
if (closedCount >= NIP50_RELAYS.length) {
isSearching.value = false
}
}
ws.onerror = () => {
clearTimeout(timer)
}
} catch {
closedCount++
if (closedCount >= NIP50_RELAYS.length) {
isSearching.value = false
}
}
}
}
export interface PublishResult {
url: string
success: boolean
@@ -388,6 +467,9 @@ export function useNostr() {
disconnect,
fetchNote,
publishEvent,
searchResults,
isSearching,
searchNostr,
addRelay,
removeRelay,
toggleRelayRead,