feat: support kind 20
This commit is contained in:
@@ -1,7 +1,15 @@
|
||||
import { COMMENT_EVENT_KIND, PICTURE_EVENT_KIND } from '@/constants'
|
||||
import { TDraftEvent } from '@/types'
|
||||
import dayjs from 'dayjs'
|
||||
import { Event, kinds } from 'nostr-tools'
|
||||
import { extractHashtags, extractMentions, getEventCoordinate, isReplaceable } from './event'
|
||||
import {
|
||||
extractCommentMentions,
|
||||
extractHashtags,
|
||||
extractImagesFromContent,
|
||||
extractMentions,
|
||||
getEventCoordinate,
|
||||
isReplaceable
|
||||
} from './event'
|
||||
|
||||
// https://github.com/nostr-protocol/nips/blob/master/25.md
|
||||
export function createReactionDraftEvent(event: Event): TDraftEvent {
|
||||
@@ -73,3 +81,79 @@ export async function createShortTextNoteDraftEvent(
|
||||
created_at: dayjs().unix()
|
||||
}
|
||||
}
|
||||
|
||||
export async function createPictureNoteDraftEvent(
|
||||
content: string,
|
||||
options: {
|
||||
addClientTag?: boolean
|
||||
} = {}
|
||||
): Promise<TDraftEvent> {
|
||||
const { pubkeys, quoteEventIds } = await extractMentions(content)
|
||||
const hashtags = extractHashtags(content)
|
||||
const { images, contentWithoutImages } = extractImagesFromContent(content)
|
||||
if (!images || !images.length) {
|
||||
throw new Error('No images found in content')
|
||||
}
|
||||
|
||||
const tags = images
|
||||
.map((image) => ['imeta', `url ${image}`])
|
||||
.concat(pubkeys.map((pubkey) => ['p', pubkey]))
|
||||
.concat(quoteEventIds.map((eventId) => ['q', eventId]))
|
||||
.concat(hashtags.map((hashtag) => ['t', hashtag]))
|
||||
|
||||
if (options.addClientTag) {
|
||||
tags.push(['client', 'jumble'])
|
||||
}
|
||||
|
||||
return {
|
||||
kind: PICTURE_EVENT_KIND,
|
||||
content: contentWithoutImages,
|
||||
tags,
|
||||
created_at: dayjs().unix()
|
||||
}
|
||||
}
|
||||
|
||||
export async function createCommentDraftEvent(
|
||||
content: string,
|
||||
parentEvent: Event,
|
||||
options: {
|
||||
addClientTag?: boolean
|
||||
} = {}
|
||||
): Promise<TDraftEvent> {
|
||||
const {
|
||||
pubkeys,
|
||||
quoteEventIds,
|
||||
rootEventId,
|
||||
rootEventKind,
|
||||
rootEventPubkey,
|
||||
parentEventId,
|
||||
parentEventKind,
|
||||
parentEventPubkey
|
||||
} = await extractCommentMentions(content, parentEvent)
|
||||
const hashtags = extractHashtags(content)
|
||||
|
||||
const tags = [
|
||||
['E', rootEventId],
|
||||
['K', rootEventKind.toString()],
|
||||
['P', rootEventPubkey],
|
||||
['e', parentEventId],
|
||||
['k', parentEventKind.toString()],
|
||||
['p', parentEventPubkey]
|
||||
].concat(
|
||||
pubkeys
|
||||
.map((pubkey) => ['p', pubkey])
|
||||
.concat(quoteEventIds.map((eventId) => ['q', eventId]))
|
||||
.concat(hashtags.map((hashtag) => ['t', hashtag]))
|
||||
)
|
||||
|
||||
if (options.addClientTag) {
|
||||
tags.push(['client', 'jumble'])
|
||||
}
|
||||
|
||||
return {
|
||||
kind: COMMENT_EVENT_KIND,
|
||||
content,
|
||||
tags,
|
||||
created_at: dayjs().unix()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { COMMENT_EVENT_KIND, PICTURE_EVENT_KIND } from '@/constants'
|
||||
import client from '@/services/client.service'
|
||||
import { Event, kinds, nip19 } from 'nostr-tools'
|
||||
import { isReplyETag, isRootETag, tagNameEquals } from './tag'
|
||||
import { extractImetaUrlFromTag, isReplyETag, isRootETag, tagNameEquals } from './tag'
|
||||
|
||||
export function isNsfwEvent(event: Event) {
|
||||
return event.tags.some(
|
||||
@@ -26,6 +27,14 @@ export function isReplyNoteEvent(event: Event) {
|
||||
return hasETag && !hasMarker
|
||||
}
|
||||
|
||||
export function isCommentEvent(event: Event) {
|
||||
return event.kind === COMMENT_EVENT_KIND
|
||||
}
|
||||
|
||||
export function isPictureEvent(event: Event) {
|
||||
return event.kind === PICTURE_EVENT_KIND
|
||||
}
|
||||
|
||||
export function getParentEventId(event?: Event) {
|
||||
return event?.tags.find(isReplyETag)?.[1]
|
||||
}
|
||||
@@ -116,6 +125,54 @@ export async function extractMentions(content: string, parentEvent?: Event) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function extractCommentMentions(content: string, parentEvent: Event) {
|
||||
const pubkeySet = new Set<string>()
|
||||
const quoteEventIdSet = new Set<string>()
|
||||
const rootEventId = parentEvent.tags.find(tagNameEquals('E'))?.[1] ?? parentEvent.id
|
||||
const rootEventKind = parentEvent.tags.find(tagNameEquals('K'))?.[1] ?? parentEvent.kind
|
||||
const rootEventPubkey = parentEvent.tags.find(tagNameEquals('P'))?.[1] ?? parentEvent.pubkey
|
||||
const parentEventId = parentEvent.id
|
||||
const parentEventKind = parentEvent.kind
|
||||
const parentEventPubkey = parentEvent.pubkey
|
||||
|
||||
const matches = content.match(
|
||||
/nostr:(npub1[a-z0-9]{58}|nprofile1[a-z0-9]+|note1[a-z0-9]{58}|nevent1[a-z0-9]+)/g
|
||||
)
|
||||
|
||||
for (const m of matches || []) {
|
||||
try {
|
||||
const id = m.split(':')[1]
|
||||
const { type, data } = nip19.decode(id)
|
||||
if (type === 'nprofile') {
|
||||
pubkeySet.add(data.pubkey)
|
||||
} else if (type === 'npub') {
|
||||
pubkeySet.add(data)
|
||||
} else if (['nevent', 'note', 'naddr'].includes(type)) {
|
||||
const event = await client.fetchEvent(id)
|
||||
if (event) {
|
||||
pubkeySet.add(event.pubkey)
|
||||
quoteEventIdSet.add(event.id)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
pubkeySet.add(parentEvent.pubkey)
|
||||
|
||||
return {
|
||||
pubkeys: Array.from(pubkeySet),
|
||||
quoteEventIds: Array.from(quoteEventIdSet),
|
||||
rootEventId,
|
||||
rootEventKind,
|
||||
rootEventPubkey,
|
||||
parentEventId,
|
||||
parentEventKind,
|
||||
parentEventPubkey
|
||||
}
|
||||
}
|
||||
|
||||
export function extractHashtags(content: string) {
|
||||
const hashtags: string[] = []
|
||||
const matches = content.match(/#[\p{L}\p{N}\p{M}]+/gu)
|
||||
@@ -127,3 +184,22 @@ export function extractHashtags(content: string) {
|
||||
})
|
||||
return hashtags
|
||||
}
|
||||
|
||||
export function extractFirstPictureFromPictureEvent(event: Event) {
|
||||
if (!isPictureEvent(event)) return null
|
||||
for (const tag of event.tags) {
|
||||
const url = extractImetaUrlFromTag(tag)
|
||||
if (url) return url
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function extractImagesFromContent(content: string) {
|
||||
const images = content.match(/https?:\/\/[^\s"']+\.(jpg|jpeg|png|gif|webp|heic)/gi)
|
||||
let contentWithoutImages = content
|
||||
images?.forEach((url) => {
|
||||
contentWithoutImages = contentWithoutImages.replace(url, '').trim()
|
||||
})
|
||||
contentWithoutImages = contentWithoutImages.replace(/\n{3,}/g, '\n\n').trim()
|
||||
return { images, contentWithoutImages }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Event, nip19 } from 'nostr-tools'
|
||||
|
||||
export const toHome = () => '/'
|
||||
export const toNote = (eventOrId: Event | string) => {
|
||||
export const toNote = (eventOrId: Pick<Event, 'id' | 'pubkey'> | string) => {
|
||||
if (typeof eventOrId === 'string') return `/notes/${eventOrId}`
|
||||
const nevent = nip19.neventEncode({ id: eventOrId.id, author: eventOrId.pubkey })
|
||||
return `/notes/${nevent}`
|
||||
|
||||
@@ -13,3 +13,10 @@ export function isRootETag([tagName, , , marker]: string[]) {
|
||||
export function isMentionETag([tagName, , , marker]: string[]) {
|
||||
return tagName === 'e' && marker === 'mention'
|
||||
}
|
||||
|
||||
export function extractImetaUrlFromTag(tag: string[]) {
|
||||
if (tag[0] !== 'imeta') return null
|
||||
const urlItem = tag.find((item) => item.startsWith('url '))
|
||||
const url = urlItem?.slice(4)
|
||||
return url || null
|
||||
}
|
||||
|
||||
@@ -18,3 +18,21 @@ export function normalizeUrl(url: string): string {
|
||||
export function simplifyUrl(url: string): string {
|
||||
return url.replace('wss://', '').replace('ws://', '').replace(/\/$/, '')
|
||||
}
|
||||
|
||||
export function isImage(url: string) {
|
||||
try {
|
||||
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.heic', '.svg']
|
||||
return imageExtensions.some((ext) => new URL(url).pathname.toLowerCase().endsWith(ext))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function isVideo(url: string) {
|
||||
try {
|
||||
const videoExtensions = ['.mp4', '.webm', '.ogg', '.mov']
|
||||
return videoExtensions.some((ext) => new URL(url).pathname.toLowerCase().endsWith(ext))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user