Los relays no te dejan curiosear. Les das una lista de deseos ¡y te traen exactamente eso!
El bibliotecario más paciente del mundo
Imagina una biblioteca sin estanterías por las que pasear. Solo hay un mostrador y un bibliotecario:
el relayRelay: Un servidor que acepta eventos de los clientes, los guarda y los envía a quien se suscriba con un filtro que coincida.. No puedes andar curioseando. En su lugar, rellenas una
ficha de pedido y se la entregas: “notas de Alice, por favor” o “todo lo etiquetado #nostr desde
ayer”. El bibliotecario va a la trastienda, reúne todo lo que coincide y lo trae al mostrador,
empezando por lo más nuevo.
Esa ficha es un filtroFiltro: Un objeto JSON que describe qué eventos quieres: por ids, authors, kinds, tags (#e, #p…), since, until y limit.. Es la única forma en que un
clienteCliente: La app que usas (web, móvil o de escritorio) para crear, firmar, publicar y leer eventos de Nostr. le pide algo a un relay. Tu timeline, tus
notificaciones, una página de perfil, un hilo de respuestas: por debajo, cada pantalla de cada app
de Nostr no es más que un puñado de filtros.
Un filtro es un objeto JSON diminuto. Cada clave es una pregunta sobre un eventoEvento: El único tipo de dato de Nostr: un objeto JSON firmado con id, pubkey, created_at, kind, tags, content y sig.:
authors: ¿quién lo firmó? (una lista de claves públicasClave pública: Tu identidad pública: una clave secp256k1 de 32 bytes, mostrada como 64 caracteres hex o como npub. Puedes compartirla sin problema.)
kinds: ¿qué tipo de cosa es? (1 = nota, 7 = reacción, 0 = perfil…)
ids: ¿es uno de estos eventos exactos? (por id de eventoId de evento: El hash SHA-256 de los campos serializados de un evento, escrito como 64 caracteres hex en minúsculas. Nombra exactamente un evento.)
#e, #p, #t: ¿lleva una etiquetaTag (etiqueta): Un array de strings adjunto a un evento, como ["p", <pubkey>] para mencionar a alguien o ["e", <id de evento>] para responder a una nota. que apunta a este evento, menciona a esta persona o usa este hashtag?
since, until: ¿se creó dentro de esta ventana de tiempo?
limit: ¿cuántos de los más nuevos quiero ahora mismo?
Constrúyelo tú
Aquí abajo está la estantería completa de nuestro relay de ejemplo: 60 eventos reales y firmados de
Alice, Bob, Carol y compañía. Todas las tarjetas empiezan encendidas, porque un filtro vacío
coincide con todo. Empieza a añadir condiciones y mira cómo se apagan las tarjetas que no encajan.
Haz clic en cualquier tarjeta para preguntar “¿por qué esta está dentro (o fuera)?”
Constructor de filtros
Activa los interruptores y mira la estantería del relay: los eventos que coinciden se iluminan y el resto se apaga.
Un filtro vacío: ¡dame todo lo que tengas! 60 de 60 eventos coinciden
El filtro, en JSON
{}
…envuelto en un mensaje REQ
json
["REQ","ch05",{}]
60 de 60 eventos coinciden
Elige cualquier tarjeta para ver qué condiciones cumple.
▸⚙Bajo el capó: el REQ en el cableMostrar detalles
El JSON del constructor viaja por el WebSocketWebSocket: Una conexión bidireccional de larga duración entre navegador y servidor. Clientes y relays intercambian mensajes de Nostr por ella como arrays JSON. envuelto en un
mensaje REQREQ: El mensaje del cliente que pide eventos a un relay: ["REQ", <id de suscripción>, <filtro>, …]., junto con un id de suscripción que eliges tú:
El relay responde con un ["EVENT", "alice-notes", {…}] por cada coincidencia guardada y luego
["EOSE", "alice-notes"] (fin de los eventos guardadosEOSE: "End of stored events" (fin de los eventos guardados): la señal del relay de que ya envió todo lo que tenía guardado para una suscripción; lo que llegue después es en vivo.). La suscripciónSuscripción: Una consulta permanente en un relay, abierta con REQ y un id de suscripción: primero llegan las coincidencias guardadas y luego los eventos nuevos en tiempo real. sigue abierta: los nuevos eventos que coincidan
siguen llegando hasta que envíes ["CLOSE", "alice-notes"]. Con nostr-tools:
import { SimplePool } from "nostr-tools/pool";import { matchFilter } from "nostr-tools/filter";const pool = new SimplePool();const sub = pool.subscribeMany( ["wss://relay.damus.io", "wss://nos.lol"], { authors: [alicePubkey], kinds: [1], limit: 20 }, { onevent: (event) => console.log(event.content), oneose: () => console.log("eventos guardados listos, ahora en directo"), },);// más tarde: sub.close();matchFilter({ kinds: [1], "#t": ["nostr"] }, someEvent); // la misma comprobación, en local
Las reglas para combinar condiciones caben en una nota adhesiva:
Dentro de un campo, es O."authors": [alice, bob] significa de Alice o de Bob.
"kinds": [1, 7] significa una nota o una reacción.
Entre campos, es Y.{"authors": [alice], "kinds": [1]} significa de Alice y que sea una nota.
Cada condición que añades solo puede hacer el resultado más pequeño. Basta con que falle una
condición para dejar fuera un evento (eso es lo que muestran las marcas ✓ y ✗ del panel “¿por qué?”).
Varios filtros en un REQ se combinan con O. ¿Quieres las notas de Alice más las reacciones
de todo el mundo a ellas? Manda dos fichas en el mismo sobre: ["REQ", "sub", {filtro A}, {filtro B}].
★
Campo ausente = me da igual
Omitir un campo significa “cualquier valor vale”. Por eso {} devuelve la estantería entera, y por
eso nunca se envía una lista vacía como "kinds": []: una lista sin nada dentro nunca puede coincidir.
Etiquetas: siguiendo los hilos
Los eventos se apuntan unos a otros mediante etiquetas, y los filtros pueden seguir esos punteros
hacia atrás. El truco de NIP-01NIP-01: La especificación central: estructura de eventos, ids y firmas, rangos de kinds, filtros y los mensajes entre cliente y relay. es el prefijo #: "#x": [...]
coincide con los eventos que tienen una etiqueta llamada x cuyo primer valor está en la lista.
#e: todo lo que apunta a un evento. Las respuestas, reaccionesReacción: Un evento kind 7 que reacciona a otro evento: "+" significa me gusta, "-" no me gusta, o cualquier emoji.,
reposts y zaps llevan una etiqueta e, así que {"#e": [threadId]} reúne una conversación entera.
#p: todo lo que menciona a una persona. Ahí tienes tu pestaña de notificaciones en una línea.
#t: hashtags. Los clientes ponen #nostr en el texto y una etiqueta ["t", "nostr"], porque
los relays solo indexan etiquetas, nunca el contenido.
Prueba la receta “El hilo de Alice” en el constructor y luego añade el kind 7: acabas de construir
un contador de likes.
Ventanas de tiempo y límites
since y until son marcas de tiempo Unix (segundos desde el 1 de enero de 1970), igual que el
created_at de un evento. Un evento coincide cuando since <= created_at <= until, con ambos
extremos incluidos. Las apps los usan para los botones de “cargar publicaciones anteriores”
(until = lo más antiguo que hay en pantalla) y para ponerse al día (since = la última vez que miraste).
limit es distinto: no es una condición, es una petición sobre cuántos. El relay ordena las
coincidencias de la más nueva a la más vieja y envía como mucho esa cantidad en el primer lote. En el
constructor, las coincidencias que se quedaron fuera llevan un borde discontinuo y un ✂.
▸⚙Bajo el capó: las reglas de filtros de NIP-01Mostrar detalles
ids, authors, #e y #p deben contener valores hex en minúsculas de exactamente 64
caracteres. (Los relays antiguos aceptaban prefijos; eso se eliminó, así que no cuentes con ello.)
El constructor convierte npub1…, note1…, nprofile1… y nevent1… a hex por ti, porque las
cadenas NIP-19 nunca viajan por el cable.
Solo se indexan las etiquetas de una sola letra (a–z, A–Z), y solo por su primer valor.
#d encuentra eventos direccionables por su identificador, #a sigue direcciones y #k encuentra
reacciones según el kind de lo que reaccionan.
limit solo se aplica a la consulta inicial y se ignora para los eventos que llegan después de
EOSE. Los relays DEBERÍAN devolver los limit eventos más nuevos, desempatando por el id más bajo
cuando coincide el created_at, y pueden devolver menos. Cada relay lo aplica por su cuenta, así que
pedir "limit": 10 a tres relays puede traerte hasta 30 eventos (menos los duplicados, que tu
cliente fusiona por id).
Los relays pueden limitar lo que devuelven, rechazar filtros costosos o responder con
["CLOSED", subId, "motivo"].
Extensiones relacionadas: NIP-45 añade
["COUNT", subId, filter] para preguntar cuántos sin descargar nada, y
NIP-50 añade un campo search para
búsquedas de texto completo en los relays que lo soportan.
Misiones de filtros
¿Listo para juntarlo todo? Cada misión describe un conjunto de eventos con palabras sencillas.
Construye cualquier filtro cuyos resultados sean exactamente ese conjunto. ¡Casi siempre hay más
de una respuesta correcta!
Constructor de filtros
Activa los interruptores y mira la estantería del relay: los eventos que coinciden se iluminan y el resto se apaga.
Construye un filtro cuyos resultados sean exactamente lo que pide cada misión. ¡Vale cualquier filtro que lo consiga!
☆AplausómetroEncuentra todas las reacciones (kind 7) al hilo de Alice sobre relays, y nada más.
☆A la caza de opiniones picantesEncuentra las notas cortas de Bob etiquetadas #nostr.
☆Caras nuevasObtén solo los 3 perfiles más nuevos (kind 0).
☆NocheviejaTodo lo que Alice firmó desde el 31 dic 2024, 00:00 UTC en adelante.
0 de 4 misiones resueltas
Quién firmó el evento (clave pública en hex o npub1…).
Qué tipo de evento: 1 = nota, 7 = reacción, 0 = perfil…
Hashtags (etiquetas t), en minúsculas y sin el #.
Eventos con una etiqueta p que menciona a esta persona.
Eventos con una etiqueta e que apunta a este evento: respuestas, reacciones, reposts.
Ids exactos de eventos (hex o note1…). Truco: elige una tarjeta y fija su id.
Un filtro vacío: ¡dame todo lo que tengas! 60 de 60 eventos coinciden
El filtro, en JSON
{}
…envuelto en un mensaje REQ
json
["REQ","ch05",{}]
60 de 60 eventos coinciden
Elige cualquier tarjeta para ver qué condiciones cumple.
i
Ve más allá
El laboratorio de filtros
tiene el mismo constructor más un editor de JSON, y puede enviar tu filtro como un REQ real. Activa
el modo en vivo en la cabecera y consultará relays públicos reales (solo lectura; nunca publicamos nada).
Comprobación rápida
Elige una respuesta
Elige una respuesta
Elige una respuesta
Lo esencial
Un filtro es un pequeño objeto JSON que describe los eventos que quieres; se lo envías a un relay dentro de un mensaje REQ.
Dentro de un mismo campo, los valores se combinan con O (cualquiera de estos autores). Entre campos, las condiciones se combinan con Y (este autor Y este kind).
Varios filtros en el mismo REQ se combinan con O: el relay envía los eventos que cumplan cualquiera de ellos.
Los filtros de etiquetas como #e, #p y #t siguen hilos, menciones y hashtags; since, until y limit recortan por tiempo.
limit solo da forma al primer lote, de lo más nuevo a lo más viejo; después de EOSE la suscripción sigue abierta para eventos nuevos.