Repost.ts raw
1 import { Event, kinds } from 'nostr-tools'
2 import { EventId, Pubkey, Timestamp } from '../shared'
3
4 /**
5 * Repost Entity
6 *
7 * Represents a repost (kind 6 or 16) of another Nostr event.
8 */
9 export class Repost {
10 private readonly _embeddedEvent: Event | null
11
12 private constructor(
13 private readonly _event: Event,
14 private readonly _targetEventId: EventId,
15 private readonly _targetAuthor: Pubkey,
16 embeddedEvent: Event | null
17 ) {
18 this._embeddedEvent = embeddedEvent
19 }
20
21 /**
22 * Create a Repost from a Nostr Event
23 */
24 static fromEvent(event: Event): Repost {
25 if (event.kind !== kinds.Repost && event.kind !== kinds.GenericRepost) {
26 throw new Error(`Expected kind ${kinds.Repost} or ${kinds.GenericRepost}, got ${event.kind}`)
27 }
28
29 // Find the target event (first 'e' tag)
30 const eTag = event.tags.find((t) => t[0] === 'e')
31 if (!eTag?.[1]) {
32 throw new Error('Repost must have an e tag')
33 }
34
35 // Find the target author (first 'p' tag)
36 const pTag = event.tags.find((t) => t[0] === 'p')
37 if (!pTag?.[1]) {
38 throw new Error('Repost must have a p tag')
39 }
40
41 const targetEventId = EventId.fromHex(eTag[1])
42 const targetAuthor = Pubkey.fromHex(pTag[1])
43
44 // Try to parse embedded event from content
45 let embeddedEvent: Event | null = null
46 if (event.content) {
47 try {
48 embeddedEvent = JSON.parse(event.content) as Event
49 } catch {
50 // Content is not valid JSON, that's fine
51 }
52 }
53
54 return new Repost(event, targetEventId, targetAuthor, embeddedEvent)
55 }
56
57 /**
58 * Try to create a Repost from an Event, returns null if invalid
59 */
60 static tryFromEvent(event: Event | null | undefined): Repost | null {
61 if (!event) return null
62 try {
63 return Repost.fromEvent(event)
64 } catch {
65 return null
66 }
67 }
68
69 /**
70 * The underlying Nostr event
71 */
72 get event(): Event {
73 return this._event
74 }
75
76 /**
77 * The repost's event ID
78 */
79 get id(): EventId {
80 return EventId.fromHex(this._event.id)
81 }
82
83 /**
84 * The author who reposted
85 */
86 get author(): Pubkey {
87 return Pubkey.fromHex(this._event.pubkey)
88 }
89
90 /**
91 * The event ID being reposted
92 */
93 get targetEventId(): EventId {
94 return this._targetEventId
95 }
96
97 /**
98 * The author of the original event
99 */
100 get targetAuthor(): Pubkey {
101 return this._targetAuthor
102 }
103
104 /**
105 * When the repost was created
106 */
107 get createdAt(): Timestamp {
108 return Timestamp.fromUnix(this._event.created_at)
109 }
110
111 /**
112 * Whether this is a standard repost (kind 6)
113 */
114 get isStandardRepost(): boolean {
115 return this._event.kind === kinds.Repost
116 }
117
118 /**
119 * Whether this is a generic repost (kind 16)
120 */
121 get isGenericRepost(): boolean {
122 return this._event.kind === kinds.GenericRepost
123 }
124
125 /**
126 * The embedded/quoted event (if included in content)
127 */
128 get embeddedEvent(): Event | null {
129 return this._embeddedEvent
130 }
131
132 /**
133 * Whether the repost includes the embedded event
134 */
135 get hasEmbeddedEvent(): boolean {
136 return this._embeddedEvent !== null
137 }
138
139 /**
140 * Get the kind of the reposted event (from k tag)
141 */
142 get targetKind(): number | undefined {
143 const kTag = this._event.tags.find((t) => t[0] === 'k')
144 return kTag?.[1] ? parseInt(kTag[1], 10) : undefined
145 }
146 }
147