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.
 
 
 
 

116 lines
3.9 KiB

import { normalizeToDTag, parseAdvancedSearch } from '@/lib/search-parser'
import type { TSearchParams } from '@/types'
import { nip19 } from 'nostr-tools'
export const ALEXANDRIA_NEXT_EVENTS_BASE = 'https://next-alexandria.gitcitadel.eu/events'
const NIP05_STANDALONE = /^\s*[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\s*$/
/** `id` query for Alexandria: hex for note/nevent; full bech32 for naddr. */
function nip19ToAlexandriaIdValue(bech32Input: string): string | null {
try {
let id = bech32Input.trim()
if (id.startsWith('nostr:')) id = id.slice(6)
const decoded = nip19.decode(id)
if (decoded.type === 'note' || decoded.type === 'nevent') {
const d = decoded.data as string | { id: string }
const hex = typeof d === 'string' ? d : d.id
return hex ? hex.toLowerCase() : null
}
if (decoded.type === 'naddr') {
return id
}
return null
} catch {
return null
}
}
/**
* Maps a free-form “notes” / NIP-50 style query to Alexandria `/events` query params
* (`d`, `t`, `n`, `q`, `id`) per https://next-alexandria.gitcitadel.eu/events.
*/
export function buildAlexandriaEventsSearchUrlFromNotesQuery(query: string): string | null {
const q = query.trim()
if (!q) return null
const lower = q.toLowerCase()
if (lower.startsWith('d:')) {
const inner = q.slice(2).trim()
const d = normalizeToDTag(inner) || inner.toLowerCase().replace(/\s+/g, '-').replace(/^-+|-+$/g, '')
if (!d) return null
return `${ALEXANDRIA_NEXT_EVENTS_BASE}?d=${encodeURIComponent(d)}`
}
if (lower.startsWith('t:')) {
const inner = q.slice(2).trim().toLowerCase()
if (!inner) return null
return `${ALEXANDRIA_NEXT_EVENTS_BASE}?t=${encodeURIComponent(inner)}`
}
if (lower.startsWith('n:')) {
const inner = q.slice(2).trim()
if (!inner) return null
return `${ALEXANDRIA_NEXT_EVENTS_BASE}?n=${encodeURIComponent(inner)}`
}
if (q.startsWith('#')) {
const inner = q.slice(1).trim().toLowerCase()
if (!inner) return null
return `${ALEXANDRIA_NEXT_EVENTS_BASE}?t=${encodeURIComponent(inner)}`
}
if (/^[0-9a-f]{64}$/i.test(q)) {
return `${ALEXANDRIA_NEXT_EVENTS_BASE}?id=${encodeURIComponent(q.toLowerCase())}`
}
const nip19Id = nip19ToAlexandriaIdValue(q)
if (nip19Id) {
return `${ALEXANDRIA_NEXT_EVENTS_BASE}?id=${encodeURIComponent(nip19Id)}`
}
if (NIP05_STANDALONE.test(q)) {
return `${ALEXANDRIA_NEXT_EVENTS_BASE}?q=${encodeURIComponent(q.trim())}`
}
const adv = parseAdvancedSearch(q)
if (adv.hashtag) {
const raw = Array.isArray(adv.hashtag) ? adv.hashtag[0] : adv.hashtag
const tag = raw?.toString().trim().toLowerCase()
if (tag) return `${ALEXANDRIA_NEXT_EVENTS_BASE}?t=${encodeURIComponent(tag)}`
}
return `${ALEXANDRIA_NEXT_EVENTS_BASE}?q=${encodeURIComponent(q)}`
}
export function buildAlexandriaEventsSearchUrlForTSearchParams(params: TSearchParams): string | null {
if (params.type === 'hashtag') {
const tag = params.search?.trim().toLowerCase()
if (!tag) return null
return `${ALEXANDRIA_NEXT_EVENTS_BASE}?t=${encodeURIComponent(tag)}`
}
if (params.type === 'profiles') {
let n = params.search.trim()
if (n.toLowerCase().startsWith('n:')) n = n.slice(2).trim()
if (!n) return null
return `${ALEXANDRIA_NEXT_EVENTS_BASE}?n=${encodeURIComponent(n)}`
}
if (params.type === 'notes') {
return buildAlexandriaEventsSearchUrlFromNotesQuery(params.search)
}
return null
}
export function buildAlexandriaEventsUrlForHashtagParam(tag: string): string | null {
const t = tag.trim().toLowerCase()
if (!t) return null
return `${ALEXANDRIA_NEXT_EVENTS_BASE}?t=${encodeURIComponent(t)}`
}
export function buildAlexandriaEventsUrlForDTagParam(d: string): string | null {
const v = d.trim()
if (!v) return null
const normalized = normalizeToDTag(v) || v
return `${ALEXANDRIA_NEXT_EVENTS_BASE}?d=${encodeURIComponent(normalized)}`
}