refactor: extract tag building functions
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { ApplicationDataKey, ExtendedKind } from '@/constants'
|
||||
import client from '@/services/client.service'
|
||||
import mediaUpload from '@/services/media-upload.service'
|
||||
import { TDraftEvent, TEmoji, TMailboxRelay, TRelaySet } from '@/types'
|
||||
import { TDraftEvent, TEmoji, TMailboxRelay, TMailboxRelayScope, TRelaySet } from '@/types'
|
||||
import dayjs from 'dayjs'
|
||||
import { Event, kinds, nip19 } from 'nostr-tools'
|
||||
import {
|
||||
@@ -11,24 +11,18 @@ import {
|
||||
isReplaceableEvent
|
||||
} from './event'
|
||||
import { generateBech32IdFromETag, tagNameEquals } from './tag'
|
||||
import { normalizeHttpUrl } from './url'
|
||||
|
||||
// https://github.com/nostr-protocol/nips/blob/master/25.md
|
||||
export function createReactionDraftEvent(event: Event, emoji: TEmoji | string = '+'): TDraftEvent {
|
||||
const tags: string[][] = []
|
||||
const hint = client.getEventHint(event.id)
|
||||
tags.push(['e', event.id, hint, event.pubkey])
|
||||
tags.push(['p', event.pubkey])
|
||||
tags.push(buildETag(event.id, event.pubkey))
|
||||
tags.push(buildPTag(event.pubkey))
|
||||
if (event.kind !== kinds.ShortTextNote) {
|
||||
tags.push(['k', event.kind.toString()])
|
||||
tags.push(buildKTag(event.kind))
|
||||
}
|
||||
|
||||
if (isReplaceableEvent(event.kind)) {
|
||||
tags.push(
|
||||
hint
|
||||
? ['a', getReplaceableEventCoordinate(event), hint]
|
||||
: ['a', getReplaceableEventCoordinate(event)]
|
||||
)
|
||||
tags.push(buildATag(event))
|
||||
}
|
||||
|
||||
let content: string
|
||||
@@ -36,7 +30,7 @@ export function createReactionDraftEvent(event: Event, emoji: TEmoji | string =
|
||||
content = emoji
|
||||
} else {
|
||||
content = `:${emoji.shortcode}:`
|
||||
tags.push(['emoji', emoji.shortcode, emoji.url])
|
||||
tags.push(buildEmojiTag(emoji))
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -50,10 +44,7 @@ export function createReactionDraftEvent(event: Event, emoji: TEmoji | string =
|
||||
// https://github.com/nostr-protocol/nips/blob/master/18.md
|
||||
export function createRepostDraftEvent(event: Event): TDraftEvent {
|
||||
const isProtected = isProtectedEvent(event)
|
||||
const tags = [
|
||||
['e', event.id, client.getEventHint(event.id), '', event.pubkey],
|
||||
['p', event.pubkey]
|
||||
]
|
||||
const tags = [buildETag(event.id, event.pubkey), buildPTag(event.pubkey)]
|
||||
|
||||
return {
|
||||
kind: kinds.Repost,
|
||||
@@ -80,7 +71,7 @@ export async function createShortTextNoteDraftEvent(
|
||||
)
|
||||
const hashtags = extractHashtags(content)
|
||||
|
||||
const tags = hashtags.map((hashtag) => ['t', hashtag])
|
||||
const tags = hashtags.map((hashtag) => buildTTag(hashtag))
|
||||
|
||||
// imeta tags
|
||||
const images = extractImagesFromContent(content)
|
||||
@@ -89,7 +80,7 @@ export async function createShortTextNoteDraftEvent(
|
||||
}
|
||||
|
||||
// q tags
|
||||
tags.push(...quoteEventIds.map((eventId) => ['q', eventId, client.getEventHint(eventId)]))
|
||||
tags.push(...quoteEventIds.map((eventId) => buildQTag(eventId)))
|
||||
|
||||
// e tags
|
||||
if (rootETag.length) {
|
||||
@@ -101,18 +92,18 @@ export async function createShortTextNoteDraftEvent(
|
||||
}
|
||||
|
||||
// p tags
|
||||
tags.push(...mentions.map((pubkey) => ['p', pubkey]))
|
||||
tags.push(...mentions.map((pubkey) => buildPTag(pubkey)))
|
||||
|
||||
if (options.addClientTag) {
|
||||
tags.push(['client', 'jumble'])
|
||||
tags.push(buildClientTag())
|
||||
}
|
||||
|
||||
if (options.isNsfw) {
|
||||
tags.push(['content-warning', 'NSFW'])
|
||||
tags.push(buildNsfwTag())
|
||||
}
|
||||
|
||||
if (options.protectedEvent) {
|
||||
tags.push(['-'])
|
||||
tags.push(buildProtectedTag())
|
||||
}
|
||||
|
||||
const baseDraft = {
|
||||
@@ -137,9 +128,9 @@ export function createRelaySetDraftEvent(relaySet: TRelaySet): TDraftEvent {
|
||||
kind: kinds.Relaysets,
|
||||
content: '',
|
||||
tags: [
|
||||
['d', relaySet.id],
|
||||
['title', relaySet.name],
|
||||
...relaySet.relayUrls.map((url) => ['relay', url])
|
||||
buildDTag(relaySet.id),
|
||||
buildTitleTag(relaySet.name),
|
||||
...relaySet.relayUrls.map((url) => buildRelayTag(url))
|
||||
],
|
||||
created_at: dayjs().unix()
|
||||
}
|
||||
@@ -161,17 +152,17 @@ export async function createPictureNoteDraftEvent(
|
||||
}
|
||||
|
||||
const tags = pictureInfos
|
||||
.map((info) => ['imeta', ...info.tags.map(([n, v]) => `${n} ${v}`)])
|
||||
.concat(hashtags.map((hashtag) => ['t', hashtag]))
|
||||
.concat(quoteEventIds.map((eventId) => ['q', eventId, client.getEventHint(eventId)]))
|
||||
.concat(mentions.map((pubkey) => ['p', pubkey]))
|
||||
.map((info) => buildImetaTag(info.tags))
|
||||
.concat(hashtags.map((hashtag) => buildTTag(hashtag)))
|
||||
.concat(quoteEventIds.map((eventId) => buildQTag(eventId)))
|
||||
.concat(mentions.map((pubkey) => buildPTag(pubkey)))
|
||||
|
||||
if (options.addClientTag) {
|
||||
tags.push(['client', 'jumble'])
|
||||
tags.push(buildClientTag())
|
||||
}
|
||||
|
||||
if (options.protectedEvent) {
|
||||
tags.push(['-'])
|
||||
tags.push(buildProtectedTag())
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -193,69 +184,57 @@ export async function createCommentDraftEvent(
|
||||
isNsfw?: boolean
|
||||
} = {}
|
||||
): Promise<TDraftEvent> {
|
||||
const {
|
||||
quoteEventIds,
|
||||
rootEventId,
|
||||
rootCoordinateTag,
|
||||
rootKind,
|
||||
rootPubkey,
|
||||
rootUrl,
|
||||
parentEventId,
|
||||
parentCoordinate,
|
||||
parentKind,
|
||||
parentPubkey
|
||||
} = await extractCommentMentions(content, parentEvent)
|
||||
const { quoteEventIds, rootEventId, rootCoordinateTag, rootKind, rootPubkey, rootUrl } =
|
||||
await extractCommentMentions(content, parentEvent)
|
||||
const hashtags = extractHashtags(content)
|
||||
|
||||
const tags = hashtags
|
||||
.map((hashtag) => ['t', hashtag])
|
||||
.concat(quoteEventIds.map((eventId) => ['q', eventId, client.getEventHint(eventId)]))
|
||||
.map((hashtag) => buildTTag(hashtag))
|
||||
.concat(quoteEventIds.map((eventId) => buildQTag(eventId)))
|
||||
|
||||
const images = extractImagesFromContent(content)
|
||||
if (images && images.length) {
|
||||
tags.push(...generateImetaTags(images))
|
||||
}
|
||||
|
||||
tags.push(...mentions.filter((pubkey) => pubkey !== parentPubkey).map((pubkey) => ['p', pubkey]))
|
||||
tags.push(
|
||||
...mentions.filter((pubkey) => pubkey !== parentEvent.pubkey).map((pubkey) => buildPTag(pubkey))
|
||||
)
|
||||
|
||||
if (rootCoordinateTag) {
|
||||
tags.push(rootCoordinateTag)
|
||||
} else if (rootEventId) {
|
||||
tags.push(
|
||||
rootPubkey
|
||||
? ['E', rootEventId, client.getEventHint(rootEventId), rootPubkey]
|
||||
: ['E', rootEventId, client.getEventHint(rootEventId)]
|
||||
)
|
||||
tags.push(buildETag(rootEventId, rootPubkey, '', true))
|
||||
}
|
||||
if (rootPubkey) {
|
||||
tags.push(['P', rootPubkey])
|
||||
tags.push(buildPTag(rootPubkey, true))
|
||||
}
|
||||
if (rootKind) {
|
||||
tags.push(['K', rootKind.toString()])
|
||||
tags.push(buildKTag(rootKind, true))
|
||||
}
|
||||
if (rootUrl) {
|
||||
tags.push(['I', rootUrl])
|
||||
tags.push(buildITag(rootUrl, true))
|
||||
}
|
||||
tags.push(
|
||||
...[
|
||||
parentCoordinate
|
||||
? ['a', parentCoordinate, client.getEventHint(parentEventId)]
|
||||
: ['e', parentEventId, client.getEventHint(parentEventId), parentPubkey],
|
||||
['k', parentKind.toString()],
|
||||
['p', parentPubkey]
|
||||
isReplaceableEvent(parentEvent.kind)
|
||||
? buildATag(parentEvent)
|
||||
: buildETag(parentEvent.id, parentEvent.pubkey),
|
||||
buildKTag(parentEvent.kind),
|
||||
buildPTag(parentEvent.pubkey)
|
||||
]
|
||||
)
|
||||
|
||||
if (options.addClientTag) {
|
||||
tags.push(['client', 'jumble'])
|
||||
tags.push(buildClientTag())
|
||||
}
|
||||
|
||||
if (options.isNsfw) {
|
||||
tags.push(['content-warning', 'NSFW'])
|
||||
tags.push(buildNsfwTag())
|
||||
}
|
||||
|
||||
if (options.protectedEvent) {
|
||||
tags.push(['-'])
|
||||
tags.push(buildProtectedTag())
|
||||
}
|
||||
|
||||
const baseDraft = {
|
||||
@@ -278,9 +257,7 @@ export function createRelayListDraftEvent(mailboxRelays: TMailboxRelay[]): TDraf
|
||||
return {
|
||||
kind: kinds.RelayList,
|
||||
content: '',
|
||||
tags: mailboxRelays.map(({ url, scope }) =>
|
||||
scope === 'both' ? ['r', url] : ['r', url, scope]
|
||||
),
|
||||
tags: mailboxRelays.map(({ url, scope }) => buildRTag(url, scope)),
|
||||
created_at: dayjs().unix()
|
||||
}
|
||||
}
|
||||
@@ -318,10 +295,10 @@ export function createFavoriteRelaysDraftEvent(
|
||||
): TDraftEvent {
|
||||
const tags: string[][] = []
|
||||
favoriteRelays.forEach((url) => {
|
||||
tags.push(['relay', url])
|
||||
tags.push(buildRelayTag(url))
|
||||
})
|
||||
relaySetEvents.forEach((event) => {
|
||||
tags.push(['a', getReplaceableEventCoordinate(event)])
|
||||
tags.push(buildATag(event))
|
||||
})
|
||||
return {
|
||||
kind: ExtendedKind.FAVORITE_RELAYS,
|
||||
@@ -335,7 +312,7 @@ export function createSeenNotificationsAtDraftEvent(): TDraftEvent {
|
||||
return {
|
||||
kind: kinds.Application,
|
||||
content: 'Records read time to sync notification status across devices.',
|
||||
tags: [['d', ApplicationDataKey.NOTIFICATIONS_SEEN_AT]],
|
||||
tags: [buildDTag(ApplicationDataKey.NOTIFICATIONS_SEEN_AT)],
|
||||
created_at: dayjs().unix()
|
||||
}
|
||||
}
|
||||
@@ -353,7 +330,7 @@ export function createBlossomServerListDraftEvent(servers: string[]): TDraftEven
|
||||
return {
|
||||
kind: ExtendedKind.BLOSSOM_SERVER_LIST,
|
||||
content: '',
|
||||
tags: servers.map((server) => ['server', normalizeHttpUrl(server)]),
|
||||
tags: servers.map((server) => buildServerTag(server)),
|
||||
created_at: dayjs().unix()
|
||||
}
|
||||
}
|
||||
@@ -394,39 +371,21 @@ async function extractRelatedEventIds(content: string, parentEvent?: Event) {
|
||||
if (parentEvent) {
|
||||
const _rootETag = getRootETag(parentEvent)
|
||||
if (_rootETag) {
|
||||
parentETag = [
|
||||
'e',
|
||||
parentEvent.id,
|
||||
client.getEventHint(parentEvent.id),
|
||||
'reply',
|
||||
parentEvent.pubkey
|
||||
]
|
||||
parentETag = buildETagWithMarker(parentEvent.id, parentEvent.pubkey, '', 'reply')
|
||||
|
||||
const [, rootEventHexId, hint, , rootEventPubkey] = _rootETag
|
||||
if (rootEventPubkey) {
|
||||
rootETag = [
|
||||
'e',
|
||||
rootEventHexId,
|
||||
hint ?? client.getEventHint(rootEventHexId),
|
||||
'root',
|
||||
rootEventPubkey
|
||||
]
|
||||
rootETag = buildETagWithMarker(rootEventHexId, rootEventPubkey, hint, 'root')
|
||||
} else {
|
||||
const rootEventId = generateBech32IdFromETag(_rootETag)
|
||||
const rootEvent = rootEventId ? await client.fetchEvent(rootEventId) : undefined
|
||||
rootETag = rootEvent
|
||||
? ['e', rootEvent.id, hint ?? client.getEventHint(rootEvent.id), 'root', rootEvent.pubkey]
|
||||
: ['e', rootEventHexId, hint ?? client.getEventHint(rootEventHexId), 'root']
|
||||
? buildETagWithMarker(rootEvent.id, rootEvent.pubkey, hint, 'root')
|
||||
: buildETagWithMarker(rootEventHexId, rootEventPubkey, hint, 'root')
|
||||
}
|
||||
} else {
|
||||
// reply to root event
|
||||
rootETag = [
|
||||
'e',
|
||||
parentEvent.id,
|
||||
client.getEventHint(parentEvent.id),
|
||||
'root',
|
||||
parentEvent.pubkey
|
||||
]
|
||||
rootETag = buildETagWithMarker(parentEvent.id, parentEvent.pubkey, '', 'root')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,12 +398,11 @@ async function extractRelatedEventIds(content: string, parentEvent?: Event) {
|
||||
|
||||
async function extractCommentMentions(content: string, parentEvent: Event) {
|
||||
const quoteEventIds: string[] = []
|
||||
const parentEventIsReplaceable = isReplaceableEvent(parentEvent.kind)
|
||||
const rootCoordinateTag =
|
||||
parentEvent.kind === ExtendedKind.COMMENT
|
||||
? parentEvent.tags.find(tagNameEquals('A'))
|
||||
: isReplaceableEvent(parentEvent.kind)
|
||||
? ['A', getReplaceableEventCoordinate(parentEvent), client.getEventHint(parentEvent.id)]
|
||||
? buildATag(parentEvent, true)
|
||||
: undefined
|
||||
const rootEventId =
|
||||
parentEvent.kind === ExtendedKind.COMMENT
|
||||
@@ -463,13 +421,6 @@ async function extractCommentMentions(content: string, parentEvent: Event) {
|
||||
? parentEvent.tags.find(tagNameEquals('I'))?.[1]
|
||||
: undefined
|
||||
|
||||
const parentEventId = parentEvent.id
|
||||
const parentCoordinate = parentEventIsReplaceable
|
||||
? getReplaceableEventCoordinate(parentEvent)
|
||||
: undefined
|
||||
const parentKind = parentEvent.kind
|
||||
const parentPubkey = parentEvent.pubkey
|
||||
|
||||
const addToSet = (arr: string[], item: string) => {
|
||||
if (!arr.includes(item)) arr.push(item)
|
||||
}
|
||||
@@ -496,10 +447,7 @@ async function extractCommentMentions(content: string, parentEvent: Event) {
|
||||
rootKind,
|
||||
rootPubkey,
|
||||
rootUrl,
|
||||
parentEventId,
|
||||
parentCoordinate,
|
||||
parentKind,
|
||||
parentPubkey
|
||||
parentEvent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,3 +466,102 @@ function extractHashtags(content: string) {
|
||||
function extractImagesFromContent(content: string) {
|
||||
return content.match(/https?:\/\/[^\s"']+\.(jpg|jpeg|png|gif|webp|heic)/gi)
|
||||
}
|
||||
|
||||
function buildATag(event: Event, upperCase: boolean = false) {
|
||||
const coordinate = getReplaceableEventCoordinate(event)
|
||||
const hint = client.getEventHint(event.id)
|
||||
return trimTagEnd([upperCase ? 'A' : 'a', coordinate, hint])
|
||||
}
|
||||
|
||||
function buildDTag(identifier: string) {
|
||||
return ['d', identifier]
|
||||
}
|
||||
|
||||
function buildETag(
|
||||
eventHexId: string,
|
||||
pubkey: string = '',
|
||||
hint: string = '',
|
||||
upperCase: boolean = false
|
||||
) {
|
||||
if (!hint) {
|
||||
hint = client.getEventHint(eventHexId)
|
||||
}
|
||||
return trimTagEnd([upperCase ? 'E' : 'e', eventHexId, hint, pubkey])
|
||||
}
|
||||
|
||||
function buildETagWithMarker(
|
||||
eventHexId: string,
|
||||
pubkey: string = '',
|
||||
hint: string = '',
|
||||
marker: 'root' | 'reply' | '' = ''
|
||||
) {
|
||||
if (!hint) {
|
||||
hint = client.getEventHint(eventHexId)
|
||||
}
|
||||
return trimTagEnd(['e', eventHexId, hint, marker, pubkey])
|
||||
}
|
||||
|
||||
function buildITag(url: string, upperCase: boolean = false) {
|
||||
return [upperCase ? 'I' : 'i', url]
|
||||
}
|
||||
|
||||
function buildKTag(kind: number | string, upperCase: boolean = false) {
|
||||
return [upperCase ? 'K' : 'k', kind.toString()]
|
||||
}
|
||||
|
||||
function buildPTag(pubkey: string, upperCase: boolean = false) {
|
||||
return [upperCase ? 'P' : 'p', pubkey]
|
||||
}
|
||||
|
||||
function buildQTag(eventHexId: string) {
|
||||
return trimTagEnd(['q', eventHexId, client.getEventHint(eventHexId)]) // TODO: pubkey
|
||||
}
|
||||
|
||||
function buildRTag(url: string, scope: TMailboxRelayScope) {
|
||||
return scope === 'both' ? ['r', url, scope] : ['r', url]
|
||||
}
|
||||
|
||||
function buildTTag(hashtag: string) {
|
||||
return ['t', hashtag]
|
||||
}
|
||||
|
||||
function buildEmojiTag(emoji: TEmoji) {
|
||||
return ['emoji', emoji.shortcode, emoji.url]
|
||||
}
|
||||
|
||||
function buildTitleTag(title: string) {
|
||||
return ['title', title]
|
||||
}
|
||||
|
||||
function buildRelayTag(url: string) {
|
||||
return ['relay', url]
|
||||
}
|
||||
|
||||
function buildServerTag(url: string) {
|
||||
return ['server', url]
|
||||
}
|
||||
|
||||
function buildImetaTag(nip94Tags: string[][]) {
|
||||
return ['imeta', ...nip94Tags.map(([n, v]) => `${n} ${v}`)]
|
||||
}
|
||||
|
||||
function buildClientTag() {
|
||||
return ['client', 'jumble']
|
||||
}
|
||||
|
||||
function buildNsfwTag() {
|
||||
return ['content-warning', 'NSFW']
|
||||
}
|
||||
|
||||
function buildProtectedTag() {
|
||||
return ['-']
|
||||
}
|
||||
|
||||
function trimTagEnd(tag: string[]) {
|
||||
let endIndex = tag.length - 1
|
||||
while (endIndex >= 0 && tag[endIndex] === '') {
|
||||
endIndex--
|
||||
}
|
||||
|
||||
return tag.slice(0, endIndex + 1)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user