damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

Post.swift (4563B)


      1 //
      2 //  Post.swift
      3 //  damus
      4 //
      5 //  Created by William Casarin on 2022-05-07.
      6 //
      7 
      8 import Foundation
      9 
     10 struct NostrPost {
     11     let kind: NostrKind
     12     let content: String
     13     let tags: [[String]]
     14 
     15     init(content: String, kind: NostrKind = .text, tags: [[String]] = []) {
     16         self.content = content
     17         self.kind = kind
     18         self.tags = tags
     19     }
     20     
     21     func to_event(keypair: FullKeypair) -> NostrEvent? {
     22         let post_blocks = self.parse_blocks()
     23         let post_tags = self.make_post_tags(post_blocks: post_blocks, tags: self.tags)
     24         let content = post_tags.blocks
     25             .map(\.asString)
     26             .joined(separator: "")
     27         
     28         if self.kind == .highlight {
     29             var new_tags = post_tags.tags.filter({ $0[safe: 0] != "comment" })
     30             if content.count > 0 {
     31                 new_tags.append(["comment", content])
     32             }
     33             return NostrEvent(content: self.content, keypair: keypair.to_keypair(), kind: self.kind.rawValue, tags: new_tags)
     34         }
     35         
     36         return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: self.kind.rawValue, tags: post_tags.tags)
     37     }
     38     
     39     func parse_blocks() -> [Block] {
     40         guard let content_for_parsing = self.default_content_for_block_parsing() else { return [] }
     41         return parse_post_blocks(content: content_for_parsing)?.blocks ?? []
     42     }
     43     
     44     private func default_content_for_block_parsing() -> String? {
     45         switch kind {
     46             case .highlight:
     47                 return tags.filter({ $0[safe: 0] == "comment" }).first?[safe: 1]
     48             default:
     49                 return self.content
     50         }
     51     }
     52     
     53     /// Parse the post's contents to find more tags to apply to the final nostr event
     54     func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags {
     55         var new_tags = tags
     56 
     57         for post_block in post_blocks {
     58             switch post_block {
     59             case .mention(let mention):
     60                 switch(mention.ref.nip19) {
     61                 case .note, .nevent:
     62                     continue
     63                 default:
     64                     break
     65                 }
     66 
     67                 new_tags.append(mention.ref.tag)
     68             case .hashtag(let hashtag):
     69                 new_tags.append(["t", hashtag.lowercased()])
     70             case .text: break
     71             case .invoice: break
     72             case .relay: break
     73             case .url(let url):
     74                 new_tags.append(["r", url.absoluteString])
     75                 break
     76             }
     77         }
     78 
     79         return PostTags(blocks: post_blocks, tags: new_tags)
     80     }
     81 }
     82 
     83 // MARK: - Helper structures and functions
     84 
     85 extension NostrPost {
     86     /// A struct used for temporarily holding tag information that was parsed from a post contents to aid in building a nostr event
     87     struct PostTags {
     88         let blocks: [Block]
     89         let tags: [[String]]
     90     }
     91 }
     92 
     93 /// This should only be used in tests, we don't use this anymore directly
     94 func parse_note_content(content: NoteContent) -> Blocks?
     95 {
     96     switch content {
     97     case .note(let note):
     98         return parse_post_blocks(content: note.content)
     99     case .content(let content, _):
    100         return parse_post_blocks(content: content)
    101     }
    102 }
    103 
    104 /// Return a list of tags
    105 func parse_post_blocks(content: String) -> Blocks? {
    106     let buf_size = 16000
    107     var buffer = Data(capacity: buf_size)
    108     var blocks_ptr = ndb_blocks_ptr()
    109     var ok = false
    110 
    111     return content.withCString { c_content -> Blocks? in
    112         buffer.withUnsafeMutableBytes { buf in
    113             let res = ndb_parse_content(buf, Int32(buf_size), c_content, Int32(content.utf8.count), &blocks_ptr.ptr)
    114             ok = res != 0
    115         }
    116 
    117         guard ok else { return nil }
    118 
    119         let words = ndb_blocks_word_count(blocks_ptr.ptr)
    120         let bs = collect_blocks(ptr: blocks_ptr, content: c_content)
    121         return Blocks(words: Int(words), blocks: bs)
    122     }
    123 }
    124 
    125 
    126 fileprivate func collect_blocks(ptr: ndb_blocks_ptr, content: UnsafePointer<CChar>) -> [Block] {
    127     var i = ndb_block_iterator()
    128     var blocks: [Block] = []
    129     var block_ptr = ndb_block_ptr()
    130 
    131     ndb_blocks_iterate_start(content, ptr.ptr, &i);
    132     block_ptr.ptr = ndb_blocks_iterate_next(&i)
    133     while (block_ptr.ptr != nil) {
    134         // tags are only used for indexed mentions which aren't used in
    135         // posts anymore, so to simplify the API let's set this to nil
    136         if let block = Block(block: block_ptr, tags: nil) {
    137             blocks.append(block);
    138         }
    139         block_ptr.ptr = ndb_blocks_iterate_next(&i)
    140     }
    141 
    142     return blocks
    143 }