Browse Source

bug-fixes

imwald
Silberengel 1 month ago
parent
commit
1a4f656ae2
  1. 5
      src/constants.ts
  2. 10
      src/lib/draft-event.ts
  3. 58
      src/lib/thread-reply-root-match.ts
  4. 27
      src/services/note-stats.service.ts

5
src/constants.ts

@ -327,7 +327,10 @@ export const ExtendedKind = { @@ -327,7 +327,10 @@ export const ExtendedKind = {
RSS_FEED_LIST: 10895,
/** Client-only synthetic "parent" for RSS article threads; never published to relays */
RSS_THREAD_ROOT: 99999,
/** NIP-18: generic repost of any event other than kind 1 (kind 6 is kind-1-only). */
/**
* NIP-18: generic repost (kind 16) for any event **except** kind 1 zaps (9735), reactions, comments, etc.
* Kind **6** (`kinds.Repost` from nostr-tools) is only for reposting kind 1. See `createRepostDraftEvent`.
*/
GENERIC_REPOST: 16,
/** NIP-25: reaction to external content (NIP-73 `k` + `i`), e.g. http(s) URLs */
EXTERNAL_REACTION: 17,

10
src/lib/draft-event.ts

@ -122,7 +122,15 @@ export function createReactionDraftEvent(event: Event, emoji: TEmoji | string = @@ -122,7 +122,15 @@ export function createReactionDraftEvent(event: Event, emoji: TEmoji | string =
}
}
// https://github.com/nostr-protocol/nips/blob/master/18.md
/**
* NIP-18 boost / repost.
* - Kind **6** (`kinds.Repost`): only for reposting **kind 1** (short notes).
* - Kind **16** (`ExtendedKind.GENERIC_REPOST`): for every other kind e.g. zaps (9735), reactions (7),
* comments (1111), long-form, etc. Requires a **`k`** tag with the stringified target kind.
* So boosting a zap receipt always creates **kind 16** with `k` = `"9735"`.
*
* @see https://github.com/nostr-protocol/nips/blob/master/18.md
*/
export function createRepostDraftEvent(event: Event): TDraftEvent {
const isProtected = isProtectedEvent(event)
const tags: string[][] = [buildETag(event.id, event.pubkey), buildPTag(event.pubkey)]

58
src/lib/thread-reply-root-match.ts

@ -17,8 +17,43 @@ import client from '@/services/client.service' @@ -17,8 +17,43 @@ import client from '@/services/client.service'
import type { Event } from 'nostr-tools'
import { kinds } from 'nostr-tools'
/** Reply whose direct parent is a zap receipt for this thread root (hex id). */
function replyParentIsZapToRootHex(reply: Event, rootHexLower: string): boolean {
const THREAD_PARENT_WALK_MAX = 14
/**
* Whether a note (hex id) sits in the thread under `rootHexLower`: it is the root, declares that root,
* or we can reach the root by walking `e` parents in the session cache.
*/
function hexNoteParticipatesInThread(noteHexLower: string, rootHexLower: string): boolean {
const root = rootHexLower.trim().toLowerCase()
const start = noteHexLower.trim().toLowerCase()
if (!/^[0-9a-f]{64}$/i.test(start)) return false
if (start === root) return true
const seen = new Set<string>()
let curId: string | undefined = start
for (let hop = 0; hop < THREAD_PARENT_WALK_MAX && curId; hop++) {
const k = curId.toLowerCase()
if (seen.has(k)) return false
seen.add(k)
if (k === root) return true
const ev = client.peekSessionCachedEvent(k)
if (!ev) return false
if (ev.id.toLowerCase() === root) return true
const declaredRoot = getRootEventHexId(ev)?.toLowerCase()
if (declaredRoot === root) return true
const parent = getParentEventHexId(ev)?.toLowerCase()
if (!parent || !/^[0-9a-f]{64}$/i.test(parent)) return false
curId = parent
}
return false
}
/** Reply whose direct parent is a zap receipt whose zapped note is in this thread (OP or nested under OP). */
function replyParentIsZapToThreadHex(reply: Event, rootHexLower: string): boolean {
const parentHex = getParentEventHexId(reply)
if (!parentHex || !/^[0-9a-f]{64}$/i.test(parentHex)) return false
const pl = parentHex.toLowerCase()
@ -26,11 +61,8 @@ function replyParentIsZapToRootHex(reply: Event, rootHexLower: string): boolean @@ -26,11 +61,8 @@ function replyParentIsZapToRootHex(reply: Event, rootHexLower: string): boolean
const parentEv = client.peekSessionCachedEvent(pl)
if (!parentEv || parentEv.kind !== kinds.Zap) return false
const zapped = getZapInfoFromEvent(parentEv)?.originalEventId
return (
!!zapped &&
/^[0-9a-f]{64}$/i.test(zapped) &&
zapped.toLowerCase() === rootHexLower
)
if (!zapped || !/^[0-9a-f]{64}$/i.test(zapped)) return false
return hexNoteParticipatesInThread(zapped.toLowerCase(), rootHexLower)
}
function reactionTargetNoteHex(reaction: Event): string | undefined {
@ -41,15 +73,17 @@ function reactionTargetNoteHex(reaction: Event): string | undefined { @@ -41,15 +73,17 @@ function reactionTargetNoteHex(reaction: Event): string | undefined {
return undefined
}
/** Reply whose direct parent is a NIP-25 / kind-17 reaction to this thread root note. */
function replyParentIsReactionToRootHex(reply: Event, rootHexLower: string): boolean {
/** Reply whose direct parent is a reaction to some note in this thread (OP or a nested reply under OP). */
function replyParentIsReactionToThreadHex(reply: Event, rootHexLower: string): boolean {
const parentHex = getParentEventHexId(reply)
if (!parentHex || !/^[0-9a-f]{64}$/i.test(parentHex)) return false
const pl = parentHex.toLowerCase()
if (pl === rootHexLower) return false
const parentEv = client.peekSessionCachedEvent(pl)
if (!parentEv || !isNip25ReactionKind(parentEv.kind)) return false
return reactionTargetNoteHex(parentEv) === rootHexLower
const targetHex = reactionTargetNoteHex(parentEv)
if (!targetHex) return false
return hexNoteParticipatesInThread(targetHex, rootHexLower)
}
/** Matches `ReplyNoteList` / discussion thread root shapes. */
@ -79,8 +113,8 @@ export function eventReplyMatchesThreadRoot(evt: Event, root: TThreadRootRef): b @@ -79,8 +113,8 @@ export function eventReplyMatchesThreadRoot(evt: Event, root: TThreadRootRef): b
const rid = root.id.trim().toLowerCase()
const evtRootHex = getRootEventHexId(evt)?.toLowerCase()
if (evtRootHex === rid) return true
if (replyParentIsZapToRootHex(evt, rid)) return true
if (replyParentIsReactionToRootHex(evt, rid)) return true
if (replyParentIsZapToThreadHex(evt, rid)) return true
if (replyParentIsReactionToThreadHex(evt, rid)) return true
return kind1QuotesThreadRoot(evt, root)
}

27
src/services/note-stats.service.ts

@ -593,26 +593,31 @@ class NoteStatsService { @@ -593,26 +593,31 @@ class NoteStatsService {
return eventId
}
/** Target id for repost stats: `e` first (NIP-18 for both kind 6 and 16), then embedded JSON, then `a` (generic only). */
private repostStatsTargetId(evt: Event, forcedTargetEventId?: string): string | undefined {
const forced = forcedTargetEventId?.trim()
if (forced) return forced
if (!isNip18RepostKind(evt.kind)) return undefined
const hex = getFirstHexEventIdFromETags(evt.tags)
if (hex) return hex.toLowerCase()
const raw = evt.content?.trim()
if (raw) {
try {
const embedded = JSON.parse(raw) as { id?: string }
if (embedded.id && /^[0-9a-f]{64}$/i.test(embedded.id)) {
return embedded.id.toLowerCase()
}
} catch {
/* ignore */
}
}
if (evt.kind === ExtendedKind.GENERIC_REPOST) {
const aTag = evt.tags.find(tagNameEquals('a')) ?? evt.tags.find(tagNameEquals('A'))
const coord = aTag?.[1]?.trim()
if (coord) return coord
const raw = evt.content?.trim()
if (raw) {
try {
const embedded = JSON.parse(raw) as { id?: string }
if (embedded.id && /^[0-9a-f]{64}$/i.test(embedded.id)) {
return embedded.id.toLowerCase()
}
} catch {
/* ignore */
}
}
}
return undefined
}

Loading…
Cancel
Save