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 }