mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2025-12-09 16:48:50 +00:00
Merge pull request #194 from alexgleason/nip27
This commit is contained in:
2
index.ts
2
index.ts
@@ -11,7 +11,9 @@ 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 nip19 from './nip19'
|
export * as nip19 from './nip19'
|
||||||
|
export * as nip21 from './nip21'
|
||||||
export * as nip26 from './nip26'
|
export * as nip26 from './nip26'
|
||||||
|
export * as nip27 from './nip27'
|
||||||
export * as nip39 from './nip39'
|
export * as nip39 from './nip39'
|
||||||
export * as nip42 from './nip42'
|
export * as nip42 from './nip42'
|
||||||
export * as nip57 from './nip57'
|
export * as nip57 from './nip57'
|
||||||
|
|||||||
42
nip21.test.js
Normal file
42
nip21.test.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/* eslint-env jest */
|
||||||
|
const {nip21} = require('./lib/nostr.cjs')
|
||||||
|
|
||||||
|
test('test', () => {
|
||||||
|
expect(
|
||||||
|
nip21.test(
|
||||||
|
'nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6'
|
||||||
|
)
|
||||||
|
).toBe(true)
|
||||||
|
expect(
|
||||||
|
nip21.test(
|
||||||
|
'nostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky'
|
||||||
|
)
|
||||||
|
).toBe(true)
|
||||||
|
expect(
|
||||||
|
nip21.test(
|
||||||
|
' nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6'
|
||||||
|
)
|
||||||
|
).toBe(false)
|
||||||
|
expect(nip21.test('nostr:')).toBe(false)
|
||||||
|
expect(
|
||||||
|
nip21.test(
|
||||||
|
'nostr:npub108pv4cg5ag52nQq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6'
|
||||||
|
)
|
||||||
|
).toBe(false)
|
||||||
|
expect(nip21.test('gggggg')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('parse', () => {
|
||||||
|
const result = nip21.parse(
|
||||||
|
'nostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
uri: 'nostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky',
|
||||||
|
value: 'note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky',
|
||||||
|
decoded: {
|
||||||
|
type: 'note',
|
||||||
|
data: '46d731680add2990efe1cc619dc9b8014feeb23261ab9dee50e9d11814de5a2b'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
41
nip21.ts
Normal file
41
nip21.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import * as nip19 from './nip19'
|
||||||
|
import * as nip21 from './nip21'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bech32 regex.
|
||||||
|
* @see https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32
|
||||||
|
*/
|
||||||
|
export const BECH32_REGEX =
|
||||||
|
/[\x21-\x7E]{1,83}1[023456789acdefghjklmnpqrstuvwxyz]{6,}/
|
||||||
|
|
||||||
|
/** Nostr URI regex, eg `nostr:npub1...` */
|
||||||
|
export const NOSTR_URI_REGEX = new RegExp(`nostr:(${BECH32_REGEX.source})`)
|
||||||
|
|
||||||
|
/** Test whether the value is a Nostr URI. */
|
||||||
|
export function test(value: unknown): value is `nostr:${string}` {
|
||||||
|
return (
|
||||||
|
typeof value === 'string' &&
|
||||||
|
new RegExp(`^${NOSTR_URI_REGEX.source}$`).test(value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parsed Nostr URI data. */
|
||||||
|
export interface NostrURI {
|
||||||
|
/** Full URI including the `nostr:` protocol. */
|
||||||
|
uri: `nostr:${string}`
|
||||||
|
/** The bech32-encoded data (eg `npub1...`). */
|
||||||
|
value: string
|
||||||
|
/** Decoded bech32 string, according to NIP-19. */
|
||||||
|
decoded: nip19.DecodeResult
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parse and decode a Nostr URI. */
|
||||||
|
export function parse(uri: string): NostrURI {
|
||||||
|
const match = uri.match(new RegExp(`^${nip21.NOSTR_URI_REGEX.source}$`))
|
||||||
|
if (!match) throw new Error(`Invalid Nostr URI: ${uri}`)
|
||||||
|
return {
|
||||||
|
uri: match[0] as `nostr:${string}`,
|
||||||
|
value: match[1],
|
||||||
|
decoded: nip19.decode(match[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
49
nip27.test.js
Normal file
49
nip27.test.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/* eslint-env jest */
|
||||||
|
const {nip27} = require('./lib/nostr.cjs')
|
||||||
|
|
||||||
|
test('matchAll', () => {
|
||||||
|
const result = nip27.matchAll(
|
||||||
|
'Hello nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6!\n\nnostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect([...result]).toEqual([
|
||||||
|
{
|
||||||
|
uri: 'nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6',
|
||||||
|
value: 'npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6',
|
||||||
|
decoded: {
|
||||||
|
type: 'npub',
|
||||||
|
data: '79c2cae114ea28a981e7559b4fe7854a473521a8d22a66bbab9fa248eb820ff6'
|
||||||
|
},
|
||||||
|
start: 6,
|
||||||
|
end: 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'nostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky',
|
||||||
|
value: 'note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky',
|
||||||
|
decoded: {
|
||||||
|
type: 'note',
|
||||||
|
data: '46d731680add2990efe1cc619dc9b8014feeb23261ab9dee50e9d11814de5a2b'
|
||||||
|
},
|
||||||
|
start: 78,
|
||||||
|
end: 147
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('replaceAll', () => {
|
||||||
|
const content =
|
||||||
|
'Hello nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6!\n\nnostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky'
|
||||||
|
|
||||||
|
const result = nip27.replaceAll(content, ({decoded, value}) => {
|
||||||
|
switch (decoded.type) {
|
||||||
|
case 'npub':
|
||||||
|
return '@alex'
|
||||||
|
case 'note':
|
||||||
|
return '!1234'
|
||||||
|
default:
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual('Hello @alex!\n\n!1234')
|
||||||
|
})
|
||||||
63
nip27.ts
Normal file
63
nip27.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import * as nip19 from './nip19'
|
||||||
|
import * as nip21 from './nip21'
|
||||||
|
|
||||||
|
/** Regex to find NIP-21 URIs inside event content. */
|
||||||
|
export const regex = () =>
|
||||||
|
new RegExp(`\\b${nip21.NOSTR_URI_REGEX.source}\\b`, 'g')
|
||||||
|
|
||||||
|
/** Match result for a Nostr URI in event content. */
|
||||||
|
export interface NostrURIMatch extends nip21.NostrURI {
|
||||||
|
/** Index where the URI begins in the event content. */
|
||||||
|
start: number
|
||||||
|
/** Index where the URI ends in the event content. */
|
||||||
|
end: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Find and decode all NIP-21 URIs. */
|
||||||
|
export function * matchAll(content: string): Iterable<NostrURIMatch> {
|
||||||
|
const matches = content.matchAll(regex())
|
||||||
|
|
||||||
|
for (const match of matches) {
|
||||||
|
const [uri, value] = match
|
||||||
|
|
||||||
|
yield {
|
||||||
|
uri: uri as `nostr:${string}`,
|
||||||
|
value,
|
||||||
|
decoded: nip19.decode(value),
|
||||||
|
start: match.index!,
|
||||||
|
end: match.index! + uri.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace all occurrences of Nostr URIs in the text.
|
||||||
|
*
|
||||||
|
* WARNING: using this on an HTML string is potentially unsafe!
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* nip27.replaceAll(event.content, ({ decoded, value }) => {
|
||||||
|
* switch(decoded.type) {
|
||||||
|
* case 'npub':
|
||||||
|
* return renderMention(decoded)
|
||||||
|
* case 'note':
|
||||||
|
* return renderNote(decoded)
|
||||||
|
* default:
|
||||||
|
* return value
|
||||||
|
* }
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function replaceAll(
|
||||||
|
content: string,
|
||||||
|
replacer: (match: nip21.NostrURI) => string
|
||||||
|
): string {
|
||||||
|
return content.replaceAll(regex(), (uri, value) => {
|
||||||
|
return replacer({
|
||||||
|
uri: uri as `nostr:${string}`,
|
||||||
|
value,
|
||||||
|
decoded: nip19.decode(value)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user