adapters.ts raw
1 /**
2 * Adapter functions for gradual migration from legacy code to Content domain objects.
3 */
4
5 import { Event, kinds } from 'nostr-tools'
6 import { Note } from './Note'
7 import { Reaction } from './Reaction'
8 import { Repost } from './Repost'
9 import { BookmarkList } from './BookmarkList'
10 import { PinList } from './PinList'
11
12 // ============================================================================
13 // Note Adapters
14 // ============================================================================
15
16 /**
17 * Convert a Nostr event to a Note domain object
18 */
19 export const toNote = (event: Event): Note => {
20 return Note.fromEvent(event)
21 }
22
23 /**
24 * Try to create a Note from an event, returns null if invalid
25 */
26 export const tryToNote = (event: Event | null | undefined): Note | null => {
27 return Note.tryFromEvent(event)
28 }
29
30 /**
31 * Check if an event is a short text note
32 */
33 export const isNoteEvent = (event: Event): boolean => {
34 return event.kind === kinds.ShortTextNote
35 }
36
37 /**
38 * Convert multiple events to Notes
39 * Filters out non-note events
40 */
41 export const toNotes = (events: Event[]): Note[] => {
42 return events
43 .filter(isNoteEvent)
44 .map((e) => tryToNote(e))
45 .filter((n): n is Note => n !== null)
46 }
47
48 // ============================================================================
49 // Reaction Adapters
50 // ============================================================================
51
52 /**
53 * Convert a Nostr event to a Reaction domain object
54 */
55 export const toReaction = (event: Event): Reaction => {
56 return Reaction.fromEvent(event)
57 }
58
59 /**
60 * Try to create a Reaction from an event, returns null if invalid
61 */
62 export const tryToReaction = (event: Event | null | undefined): Reaction | null => {
63 return Reaction.tryFromEvent(event)
64 }
65
66 /**
67 * Check if an event is a reaction
68 */
69 export const isReactionEvent = (event: Event): boolean => {
70 return event.kind === kinds.Reaction
71 }
72
73 /**
74 * Convert multiple events to Reactions
75 */
76 export const toReactions = (events: Event[]): Reaction[] => {
77 return events
78 .filter(isReactionEvent)
79 .map((e) => tryToReaction(e))
80 .filter((r): r is Reaction => r !== null)
81 }
82
83 /**
84 * Group reactions by emoji
85 */
86 export const groupReactionsByEmoji = (
87 reactions: Reaction[]
88 ): Map<string, Reaction[]> => {
89 const groups = new Map<string, Reaction[]>()
90
91 for (const reaction of reactions) {
92 const emoji = reaction.emoji
93 const existing = groups.get(emoji) || []
94 existing.push(reaction)
95 groups.set(emoji, existing)
96 }
97
98 return groups
99 }
100
101 /**
102 * Count likes for an event
103 */
104 export const countLikes = (reactions: Reaction[]): number => {
105 return reactions.filter((r) => r.isLike).length
106 }
107
108 // ============================================================================
109 // Repost Adapters
110 // ============================================================================
111
112 /**
113 * Convert a Nostr event to a Repost domain object
114 */
115 export const toRepost = (event: Event): Repost => {
116 return Repost.fromEvent(event)
117 }
118
119 /**
120 * Try to create a Repost from an event, returns null if invalid
121 */
122 export const tryToRepost = (event: Event | null | undefined): Repost | null => {
123 return Repost.tryFromEvent(event)
124 }
125
126 /**
127 * Check if an event is a repost
128 */
129 export const isRepostEvent = (event: Event): boolean => {
130 return event.kind === kinds.Repost || event.kind === kinds.GenericRepost
131 }
132
133 /**
134 * Convert multiple events to Reposts
135 */
136 export const toReposts = (events: Event[]): Repost[] => {
137 return events
138 .filter(isRepostEvent)
139 .map((e) => tryToRepost(e))
140 .filter((r): r is Repost => r !== null)
141 }
142
143 // ============================================================================
144 // Content Type Detection
145 // ============================================================================
146
147 /**
148 * Determine the content type of an event
149 */
150 export const getContentType = (
151 event: Event
152 ): 'note' | 'reaction' | 'repost' | 'other' => {
153 if (event.kind === kinds.ShortTextNote) return 'note'
154 if (event.kind === kinds.Reaction) return 'reaction'
155 if (event.kind === kinds.Repost || event.kind === kinds.GenericRepost) return 'repost'
156 return 'other'
157 }
158
159 /**
160 * Parse any content event into its appropriate domain object
161 */
162 export const parseContentEvent = (
163 event: Event
164 ): Note | Reaction | Repost | null => {
165 const type = getContentType(event)
166
167 switch (type) {
168 case 'note':
169 return tryToNote(event)
170 case 'reaction':
171 return tryToReaction(event)
172 case 'repost':
173 return tryToRepost(event)
174 default:
175 return null
176 }
177 }
178
179 // ============================================================================
180 // BookmarkList Adapters
181 // ============================================================================
182
183 /**
184 * Convert a Nostr event to a BookmarkList domain object
185 */
186 export const toBookmarkList = (event: Event): BookmarkList => {
187 return BookmarkList.fromEvent(event)
188 }
189
190 /**
191 * Try to create a BookmarkList from an event, returns null if invalid
192 */
193 export const tryToBookmarkList = (event: Event | null | undefined): BookmarkList | null => {
194 return BookmarkList.tryFromEvent(event)
195 }
196
197 /**
198 * Check if an event is a bookmark list
199 */
200 export const isBookmarkListEvent = (event: Event): boolean => {
201 return event.kind === kinds.BookmarkList
202 }
203
204 // ============================================================================
205 // PinList Adapters
206 // ============================================================================
207
208 /**
209 * Convert a Nostr event to a PinList domain object
210 */
211 export const toPinList = (event: Event): PinList => {
212 return PinList.fromEvent(event)
213 }
214
215 /**
216 * Try to create a PinList from an event, returns null if invalid
217 */
218 export const tryToPinList = (event: Event | null | undefined): PinList | null => {
219 return PinList.tryFromEvent(event)
220 }
221
222 /**
223 * Check if an event is a pin list
224 */
225 export const isPinListEvent = (event: Event): boolean => {
226 return event.kind === kinds.Pinlist
227 }
228