diff --git a/README.md b/README.md index 808902b..6952a76 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,26 @@ let relaysForEvent = pool.seenOn( // relaysForEvent will be an array of URLs from relays a given event was seen on ``` +### Parsing references (mentions) from a content using NIP-10 and NIP-27 + +```js +import {parseReferences} from 'nostr-tools' + +let references = parseReferences(event) +let simpleAugmentedContent = event.content +for (let i = 0; i < references.length; i++) { + let {text, profile, event, address} = references[i] + let augmentedReference = profile + ? `@${profilesCache[profile.pubkey].name}` + : event + ? `${eventsCache[event.id].content.slice(0, 5)}` + : address + ? `[link]` + : text + simpleAugmentedContent.replaceAll(text, augmentedReference) +} +``` + ### Querying profile data from a NIP-05 address ```js diff --git a/index.ts b/index.ts index ac782a3..561a818 100644 --- a/index.ts +++ b/index.ts @@ -3,6 +3,7 @@ export * from './relay' export * from './event' export * from './filter' export * from './pool' +export * from './references' export * as nip04 from './nip04' export * as nip05 from './nip05' diff --git a/references.test.js b/references.test.js new file mode 100644 index 0000000..e07eedc --- /dev/null +++ b/references.test.js @@ -0,0 +1,62 @@ +/* eslint-env jest */ + +const {parseReferences} = require('./lib/nostr.cjs') + +test('parse mentions', () => { + let evt = { + tags: [ + [ + 'p', + 'c9d556c6d3978d112d30616d0d20aaa81410e3653911dd67787b5aaf9b36ade8', + 'wss://nostr.com' + ], + [ + 'e', + 'a84c5de86efc2ec2cff7bad077c4171e09146b633b7ad117fffe088d9579ac33', + 'wss://other.com', + 'reply' + ], + [ + 'e', + '31d7c2875b5fc8e6f9c8f9dc1f84de1b6b91d1947ea4c59225e55c325d330fa8', + '' + ] + ], + content: + 'hello #[0], have you seen #[2]? it was made by nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg on nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4! broken #[3]' + } + + expect(parseReferences(evt)).toEqual([ + { + text: '#[0]', + profile: { + pubkey: + 'c9d556c6d3978d112d30616d0d20aaa81410e3653911dd67787b5aaf9b36ade8', + relays: ['wss://nostr.com'] + } + }, + { + text: '#[2]', + event: { + id: '31d7c2875b5fc8e6f9c8f9dc1f84de1b6b91d1947ea4c59225e55c325d330fa8', + relays: [] + } + }, + { + text: 'nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg', + profile: { + pubkey: + 'cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393', + relays: [] + } + }, + { + text: 'nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4', + event: { + id: 'cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393', + relays: [], + author: undefined + } + } + ]) +}) diff --git a/references.ts b/references.ts new file mode 100644 index 0000000..c5e3e86 --- /dev/null +++ b/references.ts @@ -0,0 +1,104 @@ +import {Event} from './event' +import {decode, AddressPointer, ProfilePointer, EventPointer} from './nip19' + +type Reference = { + text: string + profile?: ProfilePointer + event?: EventPointer + address?: AddressPointer +} + +const mentionRegex = + /\bnostr:((note|npub|naddr|nevent|nprofile)1\w+)\b|#\[(\d+)\]/g + +export function parseReferences(evt: Event): Reference[] { + let references: Reference[] = [] + for (let ref of evt.content.matchAll(mentionRegex)) { + if (ref[2]) { + // it's a NIP-27 mention + try { + let {type, data} = decode(ref[1]) + switch (type) { + case 'npub': { + references.push({ + text: ref[0], + profile: {pubkey: data as string, relays: []} + }) + break + } + case 'nprofile': { + references.push({ + text: ref[0], + profile: data as ProfilePointer + }) + break + } + case 'note': { + references.push({ + text: ref[0], + event: {id: data as string, relays: []} + }) + break + } + case 'nevent': { + references.push({ + text: ref[0], + event: data as EventPointer + }) + break + } + case 'naddr': { + references.push({ + text: ref[0], + address: data as AddressPointer + }) + break + } + } + } catch (err) { + /***/ + } + } else if (ref[3]) { + // it's a NIP-10 mention + let idx = parseInt(ref[3], 10) + let tag = evt.tags[idx] + if (!tag) continue + + switch (tag[0]) { + case 'p': { + references.push({ + text: ref[0], + profile: {pubkey: tag[1], relays: tag[2] ? [tag[2]] : []} + }) + break + } + case 'e': { + references.push({ + text: ref[0], + event: {id: tag[1], relays: tag[2] ? [tag[2]] : []} + }) + break + } + case 'a': { + try { + let [kind, pubkey, identifier] = ref[1].split(':') + references.push({ + text: ref[0], + address: { + identifier, + pubkey, + kind: parseInt(kind, 10), + relays: tag[2] ? [tag[2]] : [] + } + }) + } catch (err) { + /***/ + } + break + } + } + } + } + + return references +}