damus

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

Contacts+.swift (5043B)


      1 //
      2 //  Contacts+.swift
      3 //  damus
      4 //
      5 //  Extra functionality and utilities for `Contacts.swift`
      6 //
      7 //  Created by Daniel D’Aquino on 2023-11-24.
      8 //
      9 
     10 import Foundation
     11 
     12 func follow_reference(box: PostBox, our_contacts: NostrEvent?, keypair: FullKeypair, follow: FollowRef) -> NostrEvent? {
     13     guard let ev = follow_user_event(our_contacts: our_contacts, keypair: keypair, follow: follow) else {
     14         return nil
     15     }
     16     
     17     box.send(ev)
     18 
     19     return ev
     20 }
     21 
     22 func unfollow_reference(postbox: PostBox, our_contacts: NostrEvent?, keypair: FullKeypair, unfollow: FollowRef) -> NostrEvent? {
     23     guard let cs = our_contacts else {
     24         return nil
     25     }
     26     
     27     guard let ev = unfollow_reference_event(our_contacts: cs, keypair: keypair, unfollow: unfollow) else {
     28         return nil
     29     }
     30 
     31     postbox.send(ev)
     32     
     33     return ev
     34 }
     35 
     36 func unfollow_reference_event(our_contacts: NostrEvent, keypair: FullKeypair, unfollow: FollowRef) -> NostrEvent? {
     37     let tags = our_contacts.tags.reduce(into: [[String]]()) { ts, tag in
     38         if let tag = FollowRef.from_tag(tag: tag), tag == unfollow {
     39             return
     40         }
     41 
     42         ts.append(tag.strings())
     43     }
     44 
     45     let kind = NostrKind.contacts.rawValue
     46 
     47     return NostrEvent(content: our_contacts.content, keypair: keypair.to_keypair(), kind: kind, tags: Array(tags))
     48 }
     49 
     50 func follow_user_event(our_contacts: NostrEvent?, keypair: FullKeypair, follow: FollowRef) -> NostrEvent? {
     51     guard let cs = our_contacts else {
     52         // don't create contacts for now so we don't nuke our contact list due to connectivity issues
     53         // we should only create contacts during profile creation
     54         //return create_contacts(relays: relays, our_pubkey: our_pubkey, follow: follow)
     55         return nil
     56     }
     57 
     58     guard let ev = follow_with_existing_contacts(keypair: keypair, our_contacts: cs, follow: follow) else {
     59         return nil
     60     }
     61     
     62     return ev
     63 }
     64 
     65 
     66 func decode_json_relays(_ content: String) -> [RelayURL: RelayInfo]? {
     67     return decode_json(content)
     68 }
     69 
     70 func remove_relay(ev: NostrEvent, current_relays: [RelayDescriptor], keypair: FullKeypair, relay: RelayURL) -> NostrEvent?{
     71     var relays = ensure_relay_info(relays: current_relays, content: ev.content)
     72     
     73     relays.removeValue(forKey: relay)
     74     
     75     guard let content = encode_json(relays) else {
     76         return nil
     77     }
     78     
     79     return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 3, tags: ev.tags.strings())
     80 }
     81 
     82 func add_relay(ev: NostrEvent, keypair: FullKeypair, current_relays: [RelayDescriptor], relay: RelayURL, info: RelayInfo) -> NostrEvent? {
     83     var relays = ensure_relay_info(relays: current_relays, content: ev.content)
     84     
     85     // If kind:3 content is empty, or if the relay doesn't exist in the list,
     86     // we want to create a kind:3 event with the new relay
     87     guard ev.content.isEmpty || relays.index(forKey: relay) == nil else {
     88         return nil
     89     }
     90     
     91     relays[relay] = info
     92     
     93     guard let content = encode_json(relays) else {
     94         return nil
     95     }
     96     
     97     return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 3, tags: ev.tags.strings())
     98 }
     99 
    100 func ensure_relay_info(relays: [RelayDescriptor], content: String) -> [RelayURL: RelayInfo] {
    101     return decode_json_relays(content) ?? make_contact_relays(relays)
    102 }
    103 
    104 func is_already_following(contacts: NostrEvent, follow: FollowRef) -> Bool {
    105     return contacts.references.contains { ref in
    106         switch (ref, follow) {
    107         case let (.hashtag(ht), .hashtag(follow_ht)):
    108             return ht.hashtag == follow_ht
    109         case let (.pubkey(pk), .pubkey(follow_pk)):
    110             return pk == follow_pk
    111         case (.hashtag, .pubkey), (.pubkey, .hashtag),
    112             (.event, _), (.quote, _), (.param, _), (.naddr, _):
    113             return false
    114         }
    115     }
    116 }
    117 func follow_with_existing_contacts(keypair: FullKeypair, our_contacts: NostrEvent, follow: FollowRef) -> NostrEvent? {
    118     // don't update if we're already following
    119     if is_already_following(contacts: our_contacts, follow: follow) {
    120         return nil
    121     }
    122 
    123     let kind = NostrKind.contacts.rawValue
    124 
    125     var tags = our_contacts.tags.strings()
    126     tags.append(follow.tag)
    127 
    128     return NostrEvent(content: our_contacts.content, keypair: keypair.to_keypair(), kind: kind, tags: tags)
    129 }
    130 
    131 func make_contact_relays(_ relays: [RelayDescriptor]) -> [RelayURL: RelayInfo] {
    132     return relays.reduce(into: [:]) { acc, relay in
    133         acc[relay.url] = relay.info
    134     }
    135 }
    136 
    137 func make_relay_metadata(relays: [RelayDescriptor], keypair: FullKeypair) -> NostrEvent? {
    138     let tags = relays.compactMap { r -> [String]? in
    139         var tag = ["r", r.url.absoluteString]
    140         if (r.info.read ?? true) != (r.info.write ?? true) {
    141             tag += r.info.read == true ? ["read"] : ["write"]
    142         }
    143         if ((r.info.read ?? true) || (r.info.write ?? true)) && r.variant == .regular {
    144             return tag;
    145         }
    146         return nil
    147     }
    148     return NostrEvent(content: "", keypair: keypair.to_keypair(), kind: 10_002, tags: tags)
    149 }