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