damus

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

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 }