mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2025-12-09 00:28:51 +00:00
Add NIP-18 utils
This commit is contained in:
1
event.ts
1
event.ts
@@ -12,6 +12,7 @@ export enum Kind {
|
|||||||
Contacts = 3,
|
Contacts = 3,
|
||||||
EncryptedDirectMessage = 4,
|
EncryptedDirectMessage = 4,
|
||||||
EventDeletion = 5,
|
EventDeletion = 5,
|
||||||
|
Repost = 6,
|
||||||
Reaction = 7,
|
Reaction = 7,
|
||||||
BadgeAward = 8,
|
BadgeAward = 8,
|
||||||
ChannelCreation = 40,
|
ChannelCreation = 40,
|
||||||
|
|||||||
1
index.ts
1
index.ts
@@ -10,6 +10,7 @@ export * as nip05 from './nip05'
|
|||||||
export * as nip06 from './nip06'
|
export * as nip06 from './nip06'
|
||||||
export * as nip10 from './nip10'
|
export * as nip10 from './nip10'
|
||||||
export * as nip13 from './nip13'
|
export * as nip13 from './nip13'
|
||||||
|
export * as nip18 from './nip18'
|
||||||
export * as nip19 from './nip19'
|
export * as nip19 from './nip19'
|
||||||
export * as nip21 from './nip21'
|
export * as nip21 from './nip21'
|
||||||
export * as nip25 from './nip25'
|
export * as nip25 from './nip25'
|
||||||
|
|||||||
101
nip18.test.js
Normal file
101
nip18.test.js
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
/* eslint-env jest */
|
||||||
|
|
||||||
|
const {nip18, finishEvent, getPublicKey, Kind} = require('./lib/nostr.cjs')
|
||||||
|
|
||||||
|
describe('finishRepostEvent + getRepostedEventPointer + getRepostedEvent', () => {
|
||||||
|
const privateKey =
|
||||||
|
'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf'
|
||||||
|
const relayUrl = 'https://relay.example.com'
|
||||||
|
|
||||||
|
const publicKey = getPublicKey(privateKey)
|
||||||
|
|
||||||
|
const repostedEvent = finishEvent({
|
||||||
|
kind: Kind.Text,
|
||||||
|
tags: [
|
||||||
|
['e', 'replied event id'],
|
||||||
|
['p', 'replied event pubkey'],
|
||||||
|
],
|
||||||
|
content: 'Replied to a post',
|
||||||
|
created_at: 1617932115
|
||||||
|
}, privateKey)
|
||||||
|
|
||||||
|
it('should create a signed event from a minimal template', () => {
|
||||||
|
const template = {
|
||||||
|
created_at: 1617932115
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = nip18.finishRepostEvent(template, repostedEvent, relayUrl, privateKey)
|
||||||
|
|
||||||
|
expect(event.kind).toEqual(Kind.Repost)
|
||||||
|
expect(event.tags).toEqual([
|
||||||
|
[
|
||||||
|
'e',
|
||||||
|
repostedEvent.id,
|
||||||
|
relayUrl,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'p',
|
||||||
|
repostedEvent.pubkey,
|
||||||
|
],
|
||||||
|
])
|
||||||
|
expect(event.content).toEqual(JSON.stringify(repostedEvent))
|
||||||
|
expect(event.created_at).toEqual(template.created_at)
|
||||||
|
expect(event.pubkey).toEqual(publicKey)
|
||||||
|
expect(typeof event.id).toEqual('string')
|
||||||
|
expect(typeof event.sig).toEqual('string')
|
||||||
|
|
||||||
|
const repostedEventPointer = nip18.getRepostedEventPointer(event)
|
||||||
|
|
||||||
|
expect(repostedEventPointer.id).toEqual(repostedEvent.id)
|
||||||
|
expect(repostedEventPointer.author).toEqual(repostedEvent.pubkey)
|
||||||
|
expect(repostedEventPointer.relays).toEqual([ relayUrl ])
|
||||||
|
|
||||||
|
const repostedEventFromContent = nip18.getRepostedEvent(event)
|
||||||
|
|
||||||
|
expect(repostedEventFromContent).toEqual(repostedEvent)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create a signed event from a filled template', () => {
|
||||||
|
const template = {
|
||||||
|
tags: [
|
||||||
|
['nonstandard', 'tag'],
|
||||||
|
],
|
||||||
|
content: '',
|
||||||
|
created_at: 1617932115
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = nip18.finishRepostEvent(template, repostedEvent, relayUrl, privateKey)
|
||||||
|
|
||||||
|
expect(event.kind).toEqual(Kind.Repost)
|
||||||
|
expect(event.tags).toEqual([
|
||||||
|
[
|
||||||
|
'nonstandard',
|
||||||
|
'tag',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'e',
|
||||||
|
repostedEvent.id,
|
||||||
|
relayUrl,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'p',
|
||||||
|
repostedEvent.pubkey,
|
||||||
|
],
|
||||||
|
])
|
||||||
|
expect(event.content).toEqual('')
|
||||||
|
expect(event.created_at).toEqual(template.created_at)
|
||||||
|
expect(event.pubkey).toEqual(publicKey)
|
||||||
|
expect(typeof event.id).toEqual('string')
|
||||||
|
expect(typeof event.sig).toEqual('string')
|
||||||
|
|
||||||
|
const repostedEventPointer = nip18.getRepostedEventPointer(event)
|
||||||
|
|
||||||
|
expect(repostedEventPointer.id).toEqual(repostedEvent.id)
|
||||||
|
expect(repostedEventPointer.author).toEqual(repostedEvent.pubkey)
|
||||||
|
expect(repostedEventPointer.relays).toEqual([ relayUrl ])
|
||||||
|
|
||||||
|
const repostedEventFromContent = nip18.getRepostedEvent(event)
|
||||||
|
|
||||||
|
expect(repostedEventFromContent).toEqual(undefined)
|
||||||
|
})
|
||||||
|
})
|
||||||
97
nip18.ts
Normal file
97
nip18.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { Event, finishEvent, Kind, verifySignature } from './event'
|
||||||
|
import { EventPointer } from './nip19'
|
||||||
|
|
||||||
|
export type RepostEventTemplate = {
|
||||||
|
/**
|
||||||
|
* Pass only non-nip18 tags if you have to.
|
||||||
|
* Nip18 tags ('e' and 'p' tags pointing to the reposted event) will be added automatically.
|
||||||
|
*/
|
||||||
|
tags?: string[][]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass an empty string to NOT include the stringified JSON of the reposted event.
|
||||||
|
* Any other content will be ignored and replaced with the stringified JSON of the reposted event.
|
||||||
|
* @default Stringified JSON of the reposted event
|
||||||
|
*/
|
||||||
|
content?: '';
|
||||||
|
|
||||||
|
created_at: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export function finishRepostEvent(
|
||||||
|
t: RepostEventTemplate,
|
||||||
|
reposted: Event<number>,
|
||||||
|
relayUrl: string,
|
||||||
|
privateKey: string,
|
||||||
|
): Event<Kind.Repost> {
|
||||||
|
return finishEvent({
|
||||||
|
kind: Kind.Repost,
|
||||||
|
tags: [
|
||||||
|
...(t.tags ?? []),
|
||||||
|
[ 'e', reposted.id, relayUrl ],
|
||||||
|
[ 'p', reposted.pubkey ],
|
||||||
|
],
|
||||||
|
content: t.content === '' ? '' : JSON.stringify(reposted),
|
||||||
|
created_at: t.created_at,
|
||||||
|
}, privateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRepostedEventPointer(event: Event<number>): undefined | EventPointer {
|
||||||
|
if (event.kind !== Kind.Repost) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastETag: undefined | string[]
|
||||||
|
let lastPTag: undefined | string[]
|
||||||
|
|
||||||
|
for (let i = event.tags.length - 1; i >= 0 && (lastETag === undefined || lastPTag === undefined); i--) {
|
||||||
|
const tag = event.tags[i]
|
||||||
|
if (tag.length >= 2) {
|
||||||
|
if (tag[0] === 'e' && lastETag === undefined) {
|
||||||
|
lastETag = tag
|
||||||
|
} else if (tag[0] === 'p' && lastPTag === undefined) {
|
||||||
|
lastPTag = tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastETag === undefined || lastPTag === undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: lastETag[1],
|
||||||
|
relays: [ lastETag[2], lastPTag[2] ].filter((x) => x !== undefined),
|
||||||
|
author: lastPTag[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetRepostedEventOptions = {
|
||||||
|
skipVerification?: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getRepostedEvent(event: Event<number>, { skipVerification }: GetRepostedEventOptions = {}): undefined | Event<number> {
|
||||||
|
const pointer = getRepostedEventPointer(event)
|
||||||
|
|
||||||
|
if (pointer === undefined || event.content === '') {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
let repostedEvent: undefined | Event<number>
|
||||||
|
|
||||||
|
try {
|
||||||
|
repostedEvent = JSON.parse(event.content) as Event<number>
|
||||||
|
} catch (error) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repostedEvent.id !== pointer.id) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skipVerification && !verifySignature(repostedEvent)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return repostedEvent
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user