commit 1702c21d3be100ccd57a04d7d9ecc4153aa916e1 Author: William Casarin Date: Wed Jul 26 16:09:12 2023 -0700 wip proper data diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 9ce74c5bd625..f14906e39b81 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -76,6 +76,7 @@ 4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8B28398BC6008A31F1 /* Keys.swift */; }; 4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */; }; 4C28A4122A6D03D200C1A7A5 /* ReferencedId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C28A4112A6D03D200C1A7A5 /* ReferencedId.swift */; }; + 4C2B7BF22A71B6540049DEE7 /* Id.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B7BF12A71B6540049DEE7 /* Id.swift */; }; 4C2CDDF7299D4A5E00879FD5 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */; }; 4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */; }; 4C30AC7429A5680900E2BD5A /* EventGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7329A5680900E2BD5A /* EventGroupView.swift */; }; @@ -529,6 +530,7 @@ 4C285C8B28398BC6008A31F1 /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = ""; }; 4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveKeysView.swift; sourceTree = ""; }; 4C28A4112A6D03D200C1A7A5 /* ReferencedId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferencedId.swift; sourceTree = ""; }; + 4C2B7BF12A71B6540049DEE7 /* Id.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Id.swift; sourceTree = ""; }; 4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debouncer.swift; sourceTree = ""; }; 4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = ""; }; 4C30AC7329A5680900E2BD5A /* EventGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventGroupView.swift; sourceTree = ""; }; @@ -1223,6 +1225,7 @@ 4C363A8F28247A1D006E126D /* NostrLink.swift */, 50088DA029E8271A008A1FDF /* WebSocket.swift */, 4C28A4112A6D03D200C1A7A5 /* ReferencedId.swift */, + 4C2B7BF12A71B6540049DEE7 /* Id.swift */, ); path = Nostr; sourceTree = ""; @@ -2021,6 +2024,7 @@ 4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */, 4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */, 4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */, + 4C2B7BF22A71B6540049DEE7 /* Id.swift in Sources */, 7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */, 4C7D09722A0AEF5E00943473 /* DamusGradient.swift in Sources */, 4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */, diff --git a/damus/Components/ImageCarousel.swift b/damus/Components/ImageCarousel.swift index 0bd4fde50cba..f99b672959fc 100644 --- a/damus/Components/ImageCarousel.swift +++ b/damus/Components/ImageCarousel.swift @@ -57,7 +57,7 @@ enum ImageShape { struct ImageCarousel: View { var urls: [MediaUrl] - let evid: String + let evid: NoteId let state: DamusState @@ -72,7 +72,7 @@ struct ImageCarousel: View { @State private var selectedIndex = 0 @State private var video_size: CGSize? = nil - init(state: DamusState, evid: String, urls: [MediaUrl]) { + init(state: DamusState, evid: NoteId, urls: [MediaUrl]) { _open_sheet = State(initialValue: false) _current_url = State(initialValue: nil) let media_model = state.events.get_cache_data(evid).media_metadata_model @@ -289,7 +289,7 @@ public struct ImageFill { struct ImageCarousel_Previews: PreviewProvider { static var previews: some View { let url: MediaUrl = .image(URL(string: "https://jb55.com/red-me.jpg")!) - ImageCarousel(state: test_damus_state(), evid: "evid", urls: [url, url]) + ImageCarousel(state: test_damus_state(), evid: test_note.id, urls: [url, url]) } } diff --git a/damus/Components/InvoiceView.swift b/damus/Components/InvoiceView.swift index c7afc21a1da3..7673be033cf6 100644 --- a/damus/Components/InvoiceView.swift +++ b/damus/Components/InvoiceView.swift @@ -9,7 +9,7 @@ import SwiftUI struct InvoiceView: View { @Environment(\.colorScheme) var colorScheme - let our_pubkey: String + let our_pubkey: Pubkey let invoice: Invoice @State var showing_select_wallet: Bool = false @State var copied = false @@ -108,7 +108,7 @@ let test_invoice = Invoice(description: .description("this is a description"), a struct InvoiceView_Previews: PreviewProvider { static var previews: some View { - InvoiceView(our_pubkey: "", invoice: test_invoice, settings: test_damus_state().settings) + InvoiceView(our_pubkey: .empty.pubkey, invoice: test_invoice, settings: test_damus_state().settings) .frame(width: 300, height: 200) } } diff --git a/damus/Components/InvoicesView.swift b/damus/Components/InvoicesView.swift index 86f851d007e3..9c587abb6dab 100644 --- a/damus/Components/InvoicesView.swift +++ b/damus/Components/InvoicesView.swift @@ -8,7 +8,7 @@ import SwiftUI struct InvoicesView: View { - let our_pubkey: String + let our_pubkey: Pubkey var invoices: [Invoice] let settings: UserSettingsStore @@ -29,7 +29,7 @@ struct InvoicesView: View { struct InvoicesView_Previews: PreviewProvider { static var previews: some View { - InvoicesView(our_pubkey: "", invoices: [Invoice.init(description: .description("description"), amount: .specific(10000), string: "invstr", expiry: 100000, payment_hash: Data(), created_at: 1000000)], settings: test_damus_state().settings) + InvoicesView(our_pubkey: test_note.pubkey, invoices: [Invoice.init(description: .description("description"), amount: .specific(10000), string: "invstr", expiry: 100000, payment_hash: Data(), created_at: 1000000)], settings: test_damus_state().settings) .frame(width: 300) } } diff --git a/damus/Components/NIP05Badge.swift b/damus/Components/NIP05Badge.swift index 759b733e5532..0c2552dbb1ae 100644 --- a/damus/Components/NIP05Badge.swift +++ b/damus/Components/NIP05Badge.swift @@ -9,14 +9,14 @@ import SwiftUI struct NIP05Badge: View { let nip05: NIP05 - let pubkey: String + let pubkey: Pubkey let contacts: Contacts let show_domain: Bool let profiles: Profiles @Environment(\.openURL) var openURL - init(nip05: NIP05, pubkey: String, contacts: Contacts, show_domain: Bool, profiles: Profiles) { + init(nip05: NIP05, pubkey: Pubkey, contacts: Contacts, show_domain: Bool, profiles: Profiles) { self.nip05 = nip05 self.pubkey = pubkey self.contacts = contacts @@ -91,7 +91,7 @@ extension View { } } -func use_nip05_color(pubkey: String, contacts: Contacts) -> Bool { +func use_nip05_color(pubkey: Pubkey, contacts: Contacts) -> Bool { return contacts.is_friend_or_self(pubkey) ? true : false } @@ -105,7 +105,7 @@ struct NIP05Badge_Previews: PreviewProvider { NIP05Badge(nip05: NIP05(username: "jb55", host: "jb55.com"), pubkey: test_state.pubkey, contacts: test_state.contacts, show_domain: true, profiles: test_state.profiles) - NIP05Badge(nip05: NIP05(username: "jb55", host: "jb55.com"), pubkey: test_state.pubkey, contacts: Contacts(our_pubkey: "sdkfjsdf"), show_domain: true, profiles: test_state.profiles) + NIP05Badge(nip05: NIP05(username: "jb55", host: "jb55.com"), pubkey: test_state.pubkey, contacts: Contacts(our_pubkey: test_pubkey), show_domain: true, profiles: test_state.profiles) } } } diff --git a/damus/Components/Reposted.swift b/damus/Components/Reposted.swift index 4808e8701040..f4e5e80f9fdb 100644 --- a/damus/Components/Reposted.swift +++ b/damus/Components/Reposted.swift @@ -9,7 +9,7 @@ import SwiftUI struct Reposted: View { let damus: DamusState - let pubkey: String + let pubkey: Pubkey let profile: Profile? var body: some View { diff --git a/damus/Components/TranslateView.swift b/damus/Components/TranslateView.swift index d41352624493..47830f9a0442 100644 --- a/damus/Components/TranslateView.swift +++ b/damus/Components/TranslateView.swift @@ -121,11 +121,11 @@ extension View { struct TranslateView_Previews: PreviewProvider { static var previews: some View { let ds = test_damus_state() - TranslateView(damus_state: ds, event: test_event, size: .normal) + TranslateView(damus_state: ds, event: test_note, size: .normal) } } -func translate_note(profiles: Profiles, privkey: String?, event: NostrEvent, settings: UserSettingsStore, note_lang: String) async -> TranslateStatus { +func translate_note(profiles: Profiles, privkey: Privkey?, event: NostrEvent, settings: UserSettingsStore, note_lang: String) async -> TranslateStatus { // If the note language is different from our preferred languages, send a translation request. let translator = Translator(settings) diff --git a/damus/Components/UserView.swift b/damus/Components/UserView.swift index 524a8dad54b8..e59fe24bb773 100644 --- a/damus/Components/UserView.swift +++ b/damus/Components/UserView.swift @@ -9,8 +9,8 @@ import SwiftUI struct UserViewRow: View { let damus_state: DamusState - let pubkey: String - + let pubkey: Pubkey + var body: some View { UserView(damus_state: damus_state, pubkey: pubkey) .contentShape(Rectangle()) @@ -20,12 +20,12 @@ struct UserViewRow: View { struct UserView: View { let damus_state: DamusState - let pubkey: String + let pubkey: Pubkey let spacer: Bool @State var about_text: Text? = nil - init(damus_state: DamusState, pubkey: String, spacer: Bool = true) { + init(damus_state: DamusState, pubkey: Pubkey, spacer: Bool = true) { self.damus_state = damus_state self.pubkey = pubkey self.spacer = spacer @@ -56,6 +56,6 @@ struct UserView: View { struct UserView_Previews: PreviewProvider { static var previews: some View { - UserView(damus_state: test_damus_state(), pubkey: "pk") + UserView(damus_state: test_damus_state(), pubkey: test_note.pubkey) } } diff --git a/damus/Components/ZapButton.swift b/damus/Components/ZapButton.swift index 757102ffa68d..316a070d3d6f 100644 --- a/damus/Components/ZapButton.swift +++ b/damus/Components/ZapButton.swift @@ -141,10 +141,10 @@ struct ZapButton: View { struct ZapButton_Previews: PreviewProvider { static var previews: some View { - let pending_zap = PendingZap(amount_msat: 1000, target: ZapTarget.note(id: "noteid", author: "author"), request: .normal(test_zap_request), type: .pub, state: .external(.init(state: .fetching_invoice))) + let pending_zap = PendingZap(amount_msat: 1000, target: ZapTarget.note(id: test_note.id, author: test_note.pubkey), request: .normal(test_zap_request), type: .pub, state: .external(.init(state: .fetching_invoice))) let zaps = ZapsDataModel([.pending(pending_zap)]) - ZapButton(damus_state: test_damus_state(), target: ZapTarget.note(id: test_event.id, author: test_event.pubkey), lnurl: "lnurl", zaps: zaps) + ZapButton(damus_state: test_damus_state(), target: ZapTarget.note(id: test_note.id, author: test_note.pubkey), lnurl: "lnurl", zaps: zaps) } } diff --git a/damus/ContentParsing.swift b/damus/ContentParsing.swift index 0f9d9ded740d..b2449c8cf853 100644 --- a/damus/ContentParsing.swift +++ b/damus/ContentParsing.swift @@ -63,34 +63,33 @@ func interpret_event_refs_ndb(blocks: [Block], tags: TagsSequence) -> [EventRef] /// simpler case with no mentions if mention_indices.count == 0 { - let ev_refs = References.ids(tags: tags) - return interp_event_refs_without_mentions_ndb(ev_refs) + return interp_event_refs_without_mentions_ndb(tags.referenced_evids) } return interp_event_refs_with_mentions_ndb(tags: tags, mention_indices: mention_indices) } -func interp_event_refs_without_mentions_ndb(_ ev_tags: LazyFilterSequence) -> [EventRef] { +func interp_event_refs_without_mentions_ndb(_ ev_tags: EventIdReferences) -> [EventRef] { var count = 0 var evrefs: [EventRef] = [] var first: Bool = true - var first_ref: Reference? = nil + var first_ref: Referenced? = nil for ref in ev_tags { if first { first_ref = ref - evrefs.append(.thread_id(ref.to_referenced_id())) + evrefs.append(.thread_id(ref)) first = false } else { - evrefs.append(.reply(ref.to_referenced_id())) + evrefs.append(.reply(ref)) } count += 1 } if let first_ref, count == 1 { - let r = first_ref.to_referenced_id() + let r = first_ref return [.reply_to_root(r)] } @@ -99,14 +98,11 @@ func interp_event_refs_without_mentions_ndb(_ ev_tags: LazyFilterSequence) -> [EventRef] { var mentions: [EventRef] = [] - var ev_refs: [ReferencedId] = [] + var ev_refs: [Referenced] = [] var i: Int = 0 - + for tag in tags { - if tag.count >= 2, - tag[0].matches_char("e"), - let ref = tag_to_refid(tag) - { + if let ref = tag_to_evref(tag) { if mention_indices.contains(i) { let mention = Mention(index: i, type: .event, ref: ref) mentions.append(.mention(mention)) diff --git a/damus/ContentView.swift b/damus/ContentView.swift index 3457ace5b047..fdcea7815f69 100644 --- a/damus/ContentView.swift +++ b/damus/ContentView.swift @@ -763,11 +763,11 @@ struct FindEvent { let type: FindEventType let find_from: [String]? - static func profile(pubkey: String, find_from: [String]? = nil) -> FindEvent { + static func profile(pubkey: Pubkey, find_from: [String]? = nil) -> FindEvent { return FindEvent(type: .profile(pubkey), find_from: find_from) } - static func event(evid: String, find_from: [String]? = nil) -> FindEvent { + static func event(evid: NoteId, find_from: [String]? = nil) -> FindEvent { return FindEvent(type: .event(evid), find_from: find_from) } } @@ -874,7 +874,7 @@ func timeline_name(_ timeline: Timeline?) -> String { } @discardableResult -func handle_unfollow(state: DamusState, unfollow: ReferencedId) -> Bool { +func handle_unfollow(state: DamusState, unfollow: Referenced) -> Bool { guard let keypair = state.keypair.to_full() else { return false } @@ -898,7 +898,7 @@ func handle_unfollow(state: DamusState, unfollow: ReferencedId) -> Bool { return true } -func handle_unfollow_notif(state: DamusState, notif: Notification) -> ReferencedId? { +func handle_unfollow_notif(state: DamusState, notif: Notification) -> Referenced? { let target = notif.object as! FollowTarget let pk = target.pubkey diff --git a/damus/Models/ActionBarModel.swift b/damus/Models/ActionBarModel.swift index bf51d5740b45..d0109757e600 100644 --- a/damus/Models/ActionBarModel.swift +++ b/damus/Models/ActionBarModel.swift @@ -40,7 +40,7 @@ class ActionBarModel: ObservableObject { self.our_reply = our_reply } - func update(damus: DamusState, evid: String) { + func update(damus: DamusState, evid: NoteId) { self.likes = damus.likes.counts[evid] ?? 0 self.boosts = damus.boosts.counts[evid] ?? 0 self.zaps = damus.zaps.event_counts[evid] ?? 0 diff --git a/damus/Models/BookmarksManager.swift b/damus/Models/BookmarksManager.swift index 43a9c919a27a..2f694d54d8a7 100644 --- a/damus/Models/BookmarksManager.swift +++ b/damus/Models/BookmarksManager.swift @@ -7,18 +7,18 @@ import Foundation -fileprivate func get_bookmarks_key(pubkey: String) -> String { +fileprivate func get_bookmarks_key(pubkey: Pubkey) -> String { pk_setting_key(pubkey, key: "bookmarks") } -func load_bookmarks(pubkey: String) -> [NostrEvent] { +func load_bookmarks(pubkey: Pubkey) -> [NostrEvent] { let key = get_bookmarks_key(pubkey: pubkey) return (UserDefaults.standard.stringArray(forKey: key) ?? []).compactMap { event_from_json(dat: $0) } } -func save_bookmarks(pubkey: String, current_value: [NostrEvent], value: [NostrEvent]) -> Bool { +func save_bookmarks(pubkey: Pubkey, current_value: [NostrEvent], value: [NostrEvent]) -> Bool { let uniq_bookmarks = uniq(value) if uniq_bookmarks != current_value { @@ -32,8 +32,8 @@ func save_bookmarks(pubkey: String, current_value: [NostrEvent], value: [NostrEv class BookmarksManager: ObservableObject { - private let pubkey: String - + private let pubkey: Pubkey + private var _bookmarks: [NostrEvent] var bookmarks: [NostrEvent] { get { @@ -47,7 +47,7 @@ class BookmarksManager: ObservableObject { } } - init(pubkey: String) { + init(pubkey: Pubkey) { self._bookmarks = load_bookmarks(pubkey: pubkey) self.pubkey = pubkey } diff --git a/damus/Models/Contacts.swift b/damus/Models/Contacts.swift index 9dbb2f27def5..6aad89a5a60c 100644 --- a/damus/Models/Contacts.swift +++ b/damus/Models/Contacts.swift @@ -9,21 +9,21 @@ import Foundation class Contacts { - private var friends: Set = Set() - private var friend_of_friends: Set = Set() + private var friends: Set = Set() + private var friend_of_friends: Set = Set() /// Tracks which friends are friends of a given pubkey. - private var pubkey_to_our_friends = [String : Set]() - private var muted: Set = Set() - - let our_pubkey: String + private var pubkey_to_our_friends = [Pubkey : Set]() + private var muted: Set = Set() + + let our_pubkey: Pubkey var event: NostrEvent? var mutelist: NostrEvent? - init(our_pubkey: String) { + init(our_pubkey: Pubkey) { self.our_pubkey = our_pubkey } - func is_muted(_ pk: String) -> Bool { + func is_muted(_ pk: Pubkey) -> Bool { return muted.contains(pk) } @@ -34,20 +34,20 @@ class Contacts { let old = Set(oldlist?.referenced_pubkeys.map({ $0.ref_id }) ?? []) let new = Set(ev.referenced_pubkeys.map({ $0.ref_id })) let diff = old.symmetricDifference(new) - - var new_mutes = Array() - var new_unmutes = Array() - + + var new_mutes = Array() + var new_unmutes = Array() + for d in diff { if new.contains(d) { - new_mutes.append(d.string()) + new_mutes.append(d) } else { - new_unmutes.append(d.string()) + new_unmutes.append(d) } } // TODO: set local mutelist here - self.muted = Set(ev.referenced_pubkeys.map({ $0.ref_id.string() })) + self.muted = Set(ev.referenced_pubkeys.map({ $0.ref_id })) if new_mutes.count > 0 { notify(.new_mutes, new_mutes) @@ -58,7 +58,7 @@ class Contacts { } } - func remove_friend(_ pubkey: String) { + func remove_friend(_ pubkey: Pubkey) { friends.remove(pubkey) pubkey_to_our_friends.forEach { @@ -66,7 +66,7 @@ class Contacts { } } - func get_friend_list() -> Set { + func get_friend_list() -> Set { return friends } @@ -75,20 +75,20 @@ class Contacts { return Set(ev.referenced_hashtags.map({ $0.ref_id.string() })) } - func add_friend_pubkey(_ pubkey: String) { + func add_friend_pubkey(_ pubkey: Pubkey) { friends.insert(pubkey) } func add_friend_contact(_ contact: NostrEvent) { friends.insert(contact.pubkey) for tag in contact.referenced_pubkeys { - let pk = tag.ref_id.string() + let pk = tag.ref_id friend_of_friends.insert(pk) // Exclude themself and us. if contact.pubkey != our_pubkey && contact.pubkey != pk { if pubkey_to_our_friends[pk] == nil { - pubkey_to_our_friends[pk] = Set() + pubkey_to_our_friends[pk] = Set() } pubkey_to_our_friends[pk]?.insert(contact.pubkey) @@ -96,33 +96,33 @@ class Contacts { } } - func is_friend_of_friend(_ pubkey: String) -> Bool { + func is_friend_of_friend(_ pubkey: Pubkey) -> Bool { return friend_of_friends.contains(pubkey) } - func is_in_friendosphere(_ pubkey: String) -> Bool { + func is_in_friendosphere(_ pubkey: Pubkey) -> Bool { return friends.contains(pubkey) || friend_of_friends.contains(pubkey) } - func is_friend(_ pubkey: String) -> Bool { + func is_friend(_ pubkey: Pubkey) -> Bool { return friends.contains(pubkey) } - func is_friend_or_self(_ pubkey: String) -> Bool { + func is_friend_or_self(_ pubkey: Pubkey) -> Bool { return pubkey == our_pubkey || is_friend(pubkey) } - func follow_state(_ pubkey: String) -> FollowState { + func follow_state(_ pubkey: Pubkey) -> FollowState { return is_friend(pubkey) ? .follows : .unfollows } /// Gets the list of pubkeys of our friends who follow the given pubkey. - func get_friended_followers(_ pubkey: String) -> [String] { + func get_friended_followers(_ pubkey: Pubkey) -> [Pubkey] { return Array((pubkey_to_our_friends[pubkey] ?? Set())) } } -func follow_reference(box: PostBox, our_contacts: NostrEvent?, keypair: FullKeypair, follow: ReferencedId) -> NostrEvent? { +func follow_reference(box: PostBox, our_contacts: NostrEvent?, keypair: FullKeypair, follow: Referenced) -> NostrEvent? { guard let ev = follow_user_event(our_contacts: our_contacts, keypair: keypair, follow: follow) else { return nil } @@ -132,7 +132,7 @@ func follow_reference(box: PostBox, our_contacts: NostrEvent?, keypair: FullKeyp return ev } -func unfollow_reference(postbox: PostBox, our_contacts: NostrEvent?, keypair: FullKeypair, unfollow: ReferencedId) -> NostrEvent? { +func unfollow_reference(postbox: PostBox, our_contacts: NostrEvent?, keypair: FullKeypair, unfollow: Referenced) -> NostrEvent? { guard let cs = our_contacts else { return nil } @@ -146,13 +146,11 @@ func unfollow_reference(postbox: PostBox, our_contacts: NostrEvent?, keypair: Fu return ev } -func unfollow_reference_event(our_contacts: NostrEvent, keypair: FullKeypair, unfollow: ReferencedId) -> NostrEvent? { +func unfollow_reference_event(our_contacts: NostrEvent, keypair: FullKeypair, unfollow: Referenced) -> NostrEvent? { let tags = our_contacts.tags.reduce(into: [[String]]()) { ts, tag in if tag.count >= 2, - let fst = unfollow.key.first, - let afst = AsciiCharacter(fst), - tag[0].matches_char(afst), - tag[1].matches_str(unfollow.ref_id) + tag[0].matches_char(unfollow.key), + tag[1].matches_str(unfollow.ref_id.description) { return } @@ -165,7 +163,7 @@ func unfollow_reference_event(our_contacts: NostrEvent, keypair: FullKeypair, un return NostrEvent(content: our_contacts.content, keypair: keypair.to_keypair(), kind: kind, tags: Array(tags)) } -func follow_user_event(our_contacts: NostrEvent?, keypair: FullKeypair, follow: ReferencedId) -> NostrEvent? { +func follow_user_event(our_contacts: NostrEvent?, keypair: FullKeypair, follow: Referenced) -> NostrEvent? { guard let cs = our_contacts else { // don't create contacts for now so we don't nuke our contact list due to connectivity issues // we should only create contacts during profile creation @@ -219,12 +217,13 @@ func ensure_relay_info(relays: [RelayDescriptor], content: String) -> [String: R return relay_info } -func is_already_following(contacts: NostrEvent, follow: ReferencedId) -> Bool { - guard let key = follow.key.first_char() else { return false } - return contacts.references(id: follow.ref_id, key: key) +func is_already_following(contacts: NostrEvent, follow: Referenced) -> Bool { + return contacts.references.first(where: { r in + r == follow + }) != nil } -func follow_with_existing_contacts(keypair: FullKeypair, our_contacts: NostrEvent, follow: ReferencedId) -> NostrEvent? { +func follow_with_existing_contacts(keypair: FullKeypair, our_contacts: NostrEvent, follow: Referenced) -> NostrEvent? { // don't update if we're already following if is_already_following(contacts: our_contacts, follow: follow) { return nil diff --git a/damus/Models/DamusState.swift b/damus/Models/DamusState.swift index c4a6cd027a88..fd0564edafc5 100644 --- a/damus/Models/DamusState.swift +++ b/damus/Models/DamusState.swift @@ -50,7 +50,7 @@ struct DamusState { return stored } - var pubkey: String { + var pubkey: Pubkey { return keypair.pubkey } @@ -60,5 +60,33 @@ struct DamusState { static var empty: DamusState { let user_search_cache = UserSearchCache() - return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), profiles: Profiles(user_search_cache: user_search_cache), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_model_cache: RelayModelCache(), drafts: Drafts(), events: EventCache(), bookmarks: BookmarksManager(pubkey: ""), postbox: PostBox(pool: RelayPool()), bootstrap_relays: [], replies: ReplyCounter(our_pubkey: ""), muted_threads: MutedThreadsManager(keypair: Keypair(pubkey: "", privkey: nil)), wallet: WalletModel(settings: UserSettingsStore()), nav: NavigationCoordinator(), user_search_cache: user_search_cache) } + let empty_pub: Pubkey = .empty.pubkey + let empty_sec: Privkey = .empty.privkey + let kp = Keypair(pubkey: empty_pub, privkey: nil) + + return DamusState.init( + pool: RelayPool(), + keypair: Keypair(pubkey: empty_pub, privkey: empty_sec), + likes: EventCounter(our_pubkey: empty_pub), + boosts: EventCounter(our_pubkey: empty_pub), + contacts: Contacts(our_pubkey: empty_pub), + profiles: Profiles(user_search_cache: user_search_cache), + dms: DirectMessagesModel(our_pubkey: empty_pub), + previews: PreviewCache(), + zaps: Zaps(our_pubkey: empty_pub), + lnurls: LNUrls(), + settings: UserSettingsStore(), + relay_filters: RelayFilters(our_pubkey: empty_pub), + relay_model_cache: RelayModelCache(), + drafts: Drafts(), + events: EventCache(), + bookmarks: BookmarksManager(pubkey: empty_pub), + postbox: PostBox(pool: RelayPool()), + bootstrap_relays: [], + replies: ReplyCounter(our_pubkey: empty_pub), + muted_threads: MutedThreadsManager(keypair: kp), + wallet: WalletModel(settings: UserSettingsStore()), + nav: NavigationCoordinator(), + user_search_cache: user_search_cache) + } } diff --git a/damus/Models/DirectMessageModel.swift b/damus/Models/DirectMessageModel.swift index f5b1a1b4a6d4..3d6e0470d82b 100644 --- a/damus/Models/DirectMessageModel.swift +++ b/damus/Models/DirectMessageModel.swift @@ -16,11 +16,11 @@ class DirectMessageModel: ObservableObject { @Published var draft: String = "" - let pubkey: String - + let pubkey: Pubkey + var is_request = false - var our_pubkey: String - + var our_pubkey: Pubkey + func determine_is_request() -> Bool { for event in events { if event.pubkey == our_pubkey { @@ -31,7 +31,7 @@ class DirectMessageModel: ObservableObject { return true } - init(events: [NostrEvent] = [], our_pubkey: String, pubkey: String) { + init(events: [NostrEvent] = [], our_pubkey: Pubkey, pubkey: Pubkey) { self.events = events self.our_pubkey = our_pubkey self.pubkey = pubkey diff --git a/damus/Models/DirectMessagesModel.swift b/damus/Models/DirectMessagesModel.swift index 7402de671d69..0768ee4a7316 100644 --- a/damus/Models/DirectMessagesModel.swift +++ b/damus/Models/DirectMessagesModel.swift @@ -11,10 +11,10 @@ class DirectMessagesModel: ObservableObject { @Published var dms: [DirectMessageModel] = [] @Published var loading: Bool = false @Published var open_dm: Bool = false - @Published private(set) var active_model: DirectMessageModel = DirectMessageModel(our_pubkey: "", pubkey: "") - let our_pubkey: String - - init(our_pubkey: String) { + @Published private(set) var active_model: DirectMessageModel = DirectMessageModel(our_pubkey: .empty.pubkey, pubkey: .empty.pubkey) + let our_pubkey: Pubkey + + init(our_pubkey: Pubkey) { self.our_pubkey = our_pubkey } @@ -30,14 +30,14 @@ class DirectMessagesModel: ObservableObject { self.active_model = model } - func set_active_dm(_ pubkey: String) { + func set_active_dm(_ pubkey: Pubkey) { for model in self.dms where model.pubkey == pubkey { self.set_active_dm_model(model) break } } - func lookup_or_create(_ pubkey: String) -> DirectMessageModel { + func lookup_or_create(_ pubkey: Pubkey) -> DirectMessageModel { if let dm = lookup(pubkey) { return dm } @@ -47,7 +47,7 @@ class DirectMessagesModel: ObservableObject { return new } - func lookup(_ pubkey: String) -> DirectMessageModel? { + func lookup(_ pubkey: Pubkey) -> DirectMessageModel? { for dm in dms { if pubkey == dm.pubkey { return dm diff --git a/damus/Models/EventRef.swift b/damus/Models/EventRef.swift index a2d08ca6b8b4..9045fdc04525 100644 --- a/damus/Models/EventRef.swift +++ b/damus/Models/EventRef.swift @@ -8,19 +8,19 @@ import Foundation enum EventRef: Equatable { - case mention(Mention) - case thread_id(ReferencedId) - case reply(ReferencedId) - case reply_to_root(ReferencedId) - - var is_mention: Mention? { + case mention(Mention) + case thread_id(Referenced) + case reply(Referenced) + case reply_to_root(Referenced) + + var is_mention: Mention? { if case .mention(let m) = self { return m } return nil } - var is_direct_reply: ReferencedId? { + var is_direct_reply: Referenced? { switch self { case .mention: return nil @@ -33,7 +33,7 @@ enum EventRef: Equatable { } } - var is_thread_id: ReferencedId? { + var is_thread_id: Referenced? { switch self { case .mention: return nil @@ -46,7 +46,7 @@ enum EventRef: Equatable { } } - var is_reply: ReferencedId? { + var is_reply: Referenced? { switch self { case .mention: return nil @@ -83,7 +83,7 @@ func build_mention_indices(_ blocks: [Block], type: MentionType) -> Set { } } -func interp_event_refs_without_mentions(_ refs: [ReferencedId]) -> [EventRef] { +func interp_event_refs_without_mentions(_ refs: [Referenced]) -> [EventRef] { if refs.count == 0 { return [] } @@ -107,13 +107,13 @@ func interp_event_refs_without_mentions(_ refs: [ReferencedId]) -> [EventRef] { func interp_event_refs_with_mentions(tags: Tags, mention_indices: Set) -> [EventRef] { var mentions: [EventRef] = [] - var ev_refs: [ReferencedId] = [] + var ev_refs: [Referenced] = [] var i: Int = 0 - + for tag in tags { - if tag.count >= 2, tag[0].matches_char("e"), let ref = tag_to_refid(tag) { + if let ref = tag_to_evref(tag) { if mention_indices.contains(i) { - let mention = Mention(index: i, type: .event, ref: ref) + let mention = Mention(index: i, type: MentionType.event, ref: ref) mentions.append(.mention(mention)) } else { ev_refs.append(ref) @@ -137,8 +137,7 @@ func interpret_event_refs(blocks: [Block], tags: Tags) -> [EventRef] { /// simpler case with no mentions if mention_indices.count == 0 { - let ev_refs = References.ids(tags: tags) - return interp_event_refs_without_mentions_ndb(ev_refs) + return interp_event_refs_without_mentions_ndb(tags.referenced_evids) } return interp_event_refs_with_mentions(tags: tags, mention_indices: mention_indices) diff --git a/damus/Models/EventsModel.swift b/damus/Models/EventsModel.swift index aa9130ba257f..ba171236486c 100644 --- a/damus/Models/EventsModel.swift +++ b/damus/Models/EventsModel.swift @@ -10,14 +10,14 @@ import Foundation class EventsModel: ObservableObject { let state: DamusState - let target: String + let target: NoteId let kind: NostrKind let sub_id = UUID().uuidString let profiles_id = UUID().uuidString @Published var events: [NostrEvent] = [] - init(state: DamusState, target: String, kind: NostrKind) { + init(state: DamusState, target: NoteId, kind: NostrKind) { self.state = state self.target = target self.kind = kind @@ -45,10 +45,10 @@ class EventsModel: ObservableObject { return } - guard ev.referenced_ids.last?.ref_id.string() == target else { + guard ev.referenced_ids.last?.ref_id == target else { return } - + if insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { a, b in a.created_at < b.created_at } ) { objectWillChange.send() } diff --git a/damus/Models/FollowTarget.swift b/damus/Models/FollowTarget.swift index bfe8b1a98f0e..caeb2897610b 100644 --- a/damus/Models/FollowTarget.swift +++ b/damus/Models/FollowTarget.swift @@ -9,10 +9,10 @@ import Foundation enum FollowTarget { - case pubkey(String) + case pubkey(Pubkey) case contact(NostrEvent) - var pubkey: String { + var pubkey: Pubkey { switch self { case .pubkey(let pk): return pk diff --git a/damus/Models/FollowersModel.swift b/damus/Models/FollowersModel.swift index 1f90df29fb94..c413cb695b6c 100644 --- a/damus/Models/FollowersModel.swift +++ b/damus/Models/FollowersModel.swift @@ -9,11 +9,11 @@ import Foundation class FollowersModel: ObservableObject { let damus_state: DamusState - let target: String - - @Published var contacts: [String]? = nil - var has_contact: Set = Set() - + let target: Pubkey + + @Published var contacts: [Pubkey]? = nil + var has_contact: Set = Set() + let sub_id: String = UUID().description let profiles_id: String = UUID().description @@ -24,20 +24,19 @@ class FollowersModel: ObservableObject { return contacts.count } - init(damus_state: DamusState, target: String) { + init(damus_state: DamusState, target: Pubkey) { self.damus_state = damus_state self.target = target } func get_filter() -> NostrFilter { - NostrFilter(kinds: [.contacts], - pubkeys: [target]) + NostrFilter(kinds: [.contacts], pubkeys: [target]) } func subscribe() { let filter = get_filter() let filters = [filter] - print_filters(relay_id: "following", filters: [filters]) + //print_filters(relay_id: "following", filters: [filters]) self.damus_state.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_event) } diff --git a/damus/Models/FollowingModel.swift b/damus/Models/FollowingModel.swift index 08fe905faf48..5a2306a2e6b7 100644 --- a/damus/Models/FollowingModel.swift +++ b/damus/Models/FollowingModel.swift @@ -11,18 +11,18 @@ class FollowingModel { let damus_state: DamusState var needs_sub: Bool = true - let contacts: [String] - + let contacts: [Pubkey] + let sub_id: String = UUID().description - init(damus_state: DamusState, contacts: [String]) { + init(damus_state: DamusState, contacts: [Pubkey]) { self.damus_state = damus_state self.contacts = contacts } func get_filter() -> NostrFilter { var f = NostrFilter(kinds: [.metadata]) - f.authors = self.contacts.reduce(into: Array()) { acc, pk in + f.authors = self.contacts.reduce(into: Array()) { acc, pk in // don't fetch profiles we already have if damus_state.profiles.has_fresh_profile(id: pk) { return @@ -39,7 +39,7 @@ class FollowingModel { return } let filters = [filter] - print_filters(relay_id: "following", filters: [filters]) + //print_filters(relay_id: "following", filters: [filters]) self.damus_state.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_event) } diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift index 16992773d12c..461d509c23f8 100644 --- a/damus/Models/HomeModel.swift +++ b/damus/Models/HomeModel.swift @@ -25,20 +25,20 @@ struct NewEventsBits: OptionSet { enum Resubscribe { case following - case unfollowing(ReferencedId) + case unfollowing(Referenced) } enum HomeResubFilter { - case pubkey(String) + case pubkey(Pubkey) case hashtag(String) - init?(from: ReferencedId) { - if from.key == "p" { - self = .pubkey(from.ref_id) - return - } else if from.key == "t" { - self = .hashtag(from.ref_id) - return + init?(from: Referenced) { + switch from.ref_id { + case .event: return nil + case .param: return nil + case .quote: return nil + case .hashtag(let ht): self = .hashtag(ht.string()) + case .pubkey(let pk): self = .pubkey(pk) } return nil @@ -52,7 +52,9 @@ enum HomeResubFilter { if contacts.is_friend(ev.pubkey) { return false } - return ev.references(id: ht, key: "t") + return ev.referenced_hashtags.first(where: { + te in te.ref_id.matches_str(ht) + }) != nil } } } @@ -63,9 +65,9 @@ class HomeModel { var damus_state: DamusState - var has_event: [String: Set] = [:] - var deleted_events: Set = Set() - var channels: [String: NostrEvent] = [:] + // NDBTODO: let's get rid of this entirely, let nostrdb handle it + var has_event: [String: Set] = [:] + var deleted_events: Set = Set() var last_event_of_kind: [String: [UInt32: NostrEvent]] = [:] var done_init: Bool = false var incoming_dms: [NostrEvent] = [] @@ -109,7 +111,7 @@ class HomeModel { return damus_state.dms } - func has_sub_id_event(sub_id: String, ev_id: String) -> Bool { + func has_sub_id_event(sub_id: String, ev_id: NoteId) -> Bool { if !has_event.keys.contains(sub_id) { has_event[sub_id] = Set() return false @@ -181,10 +183,6 @@ class HomeModel { handle_dm(ev) case .delete: handle_delete_event(ev) - case .channel_create: - handle_channel_create(ev) - case .channel_meta: - break case .zap: handle_zap_event(ev) case .zap_request: @@ -260,10 +258,6 @@ class HomeModel { } - func handle_channel_create(_ ev: NostrEvent) { - self.channels[ev.id] = ev - } - func filter_events() { events.filter { ev in !damus_state.contacts.is_muted(ev.pubkey) @@ -502,13 +496,13 @@ class HomeModel { pool.send(.unsubscribe(home_subid)) } - func get_friends() -> [String] { + func get_friends() -> [Pubkey] { var friends = damus_state.contacts.get_friend_list() friends.insert(damus_state.pubkey) return Array(friends) } - func subscribe_to_home_filters(friends fs: [String]? = nil, relay_id: String? = nil) { + func subscribe_to_home_filters(friends fs: [Pubkey]? = nil, relay_id: String? = nil) { // TODO: separate likes? var home_filter_kinds: [NostrKind] = [ .text, .longform, .boost @@ -551,7 +545,7 @@ class HomeModel { } } - guard let name = References.list(tags: ev.tags).first else { + guard let name = ev.referenced_params.first(where: {_ in true}) else { return } @@ -697,14 +691,14 @@ func add_contact_if_friend(contacts: Contacts, ev: NostrEvent) { func load_our_contacts(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) { let contacts = state.contacts - var new_refs = Set() + var new_refs = Set>() // our contacts for tag in ev.tags { guard let ref = tag_to_refid(tag) else { continue } new_refs.insert(ref) } - var old_refs = Set() + var old_refs = Set>() // find removed contacts if let old_ev = m_old_ev { for tag in old_ev.tags { @@ -717,13 +711,25 @@ func load_our_contacts(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) for ref in diff { if new_refs.contains(ref) { notify(.followed, ref) - if ref.key == "p" { - contacts.add_friend_pubkey(ref.ref_id) + switch ref.ref_id { + case .pubkey(let pk): + contacts.add_friend_pubkey(pk) + case .event: break + case .param: break + case .quote: break + case .hashtag: + // I guess I could cache followed hashtags here... whatever + break } } else { notify(.unfollowed, ref) - if ref.key == "p" { - contacts.remove_friend(ref.ref_id) + switch ref.ref_id { + case .pubkey(let pk): + contacts.remove_friend(pk) + case .event: break + case .param: break + case .hashtag: break + case .quote: break } } } @@ -756,6 +762,7 @@ func abbrev_ids_field(_ n: String, _ ids: [String]?) -> String { return "\(n): \(abbrev_ids(ids))" } +/* func print_filter(_ f: NostrFilter) { let fmt = [ abbrev_ids_field("ids", f.ids), @@ -781,8 +788,9 @@ func print_filters(relay_id: String?, filters groups: [[NostrFilter]]) { } print("-----") } + */ -func process_metadata_profile(our_pubkey: String, profiles: Profiles, profile: Profile, ev: NostrEvent) { +func process_metadata_profile(our_pubkey: Pubkey, profiles: Profiles, profile: Profile, ev: NostrEvent) { var old_nip05: String? = nil let mprof = profiles.lookup_with_timestamp(id: ev.pubkey) @@ -847,7 +855,7 @@ func guard_valid_event(events: EventCache, ev: NostrEvent, callback: @escaping ( } } -func process_metadata_event(events: EventCache, our_pubkey: String, profiles: Profiles, ev: NostrEvent, completion: ((Profile?) -> Void)? = nil) { +func process_metadata_event(events: EventCache, our_pubkey: Pubkey, profiles: Profiles, ev: NostrEvent, completion: ((Profile?) -> Void)? = nil) { guard_valid_event(events: events, ev: ev) { DispatchQueue.global(qos: .background).async { guard let profile: Profile = decode_data(Data(ev.content.utf8)) else { @@ -865,8 +873,8 @@ func process_metadata_event(events: EventCache, our_pubkey: String, profiles: Pr } } -func robohash(_ pk: String) -> String { - return "https://robohash.org/" + pk +func robohash(_ pk: Pubkey) -> String { + return "https://robohash.org/" + pk.hex() } func load_our_stuff(state: DamusState, ev: NostrEvent) { @@ -991,7 +999,7 @@ func fetch_relay_metadata(relay_id: String) async throws -> RelayMetadata? { } @discardableResult -func handle_incoming_dm(ev: NostrEvent, our_pubkey: String, dms: DirectMessagesModel, prev_events: NewEventsBits) -> (Bool, NewEventsBits?) { +func handle_incoming_dm(ev: NostrEvent, our_pubkey: Pubkey, dms: DirectMessagesModel, prev_events: NewEventsBits) -> (Bool, NewEventsBits?) { var inserted = false var found = false @@ -1001,7 +1009,7 @@ func handle_incoming_dm(ev: NostrEvent, our_pubkey: String, dms: DirectMessagesM var the_pk = ev.pubkey if ours { if let ref_pk = ev.referenced_pubkeys.first { - the_pk = ref_pk.ref_id.string() + the_pk = ref_pk.ref_id } else { // self dm!? print("TODO: handle self dm?") @@ -1035,7 +1043,7 @@ func handle_incoming_dm(ev: NostrEvent, our_pubkey: String, dms: DirectMessagesM } @discardableResult -func handle_incoming_dms(prev_events: NewEventsBits, dms: DirectMessagesModel, our_pubkey: String, evs: [NostrEvent]) -> NewEventsBits? { +func handle_incoming_dms(prev_events: NewEventsBits, dms: DirectMessagesModel, our_pubkey: Pubkey, evs: [NostrEvent]) -> NewEventsBits? { var inserted = false var new_events: NewEventsBits? = nil @@ -1115,17 +1123,10 @@ func handle_last_events(new_events: NewEventsBits, ev: NostrEvent, timeline: Tim /// Sometimes we get garbage in our notifications. Ensure we have our pubkey on this event -func event_has_our_pubkey(_ ev: NostrEvent, our_pubkey: String) -> Bool { - guard let our_pk = hex_decode(our_pubkey) else { - return false - } - for tag in ev.tags { - if tag.count >= 2, tag[0].matches_char("p"), tag[1].matches_id(our_pk) { - return true - } - } - - return false +func event_has_our_pubkey(_ ev: NostrEvent, our_pubkey: Pubkey) -> Bool { + return ev.tags.referenced_pubkeys.first(where: { ref in + ref.ref_id == our_pubkey + }) != nil } @@ -1174,13 +1175,13 @@ func zap_notification_body(profiles: Profiles, zap: Zap, locale: Locale = Locale } } -func create_in_app_profile_zap_notification(profiles: Profiles, zap: Zap, locale: Locale = Locale.current, profile_id: String) { +func create_in_app_profile_zap_notification(profiles: Profiles, zap: Zap, locale: Locale = Locale.current, profile_id: Pubkey) { let content = UNMutableNotificationContent() content.title = zap_notification_title(zap) content.body = zap_notification_body(profiles: profiles, zap: zap, locale: locale) content.sound = UNNotificationSound.default - content.userInfo = LossyLocalNotification(type: .profile_zap, event_id: profile_id).to_user_info() + content.userInfo = LossyLocalNotification(type: .profile_zap, event_id: profile_id.any_id).to_user_info() let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) @@ -1195,13 +1196,13 @@ func create_in_app_profile_zap_notification(profiles: Profiles, zap: Zap, locale } } -func create_in_app_event_zap_notification(profiles: Profiles, zap: Zap, locale: Locale = Locale.current, evId: String) { +func create_in_app_event_zap_notification(profiles: Profiles, zap: Zap, locale: Locale = Locale.current, evId: NoteId) { let content = UNMutableNotificationContent() content.title = zap_notification_title(zap) content.body = zap_notification_body(profiles: profiles, zap: zap, locale: locale) content.sound = UNNotificationSound.default - content.userInfo = LossyLocalNotification(type: .zap, event_id: evId).to_user_info() + content.userInfo = LossyLocalNotification(type: .zap, event_id: evId.any_id).to_user_info() let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) @@ -1216,7 +1217,7 @@ func create_in_app_event_zap_notification(profiles: Profiles, zap: Zap, locale: } } -func render_notification_content_preview(cache: EventCache, ev: NostrEvent, profiles: Profiles, privkey: String?) -> String { +func render_notification_content_preview(cache: EventCache, ev: NostrEvent, profiles: Profiles, privkey: Privkey?) -> String { let prefix_len = 50 let artifacts = cache.get_cache_data(ev.id).artifacts.artifacts ?? render_note_content(ev: ev, profiles: profiles, privkey: privkey) @@ -1261,7 +1262,7 @@ func process_local_notification(damus_state: DamusState, event ev: NostrEvent) { if type == .text && damus_state.settings.mention_notification { let blocks = ev.blocks(damus_state.keypair.privkey).blocks - for case .mention(let mention) in blocks where mention.ref.ref_id == damus_state.keypair.pubkey { + for case .mention(let mention) in blocks where mention.ref.ref_id.pubkey == damus_state.keypair.pubkey { let content_preview = render_notification_content_preview(cache: damus_state.events, ev: ev, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey) let notify = LocalNotification(type: .mention, event: ev, target: ev, content: content_preview) create_local_notification(profiles: damus_state.profiles, notify: notify ) @@ -1272,7 +1273,7 @@ func process_local_notification(damus_state: DamusState, event ev: NostrEvent) { create_local_notification(profiles: damus_state.profiles, notify: notify) } else if type == .like && damus_state.settings.like_notification, let evid = ev.referenced_ids.last?.ref_id, - let liked_event = damus_state.events.lookup(evid.string()) + let liked_event = damus_state.events.lookup(evid) { let content_preview = render_notification_content_preview(cache: damus_state.events, ev: liked_event, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey) let notify = LocalNotification(type: .like, event: ev, target: liked_event, content: content_preview) @@ -1347,7 +1348,7 @@ extension Sequence { // securely get the zap target's pubkey. this can be faked so we need to be // careful -func get_zap_target_pubkey(ev: NostrEvent, events: EventCache) -> String? { +func get_zap_target_pubkey(ev: NostrEvent, events: EventCache) -> Pubkey? { let etags = Array(ev.referenced_ids) guard let etag = etags.first else { @@ -1358,7 +1359,7 @@ func get_zap_target_pubkey(ev: NostrEvent, events: EventCache) -> String? { } // TODO: just return data here - return a.ref_id.string() + return a.ref_id } // we have an e-tag @@ -1369,7 +1370,7 @@ func get_zap_target_pubkey(ev: NostrEvent, events: EventCache) -> String? { } // we can't trust the p tag on note zaps because they can be faked - return events.lookup(etag.ref_id.string())?.pubkey + return events.lookup(etag.ref_id)?.pubkey } func process_zap_event(damus_state: DamusState, ev: NostrEvent, completion: @escaping (ProcessZapResult) -> Void) { @@ -1425,7 +1426,7 @@ func process_zap_event(damus_state: DamusState, ev: NostrEvent, completion: @esc } -fileprivate func process_zap_event_with_zapper(damus_state: DamusState, ev: NostrEvent, zapper: String) -> Zap? { +fileprivate func process_zap_event_with_zapper(damus_state: DamusState, ev: NostrEvent, zapper: Pubkey) -> Zap? { let our_keypair = damus_state.keypair guard let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: our_keypair.privkey) else { diff --git a/damus/Models/LikeCounter.swift b/damus/Models/LikeCounter.swift index d8dbe42e65f5..4cbca08504be 100644 --- a/damus/Models/LikeCounter.swift +++ b/damus/Models/LikeCounter.swift @@ -13,16 +13,16 @@ enum CountResult { } class EventCounter { - var counts: [String: Int] = [:] - var user_events: [String: Set] = [:] - var our_events: [String: NostrEvent] = [:] - var our_pubkey: String - - init(our_pubkey: String) { + var counts: [NoteId: Int] = [:] + var user_events: [Pubkey: Set] = [:] + var our_events: [NoteId: NostrEvent] = [:] + var our_pubkey: Pubkey + + init(our_pubkey: Pubkey) { self.our_pubkey = our_pubkey } - func add_event(_ ev: NostrEvent, target: String) -> CountResult { + func add_event(_ ev: NostrEvent, target: NoteId) -> CountResult { let pubkey = ev.pubkey if self.user_events[pubkey] == nil { diff --git a/damus/Models/Liked.swift b/damus/Models/Liked.swift index 5d7eb35ae612..59b9263b02d6 100644 --- a/damus/Models/Liked.swift +++ b/damus/Models/Liked.swift @@ -9,6 +9,6 @@ import Foundation struct Counted { let event: NostrEvent - let id: String + let id: NoteId let total: Int } diff --git a/damus/Models/Mentions.swift b/damus/Models/Mentions.swift index 4b428329f3ab..ea5e74a3ef35 100644 --- a/damus/Models/Mentions.swift +++ b/damus/Models/Mentions.swift @@ -7,31 +7,32 @@ import Foundation -enum MentionType { - case pubkey - case event +enum MentionId: Equatable { + case pubkey(Pubkey) + case event(NoteId) - var ref: String { + var key: String { switch self { - case .pubkey: - return "p" - case .event: - return "e" + case .pubkey: return "p" + case .event: return "e" } } } -struct Mention: Equatable { +struct Mention: Equatable { let index: Int? - let type: MentionType - let ref: ReferencedId + let ref: T - static func note(_ id: String) -> Mention { - return Mention(index: nil, type: .event, ref: .e(id)) + static func any(_ mention_id: MentionId) -> Mention { + return Mention(index: nil, ref: mention_id) } - static func pubkey(_ pubkey: String) -> Mention { - return Mention(index: nil, type: .pubkey, ref: .p(pubkey)) + static func note(_ id: NoteId) -> Mention { + return Mention(index: nil, ref: id) + } + + static func pubkey(_ pubkey: Pubkey) -> Mention { + return Mention(index: nil, ref: pubkey) } } @@ -80,7 +81,7 @@ enum Block: Equatable { } case text(String) - case mention(Mention) + case mention(Mention) case hashtag(String) case url(URL) case invoice(Invoice) @@ -116,14 +117,14 @@ enum Block: Equatable { } var is_note_mention: Bool { - guard case .mention(let mention) = self else { - return false + if case .mention(let mention) = self, + case .event = mention.ref { + return true } - - return mention.type == .event + return false } - var is_mention: Mention? { + var is_mention: Mention? { if case .mention(let m) = self { return m } @@ -291,41 +292,29 @@ func convert_mention_bech32_block(_ b: mention_bech32_block) -> Block? switch b.bech32.type { case NOSTR_BECH32_NOTE: let note = b.bech32.data.note; - let event_id = hex_encode(Data(bytes: note.event_id, count: 32)) - let event_id_ref = ReferencedId(ref_id: event_id, relay_id: nil, key: "e") - return .mention(Mention(index: nil, type: .event, ref: event_id_ref)) - + let event_id = NoteId(Data(bytes: note.event_id, count: 32)) + return .mention(.note_or_pk(.event(note_id))) + case NOSTR_BECH32_NEVENT: let nevent = b.bech32.data.nevent; - let event_id = hex_encode(Data(bytes: nevent.event_id, count: 32)) - var relay_id: String? = nil - if nevent.relays.num_relays > 0 { - relay_id = strblock_to_string(nevent.relays.relays.0) - } - let event_id_ref = ReferencedId(ref_id: event_id, relay_id: relay_id, key: "e") - return .mention(Mention(index: nil, type: .event, ref: event_id_ref)) + let note_id = NoteId(Data(bytes: nevent.event_id, count: 32)) + return .mention(.note_or_pk(.event(note_id))) case NOSTR_BECH32_NPUB: let npub = b.bech32.data.npub - let pubkey = hex_encode(Data(bytes: npub.pubkey, count: 32)) - let pubkey_ref = ReferencedId(ref_id: pubkey, relay_id: nil, key: "p") - return .mention(Mention(index: nil, type: .pubkey, ref: pubkey_ref)) + let pubkey = Pubkey(Data(bytes: npub.pubkey, count: 32)) + return .mention(.note_or_pk(.pubkey(pubkey))) case NOSTR_BECH32_NSEC: let nsec = b.bech32.data.nsec - let nsec_bytes = Data(bytes: nsec.nsec, count: 32) - let pubkey = privkey_to_pubkey_raw(sec: nsec_bytes.bytes) ?? hex_encode(nsec_bytes) - return .mention(.pubkey(pubkey)) + let privkey = Privkey(Data(bytes: nsec.nsec, count: 32)) + guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil } + return .mention(.note_or_pk(.pubkey(pubkey))) case NOSTR_BECH32_NPROFILE: let nprofile = b.bech32.data.nprofile - let pubkey = hex_encode(Data(bytes: nprofile.pubkey, count: 32)) - var relay_id: String? = nil - if nprofile.relays.num_relays > 0 { - relay_id = strblock_to_string(nprofile.relays.relays.0) - } - let pubkey_ref = ReferencedId(ref_id: pubkey, relay_id: relay_id, key: "p") - return .mention(Mention(index: nil, type: .pubkey, ref: pubkey_ref)) + let pubkey = Pubkey(Data(bytes: nprofile.pubkey, count: 32)) + return .mention(.note_or_pk(.pubkey(pubkey))) case NOSTR_BECH32_NRELAY: let nrelay = b.bech32.data.nrelay diff --git a/damus/Models/MutedThreadsManager.swift b/damus/Models/MutedThreadsManager.swift index 390c970ea303..0d404fda753c 100644 --- a/damus/Models/MutedThreadsManager.swift +++ b/damus/Models/MutedThreadsManager.swift @@ -7,16 +7,20 @@ import Foundation -fileprivate func getMutedThreadsKey(pubkey: String) -> String { +fileprivate func getMutedThreadsKey(pubkey: Pubkey) -> String { pk_setting_key(pubkey, key: "muted_threads") } -func loadMutedThreads(pubkey: String) -> [String] { +func loadMutedThreads(pubkey: Pubkey) -> [NoteId] { let key = getMutedThreadsKey(pubkey: pubkey) - return UserDefaults.standard.stringArray(forKey: key) ?? [] + let xs = UserDefaults.standard.stringArray(forKey: key) ?? [] + return xs.reduce(into: [NoteId]()) { ids, k in + guard let note_id = hex_decode(k) else { return } + ids.append(NoteId(Data(note_id))) + } } -func saveMutedThreads(pubkey: String, currentValue: [String], value: [String]) -> Bool { +func saveMutedThreads(pubkey: Pubkey, currentValue: [NoteId], value: [NoteId]) -> Bool { let uniqueMutedThreads = Array(Set(value)) if uniqueMutedThreads != currentValue { @@ -31,9 +35,9 @@ class MutedThreadsManager: ObservableObject { private let keypair: Keypair - private var _mutedThreadsSet: Set - private var _mutedThreads: [String] - var mutedThreads: [String] { + private var _mutedThreadsSet: Set + private var _mutedThreads: [NoteId] + var mutedThreads: [NoteId] { get { return _mutedThreads } @@ -51,7 +55,7 @@ class MutedThreadsManager: ObservableObject { self.keypair = keypair } - func isMutedThread(_ ev: NostrEvent, privkey: String?) -> Bool { + func isMutedThread(_ ev: NostrEvent, privkey: Privkey?) -> Bool { return _mutedThreadsSet.contains(ev.thread_id(privkey: privkey)) } diff --git a/damus/Models/NotificationsModel.swift b/damus/Models/NotificationsModel.swift index fae9bce1680c..dd1d677c1e6e 100644 --- a/damus/Models/NotificationsModel.swift +++ b/damus/Models/NotificationsModel.swift @@ -8,10 +8,10 @@ import Foundation enum NotificationItem { - case repost(String, EventGroup) - case reaction(String, EventGroup) + case repost(NoteId, EventGroup) + case reaction(NoteId, EventGroup) case profile_zap(ZapGroup) - case event_zap(String, ZapGroup) + case event_zap(NoteId, ZapGroup) case reply(NostrEvent) var is_reply: NostrEvent? { @@ -39,15 +39,15 @@ enum NotificationItem { var id: String { switch self { case .repost(let evid, _): - return "repost_" + evid + return "repost_" + evid.hex() case .reaction(let evid, _): - return "reaction_" + evid + return "reaction_" + evid.hex() case .profile_zap: return "profile_zap" case .event_zap(let evid, _): - return "event_zap_" + evid + return "event_zap_" + evid.hex() case .reply(let ev): - return "reply_" + ev.id + return "reply_" + ev.id.hex() } } @@ -104,23 +104,23 @@ class NotificationsModel: ObservableObject, ScrollQueue { var should_queue: Bool = true // mappings from events to - var zaps: [String: ZapGroup] = [:] + var zaps: [NoteId: ZapGroup] = [:] var profile_zaps = ZapGroup() - var reactions: [String: EventGroup] = [:] - var reposts: [String: EventGroup] = [:] + var reactions: [NoteId: EventGroup] = [:] + var reposts: [NoteId: EventGroup] = [:] var replies: [NostrEvent] = [] - var has_reply = Set() - var has_ev = Set() - + var has_reply = Set() + var has_ev = Set() + @Published var notifications: [NotificationItem] = [] func set_should_queue(_ val: Bool) { self.should_queue = val } - func uniq_pubkeys() -> [String] { - var pks = Set() - + func uniq_pubkeys() -> [Pubkey] { + var pks = Set() + for ev in incoming_events { pks.insert(ev.pubkey) } @@ -214,11 +214,11 @@ class NotificationsModel: ObservableObject, ScrollQueue { let id = ref_id.ref_id - if let evgrp = self.reactions[id.string()] { + if let evgrp = self.reactions[id] { return evgrp.insert(ev) } else { let evgrp = EventGroup() - self.reactions[id.string()] = evgrp + self.reactions[id] = evgrp return evgrp.insert(ev) } } diff --git a/damus/Models/Post.swift b/damus/Models/Post.swift index eb40ca195777..3fa3b45394fb 100644 --- a/damus/Models/Post.swift +++ b/damus/Models/Post.swift @@ -10,10 +10,10 @@ import Foundation struct NostrPost { let kind: NostrKind let content: String - let references: [ReferencedId] + let references: [Referenced] let tags: [[String]] - - init(content: String, references: [ReferencedId], kind: NostrKind = .text, tags: [[String]] = []) { + + init(content: String, references: [Referenced], kind: NostrKind = .text, tags: [[String]] = []) { self.content = content self.references = references self.kind = kind @@ -21,93 +21,6 @@ struct NostrPost { } } -func parse_post_mention_type(_ p: Parser) -> MentionType? { - if parse_char(p, "@") { - return .pubkey - } - - if parse_char(p, "&") { - return .event - } - - return nil -} - -func parse_post_reference(_ p: Parser) -> ReferencedId? { - let start = p.pos - - guard let typ = parse_post_mention_type(p) else { - return parse_nostr_ref_uri(p) - } - - if let ref = parse_post_mention(p, mention_type: typ) { - return ref - } - - p.pos = start - - return nil -} - -func is_bech32_char(_ c: Character) -> Bool { - let contains = "qpzry9x8gf2tvdw0s3jn54khce6mua7l".contains(c) - return contains -} - -func parse_post_mention(_ p: Parser, mention_type: MentionType) -> ReferencedId? { - if let id = parse_hexstr(p, len: 64) { - return ReferencedId(ref_id: id, relay_id: nil, key: mention_type.ref) - } else if let bech32_ref = parse_post_bech32_mention(p) { - return bech32_ref - } else { - return nil - } -} - -// TODO: replace this with our C parser -func parse_post_bech32_mention(_ p: Parser) -> ReferencedId? { - let start = p.pos - if parse_str(p, "note") { - } else if parse_str(p, "npub") { - } else if parse_str(p, "nsec") { - } else { - return nil - } - - if !parse_char(p, "1") { - p.pos = start - return nil - } - - guard consume_until(p, match: { c in !is_bech32_char(c) }, end_ok: true) else { - return nil - } - - let end = p.pos - - let sliced = String(substring(p.str, start: start, end: end)) - guard let decoded = try? bech32_decode(sliced) else { - p.pos = start - return nil - } - - let hex = hex_encode(decoded.data) - switch decoded.hrp { - case "note": - return ReferencedId(ref_id: hex, relay_id: nil, key: "e") - case "npub": - return ReferencedId(ref_id: hex, relay_id: nil, key: "p") - case "nsec": - guard let pubkey = privkey_to_pubkey(privkey: hex) else { - p.pos = start - return nil - } - return ReferencedId(ref_id: pubkey, relay_id: nil, key: "p") - default: - p.pos = start - return nil - } -} /// Return a list of tags func parse_post_blocks(content: String) -> [Block] { diff --git a/damus/Models/ProfileModel.swift b/damus/Models/ProfileModel.swift index 05b8e8bfce6c..bd9fae621227 100644 --- a/damus/Models/ProfileModel.swift +++ b/damus/Models/ProfileModel.swift @@ -14,14 +14,14 @@ class ProfileModel: ObservableObject, Equatable { @Published var progress: Int = 0 var events: EventHolder - let pubkey: String + let pubkey: Pubkey let damus: DamusState - var seen_event: Set = Set() + var seen_event: Set = Set() var sub_id = UUID().description var prof_subid = UUID().description - init(pubkey: String, damus: DamusState) { + init(pubkey: Pubkey, damus: DamusState) { self.pubkey = pubkey self.damus = damus self.events = EventHolder(on_queue: { ev in @@ -77,7 +77,7 @@ class ProfileModel: ObservableObject, Equatable { text_filter.limit = 500 print("subscribing to profile \(pubkey) with sub_id \(sub_id)") - print_filters(relay_id: "profile", filters: [[text_filter], [profile_filter]]) + //print_filters(relay_id: "profile", filters: [[text_filter], [profile_filter]]) damus.pool.subscribe(sub_id: sub_id, filters: [text_filter], handler: handle_event) damus.pool.subscribe(sub_id: prof_subid, filters: [profile_filter], handler: handle_event) } diff --git a/damus/Models/ProfileUpdate.swift b/damus/Models/ProfileUpdate.swift index bcbb165b3335..7e302fba013f 100644 --- a/damus/Models/ProfileUpdate.swift +++ b/damus/Models/ProfileUpdate.swift @@ -9,6 +9,6 @@ import Foundation struct ProfileUpdate { - let pubkey: String + let pubkey: Pubkey let profile: Profile } diff --git a/damus/Models/ReactionsModel.swift b/damus/Models/ReactionsModel.swift index 7d25441f4bee..3f3bf723965c 100644 --- a/damus/Models/ReactionsModel.swift +++ b/damus/Models/ReactionsModel.swift @@ -10,7 +10,7 @@ import Foundation final class ReactionsModel: EventsModel { - init(state: DamusState, target: String) { + init(state: DamusState, target: NoteId) { super.init(state: state, target: target, kind: .like) } } diff --git a/damus/Models/ReplyMap.swift b/damus/Models/ReplyMap.swift index 3064fa056291..c3da10dcc2f6 100644 --- a/damus/Models/ReplyMap.swift +++ b/damus/Models/ReplyMap.swift @@ -8,20 +8,20 @@ import Foundation class ReplyMap { - var replies: [String: Set] = [:] - - func lookup(_ id: String) -> Set? { + var replies: [NoteId: Set] = [:] + + func lookup(_ id: NoteId) -> Set? { return replies[id] } - private func ensure_set(id: String) { + private func ensure_set(id: NoteId) { if replies[id] == nil { replies[id] = Set() } } @discardableResult - func add(id: String, reply_id: String) -> Bool { + func add(id: NoteId, reply_id: NoteId) -> Bool { ensure_set(id: id) if (replies[id]!).contains(reply_id) { return false diff --git a/damus/Models/RepostsModel.swift b/damus/Models/RepostsModel.swift index 4b0fccced017..2369b900bd25 100644 --- a/damus/Models/RepostsModel.swift +++ b/damus/Models/RepostsModel.swift @@ -9,7 +9,7 @@ import Foundation final class RepostsModel: EventsModel { - init(state: DamusState, target: String) { + init(state: DamusState, target: NoteId) { super.init(state: state, target: target, kind: .boost) } } diff --git a/damus/Models/SearchHomeModel.swift b/damus/Models/SearchHomeModel.swift index e4f3a223ee96..6931f395078f 100644 --- a/damus/Models/SearchHomeModel.swift +++ b/damus/Models/SearchHomeModel.swift @@ -13,7 +13,7 @@ class SearchHomeModel: ObservableObject { var events: EventHolder @Published var loading: Bool = false - var seen_pubkey: Set = Set() + var seen_pubkey: Set = Set() let damus_state: DamusState let base_subid = UUID().description let profiles_subid = UUID().description @@ -91,7 +91,7 @@ class SearchHomeModel: ObservableObject { } } -func find_profiles_to_fetch(profiles: Profiles, load: PubkeysToLoad, cache: EventCache) -> [String] { +func find_profiles_to_fetch(profiles: Profiles, load: PubkeysToLoad, cache: EventCache) -> [Pubkey] { switch load { case .from_events(let events): return find_profiles_to_fetch_from_events(profiles: profiles, events: events, cache: cache) @@ -100,13 +100,13 @@ func find_profiles_to_fetch(profiles: Profiles, load: PubkeysToLoad, cache: Even } } -func find_profiles_to_fetch_from_keys(profiles: Profiles, pks: [String]) -> [String] { +func find_profiles_to_fetch_from_keys(profiles: Profiles, pks: [Pubkey]) -> [Pubkey] { Array(Set(pks.filter { pk in !profiles.has_fresh_profile(id: pk) })) } -func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent], cache: EventCache) -> [String] { - var pubkeys = Set() - +func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent], cache: EventCache) -> [Pubkey] { + var pubkeys = Set() + for ev in events { // lookup profiles from boosted events if ev.known_kind == .boost, let bev = ev.get_inner_event(cache: cache), !profiles.has_fresh_profile(id: bev.pubkey) { @@ -123,7 +123,7 @@ func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent] enum PubkeysToLoad { case from_events([NostrEvent]) - case from_keys([String]) + case from_keys([Pubkey]) } func load_profiles(profiles_subid: String, relay_id: String, load: PubkeysToLoad, damus_state: DamusState) { diff --git a/damus/Models/SearchModel.swift b/damus/Models/SearchModel.swift index c2a87970ed8e..7922b41ade0c 100644 --- a/damus/Models/SearchModel.swift +++ b/damus/Models/SearchModel.swift @@ -68,10 +68,6 @@ class SearchModel: ObservableObject { let (sub_id, done) = handle_subid_event(pool: state.pool, relay_id: relay_id, ev: ev) { sub_id, ev in if ev.is_textlike && ev.should_show_event { self.add_event(ev) - } else if ev.known_kind == .channel_create { - // unimplemented - } else if ev.known_kind == .channel_meta { - // unimplemented } } diff --git a/damus/Models/ThreadModel.swift b/damus/Models/ThreadModel.swift index d84f549ca528..18bd7ebb7856 100644 --- a/damus/Models/ThreadModel.swift +++ b/damus/Models/ThreadModel.swift @@ -128,7 +128,7 @@ class ThreadModel: ObservableObject { } -func get_top_zap(events: EventCache, evid: String) -> Zapping? { +func get_top_zap(events: EventCache, evid: NoteId) -> Zapping? { return events.get_cache_data(evid).zaps_model.zaps.first(where: { zap in !zap.request.marked_hidden }) diff --git a/damus/Models/UserSearchCache.swift b/damus/Models/UserSearchCache.swift index f7c5257a89ea..112f6f42bcc9 100644 --- a/damus/Models/UserSearchCache.swift +++ b/damus/Models/UserSearchCache.swift @@ -11,15 +11,15 @@ import Foundation /// Optimized for fast searches of substrings by using a Trie. /// Optimal for performing user searches that could be initiated by typing quickly on a keyboard into a text input field. class UserSearchCache { - private let trie = Trie() + private let trie = Trie() - func search(key: String) -> [String] { + func search(key: String) -> [Pubkey] { let results = trie.find(key: key) return results } /// Computes the differences between an old profile, if it exists, and a new profile, and updates the user search cache accordingly. - func updateProfile(id: String, profiles: Profiles, oldProfile: Profile?, newProfile: Profile) { + func updateProfile(id: Pubkey, profiles: Profiles, oldProfile: Profile?, newProfile: Profile) { // Remove searchable keys tied to the old profile if they differ from the new profile // to keep the trie clean without empty nodes while avoiding excessive graph searching. if let oldProfile { @@ -38,7 +38,7 @@ class UserSearchCache { } /// Adds a profile to the user search cache. - private func addProfile(id: String, profiles: Profiles, profile: Profile) { + private func addProfile(id: Pubkey, profiles: Profiles, profile: Profile) { // Searchable by name. if let name = profile.name { trie.insert(key: name.lowercased(), value: id) @@ -56,23 +56,25 @@ class UserSearchCache { } /// Computes the diffences between an old contacts event and a new contacts event for our own user, and updates the search cache accordingly. - func updateOwnContactsPetnames(id: String, oldEvent: NostrEvent?, newEvent: NostrEvent) { + func updateOwnContactsPetnames(id: Pubkey, oldEvent: NostrEvent?, newEvent: NostrEvent) { guard newEvent.known_kind == .contacts && newEvent.pubkey == id else { return } - var petnames: [String: String] = [:] + var petnames: [Pubkey: String] = [:] // Gets all petnames from our new contacts list. newEvent.tags.forEach { tag in - guard tag.count >= 4 && tag[0].matches_char("p") else { + guard tag.count >= 4, + tag[0].matches_char("p"), + let pubkey = tag[1].id()?.pubkey + else { return } - let pubkey = tag[1] let petname = tag[3] - petnames[pubkey.string()] = petname.string() + petnames[pubkey] = petname.string() } // Compute the diff with the old contacts list, if it exists, @@ -80,11 +82,13 @@ class UserSearchCache { // and remove the old ones that are different from the user search cache. if let oldEvent, oldEvent.known_kind == .contacts && oldEvent.pubkey == id { oldEvent.tags.forEach { tag in - guard tag.count >= 4 && tag[0].matches_char("p") else { + guard tag.count >= 4, + tag[0].matches_char("p"), + let pubkey = tag[1].id()?.pubkey + else { return } - let pubkey = tag[1].string() let oldPetname = tag[3].string() if let newPetname = petnames[pubkey] { diff --git a/damus/Models/UserSettingsStore.swift b/damus/Models/UserSettingsStore.swift index b0c4a0bf446f..9cd6b548c79e 100644 --- a/damus/Models/UserSettingsStore.swift +++ b/damus/Models/UserSettingsStore.swift @@ -11,7 +11,7 @@ import UIKit let fallback_zap_amount = 1000 func setting_property_key(key: String) -> String { - return pk_setting_key(UserSettingsStore.pubkey ?? "", key: key) + return pk_setting_key(UserSettingsStore.pubkey ?? .empty.pubkey, key: key) } func setting_get_property_value(key: String, scoped_key: String, default_value: T) -> T { @@ -91,7 +91,7 @@ func setting_set_property_value(scoped_key: String, old_value: T, } class UserSettingsStore: ObservableObject { - static var pubkey: String? = nil + static var pubkey: Pubkey? = nil static var shared: UserSettingsStore? = nil static var bool_options = Set() @@ -255,6 +255,6 @@ class UserSettingsStore: ObservableObject { } } -func pk_setting_key(_ pubkey: String, key: String) -> String { - return "\(pubkey)_\(key)" +func pk_setting_key(_ pubkey: Pubkey, key: String) -> String { + return "\(pubkey.hex())_\(key)" } diff --git a/damus/Models/ZapsModel.swift b/damus/Models/ZapsModel.swift index 42c6d74b6558..5558108ce066 100644 --- a/damus/Models/ZapsModel.swift +++ b/damus/Models/ZapsModel.swift @@ -56,15 +56,10 @@ class ZapsModel: ObservableObject { let events = state.events.lookup_zaps(target: target).map { $0.request.ev } load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events), damus_state: state) case .event(_, let ev): - guard ev.kind == 9735 else { - return - } - - guard let zapper = state.profiles.lookup_zapper(pubkey: target.pubkey) else { - return - } - - guard let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: state.keypair.privkey) else { + guard ev.kind == 9735, + let zapper = state.profiles.lookup_zapper(pubkey: target.pubkey), + let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: state.keypair.privkey) + else { return } diff --git a/damus/Nostr/Id.swift b/damus/Nostr/Id.swift new file mode 100644 index 000000000000..5cfe2d27bd90 --- /dev/null +++ b/damus/Nostr/Id.swift @@ -0,0 +1,129 @@ +// +// Id.swift +// damus +// +// Created by William Casarin on 2023-07-26. +// + +import Foundation + +struct TagRef: Hashable, Equatable, Encodable { + let elem: TagElem + + init(_ elem: TagElem) { + self.elem = elem + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(elem.string()) + } +} + +struct Id: Hashable, Equatable, Codable { + let data: Data + + init(_ data: Data) { + precondition(data.count == 32) + self.data = data + } + + func hex() -> String { + hex_encode(self.data) + } + + var any_id: AnyId { + AnyId(self.data) + } + + static var empty: AnyId { + AnyId(Data([UInt8].init(repeating: 0, count: 32))) + } + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + guard let arr = hex_decode(try container.decode(String.self)) else { + throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "hex string")) + } + self.data = Data(arr) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(hex_encode(self.data)) + } +} + +// String phantom types +enum HashtagMarker {} +enum ParamMarker {} + +// Empty types to use as phantom types +enum NoteIdMarker {} +enum PubkeyId {} +enum PrivkeyId {} +enum AnyIdMarker {} +enum QuoteIdMarker {} +enum RelayIdMarker {} +enum AnyStringMarker {} + +// Refactor your original structs +typealias NoteId = Id +typealias QuoteId = Id +typealias Pubkey = Id +typealias Privkey = Id +typealias AnyId = Id + +typealias Hashtag = TagRef +typealias Param = TagRef + +extension Id where T == AnyIdMarker { + var noteid: NoteId { + NoteId(self.data) + } + + var pubkey: Pubkey { + Pubkey(self.data) + } + + var privkey: Privkey { + Privkey(self.data) + } + +} + +extension TagRef where T == AnyStringMarker { + var hashtag: Hashtag { + Hashtag(self.elem) + } + + var param: Param { + Param(self.elem) + } +} + +extension Id where T == PubkeyId { + var npub: String { + bech32_pubkey(self) + } +} + +extension Id where T == NoteIdMarker { + var bech32: String { + bech32_note_id(self) + } + + /// Refer to this NoteId as a QuoteId + var quote_id: QuoteId { + QuoteId(self.data) + } +} + + +struct Signature: Hashable, Equatable { + let data: Data + + init(_ p: Data) { + self.data = p + } +} diff --git a/damus/Nostr/Nostr.swift b/damus/Nostr/Nostr.swift index 4ff8afb70105..1e439f254701 100644 --- a/damus/Nostr/Nostr.swift +++ b/damus/Nostr/Nostr.swift @@ -201,7 +201,7 @@ class Profile: Codable { try container.encode(value) } - static func displayName(profile: Profile?, pubkey: String) -> DisplayName { + static func displayName(profile: Profile?, pubkey: Pubkey) -> DisplayName { return parse_display_name(profile: profile, pubkey: pubkey) } } diff --git a/damus/Nostr/NostrEvent.swift b/damus/Nostr/NostrEvent.swift index 796fe08329a9..c742989b9346 100644 --- a/damus/Nostr/NostrEvent.swift +++ b/damus/Nostr/NostrEvent.swift @@ -383,7 +383,7 @@ func decode_data(_ data: Data) -> T? { return nil } -func event_commitment(pubkey: String, created_at: UInt32, kind: UInt32, tags: [[String]], content: String) -> String { +func event_commitment(pubkey: Pubkey, created_at: UInt32, kind: UInt32, tags: [[String]], content: String) -> String { let encoder = JSONEncoder() encoder.outputFormatting = .withoutEscapingSlashes let str_data = try! encoder.encode(content) @@ -394,17 +394,17 @@ func event_commitment(pubkey: String, created_at: UInt32, kind: UInt32, tags: [[ let tags_data = try! tags_encoder.encode(tags) let tags = String(decoding: tags_data, as: UTF8.self) - return "[0,\"\(pubkey)\",\(created_at),\(kind),\(tags),\(content)]" + return "[0,\"\(pubkey.hex())\",\(created_at),\(kind),\(tags),\(content)]" } -func calculate_event_commitment(pubkey: String, created_at: UInt32, kind: UInt32, tags: [[String]], content: String) -> Data { +func calculate_event_commitment(pubkey: Pubkey, created_at: UInt32, kind: UInt32, tags: [[String]], content: String) -> Data { let target = event_commitment(pubkey: pubkey, created_at: created_at, kind: kind, tags: tags, content: content) return target.data(using: .utf8)! } -func calculate_event_id(pubkey: String, created_at: UInt32, kind: UInt32, tags: [[String]], content: String) -> Data { +func calculate_event_id(pubkey: Pubkey, created_at: UInt32, kind: UInt32, tags: [[String]], content: String) -> NoteId { let commitment = calculate_event_commitment(pubkey: pubkey, created_at: created_at, kind: kind, tags: tags, content: content) - return sha256(commitment) + return NoteId(sha256(commitment)) } @@ -452,26 +452,72 @@ func random_bytes(count: Int) -> Data { return Data(bytes: bytes, count: count) } -func refid_to_tag(_ ref: ReferencedId) -> [String] { - var tag = [ref.key, ref.ref_id] - if let relay_id = ref.relay_id { - tag.append(relay_id) +func refid_to_tag(_ ref: Referenced) -> [String] { + return [ref.key.string, ref.ref_id.description] +} + +func tag_to_refid(_ tag: TagSequence) -> Referenced? { + guard tag.count >= 2, let key = tag[0].single_char + else { return nil } + + switch key { + case "e": + guard let id = tag[1].id()?.noteid else { return nil } + return Referenced(ref_id: .event(id), key: key) + case "p": + guard let pk = tag[1].id()?.pubkey else { return nil } + return Referenced(ref_id: .pubkey(pk), key: key) + case "t": + return Referenced(ref_id: .hashtag(tag[1]), key: key) + case "d": + return Referenced(ref_id: .param(tag[1]), key: key) + default: + return nil } - return tag } -func tag_to_refid(_ tag: TagSequence) -> ReferencedId? { - guard tag.count >= 2 else { return nil } +func tag_to_evref(_ tag: TagSequence) -> Referenced? { + guard tag.count >= 2, + let key = tag[0].single_char, key == "e", + let ref_id = tag[1].id()?.noteid + else { return nil } - let key = tag[0].string() - let ref_id = tag[1].string() + return Referenced(ref_id: ref_id, key: key) +} + +func tag_to_pkref(_ tag: TagSequence) -> Referenced? { + guard tag.count >= 2, + let key = tag[0].single_char, key == "p", + let ref_id = tag[1].id()?.pubkey + else { return nil } - var relay_id: String? = nil - if tag.count >= 3 { - relay_id = tag[2].string() + return Referenced(ref_id: ref_id, key: key) +} + +func tag_to_paramref(_ tag: TagSequence) -> Referenced? { + guard tag.count >= 2, let key = tag[0].single_char, key == "d" + else { return nil } + + let t1 = tag[1] + if t1.isEmpty { return nil } + + return Referenced(ref_id: t1, key: key) +} + +func tag_to_hashtag_ref(_ tag: TagSequence) -> Referenced? { + guard tag.count >= 2, + let key = tag[0].single_char, + key == "t" + else { + return nil + } + + let t1 = tag[1] + if t1.isEmpty { + return nil } - return ReferencedId(ref_id: ref_id, relay_id: relay_id, key: key) + return Referenced(ref_id: t1, key: key) } @@ -488,7 +534,7 @@ func make_first_contact_event(keypair: Keypair) -> NostrEvent? { let damus_pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681" let tags = [ ["p", damus_pubkey], - ["p", keypair.pubkey] // you're a friend of yourself! + ["p", keypair.pubkey.hex()] // you're a friend of yourself! ] return NostrEvent(content: relay_json, keypair: keypair, kind: NostrKind.contacts.rawValue, tags: tags) } @@ -510,30 +556,35 @@ func make_boost_event(keypair: FullKeypair, boosted: NostrEvent) -> NostrEvent? ts.append(tag.strings()) } - tags.append(["e", boosted.id, "", "root"]) - tags.append(["p", boosted.pubkey]) + tags.append(["e", boosted.id.hex(), "", "root"]) + tags.append(["p", boosted.pubkey.hex()]) - return NostrEvent(content: event_to_json(ev: boosted), keypair: keypair.to_keypair(), kind: 6, tags: tags) + let content = boosted.content_len <= 100 ? event_to_json(ev: boosted) : "" + return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 6, tags: tags) } func make_like_event(keypair: FullKeypair, liked: NostrEvent, content: String = "🤙") -> NostrEvent? { var tags = liked.tags.reduce(into: [[String]]()) { ts, tag in - guard tag.count >= 2 && (tag[0].matches_char("e") || tag[0].matches_char("p")) else { + guard tag.count >= 2, + (tag[0].matches_char("e") || tag[0].matches_char("p")) else { return } ts.append(tag.strings()) } - tags.append(["e", liked.id]) - tags.append(["p", liked.pubkey]) + + tags.append(["e", liked.id.hex()]) + tags.append(["p", liked.pubkey.hex()]) + return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 7, tags: tags) } func zap_target_to_tags(_ target: ZapTarget) -> [[String]] { switch target { case .profile(let pk): - return [["p", pk]] + return [["p", pk.hex()]] case .note(let note_target): - return [["e", note_target.note_id], ["p", note_target.author]] + return [["e", note_target.note_id.hex()], + ["p", note_target.author.hex()]] } } @@ -556,7 +607,7 @@ func make_private_zap_request_event(identity: FullKeypair, enc_key: FullKeypair, return PrivateZapRequest(req: ZapRequest(ev: note), enc: enc) } -func decrypt_private_zap(our_privkey: String, zapreq: NostrEvent, target: ZapTarget) -> NostrEvent? { +func decrypt_private_zap(our_privkey: Privkey, zapreq: NostrEvent, target: ZapTarget) -> NostrEvent? { guard let anon_tag = zapreq.tags.first(where: { t in t.count >= 2 && t[0].matches_str("anon") }) else { @@ -569,7 +620,7 @@ func decrypt_private_zap(our_privkey: String, zapreq: NostrEvent, target: ZapTar // check to see if the private note was from us if note == nil { - guard let our_private_keypair = generate_private_keypair(our_privkey: our_privkey, id: target.id, created_at: zapreq.created_at) else { + guard let our_private_keypair = generate_private_keypair(our_privkey: our_privkey, id: target.id.noteid, created_at: zapreq.created_at) else { return nil } // use our private keypair and their pubkey to get the shared secret @@ -605,17 +656,15 @@ func decrypt_private_zap(our_privkey: String, zapreq: NostrEvent, target: ZapTar return note } -func generate_private_keypair(our_privkey: String, id: String, created_at: UInt32) -> FullKeypair? { - let to_hash = our_privkey + id + String(created_at) +func generate_private_keypair(our_privkey: Privkey, id: NoteId, created_at: UInt32) -> FullKeypair? { + let to_hash = our_privkey.hex() + id.hex() + String(created_at) guard let dat = to_hash.data(using: .utf8) else { return nil } let privkey_bytes = sha256(dat) - let privkey = hex_encode(privkey_bytes) - guard let pubkey = privkey_to_pubkey(privkey: privkey) else { - return nil - } - + let privkey = Privkey(privkey_bytes) + guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil } + return FullKeypair(pubkey: pubkey, privkey: privkey) } @@ -664,7 +713,7 @@ func make_zap_request_event(keypair: FullKeypair, content: String, relays: [Rela tags.append(["anon"]) kp = generate_new_keypair().to_full()! case .priv: - guard let priv_kp = generate_private_keypair(our_privkey: keypair.privkey, id: target.id, created_at: now) else { + guard let priv_kp = generate_private_keypair(our_privkey: keypair.privkey, id: target.id.noteid, created_at: now) else { return nil } kp = priv_kp @@ -702,30 +751,30 @@ func uniq(_ xs: [T]) -> [T] { return ys } -func gather_reply_ids(our_pubkey: String, from: NostrEvent) -> [ReferencedId] { - var ids = from.referenced_ids.first.map { [$0.to_referenced_id()] } ?? [] +func gather_reply_ids(our_pubkey: Pubkey, from: NostrEvent) -> [RefId] { + var ids: [RefId] = from.referenced_ids.first.map({ ref in [ ref.to_refid() ] }) ?? [] - let our_pk = hex_decode(our_pubkey) - let pks = from.referenced_pubkeys.reduce(into: [ReferencedId]()) { rs, r in - if let our_pk, r.ref_id.matches_id(our_pk) { + let pks = from.referenced_pubkeys.reduce(into: [RefId]()) { rs, r in + if r.ref_id == our_pubkey { return } - rs.append(r.to_referenced_id()) + rs.append(.pubkey(r.ref_id)) } - ids.append(.e(from.id)) + let noteid: RefId = .event(from.id) + ids.append(noteid) ids.append(contentsOf: uniq(pks)) if from.pubkey != our_pubkey { - ids.append(ReferencedId(ref_id: from.pubkey, relay_id: nil, key: "p")) + ids.append(.pubkey(from.pubkey)) } return ids } -func gather_quote_ids(our_pubkey: String, from: NostrEvent) -> [ReferencedId] { - var ids: [ReferencedId] = [.q(from.id)] +func gather_quote_ids(our_pubkey: Pubkey, from: NostrEvent) -> [RefId] { + var ids: [RefId] = [.quote(from.id.quote_id)] if from.pubkey != our_pubkey { - ids.append(ReferencedId(ref_id: from.pubkey, relay_id: nil, key: "p")) + ids.append(.pubkey(from.pubkey)) } return ids } @@ -745,7 +794,7 @@ func event_to_json(ev: NostrEvent) -> String { return str } -func decrypt_dm(_ privkey: String?, pubkey: String, content: String, encoding: EncEncoding) -> String? { +func decrypt_dm(_ privkey: Privkey?, pubkey: Pubkey, content: String, encoding: EncEncoding) -> String? { guard let privkey = privkey else { return nil } @@ -761,7 +810,7 @@ func decrypt_dm(_ privkey: String?, pubkey: String, content: String, encoding: E return String(data: dat, encoding: .utf8) } -func decrypt_note(our_privkey: String, their_pubkey: String, enc_note: String, encoding: EncEncoding) -> NostrEvent? { +func decrypt_note(our_privkey: Privkey, their_pubkey: Pubkey, enc_note: String, encoding: EncEncoding) -> NostrEvent? { guard let dec = decrypt_dm(our_privkey, pubkey: their_pubkey, content: enc_note, encoding: encoding) else { return nil } @@ -769,11 +818,11 @@ func decrypt_note(our_privkey: String, their_pubkey: String, enc_note: String, e return decode_nostr_event_json(json: dec) } -func get_shared_secret(privkey: String, pubkey: String) -> [UInt8]? { - guard let privkey_bytes = try? privkey.bytes else { +func get_shared_secret(privkey: Privkey, pubkey: Pubkey) -> [UInt8]? { + guard let privkey_bytes = try? privkey.data.bytes else { return nil } - guard var pk_bytes = try? pubkey.bytes else { + guard var pk_bytes = try? pubkey.data.bytes else { return nil } pk_bytes.insert(2, at: 0) @@ -936,41 +985,32 @@ func aes_operation(operation: CCOperation, data: [UInt8], iv: [UInt8], shared_se func validate_event(ev: NostrEvent) -> ValidationResult { - let raw_id = calculate_event_id(pubkey: ev.pubkey, created_at: ev.created_at, kind: ev.kind, tags: ev.tags.strings(), content: ev.content) - let id = hex_encode(raw_id) - + var id = calculate_event_id(pubkey: ev.pubkey, created_at: ev.created_at, kind: ev.kind, tags: ev.tags.strings(), content: ev.content) + if id != ev.id { return .bad_id } - // TODO: implement verify - guard var sig64 = hex_decode(ev.sig)?.bytes else { - return .bad_sig - } - - guard var ev_pubkey = hex_decode(ev.pubkey)?.bytes else { - return .bad_sig - } - let ctx = secp256k1.Context.raw var xonly_pubkey = secp256k1_xonly_pubkey.init() + + var ev_pubkey = ev.pubkey + var ok = secp256k1_xonly_pubkey_parse(ctx, &xonly_pubkey, &ev_pubkey) != 0 if !ok { return .bad_sig } - var raw_id_bytes = raw_id.bytes - - ok = secp256k1_schnorrsig_verify(ctx, &sig64, &raw_id_bytes, raw_id.count, &xonly_pubkey) > 0 + + var sig = ev.sig + + ok = secp256k1_schnorrsig_verify(ctx, &sig, &id, 32, &xonly_pubkey) > 0 return ok ? .ok : .bad_sig } -func first_eref_mention(ev: NostrEvent, privkey: String?) -> Mention? { +func first_eref_mention(ev: NostrEvent, privkey: Privkey?) -> Mention? { let blocks = ev.blocks(privkey).blocks.filter { block in - guard case .mention(let mention) = block else { - return false - } - - guard case .event = mention.type else { + guard case .mention(let mention) = block, + case .event = mention.ref else { return false } @@ -982,10 +1022,13 @@ func first_eref_mention(ev: NostrEvent, privkey: String?) -> Mention? { } /// MARK: - Preview - if let firstBlock = blocks.first, case .mention(let mention) = firstBlock, mention.ref.key == "e" { - return mention + if let firstBlock = blocks.first, + case .mention(let mention) = firstBlock, + case .event(let note_id) = mention.ref + { + return .note(note_id) } - + return nil } @@ -1011,20 +1054,3 @@ func to_reaction_emoji(ev: NostrEvent) -> String? { } } -extension [ReferencedId] { - var pRefs: [ReferencedId] { - get { - Set(self).filter { ref in - ref.key == "p" - } - } - } - - var eRefs: [ReferencedId] { - get { - self.filter { ref in - ref.key == "e" - } - } - } -} diff --git a/damus/Nostr/NostrFilter.swift b/damus/Nostr/NostrFilter.swift index 70013c2cb2d1..cd6472bf20a5 100644 --- a/damus/Nostr/NostrFilter.swift +++ b/damus/Nostr/NostrFilter.swift @@ -8,14 +8,14 @@ import Foundation struct NostrFilter: Codable, Equatable { - var ids: [String]? + var ids: [NoteId]? var kinds: [NostrKind]? - var referenced_ids: [String]? - var pubkeys: [String]? + var referenced_ids: [NoteId]? + var pubkeys: [Pubkey]? var since: UInt32? var until: UInt32? var limit: UInt32? - var authors: [String]? + var authors: [Pubkey]? var hashtag: [String]? var parameter: [String]? @@ -32,7 +32,7 @@ struct NostrFilter: Codable, Equatable { case limit } - init(ids: [String]? = nil, kinds: [NostrKind]? = nil, referenced_ids: [String]? = nil, pubkeys: [String]? = nil, since: UInt32? = nil, until: UInt32? = nil, limit: UInt32? = nil, authors: [String]? = nil, hashtag: [String]? = nil) { + init(ids: [NoteId]? = nil, kinds: [NostrKind]? = nil, referenced_ids: [NoteId]? = nil, pubkeys: [Pubkey]? = nil, since: UInt32? = nil, until: UInt32? = nil, limit: UInt32? = nil, authors: [Pubkey]? = nil, hashtag: [String]? = nil) { self.ids = ids self.kinds = kinds self.referenced_ids = referenced_ids diff --git a/damus/Nostr/NostrKind.swift b/damus/Nostr/NostrKind.swift index ed926ce362db..680eebdb4eeb 100644 --- a/damus/Nostr/NostrKind.swift +++ b/damus/Nostr/NostrKind.swift @@ -16,8 +16,6 @@ enum NostrKind: UInt32, Codable { case delete = 5 case boost = 6 case like = 7 - case channel_create = 40 - case channel_meta = 41 case chat = 42 case list = 30000 case longform = 30023 diff --git a/damus/Nostr/NostrLink.swift b/damus/Nostr/NostrLink.swift index dc8ef2210edb..1cb7f1c87f00 100644 --- a/damus/Nostr/NostrLink.swift +++ b/damus/Nostr/NostrLink.swift @@ -9,18 +9,18 @@ import Foundation enum NostrLink: Equatable { - case ref(ReferencedId) + case ref(RefId) case filter(NostrFilter) case script([UInt8]) } -func encode_pubkey_uri(_ ref: ReferencedId) -> String { - return "p:" + ref.ref_id +func encode_pubkey_uri(_ pubkey: Pubkey) -> String { + return "p:" + pubkey.hex() } // TODO: bech32 and relay hints -func encode_event_id_uri(_ ref: ReferencedId) -> String { - return "e:" + ref.ref_id +func encode_event_id_uri(_ noteid: NoteId) -> String { + return "e:" + noteid.hex() } func parse_nostr_ref_uri_type(_ p: Parser) -> String? { @@ -55,21 +55,6 @@ func parse_hexstr(_ p: Parser, len: Int) -> String? { return String(substring(p.str, start: start, end: p.pos)) } -func parse_nostr_ref_uri(_ p: Parser) -> ReferencedId? { - let start = p.pos - - if !parse_str(p, "nostr:") { - return nil - } - - guard let ref = parse_post_bech32_mention(p) else { - p.pos = start - return nil - } - - return ref -} - func decode_universal_link(_ s: String) -> NostrLink? { var uri = s.replacingOccurrences(of: "https://damus.io/r/", with: "") uri = uri.replacingOccurrences(of: "https://damus.io/", with: "") diff --git a/damus/Nostr/ProfileDatabase.swift b/damus/Nostr/ProfileDatabase.swift index 0e5ec17d2a1a..6d4385f0628a 100644 --- a/damus/Nostr/ProfileDatabase.swift +++ b/damus/Nostr/ProfileDatabase.swift @@ -25,8 +25,8 @@ final class ProfileDatabase { private var queue = DispatchQueue(label: "io.damus.profile_db", qos: .userInteractive, attributes: .concurrent) - private var network_pull_date_cache = [String: Date]() - + private var network_pull_date_cache = [Pubkey: Date]() + init(cache_url: URL = ProfileDatabase.profile_cache_url) { self.cache_url = cache_url set_up() @@ -73,14 +73,14 @@ final class ProfileDatabase { background_context?.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType) } - private func get_persisted(id: String, context: NSManagedObjectContext) -> PersistedProfile? { + private func get_persisted(id: Pubkey, context: NSManagedObjectContext) -> PersistedProfile? { let request = NSFetchRequest(entityName: entity_name) - request.predicate = NSPredicate(format: "id == %@", id) + request.predicate = NSPredicate(format: "id == %@", id.hex()) request.fetchLimit = 1 return try? context.fetch(request).first } - func get_network_pull_date(id: String) -> Date? { + func get_network_pull_date(id: Pubkey) -> Date? { var pull_date: Date? queue.sync { pull_date = network_pull_date_cache[id] @@ -90,7 +90,7 @@ final class ProfileDatabase { } let request = NSFetchRequest(entityName: entity_name) - request.predicate = NSPredicate(format: "id == %@", id) + request.predicate = NSPredicate(format: "id == %@", id.hex()) request.fetchLimit = 1 request.propertiesToFetch = ["network_pull_date"] guard let profile = try? persistent_container?.viewContext.fetch(request).first else { @@ -111,7 +111,7 @@ final class ProfileDatabase { /// - id: Profile id (pubkey) /// - profile: Profile object to be stored /// - last_update: Date that the Profile was updated - func upsert(id: String, profile: Profile, last_update: Date) async throws { + func upsert(id: Pubkey, profile: Profile, last_update: Date) async throws { guard let context = background_context else { throw ProfileDatabaseError.missing_context } @@ -126,7 +126,7 @@ final class ProfileDatabase { } } else { persisted_profile = NSEntityDescription.insertNewObject(forEntityName: self.entity_name, into: context) as? PersistedProfile - persisted_profile?.id = id + persisted_profile?.id = id.hex() } persisted_profile?.copyValues(from: profile) persisted_profile?.last_update = last_update @@ -141,7 +141,7 @@ final class ProfileDatabase { } } - func get(id: String) -> Profile? { + func get(id: Pubkey) -> Profile? { guard let container = persistent_container, let profile = get_persisted(id: id, context: container.viewContext) else { return nil diff --git a/damus/Nostr/Profiles.swift b/damus/Nostr/Profiles.swift index a75d1a5e8356..6523e7ef2e3a 100644 --- a/damus/Nostr/Profiles.swift +++ b/damus/Nostr/Profiles.swift @@ -21,11 +21,11 @@ class Profiles { qos: .userInteractive, attributes: .concurrent) - private var profiles: [String: TimestampedProfile] = [:] - private var validated: [String: NIP05] = [:] - var nip05_pubkey: [String: String] = [:] - var zappers: [String: String] = [:] - + private var profiles: [Pubkey: TimestampedProfile] = [:] + private var validated: [Pubkey: NIP05] = [:] + var nip05_pubkey: [String: Pubkey] = [:] + var zappers: [Pubkey: Pubkey] = [:] + private let database = ProfileDatabase() let user_search_cache: UserSearchCache @@ -34,35 +34,35 @@ class Profiles { self.user_search_cache = user_search_cache } - func is_validated(_ pk: String) -> NIP05? { + func is_validated(_ pk: Pubkey) -> NIP05? { validated_queue.sync { validated[pk] } } - func invalidate_nip05(_ pk: String) { + func invalidate_nip05(_ pk: Pubkey) { validated_queue.async(flags: .barrier) { self.validated.removeValue(forKey: pk) } } - func set_validated(_ pk: String, nip05: NIP05?) { + func set_validated(_ pk: Pubkey, nip05: NIP05?) { validated_queue.async(flags: .barrier) { self.validated[pk] = nip05 } } - func enumerated() -> EnumeratedSequence<[String: TimestampedProfile]> { + func enumerated() -> EnumeratedSequence<[Pubkey: TimestampedProfile]> { return profiles_queue.sync { return profiles.enumerated() } } - func lookup_zapper(pubkey: String) -> String? { + func lookup_zapper(pubkey: Pubkey) -> Pubkey? { zappers[pubkey] } - func add(id: String, profile: TimestampedProfile) { + func add(id: Pubkey, profile: TimestampedProfile) { profiles_queue.async(flags: .barrier) { let old_timestamped_profile = self.profiles[id] self.profiles[id] = profile @@ -78,7 +78,7 @@ class Profiles { } } - func lookup(id: String) -> Profile? { + func lookup(id: Pubkey) -> Profile? { var profile: Profile? profiles_queue.sync { profile = profiles[id]?.profile @@ -86,13 +86,13 @@ class Profiles { return profile ?? database.get(id: id) } - func lookup_with_timestamp(id: String) -> TimestampedProfile? { + func lookup_with_timestamp(id: Pubkey) -> TimestampedProfile? { profiles_queue.sync { return profiles[id] } } - func has_fresh_profile(id: String) -> Bool { + func has_fresh_profile(id: Pubkey) -> Bool { // check memory first var profile: Profile? profiles_queue.sync { @@ -111,7 +111,7 @@ class Profiles { } -func invalidate_zapper_cache(pubkey: String, profiles: Profiles, lnurl: LNUrls) { +func invalidate_zapper_cache(pubkey: Pubkey, profiles: Profiles, lnurl: LNUrls) { profiles.zappers.removeValue(forKey: pubkey) lnurl.endpoints.removeValue(forKey: pubkey) } diff --git a/damus/Nostr/ProofOfWork.swift b/damus/Nostr/ProofOfWork.swift index 72b916061260..42b385c69584 100644 --- a/damus/Nostr/ProofOfWork.swift +++ b/damus/Nostr/ProofOfWork.swift @@ -55,3 +55,10 @@ func hex_decode(_ str: String) -> [UInt8]? } +func hex_decode_pubkey(_ str: String) -> Pubkey? { + guard str.utf8.count == 64, let decoded = hex_decode(str) else { + return nil + } + + return Pubkey(Data(decoded)) +} diff --git a/damus/Nostr/ReferencedId.swift b/damus/Nostr/ReferencedId.swift index a3c4c1c16c6d..9a4f3d41303f 100644 --- a/damus/Nostr/ReferencedId.swift +++ b/damus/Nostr/ReferencedId.swift @@ -7,15 +7,6 @@ import Foundation -struct Reference: Equatable { - let key: AsciiCharacter - var ref_id: NdbTagElem - - func to_referenced_id() -> ReferencedId { - ReferencedId(ref_id: ref_id.string(), relay_id: nil, key: key.string) - } -} - func tagref_should_be_id(_ tag: NdbTagElem) -> Bool { return !(tag.matches_char("t") || tag.matches_char("d")) } @@ -24,48 +15,90 @@ struct References: Sequence, IteratorProtocol { let tags: TagsSequence var tags_iter: TagsIterator - mutating func next() -> Reference? { + init(tags: TagsSequence) { + self.tags = tags + self.tags_iter = tags.makeIterator() + } + + mutating func next() -> Referenced? { while let tag = tags_iter.next() { - guard tag.count >= 2 else { continue } - let key = tag[0] - let id = tag[1] + guard let evref = tag_to_refid(tag) else { continue } + return evref + } + return nil + } +} - guard key.count == 1, tagref_should_be_id(id) else { continue } +struct ParamReferences: Sequence, IteratorProtocol { + let tags: TagsSequence + var tags_iter: TagsIterator - for c in key { - guard let a = AsciiCharacter(c) else { break } - return Reference(key: a, ref_id: id) - } - } + init(tags: TagsSequence) { + self.tags = tags + self.tags_iter = tags.makeIterator() + } + mutating func next() -> Referenced? { + while let tag = tags_iter.next() { + guard let evref = tag_to_paramref(tag) else { continue } + return evref + } return nil } +} +struct HashtagReferences: Sequence, IteratorProtocol { + let tags: TagsSequence + var tags_iter: TagsIterator - static func ids(tags: TagsSequence) -> LazyFilterSequence { - References(tags: tags).lazy - .filter() { ref in ref.key == "e" } + init(tags: TagsSequence) { + self.tags = tags + self.tags_iter = tags.makeIterator() } - static func pubkeys(tags: TagsSequence) -> LazyFilterSequence { - References(tags: tags).lazy - .filter() { ref in ref.key == "p" } + mutating func next() -> Referenced? { + while let tag = tags_iter.next() { + guard let evref = tag_to_hashtag_ref(tag) else { continue } + return evref + } + return nil } +} + +struct PubkeyReferences: Sequence, IteratorProtocol { + let tags: TagsSequence + var tags_iter: TagsIterator - static func hashtags(tags: TagsSequence) -> LazyFilterSequence { - References(tags: tags).lazy - .filter() { ref in ref.key == "t" } + init(tags: TagsSequence) { + self.tags = tags + self.tags_iter = tags.makeIterator() } - static func list(tags: TagsSequence) -> LazyFilterSequence { - References(tags: tags).lazy - .filter() { ref in ref.key == "d" } + mutating func next() -> Referenced? { + while let tag = tags_iter.next() { + guard let evref = tag_to_pkref(tag) else { continue } + return evref + } + return nil } +} + +struct EventIdReferences: Sequence, IteratorProtocol { + let tags: TagsSequence + var tags_iter: TagsIterator init(tags: TagsSequence) { self.tags = tags self.tags_iter = tags.makeIterator() } + + mutating func next() -> Referenced? { + while let tag = tags_iter.next() { + guard let evref = tag_to_evref(tag) else { continue } + return evref + } + return nil + } } // TagsSequence transition helpers @@ -101,29 +134,74 @@ extension String { } } -struct ReferencedId: Identifiable, Hashable, Equatable { - let ref_id: String - let relay_id: String? - let key: String +enum RefId: Equatable, Hashable, CustomStringConvertible { + case event(NoteId) + case pubkey(Pubkey) + case quote(QuoteId) + case hashtag(TagElem) + case param(TagElem) + + var key: AsciiCharacter { + switch self { + case .event: "e" + case .pubkey: "p" + case .hashtag: "t" + case .param: "d" + case .quote: "q" + } + } - var id: String { - return ref_id + var tag: [String] { + [key.string, self.description] } - - static func q(_ id: String, relay_id: String? = nil) -> ReferencedId { - return ReferencedId(ref_id: id, relay_id: relay_id, key: "q") + + var description: String { + switch self { + case .event(let noteId): return noteId.hex() + case .pubkey(let pubkey): return pubkey.hex() + case .quote(let quote): return quote.hex() + case .hashtag(let string): return string + case .param(let string): return string + } + } +} + +struct Referenced: Equatable, Hashable { + let ref_id: T + let key: AsciiCharacter + + static func q(_ id: QuoteId) -> Referenced { + return Referenced(ref_id: id, key: "q") } - static func e(_ id: String, relay_id: String? = nil) -> ReferencedId { - return ReferencedId(ref_id: id, relay_id: relay_id, key: "e") + static func e(_ id: NoteId) -> Referenced { + return Referenced(ref_id: id, key: "e") } - static func p(_ pk: String, relay_id: String? = nil) -> ReferencedId { - return ReferencedId(ref_id: pk, relay_id: relay_id, key: "p") + static func p(_ pk: Pubkey) -> Referenced { + return Referenced(ref_id: pk, key: "p") } - static func t(_ hashtag: String, relay_id: String? = nil) -> ReferencedId { - return ReferencedId(ref_id: hashtag, relay_id: relay_id, key: "t") + static func t(_ hashtag: String) -> Referenced { + return Referenced(ref_id: hashtag, key: "t") + } +} + +extension Referenced where T == Pubkey { + func to_refid() -> RefId { + .pubkey(self.ref_id) + } +} + +extension Referenced where T == NoteId { + func to_refid() -> RefId { + .event(self.ref_id) + } +} + +extension Referenced where T == QuoteId { + func to_refid() -> RefId { + .quote(self.ref_id) } } diff --git a/damus/Nostr/RelayPool.swift b/damus/Nostr/RelayPool.swift index f697f791c419..474033703824 100644 --- a/damus/Nostr/RelayPool.swift +++ b/damus/Nostr/RelayPool.swift @@ -21,7 +21,7 @@ struct QueuedRequest { struct SeenEvent: Hashable { let relay_id: String - let evid: String + let evid: NoteId } class RelayPool { diff --git a/damus/TestData.swift b/damus/TestData.swift index ecefcc2ccab8..00729105ed42 100644 --- a/damus/TestData.swift +++ b/damus/TestData.swift @@ -7,14 +7,18 @@ import Foundation -let test_seckey = "8e33316b227de8215d36f4787573beaaf532229bb00398430a0ae963b658e656" -let test_pubkey = "a9952fe066ced622167acb8069a0dfd1d44d9493ef2a4c28cf93e2877248b41a" +let test_seckey = Privkey(Data([0x38, 0x65, 0x33, 0x33, 0x33, 0x31, 0x36, 0x62, 0x32, 0x32, 0x37, 0x64, 0x65, 0x38, 0x32, 0x31, 0x35, 0x64, 0x33, 0x36, 0x66, 0x34, 0x37, 0x38, 0x37, 0x35, 0x37, 0x33, 0x62, 0x65, 0x61, 0x61, 0x66, 0x35, 0x33, 0x32, 0x32, 0x32, 0x39, 0x62, 0x62, 0x30, 0x30, 0x33, 0x39, 0x38, 0x34, 0x33, 0x30, 0x61, 0x30, 0x61, 0x65, 0x39, 0x36, 0x33, 0x62, 0x36, 0x35, 0x38, 0x65, 0x36, 0x35, 0x36])) + + +let test_pubkey = Pubkey(Data([0x61, 0x39, 0x39, 0x35, 0x32, 0x66, 0x65, 0x30, 0x36, 0x36, 0x63, 0x65, 0x64, 0x36, 0x32, 0x32, 0x31, 0x36, 0x37, 0x61, 0x63, 0x62, 0x38, 0x30, 0x36, 0x39, 0x61, 0x30, 0x64, 0x66, 0x64, 0x31, 0x64, 0x34, 0x34, 0x64, 0x39, 0x34, 0x39, 0x33, 0x65, 0x66, 0x32, 0x61, 0x34, 0x63, 0x32, 0x38, 0x63, 0x66, 0x39, 0x33, 0x65, 0x32, 0x38, 0x37, 0x37, 0x32, 0x34, 0x38, 0x62, 0x34, 0x31, 0x61])) + + let test_keypair = Keypair(pubkey: test_pubkey, privkey: test_seckey) let test_keypair_full = test_keypair.to_full()! -let test_event_holder = EventHolder(events: [], incoming: [test_event]) +let test_event_holder = EventHolder(events: [], incoming: [test_note]) -let test_event = +let test_note = NostrEvent( content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jpg cool", keypair: test_keypair, @@ -32,18 +36,18 @@ let test_event_group = EventGroup(events: test_reposts) let test_zap_invoice = ZapInvoice(description: .description("description"), amount: 10000, string: "lnbc1", expiry: 1000000, payment_hash: Data(), created_at: 1000000) let test_zap_request_ev = NostrEvent(content: "hi", keypair: test_keypair, kind: 9734)! let test_zap_request = ZapRequest(ev: test_zap_request_ev) -let test_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), raw_request: test_zap_request, is_anon: false, private_request: nil) +let test_zap = Zap(event: test_note, invoice: test_zap_invoice, zapper: test_note.pubkey, target: .profile(test_pubkey), raw_request: test_zap_request, is_anon: false, private_request: nil) -let test_private_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), raw_request: test_zap_request, is_anon: false, private_request: .init(ev: test_event)) +let test_private_zap = Zap(event: test_note, invoice: test_zap_invoice, zapper: test_note.pubkey, target: .profile("pk"), raw_request: test_zap_request, is_anon: false, private_request: .init(ev: test_note)) -let test_pending_zap = PendingZap(amount_msat: 10000, target: .note(id: "id", author: "pk"), request: .normal(test_zap_request), type: .pub, state: .external(.init(state: .fetching_invoice))) +let test_pending_zap = PendingZap(amount_msat: 10000, target: .note(id: test_note.id, author: test_note.pubkey), request: .normal(test_zap_request), type: .pub, state: .external(.init(state: .fetching_invoice))) func test_damus_state() -> DamusState { let damus = DamusState.empty let prof = Profile(name: "damus", display_name: "damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", banner: "", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io", damus_donation: nil) - let tsprof = TimestampedProfile(profile: prof, timestamp: 0, event: test_event) + let tsprof = TimestampedProfile(profile: prof, timestamp: 0, event: test_note) damus.profiles.add(id: test_pubkey, profile: tsprof) return damus } diff --git a/damus/Util/Bech32Object.swift b/damus/Util/Bech32Object.swift index c9ce1f0c850e..3ac123f35fac 100644 --- a/damus/Util/Bech32Object.swift +++ b/damus/Util/Bech32Object.swift @@ -9,9 +9,9 @@ import Foundation enum Bech32Object { - case nsec(String) - case npub(String) - case note(String) + case nsec(Privkey) + case npub(Pubkey) + case note(NoteId) case nscript([UInt8]) static func parse(_ str: String) -> Bech32Object? { @@ -20,11 +20,11 @@ enum Bech32Object { } if decoded.hrp == "npub" { - return .npub(hex_encode(decoded.data)) + return .npub(Pubkey(decoded.data)) } else if decoded.hrp == "nsec" { - return .nsec(hex_encode(decoded.data)) + return .nsec(Privkey(decoded.data)) } else if decoded.hrp == "note" { - return .note(hex_encode(decoded.data)) + return .note(NoteId(decoded.data)) } else if decoded.hrp == "nscript" { return .nscript(decoded.data.bytes) } diff --git a/damus/Util/DisplayName.swift b/damus/Util/DisplayName.swift index 1671e8167228..72c43e883506 100644 --- a/damus/Util/DisplayName.swift +++ b/damus/Util/DisplayName.swift @@ -37,7 +37,7 @@ enum DisplayName { } -func parse_display_name(profile: Profile?, pubkey: String) -> DisplayName { +func parse_display_name(profile: Profile?, pubkey: Pubkey) -> DisplayName { if pubkey == ANON_PUBKEY { return .one(NSLocalizedString("Anonymous", comment: "Placeholder display name of anonymous user.")) } @@ -60,7 +60,7 @@ func parse_display_name(profile: Profile?, pubkey: String) -> DisplayName { return .one(abbrev_bech32_pubkey(pubkey: pubkey)) } -func abbrev_bech32_pubkey(pubkey: String) -> String { - let pk = bech32_nopre_pubkey(pubkey) ?? pubkey +func abbrev_bech32_pubkey(pubkey: Pubkey) -> String { + let pk = bech32_nopre_pubkey(pubkey) ?? pubkey.hex() return abbrev_pubkey(pk) } diff --git a/damus/Util/EventCache.swift b/damus/Util/EventCache.swift index fd9dcead8d9f..fe4ce413ff4f 100644 --- a/damus/Util/EventCache.swift +++ b/damus/Util/EventCache.swift @@ -61,7 +61,7 @@ class ZapsDataModel: ObservableObject { self.zaps = zaps } - func confirm_nwc(reqid: String) { + func confirm_nwc(reqid: NoteId) { guard let zap = zaps.first(where: { z in z.request.ev.id == reqid }), case .pending(let pzap) = zap else { @@ -82,12 +82,12 @@ class ZapsDataModel: ObservableObject { zaps.reduce(0) { total, zap in total + zap.amount } } - func from(_ pubkey: String) -> [Zapping] { + func from(_ pubkey: Pubkey) -> [Zapping] { return self.zaps.filter { z in z.request.ev.pubkey == pubkey } } @discardableResult - func remove(reqid: String) -> Bool { + func remove(reqid: NoteId) -> Bool { guard zaps.first(where: { z in z.request.ev.id == reqid }) != nil else { return false } @@ -137,13 +137,13 @@ class EventData { } class EventCache { - private var events: [String: NostrEvent] = [:] + private var events: [NoteId: NostrEvent] = [:] private var replies = ReplyMap() private var cancellable: AnyCancellable? - private var image_metadata: [String: ImageMetadataState] = [:] - private var video_meta: [String: VideoPlayerModel] = [:] - private var event_data: [String: EventData] = [:] - + private var image_metadata: [String: ImageMetadataState] = [:] // lowercased URL key + private var video_meta: [URL: VideoPlayerModel] = [:] + private var event_data: [NoteId: EventData] = [:] + //private var thread_latest: [String: Int64] init() { @@ -154,7 +154,7 @@ class EventCache { } } - func get_cache_data(_ evid: String) -> EventData { + func get_cache_data(_ evid: NoteId) -> EventData { guard let data = event_data[evid] else { let data = EventData() event_data[evid] = data @@ -164,17 +164,17 @@ class EventCache { return data } - func is_event_valid(_ evid: String) -> ValidationResult { + func is_event_valid(_ evid: NoteId) -> ValidationResult { return get_cache_data(evid).validated } - func store_event_validation(evid: String, validated: ValidationResult) { + func store_event_validation(evid: NoteId, validated: ValidationResult) { get_cache_data(evid).validated = validated } @discardableResult func store_zap(zap: Zapping) -> Bool { - let data = get_cache_data(zap.target.id).zaps_model + let data = get_cache_data(zap.target.id.noteid).zaps_model if let ev = zap.event { insert(ev) } @@ -193,7 +193,7 @@ class EventCache { } func lookup_zaps(target: ZapTarget) -> [Zapping] { - return get_cache_data(target.id).zaps_model.zaps + return get_cache_data(target.id.noteid).zaps_model.zaps } func store_img_metadata(url: URL, meta: ImageMetadataState) { @@ -214,17 +214,17 @@ class EventCache { } func store_video_player_model(url: URL, meta: VideoPlayerModel) { - video_meta[url.absoluteString] = meta + video_meta[url] = meta } @MainActor func get_video_player_model(url: URL) -> VideoPlayerModel { - if let model = video_meta[url.absoluteString] { + if let model = video_meta[url] { return model } let model = VideoPlayerModel() - video_meta[url.absoluteString] = model + video_meta[url] = model return model } @@ -278,7 +278,7 @@ class EventCache { return ev } - func lookup(_ evid: String) -> NostrEvent? { + func lookup(_ evid: NoteId) -> NostrEvent? { return events[evid] } diff --git a/damus/Util/EventHolder.swift b/damus/Util/EventHolder.swift index b25ce0b6459a..144339645e24 100644 --- a/damus/Util/EventHolder.swift +++ b/damus/Util/EventHolder.swift @@ -9,7 +9,7 @@ import Foundation /// Used for holding back events until they're ready to be displayed class EventHolder: ObservableObject, ScrollQueue { - private var has_event = Set() + private var has_event = Set() @Published var events: [NostrEvent] var incoming: [NostrEvent] var should_queue = false diff --git a/damus/Util/Keys.swift b/damus/Util/Keys.swift index 704c2168231e..d1b88cce5110 100644 --- a/damus/Util/Keys.swift +++ b/damus/Util/Keys.swift @@ -9,11 +9,18 @@ import Foundation import secp256k1 let PUBKEY_HRP = "npub" -let ANON_PUBKEY = "anon" + +// some random pubkey +let ANON_PUBKEY = Pubkey(Data([ + 0x85, 0x41, 0x5d, 0x63, 0x5c, 0x2b, 0xaf, 0x55, + 0xf5, 0xb9, 0xa1, 0xa6, 0xce, 0xb7, 0x75, 0xcc, + 0x5c, 0x45, 0x4a, 0x3a, 0x61, 0xb5, 0x3f, 0xe8, + 0x50, 0x42, 0xdc, 0x42, 0xac, 0xe1, 0x7f, 0x12 +])) struct FullKeypair: Equatable { - let pubkey: String - let privkey: String + let pubkey: Pubkey + let privkey: Privkey func to_keypair() -> Keypair { return Keypair(pubkey: pubkey, privkey: privkey) @@ -21,8 +28,8 @@ struct FullKeypair: Equatable { } struct Keypair { - let pubkey: String - let privkey: String? + let pubkey: Pubkey + let privkey: Privkey? let pubkey_bech32: String let privkey_bech32: String? @@ -34,17 +41,17 @@ struct Keypair { return FullKeypair(pubkey: pubkey, privkey: privkey) } - init(pubkey: String, privkey: String?) { + init(pubkey: Pubkey, privkey: Privkey?) { self.pubkey = pubkey self.privkey = privkey - self.pubkey_bech32 = bech32_pubkey(pubkey) ?? pubkey + self.pubkey_bech32 = bech32_pubkey(pubkey) ?? hex_encode(pubkey.data) self.privkey_bech32 = privkey.flatMap { bech32_privkey($0) } } } enum Bech32Key { - case pub(String) - case sec(String) + case pub(Pubkey) + case sec(Privkey) } func decode_bech32_key(_ key: String) -> Bech32Key? { @@ -52,69 +59,58 @@ func decode_bech32_key(_ key: String) -> Bech32Key? { return nil } - let hexed = hex_encode(decoded.data) if decoded.hrp == "npub" { - return .pub(hexed) + return .pub(Pubkey(decoded.data)) } else if decoded.hrp == "nsec" { - return .sec(hexed) + return .sec(Privkey(decoded.data)) } return nil } -func bech32_privkey(_ privkey: String) -> String? { - guard let bytes = hex_decode(privkey) else { - return nil - } - return bech32_encode(hrp: "nsec", bytes) +func bech32_privkey(_ privkey: Privkey) -> String? { + return bech32_encode(hrp: "nsec", privkey.data.bytes) } -func bech32_pubkey(_ pubkey: String) -> String? { - guard let bytes = hex_decode(pubkey) else { - return nil - } - return bech32_encode(hrp: "npub", bytes) +func bech32_pubkey(_ pubkey: Pubkey) -> String { + return bech32_encode(hrp: "npub", pubkey.data.bytes) } -func bech32_pubkey_decode(_ pubkey: String) -> String? { - guard let decoded = try? bech32_decode(pubkey), decoded.hrp == "npub" else { +func bech32_pubkey_decode(_ pubkey: String) -> Pubkey? { + guard let decoded = try? bech32_decode(pubkey), + decoded.hrp == "npub", + decoded.data.count == 32 + else { return nil } - return hex_encode(decoded.data) + return Pubkey(decoded.data) } -func bech32_nopre_pubkey(_ pubkey: String) -> String? { - guard let bytes = hex_decode(pubkey) else { - return nil - } - return bech32_encode(hrp: "", bytes) +func bech32_nopre_pubkey(_ pubkey: Pubkey) -> String { + return bech32_encode(hrp: "", pubkey.data.bytes) } -func bech32_note_id(_ evid: String) -> String? { - guard let bytes = hex_decode(evid) else { - return nil - } - return bech32_encode(hrp: "note", bytes) +func bech32_note_id(_ evid: NoteId) -> String { + return bech32_encode(hrp: "note", evid.data.bytes) } func generate_new_keypair() -> Keypair { let key = try! secp256k1.Signing.PrivateKey() - let privkey = hex_encode(key.rawRepresentation) - let pubkey = hex_encode(Data(key.publicKey.xonly.bytes)) + let privkey = Privkey(key.rawRepresentation) + let pubkey = Pubkey(Data(key.publicKey.xonly.bytes)) return Keypair(pubkey: pubkey, privkey: privkey) } -func privkey_to_pubkey_raw(sec: [UInt8]) -> String? { +func privkey_to_pubkey_raw(sec: [UInt8]) -> Pubkey? { guard let key = try? secp256k1.Signing.PrivateKey(rawRepresentation: sec) else { return nil } - return hex_encode(Data(key.publicKey.xonly.bytes)) + return Pubkey(Data(key.publicKey.xonly.bytes)) } -func privkey_to_pubkey(privkey: String) -> String? { - guard let sec = hex_decode(privkey) else { return nil } - return privkey_to_pubkey_raw(sec: sec) +func privkey_to_pubkey(privkey: Privkey) -> Pubkey? { + return privkey_to_pubkey_raw(sec: privkey.data.bytes) } func save_pubkey(pubkey: String) { @@ -151,11 +147,18 @@ func clear_keypair() throws { func get_saved_keypair() -> Keypair? { do { try removePrivateKeyFromUserDefaults() - - return get_saved_pubkey().flatMap { pubkey in - let privkey = get_saved_privkey() - return Keypair(pubkey: pubkey, privkey: privkey) + + guard let pubkey = get_saved_pubkey(), + let pk = hex_decode(pubkey) + else { + return nil } + + let privkey = get_saved_privkey().flatMap { sec in + hex_decode(sec).map { Privkey(Data($0)) } + } + + return Keypair(pubkey: Pubkey(Data(pk)), privkey: privkey) } catch { return nil } diff --git a/damus/Util/LNUrls.swift b/damus/Util/LNUrls.swift index a5c45c6ade9a..c4614c338d20 100644 --- a/damus/Util/LNUrls.swift +++ b/damus/Util/LNUrls.swift @@ -15,14 +15,14 @@ enum LNUrlState { } class LNUrls { - var endpoints: [String: LNUrlState] + var endpoints: [Pubkey: LNUrlState] init() { self.endpoints = [:] } @MainActor - func lookup_or_fetch(pubkey: String, lnurl: String) async -> LNUrlPayRequest? { + func lookup_or_fetch(pubkey: Pubkey, lnurl: String) async -> LNUrlPayRequest? { switch lookup(pubkey: pubkey) { case .failed(let tries): print("lnurls.lookup_or_fetch failed \(tries) \(lnurl)") @@ -57,7 +57,7 @@ class LNUrls { return v } - func lookup(pubkey: String) -> LNUrlState { + func lookup(pubkey: Pubkey) -> LNUrlState { return self.endpoints[pubkey] ?? .not_fetched } } diff --git a/damus/Util/Lists.swift b/damus/Util/Lists.swift index 7e805f1e10ff..2316ec8ae0ed 100644 --- a/damus/Util/Lists.swift +++ b/damus/Util/Lists.swift @@ -7,56 +7,54 @@ import Foundation -func create_or_update_mutelist(keypair: FullKeypair, mprev: NostrEvent?, to_add: String) -> NostrEvent? { +func create_or_update_mutelist(keypair: FullKeypair, mprev: NostrEvent?, to_add: RefId) -> NostrEvent? { return create_or_update_list_event(keypair: keypair, mprev: mprev, to_add: to_add, list_name: "mute", list_type: "p") } -func remove_from_mutelist(keypair: FullKeypair, prev: NostrEvent, to_remove: String) -> NostrEvent? { - return remove_from_list_event(keypair: keypair, prev: prev, to_remove: to_remove, tag_type: "p") +func remove_from_mutelist(keypair: FullKeypair, prev: NostrEvent, to_remove: RefId) -> NostrEvent? { + return remove_from_list_event(keypair: keypair, prev: prev, to_remove: to_remove) } -func create_or_update_list_event(keypair: FullKeypair, mprev: NostrEvent?, to_add: String, list_name: String, list_type: String) -> NostrEvent? { +func create_or_update_list_event(keypair: FullKeypair, mprev: NostrEvent?, to_add: RefId, list_name: String, list_type: String) -> NostrEvent? { if let prev = mprev, prev.pubkey == keypair.pubkey, matches_list_name(tags: prev.tags, name: list_name) { - return add_to_list_event(keypair: keypair, prev: prev, to_add: to_add, tag_type: list_type) + return add_to_list_event(keypair: keypair, prev: prev, to_add: to_add) } - let tags = [["d", list_name], [list_type, to_add]] + let tags = [["d", list_name], [list_type, to_add.description]] return NostrEvent(content: "", keypair: keypair.to_keypair(), kind: 30000, tags: tags) } -func remove_from_list_event(keypair: FullKeypair, prev: NostrEvent, to_remove: String, tag_type: String) -> NostrEvent? { - var exists = false - for tag in prev.tags { - if tag.count >= 2 && tag[0].matches_str(tag_type) && tag[1].matches_str(to_remove) { - exists = true +func remove_from_list_event(keypair: FullKeypair, prev: NostrEvent, to_remove: RefId) -> NostrEvent? { + var removed = false + + let tags = prev.tags.reduce(into: [[String]](), { acc, tag in + if tag_to_refid(tag)?.ref_id == to_remove { + removed = true + return } - } - - // make sure we actually have the pubkey to remove - guard exists else { + acc.append(tag.strings()) + }) + + guard removed else { return nil } - - let new_tags = prev.tags.filter { tag in - !(tag.count >= 2 && tag[0].matches_str(tag_type) && tag[1].matches_str(to_remove)) - } - - return NostrEvent(content: prev.content, keypair: keypair.to_keypair(), kind: 30000, tags: new_tags.map { $0.strings() }) + + return NostrEvent(content: prev.content, keypair: keypair.to_keypair(), kind: 30000, tags: tags) } -func add_to_list_event(keypair: FullKeypair, prev: NostrEvent, to_add: String, tag_type: String) -> NostrEvent? { +func add_to_list_event(keypair: FullKeypair, prev: NostrEvent, to_add: RefId) -> NostrEvent? { for tag in prev.tags { // we are already muting this user - if tag.count >= 2 && tag[0].matches_str(tag_type) && tag[1].matches_str(to_add) { + if let ref = tag_to_refid(tag), to_add == ref.ref_id { return nil } } var tags = prev.tags.strings() - tags.append([tag_type, to_add]) + tags.append(to_add.tag) return NostrEvent(content: prev.content, keypair: keypair.to_keypair(), kind: 30000, tags: tags) } diff --git a/damus/Util/LocalNotification.swift b/damus/Util/LocalNotification.swift index f003a3b7a4f2..225cce9dc953 100644 --- a/damus/Util/LocalNotification.swift +++ b/damus/Util/LocalNotification.swift @@ -9,21 +9,21 @@ import Foundation struct LossyLocalNotification { let type: LocalNotificationType - let event_id: String - + let event_id: AnyId + func to_user_info() -> [AnyHashable: Any] { return [ "type": self.type.rawValue, - "evid": self.event_id + "evid": self.event_id.hex() ] } static func from_user_info(user_info: [AnyHashable: Any]) -> LossyLocalNotification { - let target_id = user_info["evid"] as! String + let target_id = hex_decode(user_info["evid"] as! String)! let typestr = user_info["type"] as! String let type = LocalNotificationType(rawValue: typestr)! - return LossyLocalNotification(type: type, event_id: target_id) + return LossyLocalNotification(type: type, event_id: AnyId(Data(target_id))) } } @@ -34,7 +34,7 @@ struct LocalNotification { let content: String func to_lossy() -> LossyLocalNotification { - return LossyLocalNotification(type: self.type, event_id: self.target.id) + return LossyLocalNotification(type: self.type, event_id: self.target.id.any_id) } } diff --git a/damus/Util/NIP05.swift b/damus/Util/NIP05.swift index 455732c87984..9432a2bb8ae8 100644 --- a/damus/Util/NIP05.swift +++ b/damus/Util/NIP05.swift @@ -30,7 +30,7 @@ struct NIP05 { struct NIP05Response: Decodable { - let names: [String: String] + let names: [String: Pubkey] } func fetch_nip05(nip05: NIP05) async -> NIP05Response? { @@ -51,7 +51,7 @@ func fetch_nip05(nip05: NIP05) async -> NIP05Response? { return decoded } -func validate_nip05(pubkey: String, nip05_str: String) async -> NIP05? { +func validate_nip05(pubkey: Pubkey, nip05_str: String) async -> NIP05? { guard let nip05 = NIP05.parse(nip05_str) else { return nil } diff --git a/damus/Util/PostBox.swift b/damus/Util/PostBox.swift index 3616d88d25be..4605cb13d846 100644 --- a/damus/Util/PostBox.swift +++ b/damus/Util/PostBox.swift @@ -55,8 +55,8 @@ enum CancelSendErr { class PostBox { let pool: RelayPool - var events: [String: PostedEvent] - + var events: [NoteId: PostedEvent] + init(pool: RelayPool) { self.pool = pool self.events = [:] @@ -64,7 +64,7 @@ class PostBox { } // only works reliably on delay-sent events - func cancel_send(evid: String) -> CancelSendErr? { + func cancel_send(evid: NoteId) -> CancelSendErr? { guard let ev = events[evid] else { return .nothing_to_cancel } diff --git a/damus/Util/PreviewCache.swift b/damus/Util/PreviewCache.swift index 33b51c8705e2..e48061b56a84 100644 --- a/damus/Util/PreviewCache.swift +++ b/damus/Util/PreviewCache.swift @@ -65,9 +65,9 @@ enum PreviewState { } class PreviewCache { - private var previews: [String: Preview] = [:] + private var previews: [NoteId: Preview] = [:] - func lookup(_ evid: String) -> Preview? { + func lookup(_ evid: NoteId) -> Preview? { return previews[evid] } } diff --git a/damus/Util/Relays/RelayBootstrap.swift b/damus/Util/Relays/RelayBootstrap.swift index d27928590957..344f3679fbf6 100644 --- a/damus/Util/Relays/RelayBootstrap.swift +++ b/damus/Util/Relays/RelayBootstrap.swift @@ -14,17 +14,17 @@ let BOOTSTRAP_RELAYS = [ "wss://nos.lol", ] -func bootstrap_relays_setting_key(pubkey: String) -> String { +func bootstrap_relays_setting_key(pubkey: Pubkey) -> String { return pk_setting_key(pubkey, key: "bootstrap_relays") } -func save_bootstrap_relays(pubkey: String, relays: [String]) { +func save_bootstrap_relays(pubkey: Pubkey, relays: [String]) { let key = bootstrap_relays_setting_key(pubkey: pubkey) UserDefaults.standard.set(relays, forKey: key) } -func load_bootstrap_relays(pubkey: String) -> [String] { +func load_bootstrap_relays(pubkey: Pubkey) -> [String] { let key = bootstrap_relays_setting_key(pubkey: pubkey) guard let relays = UserDefaults.standard.stringArray(forKey: key) else { diff --git a/damus/Util/Relays/RelayFilters.swift b/damus/Util/Relays/RelayFilters.swift index 08ff8528f00a..dabd4f94558c 100644 --- a/damus/Util/Relays/RelayFilters.swift +++ b/damus/Util/Relays/RelayFilters.swift @@ -18,7 +18,7 @@ struct RelayFilter: Hashable { } class RelayFilters { - private let our_pubkey: String + private let our_pubkey: Pubkey private var disabled: Set func is_filtered(timeline: Timeline, relay_id: String) -> Bool { @@ -47,23 +47,23 @@ class RelayFilters { save_relay_filters(our_pubkey, filters: disabled) } - init(our_pubkey: String) { + init(our_pubkey: Pubkey) { self.our_pubkey = our_pubkey disabled = load_relay_filters(our_pubkey) ?? Set() } } -func save_relay_filters(_ pubkey: String, filters: Set) { +func save_relay_filters(_ pubkey: Pubkey, filters: Set) { let key = pk_setting_key(pubkey, key: "relay_filters") let arr = Array(filters.map { filter in "\(filter.timeline)\t\(filter.relay_id)" }) UserDefaults.standard.set(arr, forKey: key) } -func relay_filter_setting_key(_ pubkey: String) -> String { +func relay_filter_setting_key(_ pubkey: Pubkey) -> String { return pk_setting_key(pubkey, key: "relay_filters") } -func load_relay_filters(_ pubkey: String) -> Set? { +func load_relay_filters(_ pubkey: Pubkey) -> Set? { let key = relay_filter_setting_key(pubkey) guard let filters = UserDefaults.standard.stringArray(forKey: key) else { return nil diff --git a/damus/Util/ReplyCounter.swift b/damus/Util/ReplyCounter.swift index 89b567657682..39cf8c830c74 100644 --- a/damus/Util/ReplyCounter.swift +++ b/damus/Util/ReplyCounter.swift @@ -8,23 +8,23 @@ import Foundation class ReplyCounter { - private var replies: [String: Int] - private var counted: Set - private var our_replies: [String: NostrEvent] - private let our_pubkey: String - - init(our_pubkey: String) { + private var replies: [NoteId: Int] + private var counted: Set + private var our_replies: [NoteId: NostrEvent] + private let our_pubkey: Pubkey + + init(our_pubkey: Pubkey) { self.our_pubkey = our_pubkey replies = [:] counted = Set() our_replies = [:] } - func our_reply(_ evid: String) -> NostrEvent? { + func our_reply(_ evid: NoteId) -> NostrEvent? { return our_replies[evid] } - func get_replies(_ evid: String) -> Int { + func get_replies(_ evid: NoteId) -> Int { return replies[evid] ?? 0 } diff --git a/damus/Util/Router.swift b/damus/Util/Router.swift index 41a047d7e155..b7e6816441fb 100644 --- a/damus/Util/Router.swift +++ b/damus/Util/Router.swift @@ -8,13 +8,13 @@ import SwiftUI enum Route: Hashable { - case ProfileByKey(pubkey: String) + case ProfileByKey(pubkey: Pubkey) case Profile(profile: ProfileModel, followers: FollowersModel) case Followers(followers: FollowersModel) case Relay(relay: String, showActionButtons: Binding) case RelayDetail(relay: String, metadata: RelayMetadata) case Following(following: FollowingModel) - case MuteList(users: [String]) + case MuteList(users: [Pubkey]) case RelayConfig case Script(script: ScriptModel) case Bookmarks @@ -40,7 +40,7 @@ enum Route: Hashable { case SaveKeys(account: CreateAccountModel) case Wallet(wallet: WalletModel) case WalletScanner(result: Binding) - case FollowersYouKnow(friendedFollowers: [String], followers: FollowersModel) + case FollowersYouKnow(friendedFollowers: [Pubkey], followers: FollowersModel) @ViewBuilder func view(navigationCoordinator: NavigationCoordinator, damusState: DamusState) -> some View { diff --git a/damus/Util/Zap.swift b/damus/Util/Zap.swift index 87c9aa159b2b..06e866fa0198 100644 --- a/damus/Util/Zap.swift +++ b/damus/Util/Zap.swift @@ -7,20 +7,20 @@ import Foundation -public struct NoteZapTarget: Equatable, Hashable { - public let note_id: String - public let author: String +struct NoteZapTarget: Equatable, Hashable { + public let note_id: NoteId + public let author: Pubkey } -public enum ZapTarget: Equatable { - case profile(String) +enum ZapTarget: Equatable { + case profile(Pubkey) case note(NoteZapTarget) - public static func note(id: String, author: String) -> ZapTarget { + static func note(id: NoteId, author: Pubkey) -> ZapTarget { return .note(NoteZapTarget(note_id: id, author: author)) } - var pubkey: String { + var pubkey: Pubkey { switch self { case .profile(let pk): return pk @@ -29,12 +29,12 @@ public enum ZapTarget: Equatable { } } - var id: String { + var id: AnyId { switch self { case .note(let note_target): - return note_target.note_id + return note_target.note_id.any_id case .profile(let pk): - return pk + return pk.any_id } } } @@ -135,8 +135,8 @@ class PendingZap { } struct ZapRequestId: Equatable { - let reqid: String - + let reqid: NoteId + init(from_zap: Zapping) { self.reqid = from_zap.request.ev.id } @@ -258,7 +258,7 @@ enum Zapping { struct Zap { public let event: NostrEvent public let invoice: ZapInvoice - public let zapper: String /// zap authorizer + public let zapper: Pubkey /// zap authorizer public let target: ZapTarget public let raw_request: ZapRequest public let is_anon: Bool @@ -268,7 +268,7 @@ struct Zap { return private_request ?? self.raw_request } - public static func from_zap_event(zap_ev: NostrEvent, zapper: String, our_privkey: String?) -> Zap? { + public static func from_zap_event(zap_ev: NostrEvent, zapper: Pubkey, our_privkey: Privkey?) -> Zap? { /// Make sure that we only create a zap event if it is authorized by the profile or event guard zapper == zap_ev.pubkey else { return nil @@ -348,11 +348,11 @@ func invoice_to_zap_invoice(_ invoice: Invoice) -> ZapInvoice? { } func determine_zap_target(_ ev: NostrEvent) -> ZapTarget? { - guard let ptag = event_tag(ev, name: "p") else { + guard let ptag = ev.referenced_pubkeys.first?.ref_id else { return nil } - if let etag = event_tag(ev, name: "e") { + if let etag = ev.referenced_ids.first?.ref_id { return ZapTarget.note(id: etag, author: ptag) } @@ -405,20 +405,16 @@ func decode_nostr_event_json(_ desc: String) -> NostrEvent? { } -func fetch_zapper_from_lnurl(lnurls: LNUrls, pubkey: String, lnurl: String) async -> String? { - guard let endpoint = await lnurls.lookup_or_fetch(pubkey: pubkey, lnurl: lnurl) else { - return nil - } - - guard let allows = endpoint.allowsNostr, allows else { - return nil - } - - guard let key = endpoint.nostrPubkey, key.count == 64 else { +func fetch_zapper_from_lnurl(lnurls: LNUrls, pubkey: Pubkey, lnurl: String) async -> Pubkey? { + guard let endpoint = await lnurls.lookup_or_fetch(pubkey: pubkey, lnurl: lnurl), + let allows = endpoint.allowsNostr, allows, + let key = endpoint.nostrPubkey, + let pk = hex_decode_pubkey(key) + else { return nil } - return endpoint.nostrPubkey + return pk } func decode_lnurl(_ lnurl: String) -> URL? { diff --git a/damus/Util/Zaps.swift b/damus/Util/Zaps.swift index 698e0dcca04d..934895145812 100644 --- a/damus/Util/Zaps.swift +++ b/damus/Util/Zaps.swift @@ -8,14 +8,14 @@ import Foundation class Zaps { - private(set) var zaps: [String: Zapping] - let our_pubkey: String - var our_zaps: [String: [Zapping]] + private(set) var zaps: [NoteId: Zapping] + let our_pubkey: Pubkey + var our_zaps: [NoteId: [Zapping]] - private(set) var event_counts: [String: Int] - private(set) var event_totals: [String: Int64] - - init(our_pubkey: String) { + private(set) var event_counts: [NoteId: Int] + private(set) var event_totals: [NoteId: Int64] + + init(our_pubkey: Pubkey) { self.zaps = [:] self.our_pubkey = our_pubkey self.our_zaps = [:] @@ -23,7 +23,7 @@ class Zaps { self.event_totals = [:] } - func remove_zap(reqid: String) -> Zapping? { + func remove_zap(reqid: NoteId) -> Zapping? { var res: Zapping? = nil for kv in our_zaps { let ours = kv.value @@ -78,7 +78,7 @@ class Zaps { return } - let id = zap.target.id + let id = zap.target.id.noteid if event_counts[id] == nil { event_counts[id] = 0 } diff --git a/damus/Views/ActionBar/EventActionBar.swift b/damus/Views/ActionBar/EventActionBar.swift index 0a9a7793f610..8bbecd3ed2be 100644 --- a/damus/Views/ActionBar/EventActionBar.swift +++ b/damus/Views/ActionBar/EventActionBar.swift @@ -106,11 +106,7 @@ struct EventActionBar: View { } } .sheet(isPresented: $show_share_sheet, onDismiss: { self.show_share_sheet = false }) { - if let note_id = bech32_note_id(event.id) { - if let url = URL(string: "https://damus.io/" + note_id) { - ShareSheet(activityItems: [url]) - } - } + ShareSheet(activityItems: [URL(string: "https://damus.io/" + event.id.bech32)!]) } .sheet(isPresented: $show_repost_action, onDismiss: { self.show_repost_action = false }) { @@ -123,7 +119,7 @@ struct EventActionBar: View { } } .onReceive(handle_notify(.update_stats)) { n in - let target = n.object as! String + let target = n.object as! NoteId guard target == self.event.id else { return } self.bar.update(damus: self.damus_state, evid: target) } @@ -226,11 +222,11 @@ struct EventActionBar_Previews: PreviewProvider { let bar = ActionBarModel.empty() let likedbar = ActionBarModel(likes: 10, boosts: 0, zaps: 0, zap_total: 0, replies: 0, our_like: nil, our_boost: nil, our_zap: nil, our_reply: nil) - let likedbar_ours = ActionBarModel(likes: 10, boosts: 0, zaps: 0, zap_total: 0, replies: 0, our_like: test_event, our_boost: nil, our_zap: nil, our_reply: nil) - let maxed_bar = ActionBarModel(likes: 999, boosts: 999, zaps: 999, zap_total: 99999999, replies: 999, our_like: test_event, our_boost: test_event, our_zap: nil, our_reply: nil) - let extra_max_bar = ActionBarModel(likes: 9999, boosts: 9999, zaps: 9999, zap_total: 99999999, replies: 9999, our_like: test_event, our_boost: test_event, our_zap: nil, our_reply: test_event) - let mega_max_bar = ActionBarModel(likes: 9999999, boosts: 99999, zaps: 9999, zap_total: 99999999, replies: 9999999, our_like: test_event, our_boost: test_event, our_zap: .zap(test_zap), our_reply: test_event) - + let likedbar_ours = ActionBarModel(likes: 10, boosts: 0, zaps: 0, zap_total: 0, replies: 0, our_like: test_note, our_boost: nil, our_zap: nil, our_reply: nil) + let maxed_bar = ActionBarModel(likes: 999, boosts: 999, zaps: 999, zap_total: 99999999, replies: 999, our_like: test_note, our_boost: test_note, our_zap: nil, our_reply: nil) + let extra_max_bar = ActionBarModel(likes: 9999, boosts: 9999, zaps: 9999, zap_total: 99999999, replies: 9999, our_like: test_note, our_boost: test_note, our_zap: nil, our_reply: test_note) + let mega_max_bar = ActionBarModel(likes: 9999999, boosts: 99999, zaps: 9999, zap_total: 99999999, replies: 9999999, our_like: test_note, our_boost: test_note, our_zap: .zap(test_zap), our_reply: test_note) + VStack(spacing: 50) { EventActionBar(damus_state: ds, event: ev, bar: bar) diff --git a/damus/Views/ActionBar/EventDetailBar.swift b/damus/Views/ActionBar/EventDetailBar.swift index 3b4e7af48d1e..64844befe2cc 100644 --- a/damus/Views/ActionBar/EventDetailBar.swift +++ b/damus/Views/ActionBar/EventDetailBar.swift @@ -9,12 +9,12 @@ import SwiftUI struct EventDetailBar: View { let state: DamusState - let target: String - let target_pk: String - + let target: NoteId + let target_pk: Pubkey + @ObservedObject var bar: ActionBarModel - init(state: DamusState, target: String, target_pk: String) { + init(state: DamusState, target: NoteId, target_pk: Pubkey) { self.state = state self.target = target self.target_pk = target_pk @@ -56,6 +56,6 @@ struct EventDetailBar: View { struct EventDetailBar_Previews: PreviewProvider { static var previews: some View { - EventDetailBar(state: test_damus_state(), target: "", target_pk: "") + EventDetailBar(state: test_damus_state(), target: .empty.noteid, target_pk: .empty.pubkey) } } diff --git a/damus/Views/ActionBar/RepostAction.swift b/damus/Views/ActionBar/RepostAction.swift index b22d443c046c..986010184669 100644 --- a/damus/Views/ActionBar/RepostAction.swift +++ b/damus/Views/ActionBar/RepostAction.swift @@ -61,6 +61,6 @@ struct RepostAction: View { struct RepostAction_Previews: PreviewProvider { static var previews: some View { - RepostAction(damus_state: test_damus_state(), event: test_event) + RepostAction(damus_state: test_damus_state(), event: test_note) } } diff --git a/damus/Views/ActionBar/ShareAction.swift b/damus/Views/ActionBar/ShareAction.swift index 41571956768a..0da47e0babd3 100644 --- a/damus/Views/ActionBar/ShareAction.swift +++ b/damus/Views/ActionBar/ShareAction.swift @@ -38,7 +38,7 @@ struct ShareAction: View { ShareActionButton(img: "link", text: NSLocalizedString("Copy Link", comment: "Button to copy link to note")) { dismiss() - UIPasteboard.general.string = "https://damus.io/" + (bech32_note_id(event.id) ?? event.id) + UIPasteboard.general.string = "https://damus.io/" + (bech32_note_id(event.id) ?? event.id.hex()) } let bookmarkImg = isBookmarked ? "bookmark.fill" : "bookmark" diff --git a/damus/Views/DMChatView.swift b/damus/Views/DMChatView.swift index 02354686f095..a905c68a32c1 100644 --- a/damus/Views/DMChatView.swift +++ b/damus/Views/DMChatView.swift @@ -13,7 +13,7 @@ struct DMChatView: View, KeyboardReadable { @ObservedObject var dms: DirectMessageModel @State var showPrivateKeyWarning: Bool = false - var pubkey: String { + var pubkey: Pubkey { dms.pubkey } @@ -128,7 +128,7 @@ struct DMChatView: View, KeyboardReadable { } func send_message() { - let tags = [["p", pubkey]] + let tags = [["p", pubkey.hex()]] let post_blocks = parse_post_blocks(content: dms.draft) let content = render_blocks(blocks: post_blocks) @@ -190,7 +190,7 @@ enum EncEncoding { case bech32 } -func encrypt_message(message: String, privkey: String, to_pk: String, encoding: EncEncoding = .base64) -> String? { +func encrypt_message(message: String, privkey: Privkey, to_pk: Pubkey, encoding: EncEncoding = .base64) -> String? { let iv = random_bytes(count: 16).bytes guard let shared_sec = get_shared_secret(privkey: privkey, pubkey: to_pk) else { return nil @@ -209,7 +209,7 @@ func encrypt_message(message: String, privkey: String, to_pk: String, encoding: } -func create_encrypted_event(_ message: String, to_pk: String, tags: [[String]], keypair: FullKeypair, created_at: UInt32, kind: UInt32) -> NostrEvent? { +func create_encrypted_event(_ message: String, to_pk: Pubkey, tags: [[String]], keypair: FullKeypair, created_at: UInt32, kind: UInt32) -> NostrEvent? { let privkey = keypair.privkey guard let enc_content = encrypt_message(message: message, privkey: privkey, to_pk: to_pk) else { @@ -219,7 +219,7 @@ func create_encrypted_event(_ message: String, to_pk: String, tags: [[String]], return NostrEvent(content: enc_content, keypair: keypair.to_keypair(), kind: kind, tags: tags, createdAt: created_at) } -func create_dm(_ message: String, to_pk: String, tags: [[String]], keypair: Keypair, created_at: UInt32? = nil) -> NostrEvent? +func create_dm(_ message: String, to_pk: Pubkey, tags: [[String]], keypair: Keypair, created_at: UInt32? = nil) -> NostrEvent? { let created = created_at ?? UInt32(Date().timeIntervalSince1970) diff --git a/damus/Views/DMView.swift b/damus/Views/DMView.swift index c74cd42df0de..219fb0dd8715 100644 --- a/damus/Views/DMView.swift +++ b/damus/Views/DMView.swift @@ -18,7 +18,7 @@ struct DMView: View { var Mention: some View { Group { if let mention = first_eref_mention(ev: event, privkey: damus_state.keypair.privkey) { - BuilderEventView(damus: damus_state, event_id: mention.ref.id) + BuilderEventView(damus: damus_state, event_id: mention.ref) } else { EmptyView() } diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift index 2b7201f878f4..ea9f85663c86 100644 --- a/damus/Views/EventView.swift +++ b/damus/Views/EventView.swift @@ -20,9 +20,9 @@ struct EventView: View { let event: NostrEvent let options: EventViewOptions let damus: DamusState - let pubkey: String + let pubkey: Pubkey - init(damus: DamusState, event: NostrEvent, pubkey: String? = nil, options: EventViewOptions = []) { + init(damus: DamusState, event: NostrEvent, pubkey: Pubkey? = nil, options: EventViewOptions = []) { self.event = event self.options = options self.damus = damus @@ -54,7 +54,7 @@ struct EventView: View { } // blame the porn bots for this code -func should_show_images(settings: UserSettingsStore, contacts: Contacts, ev: NostrEvent, our_pubkey: String, booster_pubkey: String? = nil) -> Bool { +func should_show_images(settings: UserSettingsStore, contacts: Contacts, ev: NostrEvent, our_pubkey: Pubkey, booster_pubkey: Pubkey? = nil) -> Bool { if settings.always_show_images { return true } @@ -96,7 +96,7 @@ func format_date(_ created_at: UInt32) -> String { return dateFormatter.string(from: date) } -func make_actionbar_model(ev: String, damus: DamusState) -> ActionBarModel { +func make_actionbar_model(ev: NoteId, damus: DamusState) -> ActionBarModel { let model = ActionBarModel.empty() model.update(damus: damus, evid: ev) return model @@ -143,7 +143,7 @@ struct EventView_Previews: PreviewProvider { */ - EventView( damus: test_damus_state(), event: test_event ) + EventView( damus: test_damus_state(), event: test_note ) EventView( damus: test_damus_state(), event: test_longform_event.event, options: [.wide] ) } diff --git a/damus/Views/Events/BuilderEventView.swift b/damus/Views/Events/BuilderEventView.swift index da40bcb2beb8..57dcdb5e84c1 100644 --- a/damus/Views/Events/BuilderEventView.swift +++ b/damus/Views/Events/BuilderEventView.swift @@ -9,7 +9,7 @@ import SwiftUI struct BuilderEventView: View { let damus: DamusState - let event_id: String + let event_id: NoteId @State var event: NostrEvent? @State var subscription_uuid: String = UUID().description @@ -19,7 +19,7 @@ struct BuilderEventView: View { self.event_id = event.id } - init(damus: DamusState, event_id: String) { + init(damus: DamusState, event_id: NoteId) { let event = damus.events.lookup(event_id) self.event_id = event_id self.damus = damus @@ -93,7 +93,8 @@ struct BuilderEventView: View { struct BuilderEventView_Previews: PreviewProvider { static var previews: some View { - BuilderEventView(damus: test_damus_state(), event_id: "536bee9e83c818e3b82c101935128ae27a0d4290039aaf253efe5f09232c1962") + let ev_id = + BuilderEventView(damus: test_damus_state(), event_id: test_note.id) } } diff --git a/damus/Views/Events/Components/EventTop.swift b/damus/Views/Events/Components/EventTop.swift index b5a357487e1c..ffd8cd0b421a 100644 --- a/damus/Views/Events/Components/EventTop.swift +++ b/damus/Views/Events/Components/EventTop.swift @@ -10,10 +10,10 @@ import SwiftUI struct EventTop: View { let state: DamusState let event: NostrEvent - let pubkey: String + let pubkey: Pubkey let is_anon: Bool - init(state: DamusState, event: NostrEvent, pubkey: String, is_anon: Bool) { + init(state: DamusState, event: NostrEvent, pubkey: Pubkey, is_anon: Bool) { self.state = state self.event = event self.pubkey = pubkey @@ -22,7 +22,7 @@ struct EventTop: View { func ProfileName(is_anon: Bool) -> some View { let profile = state.profiles.lookup(id: self.pubkey) - let pk = is_anon ? "anon" : self.pubkey + let pk = is_anon ? ANON_PUBKEY : self.pubkey return EventProfileName(pubkey: pk, profile: profile, damus: state, size: .normal) } @@ -41,6 +41,6 @@ struct EventTop: View { struct EventTop_Previews: PreviewProvider { static var previews: some View { - EventTop(state: test_damus_state(), event: test_event, pubkey: test_event.pubkey, is_anon: false) + EventTop(state: test_damus_state(), event: test_note, pubkey: test_note.pubkey, is_anon: false) } } diff --git a/damus/Views/Events/Components/ReplyDescription.swift b/damus/Views/Events/Components/ReplyDescription.swift index 13a27d36721b..ad5c625b3343 100644 --- a/damus/Views/Events/Components/ReplyDescription.swift +++ b/damus/Views/Events/Components/ReplyDescription.swift @@ -22,7 +22,7 @@ struct ReplyDescription: View { struct ReplyDescription_Previews: PreviewProvider { static var previews: some View { - ReplyDescription(event: test_event, profiles: test_damus_state().profiles) + ReplyDescription(event: test_note, profiles: test_damus_state().profiles) } } diff --git a/damus/Views/Events/Components/ReplyPart.swift b/damus/Views/Events/Components/ReplyPart.swift index de8dea00e2ad..506b37545255 100644 --- a/damus/Views/Events/Components/ReplyPart.swift +++ b/damus/Views/Events/Components/ReplyPart.swift @@ -9,7 +9,7 @@ import SwiftUI struct ReplyPart: View { let event: NostrEvent - let privkey: String? + let privkey: Privkey? let profiles: Profiles var body: some View { @@ -25,6 +25,6 @@ struct ReplyPart: View { struct ReplyPart_Previews: PreviewProvider { static var previews: some View { - ReplyPart(event: test_event, privkey: nil, profiles: test_damus_state().profiles) + ReplyPart(event: test_note, privkey: nil, profiles: test_damus_state().profiles) } } diff --git a/damus/Views/Events/EventBody.swift b/damus/Views/Events/EventBody.swift index 37cdda15b2f3..fe2cb7260811 100644 --- a/damus/Views/Events/EventBody.swift +++ b/damus/Views/Events/EventBody.swift @@ -43,6 +43,6 @@ struct EventBody: View { struct EventBody_Previews: PreviewProvider { static var previews: some View { - EventBody(damus_state: test_damus_state(), event: test_event, size: .normal, options: []) + EventBody(damus_state: test_damus_state(), event: test_note, size: .normal, options: []) } } diff --git a/damus/Views/Events/EventMenu.swift b/damus/Views/Events/EventMenu.swift index b7f9c1ce28c2..fde3c0e220df 100644 --- a/damus/Views/Events/EventMenu.swift +++ b/damus/Views/Events/EventMenu.swift @@ -10,7 +10,7 @@ import SwiftUI struct EventMenuContext: View { let event: NostrEvent let keypair: Keypair - let target_pubkey: String + let target_pubkey: Pubkey let bookmarks: BookmarksManager let muted_threads: MutedThreadsManager @ObservedObject var settings: UserSettingsStore @@ -27,9 +27,7 @@ struct EventMenuContext: View { var body: some View { HStack { Menu { - MenuItems(event: event, keypair: keypair, target_pubkey: target_pubkey, bookmarks: bookmarks, muted_threads: muted_threads, settings: settings) - } label: { Label("", systemImage: "ellipsis") .foregroundColor(Color.gray) @@ -44,7 +42,7 @@ struct EventMenuContext: View { struct MenuItems: View { let event: NostrEvent let keypair: Keypair - let target_pubkey: String + let target_pubkey: Pubkey let bookmarks: BookmarksManager let muted_threads: MutedThreadsManager @ObservedObject var settings: UserSettingsStore @@ -52,7 +50,7 @@ struct MenuItems: View { @State private var isBookmarked: Bool = false @State private var isMutedThread: Bool = false - init(event: NostrEvent, keypair: Keypair, target_pubkey: String, bookmarks: BookmarksManager, muted_threads: MutedThreadsManager, settings: UserSettingsStore) { + init(event: NostrEvent, keypair: Keypair, target_pubkey: Pubkey, bookmarks: BookmarksManager, muted_threads: MutedThreadsManager, settings: UserSettingsStore) { let bookmarked = bookmarks.isBookmarked(event) self._isBookmarked = State(initialValue: bookmarked) diff --git a/damus/Views/Events/EventProfile.swift b/damus/Views/Events/EventProfile.swift index 6c23fc75cd11..89d727370280 100644 --- a/damus/Views/Events/EventProfile.swift +++ b/damus/Views/Events/EventProfile.swift @@ -24,7 +24,7 @@ func eventview_pfp_size(_ size: EventViewKind) -> CGFloat { struct EventProfile: View { let damus_state: DamusState - let pubkey: String + let pubkey: Pubkey let profile: Profile? let size: EventViewKind @@ -51,6 +51,6 @@ struct EventProfile: View { struct EventProfile_Previews: PreviewProvider { static var previews: some View { - EventProfile(damus_state: test_damus_state(), pubkey: "pk", profile: nil, size: .normal) + EventProfile(damus_state: test_damus_state(), pubkey: test_note.pubkey, profile: nil, size: .normal) } } diff --git a/damus/Views/Events/EventShell.swift b/damus/Views/Events/EventShell.swift index 52283cf0e01d..1ce1d31bd423 100644 --- a/damus/Views/Events/EventShell.swift +++ b/damus/Views/Events/EventShell.swift @@ -10,11 +10,11 @@ import SwiftUI struct EventShell: View { let state: DamusState let event: NostrEvent - let pubkey: String + let pubkey: Pubkey let options: EventViewOptions let content: Content - init(state: DamusState, event: NostrEvent, pubkey: String, options: EventViewOptions, @ViewBuilder content: () -> Content) { + init(state: DamusState, event: NostrEvent, pubkey: Pubkey, options: EventViewOptions, @ViewBuilder content: () -> Content) { self.state = state self.event = event self.options = options @@ -34,7 +34,7 @@ struct EventShell: View { !options.contains(.no_action_bar) } - func get_mention() -> Mention? { + func get_mention() -> Mention? { if self.options.contains(.nested) || self.options.contains(.no_mentions) { return nil } @@ -42,8 +42,8 @@ struct EventShell: View { return first_eref_mention(ev: event, privkey: state.keypair.privkey) } - func Mention(_ mention: Mention) -> some View { - return BuilderEventView(damus: state, event_id: mention.ref.id) + func Mention(_ mention: Mention) -> some View { + return BuilderEventView(damus: state, event_id: mention.ref.ref_id) } var ActionBar: some View { @@ -133,11 +133,11 @@ struct EventShell_Previews: PreviewProvider { static var previews: some View { VStack { - EventShell(state: test_damus_state(), event: test_event, options: [.no_action_bar]) { + EventShell(state: test_damus_state(), event: test_note, options: [.no_action_bar]) { Text(verbatim: "Hello") } - EventShell(state: test_damus_state(), event: test_event, options: [.no_action_bar, .wide]) { + EventShell(state: test_damus_state(), event: test_note, options: [.no_action_bar, .wide]) { Text(verbatim: "Hello") } } diff --git a/damus/Views/Events/MutedEventView.swift b/damus/Views/Events/MutedEventView.swift index 54cdc26a467a..5dae0133285d 100644 --- a/damus/Views/Events/MutedEventView.swift +++ b/damus/Views/Events/MutedEventView.swift @@ -61,7 +61,7 @@ struct MutedEventView: View { } } .onReceive(handle_notify(.new_mutes)) { notif in - guard let mutes = notif.object as? [String] else { + guard let mutes = notif.object as? [Pubkey] else { return } @@ -70,7 +70,7 @@ struct MutedEventView: View { } } .onReceive(handle_notify(.new_unmutes)) { notif in - guard let unmutes = notif.object as? [String] else { + guard let unmutes = notif.object as? [Pubkey] else { return } @@ -85,7 +85,7 @@ struct MutedEventView_Previews: PreviewProvider { static var previews: some View { - MutedEventView(damus_state: test_damus_state(), event: test_event, selected: false) + MutedEventView(damus_state: test_damus_state(), event: test_note, selected: false) .frame(width: .infinity, height: 50) } } diff --git a/damus/Views/Events/SelectedEventView.swift b/damus/Views/Events/SelectedEventView.swift index bdb5e45c8bf8..ccf678649bcf 100644 --- a/damus/Views/Events/SelectedEventView.swift +++ b/damus/Views/Events/SelectedEventView.swift @@ -12,7 +12,7 @@ struct SelectedEventView: View { let event: NostrEvent let size: EventViewKind - var pubkey: String { + var pubkey: Pubkey { event.pubkey } @@ -50,7 +50,7 @@ struct SelectedEventView: View { EventBody(damus_state: damus, event: event, size: size, options: [.wide]) if let mention = first_eref_mention(ev: event, privkey: damus.keypair.privkey) { - BuilderEventView(damus: damus, event_id: mention.ref.id) + BuilderEventView(damus: damus, event_id: mention.ref) .padding(.horizontal) } @@ -76,7 +76,7 @@ struct SelectedEventView: View { .padding([.top], 4) } .onReceive(handle_notify(.update_stats)) { n in - let target = n.object as! String + let target = n.object as! NoteId guard target == self.event.id else { return } self.bar.update(damus: self.damus, evid: target) } @@ -87,6 +87,6 @@ struct SelectedEventView: View { struct SelectedEventView_Previews: PreviewProvider { static var previews: some View { - SelectedEventView(damus: test_damus_state(), event: test_event, size: .selected) + SelectedEventView(damus: test_damus_state(), event: test_note, size: .selected) } } diff --git a/damus/Views/Events/TextEvent.swift b/damus/Views/Events/TextEvent.swift index 29b080aee848..063a48f8996e 100644 --- a/damus/Views/Events/TextEvent.swift +++ b/damus/Views/Events/TextEvent.swift @@ -75,10 +75,10 @@ func event_is_anonymous(ev: NostrEvent) -> Bool { struct TextEvent_Previews: PreviewProvider { static var previews: some View { VStack(spacing: 20) { - TextEvent(damus: test_damus_state(), event: test_event, pubkey: "pk", options: []) + TextEvent(damus: test_damus_state(), event: test_note, pubkey: "pk", options: []) .frame(height: 400) - TextEvent(damus: test_damus_state(), event: test_event, pubkey: "pk", options: [.wide]) + TextEvent(damus: test_damus_state(), event: test_note, pubkey: "pk", options: [.wide]) .frame(height: 400) } } diff --git a/damus/Views/Events/WideEventView.swift b/damus/Views/Events/WideEventView.swift index ca10f15ce49e..42f0cb8d7472 100644 --- a/damus/Views/Events/WideEventView.swift +++ b/damus/Views/Events/WideEventView.swift @@ -17,6 +17,6 @@ struct WideEventView: View { struct WideEventView_Previews: PreviewProvider { static var previews: some View { - WideEventView(event: test_event) + WideEventView(event: test_note) } } diff --git a/damus/Views/FollowingView.swift b/damus/Views/FollowingView.swift index e1aeff81c2e7..bdb5724eebee 100644 --- a/damus/Views/FollowingView.swift +++ b/damus/Views/FollowingView.swift @@ -26,7 +26,7 @@ struct FollowUserView: View { struct FollowersYouKnowView: View { let damus_state: DamusState - let friended_followers: [String] + let friended_followers: [Pubkey] @ObservedObject var followers: FollowersModel var body: some View { diff --git a/damus/Views/ImagePicker.swift b/damus/Views/ImagePicker.swift index 366d8966b89d..d4c7b4501ca7 100644 --- a/damus/Views/ImagePicker.swift +++ b/damus/Views/ImagePicker.swift @@ -15,7 +15,7 @@ struct ImagePicker: UIViewControllerRepresentable { let uploader: MediaUploader let sourceType: UIImagePickerController.SourceType - let pubkey: String + let pubkey: Pubkey @Binding var image_upload_confirm: Bool var imagesOnly: Bool = false let onImagePicked: (URL) -> Void diff --git a/damus/Views/Muting/MutelistView.swift b/damus/Views/Muting/MutelistView.swift index 9b84b08730b2..116381491c61 100644 --- a/damus/Views/Muting/MutelistView.swift +++ b/damus/Views/Muting/MutelistView.swift @@ -9,19 +9,16 @@ import SwiftUI struct MutelistView: View { let damus_state: DamusState - @State var users: [String] + @State var users: [Pubkey] - func RemoveAction(pubkey: String) -> some View { + func RemoveAction(pubkey: Pubkey) -> some View { Button { - guard let mutelist = damus_state.contacts.mutelist else { - return - } - - guard let keypair = damus_state.keypair.to_full() else { - return - } - - guard let new_ev = remove_from_mutelist(keypair: keypair, prev: mutelist, to_remove: pubkey) else { + guard let mutelist = damus_state.contacts.mutelist, + let keypair = damus_state.keypair.to_full(), + let new_ev = remove_from_mutelist(keypair: keypair, + prev: mutelist, + to_remove: .pubkey(pubkey)) + else { return } @@ -54,20 +51,13 @@ struct MutelistView: View { } -func get_mutelist_users(_ mlist: NostrEvent?) -> [String] { - guard let mutelist = mlist else { - return [] - } - - return mutelist.tags.reduce(into: Array()) { pks, tag in - if tag.count >= 2 && tag[0].matches_str("p") { - pks.append(tag[1].string()) - } - } +func get_mutelist_users(_ mutelist: NostrEvent?) -> [Pubkey] { + guard let mutelist else { return [] } + return mutelist.referenced_pubkeys.map { $0.ref_id } } struct MutelistView_Previews: PreviewProvider { static var previews: some View { - MutelistView(damus_state: test_damus_state(), users: [test_event.pubkey, test_event.pubkey+"hi"]) + MutelistView(damus_state: test_damus_state(), users: [test_note.pubkey, test_note.pubkey]) } } diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift index 43fd13adb260..c9150fb83dc9 100644 --- a/damus/Views/NoteContentView.swift +++ b/damus/Views/NoteContentView.swift @@ -231,7 +231,7 @@ struct NoteContentView: View { for block in blocks.blocks { switch block { case .mention(let m): - if m.type == .pubkey && m.ref.ref_id == profile.pubkey { + if m.type == .pubkey && m.ref.ref_id.pubkey == profile.pubkey { load(force_artifacts: true) return } @@ -266,21 +266,21 @@ func url_str(_ url: URL) -> CompatibleText { return CompatibleText(attributed: attributedString) } -func mention_str(_ m: Mention, profiles: Profiles) -> CompatibleText { - switch m.type { - case .pubkey: - let pk = m.ref.ref_id +func mention_str(_ m: Mention, profiles: Profiles) -> CompatibleText { + switch m.ref { + case .pubkey(let pk): + let npub = bech32_pubkey(pk) let profile = profiles.lookup(id: pk) let disp = Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50) var attributedString = AttributedString(stringLiteral: "@\(disp)") - attributedString.link = URL(string: "damus:\(encode_pubkey_uri(m.ref))") + attributedString.link = URL(string: "damus:nostr:\(npub)") attributedString.foregroundColor = DamusColors.purple return CompatibleText(attributed: attributedString) - case .event: - let bevid = bech32_note_id(m.ref.ref_id) ?? m.ref.ref_id + case .event(let note_id): + let bevid = bech32_note_id(note_id) var attributedString = AttributedString(stringLiteral: "@\(abbrev_pubkey(bevid))") - attributedString.link = URL(string: "damus:\(encode_event_id_uri(m.ref))") + attributedString.link = URL(string: "damus:nostr:\(bevid)") attributedString.foregroundColor = DamusColors.purple return CompatibleText(attributed: attributedString) @@ -393,7 +393,7 @@ func note_artifact_is_separated(kind: NostrKind?) -> Bool { return kind != .longform } -func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: String?) -> NoteArtifacts { +func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: Privkey?) -> NoteArtifacts { let blocks = ev.blocks(privkey) if ev.known_kind == .longform { @@ -575,7 +575,7 @@ func classify_url(_ url: URL) -> UrlType { return .link(url) } -func lookup_cached_preview_size(previews: PreviewCache, evid: String) -> CGFloat? { +func lookup_cached_preview_size(previews: PreviewCache, evid: NoteId) -> CGFloat? { guard case .value(let cached) = previews.lookup(evid) else { return nil } @@ -602,7 +602,7 @@ struct NoteContentView_Previews: PreviewProvider { let state = test_damus_state() VStack { - NoteContentView(damus_state: state, event: test_event, show_images: true, size: .normal, options: []) + NoteContentView(damus_state: state, event: test_note, show_images: true, size: .normal, options: []) NoteContentView(damus_state: state, event: test_longform_event.event, show_images: true, size: .normal, options: [.wide]) .border(Color.red) diff --git a/damus/Views/Notifications/EventGroupView.swift b/damus/Views/Notifications/EventGroupView.swift index 1496c658005c..91f4d7a94a8a 100644 --- a/damus/Views/Notifications/EventGroupView.swift +++ b/damus/Views/Notifications/EventGroupView.swift @@ -56,7 +56,7 @@ enum ReactingTo { case your_profile } -func determine_reacting_to(our_pubkey: String, ev: NostrEvent?) -> ReactingTo { +func determine_reacting_to(our_pubkey: Pubkey, ev: NostrEvent?) -> ReactingTo { guard let ev else { return .your_profile } @@ -68,21 +68,21 @@ func determine_reacting_to(our_pubkey: String, ev: NostrEvent?) -> ReactingTo { return .tagged_in } -func event_author_name(profiles: Profiles, pubkey: String) -> String { +func event_author_name(profiles: Profiles, pubkey: Pubkey) -> String { let alice_prof = profiles.lookup(id: pubkey) return Profile.displayName(profile: alice_prof, pubkey: pubkey).username.truncate(maxLength: 50) } -func event_group_unique_pubkeys(profiles: Profiles, group: EventGroupType) -> [String] { - var seen = Set() - var sorted = [String]() +func event_group_unique_pubkeys(profiles: Profiles, group: EventGroupType) -> [Pubkey] { + var seen = Set() + var sorted = [Pubkey]() if let zapgrp = group.zap_group { let zaps = zapgrp.zaps for i in 0.. [S "zapped_your_profile_2" - returned when 2 zaps occurred to the current user's profile "zapped_your_profile_3" - returned when 3 or more zaps occurred to the current user's profile */ -func reacting_to_text(profiles: Profiles, our_pubkey: String, group: EventGroupType, ev: NostrEvent?, pubkeys: [String], locale: Locale? = nil) -> String { +func reacting_to_text(profiles: Profiles, our_pubkey: Pubkey, group: EventGroupType, ev: NostrEvent?, pubkeys: [Pubkey], locale: Locale? = nil) -> String { if group.events.count == 0 { return "??" } @@ -192,7 +192,7 @@ struct EventGroupView: View { let event: NostrEvent? let group: EventGroupType - func GroupDescription(_ pubkeys: [String]) -> some View { + func GroupDescription(_ pubkeys: [Pubkey]) -> some View { Text(verbatim: "\(reacting_to_text(profiles: state.profiles, our_pubkey: state.pubkey, group: group, ev: event, pubkeys: pubkeys))") } @@ -261,11 +261,11 @@ struct EventGroupView: View { struct EventGroupView_Previews: PreviewProvider { static var previews: some View { VStack { - EventGroupView(state: test_damus_state(), event: test_event, group: .repost(test_event_group)) + EventGroupView(state: test_damus_state(), event: test_note, group: .repost(test_event_group)) .frame(height: 200) .padding() - EventGroupView(state: test_damus_state(), event: test_event, group: .reaction(test_event_group)) + EventGroupView(state: test_damus_state(), event: test_note, group: .reaction(test_event_group)) .frame(height: 200) .padding() } diff --git a/damus/Views/Notifications/NotificationsView.swift b/damus/Views/Notifications/NotificationsView.swift index 4580be1ff672..101a5f3e59a6 100644 --- a/damus/Views/Notifications/NotificationsView.swift +++ b/damus/Views/Notifications/NotificationsView.swift @@ -23,7 +23,7 @@ enum FriendFilter: String, StringCodable { self.rawValue } - func filter(contacts: Contacts, pubkey: String) -> Bool { + func filter(contacts: Contacts, pubkey: Pubkey) -> Bool { switch self { case .all: return true diff --git a/damus/Views/Notifications/ProfilePicturesView.swift b/damus/Views/Notifications/ProfilePicturesView.swift index 761ad3eae908..5a760bcbf989 100644 --- a/damus/Views/Notifications/ProfilePicturesView.swift +++ b/damus/Views/Notifications/ProfilePicturesView.swift @@ -9,8 +9,8 @@ import SwiftUI struct ProfilePicturesView: View { let state: DamusState - let pubkeys: [String] - + let pubkeys: [Pubkey] + var body: some View { HStack { ForEach(pubkeys.prefix(8), id: \.self) { pubkey in @@ -25,6 +25,7 @@ struct ProfilePicturesView: View { struct ProfilePicturesView_Previews: PreviewProvider { static var previews: some View { - ProfilePicturesView(state: test_damus_state(), pubkeys: ["a", "b"]) + let pubkey = test_note.pubkey + ProfilePicturesView(state: test_damus_state(), pubkeys: [pubkey, pubkey]) } } diff --git a/damus/Views/ParticipantsView.swift b/damus/Views/ParticipantsView.swift index 918481954c85..39067d0d9411 100644 --- a/damus/Views/ParticipantsView.swift +++ b/damus/Views/ParticipantsView.swift @@ -11,9 +11,9 @@ struct ParticipantsView: View { let damus_state: DamusState - @Binding var references: [ReferencedId] - @Binding var originalReferences: [ReferencedId] - + @Binding var references: [Referenced] + @Binding var originalReferences: [Referenced] + var body: some View { VStack { Text("Replying to", comment: "Text indicating that the view is used for editing which participants are replied to in a note.") diff --git a/damus/Views/PostView.swift b/damus/Views/PostView.swift index 61e5120bc7fe..c548c1f501ff 100644 --- a/damus/Views/PostView.swift +++ b/damus/Views/PostView.swift @@ -21,7 +21,7 @@ class TagModel: ObservableObject { enum PostTarget { case none - case user(String) + case user(Pubkey) } enum PostAction { @@ -155,8 +155,7 @@ struct PostView: View { } let profile = damus_state.profiles.lookup(id: pubkey) - let bech32_pubkey = bech32_pubkey(pubkey) ?? "" - return user_tag_attr_string(profile: profile, pubkey: bech32_pubkey) + return user_tag_attr_string(profile: profile, pubkey: pubkey) } func clear_draft() { diff --git a/damus/Views/Posting/UserSearch.swift b/damus/Views/Posting/UserSearch.swift index 570001b7268a..83141a382abb 100644 --- a/damus/Views/Posting/UserSearch.swift +++ b/damus/Views/Posting/UserSearch.swift @@ -9,9 +9,9 @@ import SwiftUI struct SearchedUser: Identifiable { let profile: Profile? - let pubkey: String - - var id: String { + let pubkey: Pubkey + + var id: Pubkey { return pubkey } } @@ -151,7 +151,8 @@ func append_user_tag(tag: NSAttributedString, post: NSMutableAttributedString, w } /// Generate a mention attributed string, including the internal damus:nostr: link -func user_tag_attr_string(profile: Profile?, pubkey: String) -> NSMutableAttributedString { +func user_tag_attr_string(profile: Profile?, pubkey: Pubkey) -> NSMutableAttributedString { + let bech32_pubkey = bech32_pubkey(pubkey) ?? pubkey.hex() let display_name = Profile.displayName(profile: profile, pubkey: pubkey) let name = display_name.username.truncate(maxLength: 50) let tagString = "@\(name)" @@ -159,7 +160,7 @@ func user_tag_attr_string(profile: Profile?, pubkey: String) -> NSMutableAttribu return NSMutableAttributedString(string: tagString, attributes: [ NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0), NSAttributedString.Key.foregroundColor: UIColor.label, - NSAttributedString.Key.link: "damus:nostr:\(pubkey)" + NSAttributedString.Key.link: "damus:nostr:\(bech32_pubkey)" ]) } diff --git a/damus/Views/Profile/EventProfileName.swift b/damus/Views/Profile/EventProfileName.swift index a770105ed95c..8e25f6202f71 100644 --- a/damus/Views/Profile/EventProfileName.swift +++ b/damus/Views/Profile/EventProfileName.swift @@ -10,7 +10,7 @@ import SwiftUI /// Profile Name used when displaying an event in the timeline struct EventProfileName: View { let damus_state: DamusState - let pubkey: String + let pubkey: Pubkey let profile: Profile? @State var display_name: DisplayName? @@ -19,7 +19,7 @@ struct EventProfileName: View { let size: EventViewKind - init(pubkey: String, profile: Profile?, damus: DamusState, size: EventViewKind = .normal) { + init(pubkey: Pubkey, profile: Profile?, damus: DamusState, size: EventViewKind = .normal) { self.damus_state = damus self.pubkey = pubkey self.profile = profile @@ -107,6 +107,6 @@ struct EventProfileName: View { struct EventProfileName_Previews: PreviewProvider { static var previews: some View { - EventProfileName(pubkey: "pk", profile: nil, damus: test_damus_state()) + EventProfileName(pubkey: test_note.pubkey, profile: nil, damus: test_damus_state()) } } diff --git a/damus/Views/Profile/MaybeAnonPfpView.swift b/damus/Views/Profile/MaybeAnonPfpView.swift index 8bc45e06448b..5a2400a7f808 100644 --- a/damus/Views/Profile/MaybeAnonPfpView.swift +++ b/damus/Views/Profile/MaybeAnonPfpView.swift @@ -10,10 +10,10 @@ import SwiftUI struct MaybeAnonPfpView: View { let state: DamusState let is_anon: Bool - let pubkey: String + let pubkey: Pubkey let size: CGFloat - init(state: DamusState, is_anon: Bool, pubkey: String, size: CGFloat) { + init(state: DamusState, is_anon: Bool, pubkey: Pubkey, size: CGFloat) { self.state = state self.is_anon = is_anon self.pubkey = pubkey diff --git a/damus/Views/Profile/ProfileName.swift b/damus/Views/Profile/ProfileName.swift index 5a7560946eb9..3404631d98f3 100644 --- a/damus/Views/Profile/ProfileName.swift +++ b/damus/Views/Profile/ProfileName.swift @@ -12,7 +12,7 @@ enum FriendType { case fof } -func get_friend_type(contacts: Contacts, pubkey: String) -> FriendType? { +func get_friend_type(contacts: Contacts, pubkey: Pubkey) -> FriendType? { if contacts.is_friend_or_self(pubkey) { return .friend } @@ -26,7 +26,7 @@ func get_friend_type(contacts: Contacts, pubkey: String) -> FriendType? { struct ProfileName: View { let damus_state: DamusState - let pubkey: String + let pubkey: Pubkey let profile: Profile? let prefix: String @@ -36,7 +36,7 @@ struct ProfileName: View { @State var nip05: NIP05? @State var donation: Int? - init(pubkey: String, profile: Profile?, prefix: String = "", damus: DamusState, show_nip5_domain: Bool = true) { + init(pubkey: Pubkey, profile: Profile?, prefix: String = "", damus: DamusState, show_nip5_domain: Bool = true) { self.pubkey = pubkey self.profile = profile self.prefix = prefix diff --git a/damus/Views/Profile/ProfileNameView.swift b/damus/Views/Profile/ProfileNameView.swift index 0477d71e50a9..b056170ac1cd 100644 --- a/damus/Views/Profile/ProfileNameView.swift +++ b/damus/Views/Profile/ProfileNameView.swift @@ -54,9 +54,9 @@ struct ProfileNameView: View { struct ProfileNameView_Previews: PreviewProvider { static var previews: some View { VStack { - ProfileNameView(pubkey: test_event.pubkey, profile: nil, follows_you: true, damus: test_damus_state()) - - ProfileNameView(pubkey: test_event.pubkey, profile: nil, follows_you: false, damus: test_damus_state()) + ProfileNameView(pubkey: test_note.pubkey, profile: nil, follows_you: true, damus: test_damus_state()) + + ProfileNameView(pubkey: test_note.pubkey, profile: nil, follows_you: false, damus: test_damus_state()) } } } diff --git a/damus/Views/Profile/ProfilePicView.swift b/damus/Views/Profile/ProfilePicView.swift index d174bc400f7e..3072058d72de 100644 --- a/damus/Views/Profile/ProfilePicView.swift +++ b/damus/Views/Profile/ProfilePicView.swift @@ -31,7 +31,7 @@ func pfp_line_width(_ h: Highlight) -> CGFloat { struct InnerProfilePicView: View { let url: URL? let fallbackUrl: URL? - let pubkey: String + let pubkey: Pubkey let size: CGFloat let highlight: Highlight let disable_animation: Bool @@ -67,7 +67,7 @@ struct InnerProfilePicView: View { } struct ProfilePicView: View { - let pubkey: String + let pubkey: Pubkey let size: CGFloat let highlight: Highlight let profiles: Profiles @@ -75,7 +75,7 @@ struct ProfilePicView: View { @State var picture: String? - init(pubkey: String, size: CGFloat, highlight: Highlight, profiles: Profiles, disable_animation: Bool, picture: String? = nil) { + init(pubkey: Pubkey, size: CGFloat, highlight: Highlight, profiles: Profiles, disable_animation: Bool, picture: String? = nil) { self.pubkey = pubkey self.profiles = profiles self.size = size @@ -100,7 +100,7 @@ struct ProfilePicView: View { } } -func get_profile_url(picture: String?, pubkey: String, profiles: Profiles) -> URL { +func get_profile_url(picture: String?, pubkey: Pubkey, profiles: Profiles) -> URL { let pic = picture ?? profiles.lookup(id: pubkey)?.picture ?? robohash(pubkey) if let url = URL(string: pic) { return url @@ -108,19 +108,19 @@ func get_profile_url(picture: String?, pubkey: String, profiles: Profiles) -> UR return URL(string: robohash(pubkey))! } -func make_preview_profiles(_ pubkey: String) -> Profiles { +func make_preview_profiles(_ pubkey: Pubkey) -> Profiles { let user_search_cache = UserSearchCache() let profiles = Profiles(user_search_cache: user_search_cache) let picture = "http://cdn.jb55.com/img/red-me.jpg" let profile = Profile(name: "jb55", display_name: "William Casarin", about: "It's me", picture: picture, banner: "", website: "https://jb55.com", lud06: nil, lud16: nil, nip05: "jb55.com", damus_donation: nil) - let ts_profile = TimestampedProfile(profile: profile, timestamp: 0, event: test_event) + let ts_profile = TimestampedProfile(profile: profile, timestamp: 0, event: test_note) profiles.add(id: pubkey, profile: ts_profile) return profiles } struct ProfilePicView_Previews: PreviewProvider { - static let pubkey = "ca48854ac6555fed8e439ebb4fa2d928410e0eef13fa41164ec45aaaa132d846" - + static let pubkey = test_note.pubkey + static var previews: some View { ProfilePicView( pubkey: pubkey, diff --git a/damus/Views/Profile/ProfileView.swift b/damus/Views/Profile/ProfileView.swift index 57cc82c527e6..9b286a871e50 100644 --- a/damus/Views/Profile/ProfileView.swift +++ b/damus/Views/Profile/ProfileView.swift @@ -90,7 +90,7 @@ struct ProfileView: View { self._followers = StateObject(wrappedValue: followers) } - init(damus_state: DamusState, pubkey: String) { + init(damus_state: DamusState, pubkey: Pubkey) { self.damus_state = damus_state self._profile = StateObject(wrappedValue: ProfileModel(pubkey: pubkey, damus: damus_state)) self._followers = StateObject(wrappedValue: FollowersModel(damus_state: damus_state, target: pubkey)) diff --git a/damus/Views/QRCodeView.swift b/damus/Views/QRCodeView.swift index b223c2b2488c..64418e0e325d 100644 --- a/damus/Views/QRCodeView.swift +++ b/damus/Views/QRCodeView.swift @@ -9,10 +9,14 @@ import SwiftUI import CoreImage.CIFilterBuiltins struct ProfileScanResult: Equatable { - let pubkey: String - - init(hex: String) { - self.pubkey = hex + let pubkey: Pubkey + + init?(hex: String) { + guard let pk = hex_decode(hex).map({ bytes in Pubkey(Data(bytes)) }) else { + return nil + } + + self.pubkey = pk } init?(string: String) { @@ -25,14 +29,17 @@ struct ProfileScanResult: Equatable { str.removeFirst("nostr:".count) } - if let _ = hex_decode(str), str.count == 64 { - self = .init(hex: str) + if let decoded = hex_decode(str), + str.count == 64 + { + self.pubkey = Pubkey(Data(decoded)) return } - if str.starts(with: "npub"), let b32 = try? bech32_decode(str) { - let hex = hex_encode(b32.data) - self = .init(hex: hex) + if str.starts(with: "npub"), + let b32 = try? bech32_decode(str) + { + self.pubkey = Pubkey(b32.data) return } @@ -42,7 +49,7 @@ struct ProfileScanResult: Equatable { struct QRCodeView: View { let damus_state: DamusState - @State var pubkey: String + @State var pubkey: Pubkey @Environment(\.presentationMode) var presentationMode @@ -297,7 +304,7 @@ struct QRCodeView: View { struct QRCodeView_Previews: PreviewProvider { static var previews: some View { - QRCodeView(damus_state: test_damus_state(), pubkey: test_event.pubkey) + QRCodeView(damus_state: test_damus_state(), pubkey: test_note.pubkey) } } diff --git a/damus/Views/ReactionsView.swift b/damus/Views/ReactionsView.swift index 7d74e3edb988..0a339a293b05 100644 --- a/damus/Views/ReactionsView.swift +++ b/damus/Views/ReactionsView.swift @@ -38,6 +38,6 @@ struct ReactionsView: View { struct ReactionsView_Previews: PreviewProvider { static var previews: some View { let state = test_damus_state() - ReactionsView(damus_state: state, model: ReactionsModel(state: state, target: "pubkey")) + ReactionsView(damus_state: state, model: ReactionsModel(state: state, target: test_pubkey)) } } diff --git a/damus/Views/ReplyView.swift b/damus/Views/ReplyView.swift index f91f70ff5a4f..963a6bcd8df0 100644 --- a/damus/Views/ReplyView.swift +++ b/damus/Views/ReplyView.swift @@ -11,14 +11,14 @@ struct ReplyView: View { let replying_to: NostrEvent let damus: DamusState - @Binding var originalReferences: [ReferencedId] - @Binding var references: [ReferencedId] + @Binding var originalReferences: [Referenced] + @Binding var references: [Referenced] @State var participantsShown: Bool = false var ReplyingToSection: some View { HStack { Group { - let names = references.pRefs + let names = references .map { pubkey in let pk = pubkey.ref_id let prof = damus.profiles.lookup(id: pk) @@ -81,7 +81,7 @@ struct ReplyView: View { struct ReplyView_Previews: PreviewProvider { static var previews: some View { VStack { - ReplyView(replying_to: test_event, damus: test_damus_state(), originalReferences: .constant([]), references: .constant([])) + ReplyView(replying_to: test_note, damus: test_damus_state(), originalReferences: .constant([]), references: .constant([])) .frame(height: 300) ReplyView(replying_to: test_longform_event.event, damus: test_damus_state(), originalReferences: .constant([]), references: .constant([])) diff --git a/damus/Views/Reposts/RepostedEvent.swift b/damus/Views/Reposts/RepostedEvent.swift index 4870dc9aa067..edf923854730 100644 --- a/damus/Views/Reposts/RepostedEvent.swift +++ b/damus/Views/Reposts/RepostedEvent.swift @@ -31,6 +31,6 @@ struct RepostedEvent: View { struct RepostedEvent_Previews: PreviewProvider { static var previews: some View { - RepostedEvent(damus: test_damus_state(), event: test_event, inner_ev: test_event, options: []) + RepostedEvent(damus: test_damus_state(), event: test_note, inner_ev: test_note, options: []) } } diff --git a/damus/Views/Search/SearchingEventView.swift b/damus/Views/Search/SearchingEventView.swift index afb6c73c6bf4..359ae59fcfa5 100644 --- a/damus/Views/Search/SearchingEventView.swift +++ b/damus/Views/Search/SearchingEventView.swift @@ -10,19 +10,18 @@ import SwiftUI enum SearchState { case searching case found(NostrEvent) - case found_profile(String) + case found_profile(Pubkey) case not_found } enum SearchType { - case event - case profile - case nip05 + case event(NoteId) + case profile(Pubkey) + case nip05(String) } struct SearchingEventView: View { let state: DamusState - let evid: String let search_type: SearchType @State var search_state: SearchState = .searching @@ -38,18 +37,18 @@ struct SearchingEventView: View { } } - func handle_search(_ evid: String) { + func handle_search() { self.search_state = .searching switch search_type { - case .nip05: - if let pk = state.profiles.nip05_pubkey[evid] { + case .nip05(let nip05): + if let pk = state.profiles.nip05_pubkey[nip05] { if state.profiles.lookup(id: pk) != nil { self.search_state = .found_profile(pk) } } else { Task { - guard let nip05 = NIP05.parse(evid) else { + guard let nip05 = NIP05.parse(nip05) else { self.search_state = .not_found return } @@ -71,16 +70,16 @@ struct SearchingEventView: View { } } - case .event: - find_event(state: state, query: .event(evid: evid)) { res in + case .event(let note_id): + find_event(state: state, query: .event(evid: note_id)) { res in guard case .event(let ev) = res else { self.search_state = .not_found return } self.search_state = .found(ev) } - case .profile: - find_event(state: state, query: .profile(pubkey: evid)) { res in + case .profile(let pubkey): + find_event(state: state, query: .profile(pubkey: pubkey)) { res in guard case .profile(_, let ev) = res else { self.search_state = .not_found return @@ -113,11 +112,8 @@ struct SearchingEventView: View { Text("\(search_name) not found", comment: "When a note or profile is not found when searching for it via its note id") } } - .onChange(of: evid, debounceTime: 0.5) { evid in - handle_search(evid) - } .onAppear { - handle_search(evid) + handle_search() } } } @@ -125,6 +121,6 @@ struct SearchingEventView: View { struct SearchingEventView_Previews: PreviewProvider { static var previews: some View { let state = test_damus_state() - SearchingEventView(state: state, evid: test_event.id, search_type: .event) + SearchingEventView(state: state, search_type: .event(test_note.id)) } } diff --git a/damus/Views/SearchResultsView.swift b/damus/Views/SearchResultsView.swift index 6ece7b62c3be..87541a0cf569 100644 --- a/damus/Views/SearchResultsView.swift +++ b/damus/Views/SearchResultsView.swift @@ -38,7 +38,7 @@ struct InnerSearchResults: View { let damus_state: DamusState let search: Search? - func ProfileSearchResult(pk: String) -> some View { + func ProfileSearchResult(pk: Pubkey) -> some View { FollowUserView(target: .pubkey(pk), damus_state: damus_state) } @@ -67,13 +67,12 @@ struct InnerSearchResults: View { HashtagSearch(ht) case .nip05(let addr): - SearchingEventView(state: damus_state, evid: addr, search_type: .nip05) - + SearchingEventView(state: damus_state, search_type: .nip05(addr)) + case .profile(let prof): - let decoded = try? bech32_decode(prof) - let hex = hex_encode(decoded!.data) - - SearchingEventView(state: damus_state, evid: hex, search_type: .profile) + let pubkey = try? bech32_pubkey_decode(prof) + + SearchingEventView(state: damus_state, search_type: .profile(pubkey)) case .hex(let h): //let prof_view = ProfileView(damus_state: damus_state, pubkey: h) //let ev_view = ThreadView(damus: damus_state, event_id: h) @@ -86,9 +85,9 @@ struct InnerSearchResults: View { case .note(let nid): let decoded = try? bech32_decode(nid) - let hex = hex_encode(decoded!.data) - - SearchingEventView(state: damus_state, evid: hex, search_type: .event) + let evid = NoteId(decoded.data) + + SearchingEventView(state: damus_state, search_type: .event(evid)) case .multi(let multi): VStack { HashtagSearch(multi.hashtag) @@ -181,13 +180,20 @@ func make_hashtagable(_ str: String) -> String { func search_profiles(profiles: Profiles, search: String) -> [SearchedUser] { // Search by hex pubkey. - if search.count == 64 && hex_decode(search) != nil, let profile = profiles.lookup(id: search) { + if search.count == 64, + let decoded = hex_decode(search), + let profile = profiles.lookup(id: Pubkey(Data(decoded))) + { return [SearchedUser(profile: profile, pubkey: search)] } // Search by npub pubkey. - if search.starts(with: "npub"), let bech32_key = decode_bech32_key(search), case Bech32Key.pub(let hex) = bech32_key, let profile = profiles.lookup(id: hex) { - return [SearchedUser(profile: profile, pubkey: hex)] + if search.starts(with: "npub"), + let bech32_key = decode_bech32_key(search), + case Bech32Key.pub(let pk) = bech32_key, + let profile = profiles.lookup(id: pk) + { + return [SearchedUser(profile: profile, pubkey: pk)] } let new = search.lowercased() diff --git a/damus/Views/ThreadView.swift b/damus/Views/ThreadView.swift index ca2c830b249c..7f24c18d4349 100644 --- a/damus/Views/ThreadView.swift +++ b/damus/Views/ThreadView.swift @@ -35,7 +35,7 @@ struct ThreadView: View { .padding(.horizontal) .onTapGesture { thread.set_active_event(parent_event) - scroll_to_event(scroller: reader, id: parent_event.id, delay: 0.1, animate: false) + scroll_to_event(scroller: reader, id: parent_event.id.hex(), delay: 0.1, animate: false) } Divider() @@ -104,7 +104,7 @@ struct ThreadView: View { struct ThreadView_Previews: PreviewProvider { static var previews: some View { let state = test_damus_state() - let thread = ThreadModel(event: test_event, damus_state: state) + let thread = ThreadModel(event: test_note, damus_state: state) ThreadView(state: state, thread: thread) } } diff --git a/damus/Views/Zaps/CustomizeZapView.swift b/damus/Views/Zaps/CustomizeZapView.swift index 6b82140f5e3e..2c3f255348df 100644 --- a/damus/Views/Zaps/CustomizeZapView.swift +++ b/damus/Views/Zaps/CustomizeZapView.swift @@ -306,7 +306,7 @@ extension View { struct CustomizeZapView_Previews: PreviewProvider { static var previews: some View { - CustomizeZapView(state: test_damus_state(), target: ZapTarget.note(id: test_event.id, author: test_event.pubkey), lnurl: "") + CustomizeZapView(state: test_damus_state(), target: ZapTarget.note(id: test_note.id, author: test_note.pubkey), lnurl: "") .frame(width: 400, height: 600) } } diff --git a/damus/Views/Zaps/ZapTypePicker.swift b/damus/Views/Zaps/ZapTypePicker.swift index 4bcef8f7de95..cbb6f0975f70 100644 --- a/damus/Views/Zaps/ZapTypePicker.swift +++ b/damus/Views/Zaps/ZapTypePicker.swift @@ -31,7 +31,7 @@ struct ZapTypePicker: View { @Binding var zap_type: ZapType @ObservedObject var settings: UserSettingsStore let profiles: Profiles - let pubkey: String + let pubkey: Pubkey @Environment(\.colorScheme) var colorScheme diff --git a/damus/Views/Zaps/ZapsView.swift b/damus/Views/Zaps/ZapsView.swift index 293b7048794a..1806853697b3 100644 --- a/damus/Views/Zaps/ZapsView.swift +++ b/damus/Views/Zaps/ZapsView.swift @@ -16,7 +16,7 @@ struct ZapsView: View { init(state: DamusState, target: ZapTarget) { self.state = state self.model = ZapsModel(state: state, target: target) - self._zaps = ObservedObject(wrappedValue: state.events.get_cache_data(target.id).zaps_model) + self._zaps = ObservedObject(wrappedValue: state.events.get_cache_data(target.id.noteid).zaps_model) } var body: some View { @@ -40,6 +40,6 @@ struct ZapsView: View { struct ZapsView_Previews: PreviewProvider { static var previews: some View { - ZapsView(state: test_damus_state(), target: .profile("pk")) + ZapsView(state: test_damus_state(), target: .profile(test_pubkey)) } } diff --git a/damusTests/EventGroupViewTests.swift b/damusTests/EventGroupViewTests.swift index f8b6e085b412..ea6f7a988ba3 100644 --- a/damusTests/EventGroupViewTests.swift +++ b/damusTests/EventGroupViewTests.swift @@ -56,16 +56,16 @@ final class EventGroupViewTests: XCTestCase { let repost2 = NostrEvent(content: encodedPost, keypair: pk2, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)! let repost3 = NostrEvent(content: encodedPost, keypair: pk3, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)! - XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [])), ev: test_event, pubkeys: [], locale: enUsLocale), "??") - XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1])), ev: test_event, pubkeys: [pk1.pubkey], locale: enUsLocale), "1jw906h6:6s7un8fx reposted a note you were tagged in") - XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2])), ev: test_event, pubkeys: [pk1.pubkey, pk2.pubkey], locale: enUsLocale), "1jw906h6:6s7un8fx and 1dwdm0t9:nqg0lsc7 reposted a note you were tagged in") - XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2, repost2])), ev: test_event, pubkeys: [pk1.pubkey, pk2.pubkey, pk3.pubkey], locale: enUsLocale), "1jw906h6:6s7un8fx and 2 others reposted a note you were tagged in") + XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [])), ev: test_note, pubkeys: [], locale: enUsLocale), "??") + XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1])), ev: test_note, pubkeys: [pk1.pubkey], locale: enUsLocale), "1jw906h6:6s7un8fx reposted a note you were tagged in") + XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2])), ev: test_note, pubkeys: [pk1.pubkey, pk2.pubkey], locale: enUsLocale), "1jw906h6:6s7un8fx and 1dwdm0t9:nqg0lsc7 reposted a note you were tagged in") + XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2, repost2])), ev: test_note, pubkeys: [pk1.pubkey, pk2.pubkey, pk3.pubkey], locale: enUsLocale), "1jw906h6:6s7un8fx and 2 others reposted a note you were tagged in") Bundle.main.localizations.map { Locale(identifier: $0) }.forEach { - XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [])), ev: test_event, pubkeys: [], locale: $0), "??") - XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1])), ev: test_event, pubkeys: [pk1.pubkey], locale: $0)) - XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2])), ev: test_event, pubkeys: [pk1.pubkey, pk2.pubkey], locale: $0)) - XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2, repost3])), ev: test_event, pubkeys: [pk1.pubkey, pk2.pubkey, pk3.pubkey], locale: $0)) + XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [])), ev: test_note, pubkeys: [], locale: $0), "??") + XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1])), ev: test_note, pubkeys: [pk1.pubkey], locale: $0)) + XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2])), ev: test_note, pubkeys: [pk1.pubkey, pk2.pubkey], locale: $0)) + XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2, repost3])), ev: test_note, pubkeys: [pk1.pubkey, pk2.pubkey, pk3.pubkey], locale: $0)) } } diff --git a/damusTests/UserSearchCacheTests.swift b/damusTests/UserSearchCacheTests.swift index 1c7564920138..ecda42dc2e4a 100644 --- a/damusTests/UserSearchCacheTests.swift +++ b/damusTests/UserSearchCacheTests.swift @@ -24,7 +24,7 @@ final class UserSearchCacheTests: XCTestCase { damusState.profiles.set_validated(pubkey, nip05: validatedNip05) let profile = Profile(name: "tyiu", display_name: "Terry Yiu", about: nil, picture: nil, banner: nil, website: nil, lud06: nil, lud16: nil, nip05: nip05, damus_donation: nil) - let timestampedProfile = TimestampedProfile(profile: profile, timestamp: 0, event: test_event) + let timestampedProfile = TimestampedProfile(profile: profile, timestamp: 0, event: test_note) damusState.profiles.add(id: pubkey, profile: timestampedProfile) // Lookup to synchronize access on profiles dictionary to avoid race conditions. @@ -56,7 +56,7 @@ final class UserSearchCacheTests: XCTestCase { damusState.profiles.set_validated(keypair.pubkey, nip05: NIP05.parse(newNip05)) let newProfile = Profile(name: "whoami", display_name: "T-DAWG", about: nil, picture: nil, banner: nil, website: nil, lud06: nil, lud16: nil, nip05: newNip05, damus_donation: nil) - let newTimestampedProfile = TimestampedProfile(profile: newProfile, timestamp: 1000, event: test_event) + let newTimestampedProfile = TimestampedProfile(profile: newProfile, timestamp: 1000, event: test_note) damusState.profiles.add(id: keypair.pubkey, profile: newTimestampedProfile) // Lookup to synchronize access on profiles dictionary to avoid race conditions. diff --git a/nostrdb/NdbNote.swift b/nostrdb/NdbNote.swift index 6d972c5b979f..0d8241665716 100644 --- a/nostrdb/NdbNote.swift +++ b/nostrdb/NdbNote.swift @@ -75,17 +75,17 @@ class NdbNote: Encodable, Equatable, Hashable { } /// NDBTODO: make this into data - var id: String { - hex_encode(Data(buffer: UnsafeBufferPointer(start: ndb_note_id(note), count: 32))) + var id: NoteId { + .init(Data(bytes: ndb_note_id(note), count: 32)) } - var sig: String { - hex_encode(Data(buffer: UnsafeBufferPointer(start: ndb_note_sig(note), count: 64))) + var sig: Signature { + .init(Data(bytes: ndb_note_sig(note), count: 64)) } /// NDBTODO: make this into data - var pubkey: String { - hex_encode(Data(buffer: UnsafeBufferPointer(start: ndb_note_pubkey(note), count: 32))) + var pubkey: Pubkey { + .init(Data(bytes: ndb_note_pubkey(note), count: 32)) } var created_at: UInt32 { @@ -126,8 +126,8 @@ class NdbNote: Encodable, Equatable, Hashable { func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - try container.encode(sig, forKey: .sig) + try container.encode(hex_encode(id.data), forKey: .id) + try container.encode(hex_encode(sig.data), forKey: .sig) try container.encode(pubkey, forKey: .pubkey) try container.encode(created_at, forKey: .created_at) try container.encode(kind, forKey: .kind) @@ -146,7 +146,7 @@ class NdbNote: Encodable, Equatable, Hashable { ndb_builder_init(&builder, buf, Int32(buflen)) - guard var pk_raw = hex_decode(keypair.pubkey) else { return nil } + var pk_raw = keypair.pubkey.data.bytes ndb_builder_set_pubkey(&builder, &pk_raw) ndb_builder_set_kind(&builder, UInt32(kind)) @@ -176,12 +176,10 @@ class NdbNote: Encodable, Equatable, Hashable { let keypair = keypair.privkey.map { sec in var kp = ndb_keypair() - return sec.withCString { secptr in - if ndb_decode_key(secptr, &kp) <= 0 { - print("bad keypair") - } - return kp + if ndb_decode_key(sec.data.bytes, &kp) <= 0 { + print("bad keypair") } + return kp } var len: Int32 = 0 @@ -258,11 +256,6 @@ extension NdbNote { return !too_big } - - //var is_valid_id: Bool { - // return calculate_event_id(ev: self) == self.id - //} - func get_blocks(content: String) -> Blocks { return parse_note_content(content: .note(self)) } @@ -274,7 +267,7 @@ extension NdbNote { if self.content_len == 0, let ref = self.referenced_ids.first { // TODO: raw id cache lookups - let id = ref.ref_id.string() + let id = ref.ref_id return cache.lookup(id) } @@ -284,19 +277,27 @@ extension NdbNote { } // TODO: References iterator - public var referenced_ids: LazyFilterSequence { - References.ids(tags: self.tags) + public var referenced_ids: EventIdReferences { + EventIdReferences(tags: self.tags) + } + + public var referenced_pubkeys: PubkeyReferences { + PubkeyReferences(tags: self.tags) } - public var referenced_pubkeys: LazyFilterSequence { - References.pubkeys(tags: self.tags) + public var referenced_hashtags: HashtagReferences { + HashtagReferences(tags: self.tags) } - public var referenced_hashtags: LazyFilterSequence { - References.hashtags(tags: self.tags) + public var referenced_params: ParamReferences { + ParamReferences(tags: self.tags) } - func event_refs(_ privkey: String?) -> [EventRef] { + public var references: References { + References(tags: self.tags) + } + + func event_refs(_ privkey: Privkey?) -> [EventRef] { if let rs = _event_refs { return rs } @@ -305,7 +306,7 @@ extension NdbNote { return refs } - func get_content(_ privkey: String?) -> String { + func get_content(_ privkey: Privkey?) -> String { if known_kind == .dm { return decrypted(privkey: privkey) ?? "*failed to decrypt content*" } @@ -313,7 +314,7 @@ extension NdbNote { return content } - func blocks(_ privkey: String?) -> Blocks { + func blocks(_ privkey: Privkey?) -> Blocks { if let bs = _blocks { return bs } let blocks = get_blocks(content: self.get_content(privkey)) @@ -322,16 +323,13 @@ extension NdbNote { } // NDBTODO: switch this to operating on bytes not strings - func decrypted(privkey: String?) -> String? { - if let decrypted_content = decrypted_content { + func decrypted(privkey: Privkey?) -> String? { + if let decrypted_content { return decrypted_content } - guard let key = privkey else { - return nil - } - - guard let our_pubkey = privkey_to_pubkey(privkey: key) else { + guard let privkey, + let our_pubkey = privkey_to_pubkey(privkey: privkey) else { return nil } @@ -340,33 +338,21 @@ extension NdbNote { // This is our DM, we need to use the pubkey of the person we're talking to instead if our_pubkey == pubkey { - guard let refkey = self.referenced_pubkeys.first else { + guard let refkey = self.referenced_pubkeys.first(where:{_ in true}) else { return nil } - pubkey = refkey.ref_id.string() + pubkey = refkey.ref_id } // NDBTODO: pass data to pubkey - let dec = decrypt_dm(key, pubkey: pubkey, content: self.content, encoding: .base64) + let dec = decrypt_dm(privkey, pubkey: pubkey, content: self.content, encoding: .base64) self.decrypted_content = dec return dec } - /* - - var description: String { - return "NostrEvent { id: \(id) pubkey \(pubkey) kind \(kind) tags \(tags) content '\(content)' }" - } - - // Not sure I should implement this - private func get_referenced_ids(key: String) -> [ReferencedId] { - return damus.get_referenced_ids(tags: self.tags, key: key) - } - */ - - public func direct_replies(_ privkey: String?) -> [ReferencedId] { + public func direct_replies(_ privkey: Privkey?) -> [Referenced] { return event_refs(privkey).reduce(into: []) { acc, evref in if let direct_reply = evref.is_direct_reply { acc.append(direct_reply) @@ -375,7 +361,7 @@ extension NdbNote { } // NDBTODO: just use Id - public func thread_id(privkey: String?) -> String { + public func thread_id(privkey: Privkey?) -> NoteId { for ref in event_refs(privkey) { if let thread_id = ref.is_thread_id { return thread_id.ref_id @@ -385,11 +371,12 @@ extension NdbNote { return self.id } - public func last_refid() -> ReferencedId? { - return self.referenced_ids.last?.to_referenced_id() + public func last_refid() -> Referenced? { + return self.referenced_ids.last } // NDBTODO: id -> data + /* public func references(id: String, key: AsciiCharacter) -> Bool { var matcher: (Reference) -> Bool = { ref in ref.ref_id.matches_str(id) } if id.count == 64, let decoded = hex_decode(id) { @@ -403,12 +390,13 @@ extension NdbNote { return false } + */ - func is_reply(_ privkey: String?) -> Bool { + func is_reply(_ privkey: Privkey?) -> Bool { return event_is_reply(self.event_refs(privkey)) } - func note_language(_ privkey: String?) -> String? { + func note_language(_ privkey: Privkey?) -> String? { assert(!Thread.isMainThread, "This function must not be run on the main thread.") // Rely on Apple's NLLanguageRecognizer to tell us which language it thinks the note is in @@ -434,34 +422,24 @@ extension NdbNote { let event_date = Date(timeIntervalSince1970: TimeInterval(created_at)) return Date.now.timeIntervalSince(event_date) } +} - /* - - func calculate_id() { - self.id = calculate_event_id(ev: self) - } - - func sign(privkey: String) { - self.sig = sign_event(privkey: privkey, ev: self) +extension EventIdReferences { + var first: Referenced? { + self.first(where: { _ in true }) } - var age: TimeInterval { - let event_date = Date(timeIntervalSince1970: TimeInterval(created_at)) - return Date.now.timeIntervalSince(event_date) + var last: Referenced? { + var last: Referenced? = nil + for t in self { + last = t + } + return last } - */ } -extension LazyFilterSequence { - var first: Element? { +extension PubkeyReferences { + var first: Referenced? { self.first(where: { _ in true }) } - - var last: Element? { - var ev: Element? = nil - for e in self { - ev = e - } - return ev - } } diff --git a/nostrdb/NdbTagElem.swift b/nostrdb/NdbTagElem.swift index e035657bc7a1..3b42bdc741a0 100644 --- a/nostrdb/NdbTagElem.swift +++ b/nostrdb/NdbTagElem.swift @@ -32,7 +32,6 @@ struct NdbStrIter: IteratorProtocol { } struct NdbTagElem: Sequence, Hashable, Equatable { - let note: NdbNote let tag: UnsafeMutablePointer let index: Int32 @@ -71,6 +70,13 @@ struct NdbTagElem: Sequence, Hashable, Equatable { return str.flag == NDB_PACKED_ID } + var isEmpty: Bool { + if str.flag == NDB_PACKED_ID { + return false + } + return str.str[0] == 0 + } + var count: Int { if str.flag == NDB_PACKED_ID { return 32 @@ -79,13 +85,19 @@ struct NdbTagElem: Sequence, Hashable, Equatable { } } + var single_char: AsciiCharacter? { + let c = str.str[0] + guard c != 0 && str.str[1] == 0 else { return nil } + return AsciiCharacter(c) + } + func matches_char(_ c: AsciiCharacter) -> Bool { return str.str[0] == c.cchar && str.str[1] == 0 } - func matches_id(_ d: [UInt8]) -> Bool { - if str.flag == NDB_PACKED_ID, d.count == 32 { - return memcmp(d, str.id, 32) == 0 + func matches_id(_ d: Id) -> Bool { + if str.flag == NDB_PACKED_ID { + return memcmp(d.data.bytes, str.id, 32) == 0 } return false } @@ -101,18 +113,13 @@ struct NdbTagElem: Sequence, Hashable, Equatable { return s.withCString { cstr in strcmp(str.str, cstr) == 0 } } - var ndbstr: ndb_str { - return ndb_tag_str(note.note, tag, index) - } - func data() -> NdbData { - let s = ndb_tag_str(note.note, tag, index) - return NdbData(note: note, str: s) + return NdbData(note: note, str: self.str) } - func id() -> Data? { + func id() -> AnyId? { guard case .id(let id) = self.data() else { return nil } - return id.id + return AnyId(id.id) } func string() -> String { diff --git a/nostrdb/NdbTagsIterator.swift b/nostrdb/NdbTagsIterator.swift index 0a61238cf34a..bd6bd46c346d 100644 --- a/nostrdb/NdbTagsIterator.swift +++ b/nostrdb/NdbTagsIterator.swift @@ -73,8 +73,12 @@ struct TagsSequence: Encodable, Sequence { return .init(note: .init(note: .allocate(capacity: 1), owned_size: nil), tag: .allocate(capacity: 1)) } - func references() -> References { - return References(tags: self) + var referenced_evids: EventIdReferences { + return EventIdReferences(tags: self) + } + + var referenced_pubkeys: PubkeyReferences { + return PubkeyReferences(tags: self) } func makeIterator() -> TagsIterator { diff --git a/nostrscript/NostrScript.swift b/nostrscript/NostrScript.swift index ac3ae87cf224..1544cde4c19f 100644 --- a/nostrscript/NostrScript.swift +++ b/nostrscript/NostrScript.swift @@ -321,7 +321,7 @@ public func nscript_set_bool(interp: UnsafeMutablePointer?, setting return 1; } - let key = pk_setting_key(UserSettingsStore.pubkey ?? "", key: setting) + let key = pk_setting_key(UserSettingsStore.pubkey ?? .empty.pubkey, key: setting) let b = val > 0 ? true : false print("nscript setting bool setting \(setting) to \(b)") UserDefaults.standard.set(b, forKey: key)