damus

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

Nostr.swift (9451B)


      1 //
      2 //  Nostr.swift
      3 //  damus
      4 //
      5 //  Created by William Casarin on 2022-04-07.
      6 //
      7 
      8 import Foundation
      9 
     10 typealias Profile = NdbProfile
     11 typealias ProfileKey = UInt64
     12 //typealias ProfileRecord = NdbProfileRecord
     13 
     14 class ProfileRecord {
     15     let data: NdbProfileRecord
     16 
     17     init(data: NdbProfileRecord, key: ProfileKey) {
     18         self.data = data
     19         self.profileKey = key
     20     }
     21 
     22     let profileKey: ProfileKey
     23     var profile: Profile? { return data.profile }
     24     var receivedAt: UInt64 { data.receivedAt }
     25     var noteKey: UInt64 { data.noteKey }
     26 
     27     private var _lnurl: String? = nil
     28     var lnurl: String? {
     29         if let _lnurl {
     30             return _lnurl
     31         }
     32         
     33         guard let profile = data.profile,
     34               let addr = (profile.lud16 ?? profile.lud06)?.trimmingCharacters(in: .whitespaces)
     35         else {
     36             return nil;
     37         }
     38         
     39         if addr.contains("@") {
     40             // this is a heavy op and is used a lot in views, cache it!
     41             let addr = lnaddress_to_lnurl(addr);
     42             self._lnurl = addr
     43             return addr
     44         }
     45         
     46         if !addr.lowercased().hasPrefix("lnurl") {
     47             return nil
     48         }
     49         
     50         return addr;
     51     }
     52     
     53 }
     54 
     55 extension NdbProfile {
     56     var display_name: String? {
     57         return displayName
     58     }
     59 
     60     static func displayName(profile: Profile?, pubkey: Pubkey) -> DisplayName {
     61         return DisplayName(name: profile?.name, display_name: profile?.display_name, pubkey: pubkey)
     62     }
     63 
     64     var damus_donation: Int? {
     65         return Int(damusDonation)
     66     }
     67 
     68     var damus_donation_v2: Int {
     69         return Int(damusDonationV2)
     70     }
     71 
     72     var website_url: URL? {
     73         if self.website?.trimmingCharacters(in: .whitespacesAndNewlines) == "" {
     74             return nil
     75         }
     76         return self.website.flatMap { url in
     77             let trim = url.trimmingCharacters(in: .whitespacesAndNewlines)
     78             if !(trim.hasPrefix("http://") || trim.hasPrefix("https://")) {
     79                 return URL(string: "https://" + trim)
     80             }
     81             return URL(string: trim)
     82         }
     83     }
     84 
     85     init(name: String? = nil, display_name: String? = nil, about: String? = nil, picture: String? = nil, banner: String? = nil, website: String? = nil, lud06: String? = nil, lud16: String? = nil, nip05: String? = nil, damus_donation: Int? = nil, reactions: Bool = true) {
     86 
     87         var fbb = FlatBufferBuilder()
     88 
     89         let name_off = fbb.create(string: name)
     90         let display_name_off = fbb.create(string: display_name)
     91         let about_off = fbb.create(string: about)
     92         let picture_off = fbb.create(string: picture)
     93         let banner_off = fbb.create(string: banner)
     94         let website_off = fbb.create(string: website)
     95         let lud06_off = fbb.create(string: lud06)
     96         let lud16_off = fbb.create(string: lud16)
     97         let nip05_off = fbb.create(string: nip05)
     98 
     99         let profile_data = NdbProfile.createNdbProfile(&fbb,
    100                                     nameOffset: name_off,
    101                                     websiteOffset: website_off,
    102                                     aboutOffset: about_off,
    103                                     lud16Offset: lud16_off,
    104                                     bannerOffset: banner_off,
    105                                     displayNameOffset: display_name_off,
    106                                     reactions: reactions,
    107                                     pictureOffset: picture_off,
    108                                     nip05Offset: nip05_off,
    109                                     damusDonation: 0,
    110                                     damusDonationV2: damus_donation.map({ Int32($0) }) ?? 0,
    111                                     lud06Offset: lud06_off)
    112 
    113         fbb.finish(offset: profile_data)
    114 
    115         var buf = ByteBuffer(bytes: fbb.sizedByteArray)
    116         let profile: Profile = try! getCheckedRoot(byteBuffer: &buf)
    117         self = profile
    118     }
    119 }
    120 
    121 /*
    122 class Profile: Codable {
    123     var value: [String: AnyCodable]
    124     
    125     init(name: String? = nil, display_name: String? = nil, about: String? = nil, picture: String? = nil, banner: String? = nil, website: String? = nil, lud06: String? = nil, lud16: String? = nil, nip05: String? = nil, damus_donation: Int? = nil) {
    126         self.value = [:]
    127         self.name = name
    128         self.display_name = display_name
    129         self.about = about
    130         self.picture = picture
    131         self.banner = banner
    132         self.website = website
    133         self.lud06 = lud06
    134         self.lud16 = lud16
    135         self.nip05 = nip05
    136         self.damus_donation = damus_donation
    137     }
    138     
    139     private func str(_ str: String) -> String? {
    140         return get_val(str)
    141     }
    142     
    143     private func int(_ key: String) -> Int? {
    144         return get_val(key)
    145     }
    146     
    147     private func get_val<T>(_ v: String) -> T? {
    148         guard let val = self.value[v] else{
    149             return nil
    150         }
    151         
    152         guard let s = val.value as? T else {
    153             return nil
    154         }
    155         
    156         return s
    157     }
    158     
    159     private func set_val<T>(_ key: String, _ val: T?) {
    160         if val == nil {
    161             self.value.removeValue(forKey: key)
    162             return
    163         }
    164         
    165         self.value[key] = AnyCodable.init(val)
    166     }
    167     
    168     private func set_str(_ key: String, _ val: String?) {
    169         set_val(key, val)
    170     }
    171     
    172     private func set_int(_ key: String, _ val: Int?) {
    173         set_val(key, val)
    174     }
    175     
    176     var reactions: Bool? {
    177         get { return get_val("reactions"); }
    178         set(s) { set_val("reactions", s) }
    179     }
    180     
    181     var display_name: String? {
    182         get { return str("display_name"); }
    183         set(s) { set_str("display_name", s) }
    184     }
    185     
    186     var name: String? {
    187         get { return str("name"); }
    188         set(s) { set_str("name", s) }
    189     }
    190     
    191     var about: String? {
    192         get { return str("about"); }
    193         set(s) { set_str("about", s) }
    194     }
    195     
    196     var damus_donation: Int? {
    197         get { return int("damus_donation_v2"); }
    198         set(s) { set_int("damus_donation_v2", s) }
    199     }
    200     
    201     var picture: String? {
    202         get { return str("picture"); }
    203         set(s) { set_str("picture", s) }
    204     }
    205     
    206     var banner: String? {
    207         get { return str("banner"); }
    208         set(s) { set_str("banner", s) }
    209     }
    210     
    211     var website: String? {
    212         get { return str("website"); }
    213         set(s) { set_str("website", s) }
    214     }
    215     
    216     var lud06: String? {
    217         get { return str("lud06"); }
    218         set(s) { set_str("lud06", s) }
    219     }
    220     
    221     var lud16: String? {
    222         get { return str("lud16"); }
    223         set(s) { set_str("lud16", s) }
    224     }
    225     
    226     var website_url: URL? {
    227         if self.website?.trimmingCharacters(in: .whitespacesAndNewlines) == "" {
    228             return nil
    229         }
    230         return self.website.flatMap { url in
    231             let trim = url.trimmingCharacters(in: .whitespacesAndNewlines)
    232             if !(trim.hasPrefix("http://") || trim.hasPrefix("https://")) {
    233                 return URL(string: "https://" + trim)
    234             }
    235             return URL(string: trim)
    236         }
    237     }
    238     
    239     func cache_lnurl() {
    240         guard self._lnurl == nil else {
    241             return
    242         }
    243         
    244         guard let addr = lud16 ?? lud06 else {
    245             return
    246         }
    247         
    248         self._lnurl = lnaddress_to_lnurl(addr)
    249     }
    250     
    251     private var _lnurl: String? = nil
    252     var lnurl: String? {
    253         if let _lnurl {
    254             return _lnurl
    255         }
    256         
    257         guard let addr = lud16 ?? lud06 else {
    258             return nil;
    259         }
    260         
    261         if addr.contains("@") {
    262             // this is a heavy op and is used a lot in views, cache it!
    263             let addr = lnaddress_to_lnurl(addr);
    264             self._lnurl = addr
    265             return addr
    266         }
    267         
    268         if !addr.lowercased().hasPrefix("lnurl") {
    269             return nil
    270         }
    271         
    272         return addr;
    273     }
    274     
    275     var nip05: String? {
    276         get { return str("nip05"); }
    277         set(s) { set_str("nip05", s) }
    278     }
    279     
    280     var lightning_uri: URL? {
    281         return make_ln_url(self.lnurl)
    282     }
    283     
    284     init() {
    285         self.value = [:]
    286     }
    287     
    288     required init(from decoder: Decoder) throws {
    289         let container = try decoder.singleValueContainer()
    290         self.value = try container.decode([String: AnyCodable].self)
    291     }
    292     
    293     func encode(to encoder: Encoder) throws {
    294         var container = encoder.singleValueContainer()
    295         try container.encode(value)
    296     }
    297     
    298     static func displayName(profile: Profile?, pubkey: Pubkey) -> DisplayName {
    299         return parse_display_name(profile: profile, pubkey: pubkey)
    300     }
    301 }
    302 */
    303 
    304 func make_test_profile() -> Profile {
    305     return Profile(name: "jb55", display_name: "Will", about: "Its a me", picture: "https://cdn.jb55.com/img/red-me.jpg", banner: "https://pbs.twimg.com/profile_banners/9918032/1531711830/600x200", website: "jb55.com", lud06: nil, lud16: "jb55@jb55.com", nip05: "jb55@jb55.com", damus_donation: 1)
    306 }
    307 
    308 func make_ln_url(_ str: String?) -> URL? {
    309     return str.flatMap { URL(string: "lightning:" + $0) }
    310 }
    311 
    312 func lnaddress_to_lnurl(_ lnaddr: String) -> String? {
    313     let parts = lnaddr.split(separator: "@")
    314     guard parts.count == 2 else {
    315         return nil
    316     }
    317     
    318     let url = "https://\(parts[1])/.well-known/lnurlp/\(parts[0])";
    319     guard let dat = url.data(using: .utf8) else {
    320         return nil
    321     }
    322     
    323     return bech32_encode(hrp: "lnurl", Array(dat))
    324 }
    325