跳至主要內容
進階

NIP-50 搜尋

深入了解 Nostr 的搜尋功能,使用 search 過濾器在中繼器上進行全文搜尋。

8 分鐘

什麼是 NIP-50?

NIP-50 定義了 Nostr 中繼器的搜尋功能,在 REQ 過濾器中加入 search 欄位, 讓客戶端可以進行全文搜尋。這是一個可選功能,需要中繼器支援(在 NIP-11 中宣告)。

注意: 不是所有中繼器都支援 NIP-50。在使用搜尋功能前, 先檢查中繼器的 NIP-11 資訊是否包含 50 在 supported_nips 中。

過濾器格式

// 基本搜尋
["REQ", "search-sub", {
  "search": "bitcoin lightning",
  "kinds": [1],
  "limit": 50
}]

// search 欄位會搜尋事件的 content
// 可以與其他過濾器組合使用

搜尋語法

語法 說明 範例
word 單詞匹配 bitcoin
word1 word2 AND(必須包含所有詞) bitcoin lightning
"phrase" 精確短語 "hello world"
-word 排除詞 bitcoin -scam

擴展語法

部分中繼器支援額外的搜尋修飾符:

// 搜尋特定語言
{
  "search": "bitcoin language:zh"
}

// 搜尋特定領域(由中繼器定義)
{
  "search": "bitcoin domain:finance"
}

// 搜尋特定情感(部分中繼器支援)
{
  "search": "bitcoin sentiment:positive"
}

// 注意:擴展語法由各中繼器自行定義
// 使用前請查閱中繼器文件

程式碼範例

基本搜尋

async function searchNotes(relay, query, limit = 50) {
  return new Promise((resolve) => {
    const results = []

    const sub = relay.subscribe([{
      kinds: [1],
      search: query,
      limit: limit
    }])

    sub.on('event', (event) => {
      results.push(event)
    })

    sub.on('eose', () => {
      sub.close()
      resolve(results)
    })
  })
}

// 使用
const notes = await searchNotes(relay, 'bitcoin lightning')
console.log(`找到 ${notes.length} 條結果`)

組合過濾器

// 搜尋特定用戶的內容
{
  "kinds": [1],
  "authors": ["pubkey1", "pubkey2"],
  "search": "bitcoin",
  "limit": 100
}

// 搜尋特定時間範圍
{
  "kinds": [1],
  "search": "announcement",
  "since": 1704067200,  // 2024-01-01
  "until": 1706745600,  // 2024-02-01
  "limit": 50
}

// 搜尋長文章
{
  "kinds": [30023],
  "search": "tutorial",
  "limit": 20
}

檢查中繼器支援

async function findSearchRelays(relayUrls) {
  const searchRelays = []

  for (const url of relayUrls) {
    const info = await getRelayInfo(url)
    if (info?.supported_nips?.includes(50)) {
      searchRelays.push({
        url,
        name: info.name || url
      })
    }
  }

  return searchRelays
}

// 只在支援搜尋的中繼器上搜尋
async function smartSearch(relayUrls, query) {
  const searchRelays = await findSearchRelays(relayUrls)

  if (searchRelays.length === 0) {
    throw new Error('沒有找到支援搜尋的中繼器')
  }

  console.log(`在 ${searchRelays.length} 個中繼器上搜尋`)

  // 並行搜尋所有中繼器
  const results = await Promise.all(
    searchRelays.map(r => searchNotes(r.url, query))
  )

  // 合併並去重
  return deduplicateEvents(results.flat())
}

支援搜尋的中繼器

relay.nostr.band

專業搜尋中繼器

全文搜尋、趨勢

nostr.wine

付費中繼器

搜尋、過濾

relay.snort.social

Snort 官方中繼器

基本搜尋

搜尋結果處理

// 搜尋結果可能包含相關性分數
// 這是中繼器特定的擴展

function sortByRelevance(events) {
  // 如果中繼器提供了排序,結果通常已按相關性排序
  // 否則可以按時間排序
  return events.sort((a, b) => b.created_at - a.created_at)
}

function highlightMatches(content, query) {
  const words = query.split(' ').filter(w => !w.startsWith('-'))

  let highlighted = content
  for (const word of words) {
    const regex = new RegExp(`(${word})`, 'gi')
    highlighted = highlighted.replace(regex, '<mark>$1</mark>')
  }

  return highlighted
}

// 顯示搜尋結果
function displayResults(events, query) {
  for (const event of events) {
    console.log('---')
    console.log('ID:', event.id.slice(0, 8))
    console.log('內容:', highlightMatches(event.content, query))
    console.log('時間:', new Date(event.created_at * 1000))
  }
}

限制與考量

技術限制

  • • 不是所有中繼器都支援
  • • 搜尋語法可能不同
  • • 索引可能不完整
  • • 結果數量有限制

隱私考量

  • • 搜尋詞會發送到中繼器
  • • 中繼器可能記錄搜尋
  • • 考慮使用多個中繼器分散
  • • 敏感搜尋要謹慎

與本地搜尋比較

特性 NIP-50 搜尋 本地搜尋
範圍 中繼器上所有事件 本地快取的事件
速度 取決於網路和中繼器 通常更快
隱私 搜尋詞會暴露 完全私密
可用性 需要中繼器支援 總是可用

最佳實踐: 結合使用 NIP-50 搜尋和本地搜尋。對於發現新內容使用中繼器搜尋, 對於已關注用戶的內容使用本地搜尋。

已複製連結
已複製到剪貼簿