adapters.ts raw
1 /**
2 * Adapter functions for gradual migration from legacy code to Social domain objects.
3 *
4 * These functions allow existing providers and services to continue working
5 * while new code can use the rich domain objects.
6 */
7
8 import { Event } from 'nostr-tools'
9 import { tryToPubkey } from '../shared'
10 import { FollowList } from './FollowList'
11 import { MuteList, MuteVisibility } from './MuteList'
12 import { PinnedUsersList } from './PinnedUsersList'
13
14 // ============================================================================
15 // FollowList Adapters
16 // ============================================================================
17
18 /**
19 * Convert a Nostr event to a FollowList domain object
20 */
21 export const toFollowList = (event: Event): FollowList => {
22 return FollowList.fromEvent(event)
23 }
24
25 /**
26 * Try to create a FollowList from an event, returns null if invalid
27 */
28 export const tryToFollowList = (event: Event | null | undefined): FollowList | null => {
29 if (!event) return null
30 try {
31 return FollowList.fromEvent(event)
32 } catch {
33 return null
34 }
35 }
36
37 /**
38 * Convert a FollowList to a legacy hex string set
39 */
40 export const fromFollowListToHexSet = (followList: FollowList): Set<string> => {
41 return new Set(followList.getFollowing().map((p) => p.hex))
42 }
43
44 /**
45 * Convert a FollowList to a legacy hex string array
46 */
47 export const fromFollowListToHexArray = (followList: FollowList): string[] => {
48 return followList.getFollowing().map((p) => p.hex)
49 }
50
51 /**
52 * Check if a hex pubkey is in a FollowList
53 */
54 export const isFollowingHex = (followList: FollowList, hex: string): boolean => {
55 const pubkey = tryToPubkey(hex)
56 return pubkey ? followList.isFollowing(pubkey) : false
57 }
58
59 /**
60 * Add a hex pubkey to a FollowList
61 * @returns true if added, false if already following or invalid
62 */
63 export const followByHex = (
64 followList: FollowList,
65 hex: string,
66 relayHint?: string,
67 petname?: string
68 ): boolean => {
69 const pubkey = tryToPubkey(hex)
70 if (!pubkey) return false
71 try {
72 const change = followList.follow(pubkey, relayHint, petname)
73 return change.type === 'added'
74 } catch {
75 return false
76 }
77 }
78
79 /**
80 * Remove a hex pubkey from a FollowList
81 * @returns true if removed, false if not following or invalid
82 */
83 export const unfollowByHex = (followList: FollowList, hex: string): boolean => {
84 const pubkey = tryToPubkey(hex)
85 if (!pubkey) return false
86 const change = followList.unfollow(pubkey)
87 return change.type === 'removed'
88 }
89
90 // ============================================================================
91 // MuteList Adapters
92 // ============================================================================
93
94 /**
95 * Convert a Nostr event to a MuteList domain object
96 *
97 * @param event The mute list event
98 * @param decryptedPrivateTags The decrypted private tags (from NIP-04 content)
99 */
100 export const toMuteList = (
101 event: Event,
102 decryptedPrivateTags: string[][] = []
103 ): MuteList => {
104 return MuteList.fromEvent(event, decryptedPrivateTags)
105 }
106
107 /**
108 * Try to create a MuteList from an event, returns null if invalid
109 */
110 export const tryToMuteList = (
111 event: Event | null | undefined,
112 decryptedPrivateTags: string[][] = []
113 ): MuteList | null => {
114 if (!event) return null
115 try {
116 return MuteList.fromEvent(event, decryptedPrivateTags)
117 } catch {
118 return null
119 }
120 }
121
122 /**
123 * Convert a MuteList to a legacy hex string set (all mutes)
124 */
125 export const fromMuteListToHexSet = (muteList: MuteList): Set<string> => {
126 return new Set(muteList.getAllMuted().map((p) => p.hex))
127 }
128
129 /**
130 * Convert a MuteList's public mutes to a legacy hex string set
131 */
132 export const fromMuteListToPublicHexSet = (muteList: MuteList): Set<string> => {
133 return new Set(muteList.getPublicMuted().map((p) => p.hex))
134 }
135
136 /**
137 * Convert a MuteList's private mutes to a legacy hex string set
138 */
139 export const fromMuteListToPrivateHexSet = (muteList: MuteList): Set<string> => {
140 return new Set(muteList.getPrivateMuted().map((p) => p.hex))
141 }
142
143 /**
144 * Check if a hex pubkey is muted
145 */
146 export const isMutedHex = (muteList: MuteList, hex: string): boolean => {
147 const pubkey = tryToPubkey(hex)
148 return pubkey ? muteList.isMuted(pubkey) : false
149 }
150
151 /**
152 * Get the mute visibility for a hex pubkey
153 */
154 export const getMuteVisibilityByHex = (
155 muteList: MuteList,
156 hex: string
157 ): MuteVisibility | null => {
158 const pubkey = tryToPubkey(hex)
159 return pubkey ? muteList.getMuteVisibility(pubkey) : null
160 }
161
162 /**
163 * Mute a hex pubkey publicly
164 * @returns true if muted, false if already muted or invalid
165 */
166 export const mutePubliclyByHex = (muteList: MuteList, hex: string): boolean => {
167 const pubkey = tryToPubkey(hex)
168 if (!pubkey) return false
169 try {
170 const change = muteList.mutePublicly(pubkey)
171 return change.type !== 'no_change'
172 } catch {
173 return false
174 }
175 }
176
177 /**
178 * Mute a hex pubkey privately
179 * @returns true if muted, false if already muted or invalid
180 */
181 export const mutePrivatelyByHex = (muteList: MuteList, hex: string): boolean => {
182 const pubkey = tryToPubkey(hex)
183 if (!pubkey) return false
184 try {
185 const change = muteList.mutePrivately(pubkey)
186 return change.type !== 'no_change'
187 } catch {
188 return false
189 }
190 }
191
192 /**
193 * Unmute a hex pubkey
194 * @returns true if unmuted, false if not muted or invalid
195 */
196 export const unmuteByHex = (muteList: MuteList, hex: string): boolean => {
197 const pubkey = tryToPubkey(hex)
198 if (!pubkey) return false
199 const change = muteList.unmute(pubkey)
200 return change.type === 'unmuted'
201 }
202
203 // ============================================================================
204 // PinnedUsersList Adapters
205 // ============================================================================
206
207 /**
208 * Convert a Nostr event to a PinnedUsersList domain object
209 *
210 * @param event The pinned users list event
211 * @param decryptedPrivateTags The decrypted private tags (from NIP-04 content)
212 */
213 export const toPinnedUsersList = (
214 event: Event,
215 decryptedPrivateTags: string[][] = []
216 ): PinnedUsersList => {
217 const list = PinnedUsersList.fromEvent(event)
218 if (decryptedPrivateTags.length > 0) {
219 list.setPrivatePins(decryptedPrivateTags)
220 }
221 return list
222 }
223
224 /**
225 * Convert a PinnedUsersList to a legacy hex string set (all pins)
226 */
227 export const fromPinnedUsersListToHexSet = (pinnedUsersList: PinnedUsersList): Set<string> => {
228 return new Set(pinnedUsersList.getPinnedPubkeys().map((p) => p.hex))
229 }
230
231 /**
232 * Check if a hex pubkey is pinned
233 */
234 export const isPinnedHex = (pinnedUsersList: PinnedUsersList, hex: string): boolean => {
235 const pubkey = tryToPubkey(hex)
236 return pubkey ? pinnedUsersList.isPinned(pubkey) : false
237 }
238
239 /**
240 * Pin a hex pubkey
241 * @returns true if pinned, false if already pinned or invalid
242 */
243 export const pinByHex = (pinnedUsersList: PinnedUsersList, hex: string): boolean => {
244 const pubkey = tryToPubkey(hex)
245 if (!pubkey) return false
246 try {
247 const change = pinnedUsersList.pin(pubkey)
248 return change.type === 'pinned'
249 } catch {
250 return false
251 }
252 }
253
254 /**
255 * Unpin a hex pubkey
256 * @returns true if unpinned, false if not pinned or invalid
257 */
258 export const unpinByHex = (pinnedUsersList: PinnedUsersList, hex: string): boolean => {
259 const pubkey = tryToPubkey(hex)
260 if (!pubkey) return false
261 const change = pinnedUsersList.unpin(pubkey)
262 return change.type === 'unpinned'
263 }
264
265 // ============================================================================
266 // Combined Adapters
267 // ============================================================================
268
269 /**
270 * Create a function to check if a pubkey should be filtered out
271 * (either muted or not following, depending on context)
272 */
273 export const createMuteFilter = (
274 muteList: MuteList
275 ): ((hex: string) => boolean) => {
276 const mutedSet = fromMuteListToHexSet(muteList)
277 return (hex: string) => mutedSet.has(hex)
278 }
279
280 /**
281 * Create a function to check if a pubkey is being followed
282 */
283 export const createFollowFilter = (
284 followList: FollowList
285 ): ((hex: string) => boolean) => {
286 const followingSet = fromFollowListToHexSet(followList)
287 return (hex: string) => followingSet.has(hex)
288 }
289
290 /**
291 * Create a function to check if a pubkey is pinned
292 */
293 export const createPinnedFilter = (
294 pinnedUsersList: PinnedUsersList
295 ): ((hex: string) => boolean) => {
296 const pinnedSet = fromPinnedUsersListToHexSet(pinnedUsersList)
297 return (hex: string) => pinnedSet.has(hex)
298 }
299