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 }