Block.swift (5513B)
1 // 2 // Block.swift 3 // damus 4 // 5 // Created by Kyle Roucis on 2023-08-21. 6 // 7 8 import Foundation 9 10 11 fileprivate extension String { 12 /// Failable initializer to build a Swift.String from a C-backed `str_block_t`. 13 init?(_ s: str_block_t) { 14 let len = s.end - s.start 15 let bytes = Data(bytes: s.start, count: len) 16 self.init(bytes: bytes, encoding: .utf8) 17 } 18 } 19 20 /// Represents a block of data stored by the NOSTR protocol. This can be 21 /// simple text, a hashtag, a url, a relay reference, a mention ref and 22 /// potentially more in the future. 23 enum Block: Equatable { 24 static func == (lhs: Block, rhs: Block) -> Bool { 25 switch (lhs, rhs) { 26 case (.text(let a), .text(let b)): 27 return a == b 28 case (.mention(let a), .mention(let b)): 29 return a == b 30 case (.hashtag(let a), .hashtag(let b)): 31 return a == b 32 case (.url(let a), .url(let b)): 33 return a == b 34 case (.invoice(let a), .invoice(let b)): 35 return a.string == b.string 36 case (_, _): 37 return false 38 } 39 } 40 41 case text(String) 42 case mention(Mention<MentionRef>) 43 case hashtag(String) 44 case url(URL) 45 case invoice(Invoice) 46 case relay(String) 47 } 48 49 struct Blocks: Equatable { 50 let words: Int 51 let blocks: [Block] 52 } 53 54 extension Block { 55 /// Failable initializer for the C-backed type `block_t`. This initializer will inspect 56 /// the underlying block type and build the appropriate enum value as needed. 57 init?(_ block: block_t, tags: TagsSequence? = nil) { 58 switch block.type { 59 case BLOCK_HASHTAG: 60 guard let str = String(block.block.str) else { 61 return nil 62 } 63 self = .hashtag(str) 64 case BLOCK_TEXT: 65 guard let str = String(block.block.str) else { 66 return nil 67 } 68 self = .text(str) 69 case BLOCK_MENTION_INDEX: 70 guard let b = Block(index: Int(block.block.mention_index), tags: tags) else { 71 return nil 72 } 73 self = b 74 case BLOCK_URL: 75 guard let b = Block(block.block.str) else { 76 return nil 77 } 78 self = b 79 case BLOCK_INVOICE: 80 guard let b = Block(invoice: block.block.invoice) else { 81 return nil 82 } 83 self = b 84 case BLOCK_MENTION_BECH32: 85 guard let b = Block(bech32: block.block.mention_bech32) else { 86 return nil 87 } 88 self = b 89 default: 90 return nil 91 } 92 } 93 } 94 fileprivate extension Block { 95 /// Failable initializer for the C-backed type `str_block_t`. 96 init?(_ b: str_block_t) { 97 guard let str = String(b) else { 98 return nil 99 } 100 101 if let url = URL(string: str) { 102 self = .url(url) 103 } 104 else { 105 self = .text(str) 106 } 107 } 108 } 109 fileprivate extension Block { 110 /// Failable initializer for a block index and a tag sequence. 111 init?(index: Int, tags: TagsSequence? = nil) { 112 guard let tags, 113 index >= 0, 114 index + 1 <= tags.count 115 else { 116 self = .text("#[\(index)]") 117 return 118 } 119 120 let tag = tags[index] 121 122 if let mention = MentionRef.from_tag(tag: tag) { 123 self = .mention(.any(mention, index: index)) 124 } 125 else { 126 self = .text("#[\(index)]") 127 } 128 } 129 } 130 fileprivate extension Block { 131 /// Failable initializer for the C-backed type `invoice_block_t`. 132 init?(invoice: invoice_block_t) { 133 guard let invstr = String(invoice.invstr) else { 134 return nil 135 } 136 137 guard var b11 = maybe_pointee(invoice.bolt11) else { 138 return nil 139 } 140 141 guard let description = convert_invoice_description(b11: b11) else { 142 return nil 143 } 144 145 let amount: Amount = maybe_pointee(b11.msat).map { .specific(Int64($0.millisatoshis)) } ?? .any 146 let payment_hash = Data(bytes: &b11.payment_hash, count: 32) 147 let created_at = b11.timestamp 148 149 tal_free(invoice.bolt11) 150 self = .invoice(Invoice(description: description, amount: amount, string: invstr, expiry: b11.expiry, payment_hash: payment_hash, created_at: created_at)) 151 } 152 } 153 154 fileprivate extension Block { 155 /// Failable initializer for the C-backed type `mention_bech32_block_t`. This initializer will inspect the 156 /// bech32 type code and build the appropriate enum type. 157 init?(bech32 b: mention_bech32_block_t) { 158 guard let decoded = decodeCBech32(b.bech32) else { 159 return nil 160 } 161 guard let ref = decoded.toMentionRef() else { 162 return nil 163 } 164 self = .mention(.any(ref)) 165 } 166 } 167 extension Block { 168 var asString: String { 169 switch self { 170 case .mention(let m): 171 if let idx = m.index { 172 return "#[\(idx)]" 173 } 174 175 return "nostr:" + Bech32Object.encode(m.ref.toBech32Object()) 176 case .relay(let relay): 177 return relay 178 case .text(let txt): 179 return txt 180 case .hashtag(let htag): 181 return "#" + htag 182 case .url(let url): 183 return url.absoluteString 184 case .invoice(let inv): 185 return inv.string 186 } 187 } 188 }