import {Event} from 'nostr-tools'; import {getReplyTo, hasEventTag, isMention, isPTag} from './events'; import {config} from './settings'; import {sub, subOnce, unsubAll} from './relays'; type SubCallback = ( event: Event, relay: string, ) => void; export const subPubkeys = ( pubkeys: string[], onEvent: SubCallback, ) => { const authorsPrefixes = pubkeys.map(pubkey => pubkey.slice(0, 32)); console.info(`subscribe to homefeed ${authorsPrefixes}`); unsubAll(); const repliesTo = new Set(); sub({ cb: (evt, relay) => { if ( evt.tags.some(hasEventTag) && !evt.tags.some(isMention) ) { const note = getReplyTo(evt); // get all reply to events instead? if (note && !repliesTo.has(note)) { repliesTo.add(note); subOnce({ cb: onEvent, filter: { ids: [note], kinds: [1], limit: 1, }, relay, }); } } onEvent(evt, relay); }, filter: { authors: authorsPrefixes, kinds: [1], limit: 20, }, }); // get metadata sub({ cb: onEvent, filter: { authors: pubkeys, kinds: [0], limit: pubkeys.length, }, unsub: true, }); }; /** subscribe to global feed */ export const subGlobalFeed = (onEvent: SubCallback) => { console.info('subscribe to global feed'); unsubAll(); const now = Math.floor(Date.now() * 0.001); const pubkeys = new Set(); const notes = new Set(); const prefix = Math.floor(config.filterDifficulty / 4); // 4 bits in each '0' character sub({ // get past events cb: (evt, relay) => { pubkeys.add(evt.pubkey); notes.add(evt.id); onEvent(evt, relay); }, filter: { ...(prefix && {ids: ['0'.repeat(prefix)]}), kinds: [1], until: now, ...(!prefix && {since: Math.floor(now - (24 * 60 * 60))}), limit: 100, }, unsub: true }); setTimeout(() => { // get profile info sub({ cb: onEvent, filter: { authors: Array.from(pubkeys), kinds: [0], limit: pubkeys.size, }, unsub: true, }); pubkeys.clear(); // get reactions sub({ cb: onEvent, filter: { '#e': Array.from(notes), kinds: [7], until: now, since: Math.floor(now - (24 * 60 * 60)), }, unsub: true, }); notes.clear(); }, 2000); // subscribe to future notes, reactions and profile updates sub({ cb: (evt, relay) => { onEvent(evt, relay); if ( evt.kind !== 1 || pubkeys.has(evt.pubkey) ) { return; } subOnce({ // get profil data relay, cb: onEvent, filter: { authors: [evt.pubkey], kinds: [0], limit: 1, } }); }, filter: { ...(prefix && {ids: ['0'.repeat(prefix)]}), kinds: [0, 1, 7], since: now, }, }); }; /** subscribe to global feed */ // export const simpleSub24hFeed = (onEvent: SubCallback) => { // unsubAll(); // sub({ // cb: onEvent, // filter: { // kinds: [0, 1, 2, 7], // // until: Math.floor(Date.now() * 0.001), // since: Math.floor((Date.now() * 0.001) - (24 * 60 * 60)), // limit: 250, // } // }); // }; /** subscribe to a note id (nip-19) */ export const subNote = ( eventId: string, onEvent: SubCallback, ) => { unsubAll(); sub({ cb: onEvent, filter: { ids: [eventId], kinds: [1], limit: 1, }, unsub: true, }); const replies = new Set(); const onReply = (evt: Event, relay: string) => { replies.add(evt.id) onEvent(evt, relay); unsubAll(); sub({ cb: onEvent, filter: { '#e': Array.from(replies), kinds: [1, 7], }, unsub: true, }); }; replies.add(eventId); setTimeout(() => { sub({ cb: onReply, filter: { '#e': [eventId], kinds: [1, 7], }, unsub: true, // TODO: probably keep this subscription also after onReply/unsubAll }); }, 200); }; /** subscribe to npub key (nip-19) */ export const subProfile = ( pubkey: string, onEvent: SubCallback, ) => { console.info(`subscribe to profile ${pubkey}`); unsubAll(); sub({ cb: onEvent, filter: { authors: [pubkey], kinds: [0], limit: 1, }, }); const repliesTo = new Set(); // get notes for profile sub({ cb: (evt, relay) => { if ( evt.tags.some(hasEventTag) && !evt.tags.some(isMention) ) { const note = getReplyTo(evt); if (note && !repliesTo.has(note)) { repliesTo.add(note); subOnce({ relay, cb: onEvent, filter: { ids: [note], kinds: [1], limit: 1, } }); } } onEvent(evt, relay); }, filter: { authors: [pubkey], kinds: [1], limit: 50, } }); setTimeout(() => { // get contacts sub({ cb: onEvent, filter: { authors: [pubkey, config.pubkey], kinds: [3], limit: 6, }, }); }, 100); }; export const subEventID = ( id: string, onEvent: SubCallback, ) => { unsubAll(); sub({ cb: onEvent, filter: { ids: [id], limit: 1, }, unsub: true, }); sub({ cb: onEvent, filter: { authors: [id], limit: 200, }, unsub: true, }); }; export const subOwnContacts = (onEvent: SubCallback) => { sub({ cb: onEvent, filter: { authors: [config.pubkey], kinds: [3], limit: 1, }, unsub: true, }); }; export const subContactList = ( pubkey: string, onEvent: SubCallback, ) => { unsubAll(); const pubkeys = new Set(); let newestEvent = 0; sub({ cb: (evt: Event, relay: string) => { if (evt.created_at <= newestEvent) { return; } newestEvent = evt.created_at; const newPubkeys = evt.tags .filter(isPTag) .filter(([, p]) => !pubkeys.has(p)) .map(([, p]) => { pubkeys.add(p); return p }); subOnce({ cb: onEvent, filter: { authors: newPubkeys, kinds: [0], }, relay, }); onEvent(evt, relay); }, filter: { authors: [pubkey], kinds: [3], limit: 1, }, }); };