{content}
@@ -91,9 +85,6 @@ export default function PostEditor({
-
-
-
{content}
diff --git a/src/components/QuoteList/index.tsx b/src/components/QuoteList/index.tsx
index e3e192f..bf4c9cf 100644
--- a/src/components/QuoteList/index.tsx
+++ b/src/components/QuoteList/index.tsx
@@ -51,7 +51,8 @@ export default function QuoteList({ event, className }: { event: Event; classNam
kinds.Highlights,
kinds.LongFormArticle,
ExtendedKind.COMMENT,
- ExtendedKind.POLL
+ ExtendedKind.POLL,
+ ExtendedKind.PUBLIC_MESSAGE
],
limit: LIMIT
}
diff --git a/src/components/ReplyNoteList/index.tsx b/src/components/ReplyNoteList/index.tsx
index 2d5bc0f..04c4b21 100644
--- a/src/components/ReplyNoteList/index.tsx
+++ b/src/components/ReplyNoteList/index.tsx
@@ -172,6 +172,14 @@ export default function ReplyNoteList({ index, event }: { index?: number; event:
limit: LIMIT
})
}
+ // For public messages (kind 24), also look for replies using 'q' tags
+ if (event.kind === ExtendedKind.PUBLIC_MESSAGE) {
+ filters.push({
+ '#q': [rootInfo.id],
+ kinds: [ExtendedKind.PUBLIC_MESSAGE],
+ limit: LIMIT
+ })
+ }
} else if (rootInfo.type === 'A') {
filters.push(
{
diff --git a/src/constants.ts b/src/constants.ts
index d39c102..8c9c210 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -106,6 +106,7 @@ export const ExtendedKind = {
COMMENT: 1111,
VOICE: 1222,
VOICE_COMMENT: 1244,
+ PUBLIC_MESSAGE: 24,
FAVORITE_RELAYS: 10012,
BLOSSOM_SERVER_LIST: 10063,
RELAY_REVIEW: 31987,
@@ -122,6 +123,7 @@ export const SUPPORTED_KINDS = [
ExtendedKind.COMMENT,
ExtendedKind.VOICE,
ExtendedKind.VOICE_COMMENT,
+ // ExtendedKind.PUBLIC_MESSAGE, // Excluded - public messages should only appear in notifications
kinds.Highlights,
kinds.LongFormArticle,
ExtendedKind.RELAY_REVIEW
diff --git a/src/lib/draft-event.ts b/src/lib/draft-event.ts
index f982cd2..1eb3708 100644
--- a/src/lib/draft-event.ts
+++ b/src/lib/draft-event.ts
@@ -10,7 +10,7 @@ import {
TPollCreateData,
TRelaySet
} from '@/types'
-import { sha256 } from '@noble/hashes/sha2'
+import { sha256 } from '@noble/hashes/sha256'
import dayjs from 'dayjs'
import { Event, kinds, nip19 } from 'nostr-tools'
import {
@@ -252,6 +252,116 @@ export async function createCommentDraftEvent(
content: transformedEmojisContent,
tags
}
+
+ return setDraftEventCache(baseDraft)
+}
+
+export async function createPublicMessageReplyDraftEvent(
+ content: string,
+ parentEvent: Event,
+ mentions: string[],
+ options: {
+ addClientTag?: boolean
+ isNsfw?: boolean
+ } = {}
+): Promise {
+ const { content: transformedEmojisContent, emojiTags } = transformCustomEmojisInContent(content)
+ const {
+ quoteEventHexIds,
+ quoteReplaceableCoordinates
+ } = await extractCommentMentions(transformedEmojisContent, parentEvent)
+ const hashtags = extractHashtags(transformedEmojisContent)
+
+ const tags = emojiTags
+ .concat(hashtags.map((hashtag) => buildTTag(hashtag)))
+ .concat(quoteEventHexIds.map((eventId) => buildQTag(eventId)))
+ .concat(quoteReplaceableCoordinates.map((coordinate) => buildReplaceableQTag(coordinate)))
+
+ const images = extractImagesFromContent(transformedEmojisContent)
+ if (images && images.length) {
+ tags.push(...generateImetaTags(images))
+ }
+
+ // For kind 24 replies, we use 'q' tag for the parent event (as per NIP-A4)
+ tags.push(buildQTag(parentEvent.id))
+
+ // Add 'p' tags for recipients (original sender and any mentions)
+ const recipients = new Set([parentEvent.pubkey])
+ mentions.forEach(pubkey => recipients.add(pubkey))
+
+ // console.log('🔧 Creating public message reply draft:', {
+ // parentEventId: parentEvent.id,
+ // parentEventPubkey: parentEvent.pubkey,
+ // mentions,
+ // recipients: Array.from(recipients),
+ // finalTags: tags.length
+ // })
+
+ tags.push(
+ ...Array.from(recipients).map((pubkey) => buildPTag(pubkey))
+ )
+
+ if (options.addClientTag) {
+ tags.push(buildClientTag())
+ }
+
+ if (options.isNsfw) {
+ tags.push(buildNsfwTag())
+ }
+
+ // console.log('📝 Final public message reply draft tags:', {
+ // pTags: tags.filter(tag => tag[0] === 'p'),
+ // qTags: tags.filter(tag => tag[0] === 'q'),
+ // allTags: tags
+ // })
+
+ const baseDraft = {
+ kind: ExtendedKind.PUBLIC_MESSAGE,
+ content: transformedEmojisContent,
+ tags
+ }
+
+ return setDraftEventCache(baseDraft)
+}
+
+export async function createPublicMessageDraftEvent(
+ content: string,
+ recipients: string[],
+ options: {
+ addClientTag?: boolean
+ isNsfw?: boolean
+ } = {}
+): Promise {
+ const { content: transformedEmojisContent, emojiTags } = transformCustomEmojisInContent(content)
+ const hashtags = extractHashtags(transformedEmojisContent)
+
+ const tags = emojiTags
+ .concat(hashtags.map((hashtag) => buildTTag(hashtag)))
+
+ const images = extractImagesFromContent(transformedEmojisContent)
+ if (images && images.length) {
+ tags.push(...generateImetaTags(images))
+ }
+
+ // Add 'p' tags for recipients
+ tags.push(
+ ...recipients.map((pubkey) => buildPTag(pubkey))
+ )
+
+ if (options.addClientTag) {
+ tags.push(buildClientTag())
+ }
+
+ if (options.isNsfw) {
+ tags.push(buildNsfwTag())
+ }
+
+ const baseDraft = {
+ kind: ExtendedKind.PUBLIC_MESSAGE,
+ content: transformedEmojisContent,
+ tags
+ }
+
return setDraftEventCache(baseDraft)
}
diff --git a/src/lib/notification.ts b/src/lib/notification.ts
index 024d5e1..6446980 100644
--- a/src/lib/notification.ts
+++ b/src/lib/notification.ts
@@ -1,4 +1,5 @@
import { kinds, NostrEvent } from 'nostr-tools'
+import { ExtendedKind } from '@/constants'
import { isMentioningMutedUsers } from './event'
import { tagNameEquals } from './tag'
@@ -31,5 +32,11 @@ export function notificationFilter(
if (targetPubkey !== pubkey) return false
}
+ // For PUBLIC_MESSAGE (kind 24) events, ensure the user is in the 'p' tags
+ if (pubkey && event.kind === ExtendedKind.PUBLIC_MESSAGE) {
+ const hasUserInPTags = event.tags.some((tag) => tag[0] === 'p' && tag[1] === pubkey)
+ if (!hasUserInPTags) return false
+ }
+
return true
}
diff --git a/src/providers/NotificationProvider.tsx b/src/providers/NotificationProvider.tsx
index 59770b0..83f4fbf 100644
--- a/src/providers/NotificationProvider.tsx
+++ b/src/providers/NotificationProvider.tsx
@@ -98,8 +98,10 @@ export function NotificationProvider({ children }: { children: React.ReactNode }
try {
let eosed = false
const relayList = await client.fetchRelayList(pubkey)
+ const notificationRelays = relayList.read.length > 0 ? relayList.read.slice(0, 5) : BIG_RELAY_URLS
+ console.log('🔔 Notification subscription for', pubkey.substring(0, 8) + '...', 'using relays:', notificationRelays)
const subCloser = client.subscribe(
- relayList.read.length > 0 ? relayList.read.slice(0, 5) : BIG_RELAY_URLS,
+ notificationRelays,
[
{
kinds: [
@@ -110,7 +112,8 @@ export function NotificationProvider({ children }: { children: React.ReactNode }
ExtendedKind.COMMENT,
ExtendedKind.POLL_RESPONSE,
ExtendedKind.VOICE_COMMENT,
- ExtendedKind.POLL
+ ExtendedKind.POLL,
+ ExtendedKind.PUBLIC_MESSAGE
],
'#p': [pubkey],
limit: 20
@@ -127,6 +130,17 @@ export function NotificationProvider({ children }: { children: React.ReactNode }
},
onevent: (evt) => {
if (evt.pubkey !== pubkey) {
+ // Debug: Log public message notifications
+ if (evt.kind === ExtendedKind.PUBLIC_MESSAGE) {
+ const hasUserInPTags = evt.tags.some((tag) => tag[0] === 'p' && tag[1] === pubkey)
+ console.log(`📨 Public message notification received by ${pubkey.substring(0, 8)}... from ${evt.pubkey.substring(0, 8)}...:`, {
+ hasUserInPTags,
+ content: evt.content.substring(0, 50),
+ tags: evt.tags.map(tag => `${tag[0]}:${tag[1]?.substring(0, 8)}...`),
+ eventId: evt.id.substring(0, 8) + '...'
+ })
+ }
+
setNewNotifications((prev) => {
if (!eosed) {
return [evt, ...prev]
diff --git a/src/services/client.service.ts b/src/services/client.service.ts
index fb9ba3e..c3d1525 100644
--- a/src/services/client.service.ts
+++ b/src/services/client.service.ts
@@ -113,7 +113,8 @@ class ClientService extends EventTarget {
})
if (mentions.length > 0) {
const relayLists = await this.fetchRelayLists(mentions)
- relayLists.forEach((relayList) => {
+ relayLists.forEach((relayList, index) => {
+ const mentionPubkey = mentions[index]
_additionalRelayUrls.push(...relayList.read.slice(0, 4))
})
}
@@ -131,9 +132,19 @@ class ClientService extends EventTarget {
}
const relayList = await this.fetchRelayList(event.pubkey)
- relays = (relayList?.write.slice(0, 6) ?? []).concat(
- Array.from(new Set(_additionalRelayUrls)) ?? []
- )
+ const senderWriteRelays = relayList?.write.slice(0, 6) ?? []
+ const recipientReadRelays = Array.from(new Set(_additionalRelayUrls))
+ relays = senderWriteRelays.concat(recipientReadRelays)
+
+ // Special logging for public messages
+ if (event.kind === ExtendedKind.PUBLIC_MESSAGE) {
+ // console.log('🎯 Final relay selection for public message:', {
+ // eventId: event.id.substring(0, 8) + '...',
+ // senderWriteRelays: senderWriteRelays.length,
+ // recipientReadRelays: recipientReadRelays.length,
+ // finalRelays: relays.length
+ // })
+ }
}
if (!relays.length) {
@@ -145,6 +156,17 @@ class ClientService extends EventTarget {
async publishEvent(relayUrls: string[], event: NEvent) {
const uniqueRelayUrls = this.optimizeRelaySelection(Array.from(new Set(relayUrls)))
+ console.log(`Publishing kind ${event.kind} event to ${uniqueRelayUrls.length} relays`)
+ // if (event.kind === ExtendedKind.PUBLIC_MESSAGE) {
+ // console.log('Public message event details:', {
+ // id: event.id,
+ // pubkey: event.pubkey,
+ // content: event.content.substring(0, 50),
+ // tags: event.tags,
+ // targetRelays: uniqueRelayUrls
+ // })
+ // }
+
await new Promise((resolve, reject) => {
let successCount = 0
let finishedCount = 0
@@ -158,15 +180,18 @@ class ClientService extends EventTarget {
return relay
.publish(event)
.then(() => {
+ console.log(`✓ Published to ${url}`)
this.trackEventSeenOn(event.id, relay)
successCount++
})
.catch((error) => {
+ console.log(`✗ Failed to publish to ${url}:`, error.message)
if (
error instanceof Error &&
error.message.startsWith('auth-required') &&
!!that.signer
) {
+ console.log(`Attempting auth for ${url}`)
return relay
.auth((authEvt: EventTemplate) => that.signer!.signEvent(authEvt))
.then(() => relay.publish(event))
@@ -175,23 +200,32 @@ class ClientService extends EventTarget {
}
})
.finally(() => {
+ finishedCount++
// If one third of the relays have accepted the event, consider it a success
const isSuccess = successCount >= uniqueRelayUrls.length / 3
if (isSuccess) {
+ console.log(`✓ Publishing successful (${successCount}/${uniqueRelayUrls.length} relays)`)
this.emitNewEvent(event)
resolve()
}
- if (++finishedCount >= uniqueRelayUrls.length) {
- reject(
- new AggregateError(
- errors.map(
- ({ url, error }) =>
- new Error(
- `${url}: ${error instanceof Error ? error.message : String(error)}`
- )
+ if (finishedCount >= uniqueRelayUrls.length) {
+ if (successCount > 0) {
+ console.log(`✓ Publishing successful (${successCount}/${uniqueRelayUrls.length} relays)`)
+ this.emitNewEvent(event)
+ resolve()
+ } else {
+ console.log(`✗ Publishing failed (0/${uniqueRelayUrls.length} relays)`)
+ reject(
+ new AggregateError(
+ errors.map(
+ ({ url, error }) =>
+ new Error(
+ `${url}: ${error instanceof Error ? error.message : String(error)}`
+ )
+ )
)
)
- )
+ }
}
})
})