Add NIP-10 thread root/reply/mention parsing

This commit is contained in:
futpib
2023-04-01 04:08:59 +04:00
committed by fiatjaf_
parent 0933fba6d5
commit 3e67f9b014
3 changed files with 295 additions and 0 deletions

View File

@@ -8,6 +8,7 @@ export * from './references'
export * as nip04 from './nip04' export * as nip04 from './nip04'
export * as nip05 from './nip05' export * as nip05 from './nip05'
export * as nip06 from './nip06' export * as nip06 from './nip06'
export * as nip10 from './nip10'
export * as nip19 from './nip19' export * as nip19 from './nip19'
export * as nip26 from './nip26' export * as nip26 from './nip26'
export * as nip39 from './nip39' export * as nip39 from './nip39'

221
nip10.test.js Normal file
View File

@@ -0,0 +1,221 @@
/* eslint-env jest */
const {nip10} = require('./lib/nostr.cjs')
describe('parse NIP10-referenced events', () => {
test('legacy + a lot of events', () => {
let event = {
tags: [
[
'e',
'b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c'
],
[
'e',
'bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631'
],
[
'e',
'5e081ebb19153357d7c31e8a10b9ceeef29313f58dc8d701f66727fab02aef64'
],
[
'e',
'49aff7ae6daeaaa2777931b90f9bb29f6cb01c5a3d7d88c8ba82d890f264afb4'
],
[
'e',
'567b7c11f0fe582361e3cea6fcc7609a8942dfe196ee1b98d5604c93fbeea976'
],
[
'e',
'090c037b2e399ee74d9f134758928948dd9154413ca1a1acb37155046e03a051'
],
[
'e',
'89f220b63465c93542b1a78caa3a952cf4f196e91a50596493c8093c533ebc4d'
],
[
'p',
'77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7'
],
[
'p',
'534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec'
],
[
'p',
'4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0'
]
],
};
expect(nip10.parse(event)).toEqual({
"mentions": [
{
"id": "bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631",
"relays": [],
},
{
"id": "5e081ebb19153357d7c31e8a10b9ceeef29313f58dc8d701f66727fab02aef64",
"relays": [],
},
{
"id": "49aff7ae6daeaaa2777931b90f9bb29f6cb01c5a3d7d88c8ba82d890f264afb4",
"relays": [],
},
{
"id": "567b7c11f0fe582361e3cea6fcc7609a8942dfe196ee1b98d5604c93fbeea976",
"relays": [],
},
{
"id": "090c037b2e399ee74d9f134758928948dd9154413ca1a1acb37155046e03a051",
"relays": [],
},
],
"pubkeys": [
"77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7",
"534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec",
"4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0",
],
"reply": {
"id": "89f220b63465c93542b1a78caa3a952cf4f196e91a50596493c8093c533ebc4d",
"relays": [],
},
"root": {
"id": "b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c",
"relays": [],
},
});
});
test('legacy + 3 events', () => {
let event = {
tags: [
[
'e',
'b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c'
],
[
'e',
'bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631'
],
[
'e',
'5e081ebb19153357d7c31e8a10b9ceeef29313f58dc8d701f66727fab02aef64'
],
[
'p',
'77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7'
],
[
'p',
'534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec'
],
[
'p',
'4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0'
]
],
};
expect(nip10.parse(event)).toEqual({
"mentions": [
{
"id": "bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631",
"relays": [],
},
],
"pubkeys": [
"77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7",
"534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec",
"4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0",
],
"reply": {
"id": "5e081ebb19153357d7c31e8a10b9ceeef29313f58dc8d701f66727fab02aef64",
"relays": [],
},
"root": {
"id": "b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c",
"relays": [],
},
});
});
test('legacy + 2 events', () => {
let event = {
tags: [
[
'e',
'b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c'
],
[
'e',
'bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631'
],
[
'p',
'77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7'
],
[
'p',
'534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec'
],
[
'p',
'4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0'
]
],
};
expect(nip10.parse(event)).toEqual({
"mentions": [],
"pubkeys": [
"77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7",
"534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec",
"4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0",
],
"reply": {
"id": "bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631",
"relays": [],
},
"root": {
"id": "b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c",
"relays": [],
},
});
});
test('legacy + 1 event', () => {
let event = {
tags: [
[
'e',
'9abbfd9b9ac5ecdab45d14b8bf8d746139ea039e931a1b376d19a239f1946590'
],
[
'p',
'534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec'
]
],
};
expect(nip10.parse(event)).toEqual({
"mentions": [],
"pubkeys": [
"534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec",
],
"reply": undefined,
"root": {
"id": "9abbfd9b9ac5ecdab45d14b8bf8d746139ea039e931a1b376d19a239f1946590",
"relays": [],
},
});
});
// No events with NIP-10 explicit root/reply/mention markers were found in the wild for these tests :(
test.todo('recommended + a lot of events');
test.todo('recommended + 3 events');
test.todo('recommended + 2 events');
test.todo('recommended + 1 event');
});

73
nip10.ts Normal file
View File

@@ -0,0 +1,73 @@
import type { Event } from "./event";
import { EventPointer } from "./nip19";
export type NIP10Result = {
/**
* Pointer to the root of the thread.
*/
root: EventPointer | undefined;
/**
* Pointer to a "parent" event that parsed event replies to (responded to).
*/
reply: EventPointer | undefined;
/**
* Pointers to events which may or may not be in the reply chain.
*/
mentions: EventPointer[];
/**
* List of pubkeys that are involved in the thread in no particular order.
*/
pubkeys: string[];
};
export function parse(event: Pick<Event, 'tags'>): NIP10Result {
const result: NIP10Result = {
reply: undefined,
root: undefined,
mentions: [],
pubkeys: [],
};
const eTags: string[][] = [];
for (const tag of event.tags) {
if (tag[0] === "e" && tag[1]) {
eTags.push(tag);
}
if (tag[0] === "p" && tag[1]) {
result.pubkeys.push(tag[1]);
}
}
for (let eTagIndex = 0; eTagIndex < eTags.length; eTagIndex++) {
const eTag = eTags[eTagIndex];
const [ _, eTagEventId, eTagRelayUrl, eTagMarker ] = eTag as [string, string, undefined | string, undefined | string];
const eventPointer: EventPointer = {
id: eTagEventId,
relays: eTagRelayUrl ? [eTagRelayUrl] : [],
};
const isFirstETag = eTagIndex === 0;
const isLastETag = eTagIndex === eTags.length - 1;
if (eTagMarker === 'root' || isFirstETag) {
result.root = eventPointer;
continue;
}
if (eTagMarker === 'reply' || isLastETag) {
result.reply = eventPointer;
continue;
}
result.mentions.push(eventPointer);
}
return result;
}