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