damus

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

Mentions.swift (8577B)


      1 //
      2 //  Mentions.swift
      3 //  damus
      4 //
      5 //  Created by William Casarin on 2022-05-04.
      6 //
      7 
      8 import Foundation
      9 
     10 enum MentionType: AsciiCharacter, TagKey {
     11     case p
     12     case e
     13     case a
     14     case r
     15 
     16     var keychar: AsciiCharacter {
     17         self.rawValue
     18     }
     19 }
     20 
     21 enum MentionRef: TagKeys, TagConvertible, Equatable, Hashable {
     22     case pubkey(Pubkey)
     23     case note(NoteId)
     24     case nevent(NEvent)
     25     case nprofile(NProfile)
     26     case nrelay(String)
     27     case naddr(NAddr)
     28 
     29     var key: MentionType {
     30         switch self {
     31         case .pubkey: return .p
     32         case .note: return .e
     33         case .nevent: return .e
     34         case .nprofile: return .p
     35         case .nrelay: return .r
     36         case .naddr: return .a
     37         }
     38     }
     39 
     40     var bech32: String {
     41         return Bech32Object.encode(toBech32Object())
     42     }
     43 
     44     static func from_bech32(str: String) -> MentionRef? {
     45         switch Bech32Object.parse(str) {
     46         case .note(let noteid): return .note(noteid)
     47         case .npub(let pubkey): return .pubkey(pubkey)
     48         default: return nil
     49         }
     50     }
     51 
     52     var pubkey: Pubkey? {
     53         switch self {
     54         case .pubkey(let pubkey): return pubkey
     55         case .note:              return nil
     56         case .nevent(let nevent): return nevent.author
     57         case .nprofile(let nprofile): return nprofile.author
     58         case .nrelay: return nil
     59         case .naddr: return nil
     60         }
     61     }
     62 
     63     var tag: [String] {
     64         switch self {
     65         case .pubkey(let pubkey): return ["p", pubkey.hex()]
     66         case .note(let noteId):   return ["e", noteId.hex()]
     67         case .nevent(let nevent): return ["e", nevent.noteid.hex()]
     68         case .nprofile(let nprofile): return ["p", nprofile.author.hex()]
     69         case .nrelay(let url): return ["r", url]
     70         case .naddr(let naddr): return ["a", naddr.kind.description + ":" + naddr.author.hex() + ":" + naddr.identifier.string()]
     71         }
     72     }
     73 
     74     static func from_tag(tag: TagSequence) -> MentionRef? {
     75         guard tag.count >= 2 else { return nil }
     76 
     77         var i = tag.makeIterator()
     78 
     79         guard let t0 = i.next(),
     80               let chr = t0.single_char,
     81               let mention_type = MentionType(rawValue: chr),
     82               let element = i.next()
     83         else {
     84             return nil
     85         }
     86 
     87         switch mention_type {
     88         case .p:
     89             guard let data = element.id() else { return nil }
     90             return .pubkey(Pubkey(data))
     91         case .e:
     92             guard let data = element.id() else { return nil }
     93             return .note(NoteId(data))
     94         case .a:
     95             let str = element.string()
     96             let data = str.split(separator: ":")
     97             if(data.count != 3) { return nil }
     98             
     99             guard let pubkey = Pubkey(hex: String(data[1])) else { return nil }
    100             guard let kind = UInt32(data[0]) else { return nil }
    101             
    102             return .naddr(NAddr(identifier: String(data[2]), author: pubkey, relays: [], kind: kind))
    103         case .r: return .nrelay(element.string())
    104         }
    105     }
    106     
    107     func toBech32Object() -> Bech32Object {
    108         switch self {
    109         case .pubkey(let pk):
    110             return .npub(pk)
    111         case .note(let noteid):
    112             return .note(noteid)
    113         case .naddr(let naddr):
    114             return .naddr(naddr)
    115         case .nevent(let nevent):
    116             return .nevent(nevent)
    117         case .nprofile(let nprofile):
    118             return .nprofile(nprofile)
    119         case .nrelay(let url):
    120             return .nrelay(url)
    121         }
    122     }
    123 }
    124 
    125 struct Mention<T: Equatable>: Equatable {
    126     let index: Int?
    127     let ref: T
    128 
    129     static func any(_ mention_id: MentionRef, index: Int? = nil) -> Mention<MentionRef> {
    130         return Mention<MentionRef>(index: index, ref: mention_id)
    131     }
    132 
    133     static func noteref(_ id: NoteRef, index: Int? = nil) -> Mention<NoteRef> {
    134         return Mention<NoteRef>(index: index, ref: id)
    135     }
    136 
    137     static func note(_ id: NoteId, index: Int? = nil) -> Mention<NoteId> {
    138         return Mention<NoteId>(index: index, ref: id)
    139     }
    140 
    141     static func pubkey(_ pubkey: Pubkey, index: Int? = nil) -> Mention<Pubkey> {
    142         return Mention<Pubkey>(index: index, ref: pubkey)
    143     }
    144 }
    145 
    146 typealias Invoice = LightningInvoice<Amount>
    147 typealias ZapInvoice = LightningInvoice<Int64>
    148 
    149 enum InvoiceDescription {
    150     case description(String)
    151     case description_hash(Data)
    152 }
    153 
    154 struct LightningInvoice<T> {
    155     let description: InvoiceDescription
    156     let amount: T
    157     let string: String
    158     let expiry: UInt64
    159     let payment_hash: Data
    160     let created_at: UInt64
    161     
    162     var description_string: String {
    163         switch description {
    164         case .description(let string):
    165             return string
    166         case .description_hash:
    167             return ""
    168         }
    169     }
    170 }
    171 
    172 func maybe_pointee<T>(_ p: UnsafeMutablePointer<T>!) -> T? {
    173     guard p != nil else {
    174         return nil
    175     }
    176     return p.pointee
    177 }
    178 
    179 enum Amount: Equatable {
    180     case any
    181     case specific(Int64)
    182     
    183     func amount_sats_str() -> String {
    184         switch self {
    185         case .any:
    186             return NSLocalizedString("Any", comment: "Any amount of sats")
    187         case .specific(let amt):
    188             return format_msats(amt)
    189         }
    190     }
    191 }
    192 
    193 func format_msats_abbrev(_ msats: Int64) -> String {
    194     let formatter = NumberFormatter()
    195     formatter.numberStyle = .decimal
    196     formatter.positiveSuffix = "m"
    197     formatter.positivePrefix = ""
    198     formatter.minimumFractionDigits = 0
    199     formatter.maximumFractionDigits = 3
    200     formatter.roundingMode = .down
    201     formatter.roundingIncrement = 0.1
    202     formatter.multiplier = 1
    203     
    204     let sats = NSNumber(value: (Double(msats) / 1000.0))
    205     
    206     if msats >= 1_000_000*1000 {
    207         formatter.positiveSuffix = "m"
    208         formatter.multiplier = 0.000001
    209     } else if msats >= 1000*1000 {
    210         formatter.positiveSuffix = "k"
    211         formatter.multiplier = 0.001
    212     } else {
    213         return sats.stringValue
    214     }
    215     
    216     return formatter.string(from: sats) ?? sats.stringValue
    217 }
    218 
    219 func format_msats(_ msat: Int64, locale: Locale = Locale.current) -> String {
    220     let numberFormatter = NumberFormatter()
    221     numberFormatter.numberStyle = .decimal
    222     numberFormatter.minimumFractionDigits = 0
    223     numberFormatter.maximumFractionDigits = 3
    224     numberFormatter.roundingMode = .down
    225     numberFormatter.locale = locale
    226 
    227     let sats = NSNumber(value: (Double(msat) / 1000.0))
    228     let formattedSats = numberFormatter.string(from: sats) ?? sats.stringValue
    229 
    230     let format = localizedStringFormat(key: "sats_count", locale: locale)
    231     return String(format: format, locale: locale, sats.decimalValue as NSDecimalNumber, formattedSats)
    232 }
    233 
    234 func convert_invoice_description(b11: bolt11) -> InvoiceDescription? {
    235     if let desc = b11.description {
    236         return .description(String(cString: desc))
    237     }
    238     
    239     if var deschash = maybe_pointee(b11.description_hash) {
    240         return .description_hash(Data(bytes: &deschash, count: 32))
    241     }
    242     
    243     return nil
    244 }
    245 
    246 func find_tag_ref(type: String, id: String, tags: [[String]]) -> Int? {
    247     var i: Int = 0
    248     for tag in tags {
    249         if tag.count >= 2 {
    250             if tag[0] == type && tag[1] == id {
    251                 return i
    252             }
    253         }
    254         i += 1
    255     }
    256     
    257     return nil
    258 }
    259 
    260 struct PostTags {
    261     let blocks: [Block]
    262     let tags: [[String]]
    263 }
    264 
    265 /// Convert
    266 func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags {
    267     var new_tags = tags
    268 
    269     for post_block in post_blocks {
    270         switch post_block {
    271         case .mention(let mention):
    272             switch(mention.ref) {
    273             case .note, .nevent:
    274                 continue
    275             default:
    276                 break
    277             }
    278 
    279             new_tags.append(mention.ref.tag)
    280         case .hashtag(let hashtag):
    281             new_tags.append(["t", hashtag.lowercased()])
    282         case .text: break
    283         case .invoice: break
    284         case .relay: break
    285         case .url(let url):
    286             new_tags.append(["r", url.absoluteString])
    287             break
    288         }
    289     }
    290     
    291     return PostTags(blocks: post_blocks, tags: new_tags)
    292 }
    293 
    294 func post_to_event(post: NostrPost, keypair: FullKeypair) -> NostrEvent? {
    295     let tags = post.references.map({ r in r.tag }) + post.tags
    296     let post_blocks = parse_post_blocks(content: post.content)
    297     let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags)
    298     let content = post_tags.blocks
    299         .map(\.asString)
    300         .joined(separator: "")
    301     return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: post.kind.rawValue, tags: post_tags.tags)
    302 }