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 }