damus

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

commit 0650a6279131865447d519ee1b26db9fd8a9c0ba
parent a4a046560582ac9500e8109f205373f7494cdf78
Author: kernelkind <kernelkind@gmail.com>
Date:   Thu, 18 Jan 2024 14:59:30 -0500

nip19: add search functionality for naddr, nprofile & nevent

The user is able to search for naddr, nprofile & nevent bech32 entities.
Additionally, these entities and others are able to have prefixes such
as damus:nostr: and damus.io links.

Closes: https://github.com/damus-io/damus/issues/1841
Closes: https://github.com/damus-io/damus/issues/1650
Changelog-Added: Add ability to search for naddr, nprofiles, nevents
Lightning-url: LNURL1DP68GURN8GHJ7EM9W3SKCCNE9E3K7MF0D3H82UNVWQHKWUN9V4HXGCTHDC6RZVGR8SW3G
Signed-off-by: kernelkind <kernelkind@gmail.com>
Reviewed-by: William Casarin <jb55@jb55.com>
Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 4++++
Mdamus/Nostr/NostrLink.swift | 22+---------------------
Adamus/Util/URIParsing.swift | 34++++++++++++++++++++++++++++++++++
Mdamus/Views/Search/SearchingEventView.swift | 11+++++++++++
Mdamus/Views/SearchResultsView.swift | 46++++++++++++++++++++++++++++++++--------------
5 files changed, 82 insertions(+), 35 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -614,6 +614,7 @@ D7FB10A72B0C371A00FA8D42 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.swift */; }; D7FF94002AC7AC5300FD969D /* RelayURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */; }; E02B54182B4DFADA0077FF42 /* Bech32ObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */; }; + E04A37C62B544F090029650D /* URIParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E04A37C52B544F090029650D /* URIParsing.swift */; }; E4FA1C032A24BB7F00482697 /* SearchSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */; }; E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; }; E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadView.swift */; }; @@ -1377,6 +1378,7 @@ D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusUserDefaults.swift; sourceTree = "<group>"; }; D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayURL.swift; sourceTree = "<group>"; }; E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bech32ObjectTests.swift; sourceTree = "<group>"; }; + E04A37C52B544F090029650D /* URIParsing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URIParsing.swift; sourceTree = "<group>"; }; E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsView.swift; sourceTree = "<group>"; }; E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; }; E9E4ED0A295867B900DD7078 /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = "<group>"; }; @@ -2055,6 +2057,7 @@ 4C7FF7D628233637009601DB /* Util */ = { isa = PBXGroup; children = ( + E04A37C52B544F090029650D /* URIParsing.swift */, 4C1D4FB02A7958E60024F453 /* VersionInfo.swift */, 4C7D09612A098D0E00943473 /* WalletConnect.swift */, 4C198DF329F88D23004C165C /* Images */, @@ -3297,6 +3300,7 @@ 50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */, 643EA5C8296B764E005081BB /* RelayFilterView.swift in Sources */, F71694EC2A662292001F4053 /* SuggestedUsersViewModel.swift in Sources */, + E04A37C62B544F090029650D /* URIParsing.swift in Sources */, 4C3EA67D28FFBBA300C48A62 /* InvoicesView.swift in Sources */, 4C363A8E28236FE4006E126D /* NoteContentView.swift in Sources */, 4C2B10282A7B0F5C008AA43E /* Log.swift in Sources */, diff --git a/damus/Nostr/NostrLink.swift b/damus/Nostr/NostrLink.swift @@ -55,14 +55,6 @@ func parse_hexstr(_ p: Parser, len: Int) -> String? { return String(substring(p.str, start: start, end: p.pos)) } -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: "") - uri = uri.replacingOccurrences(of: "/", with: "") - - return decode_nostr_bech32_uri(uri) -} - func decode_nostr_bech32_uri(_ s: String) -> NostrLink? { guard let obj = Bech32Object.parse(s) else { return nil @@ -90,19 +82,7 @@ func decode_nostr_bech32_uri(_ s: String) -> NostrLink? { } func decode_nostr_uri(_ s: String) -> NostrLink? { - if s.starts(with: "https://damus.io/") { - return decode_universal_link(s) - } - - var uri = s - uri = uri.replacingOccurrences(of: "nostr://", with: "") - uri = uri.replacingOccurrences(of: "nostr:", with: "") - - // Fix for non-latin characters resulting in second colon being encoded - uri = uri.replacingOccurrences(of: "damus:t%3A", with: "t:") - - uri = uri.replacingOccurrences(of: "damus://", with: "") - uri = uri.replacingOccurrences(of: "damus:", with: "") + let uri = remove_nostr_uri_prefix(s) let parts = uri.split(separator: ":") .reduce(into: Array<String>()) { acc, str in diff --git a/damus/Util/URIParsing.swift b/damus/Util/URIParsing.swift @@ -0,0 +1,34 @@ +// +// URIParsing.swift +// damus +// +// Created by KernelKind on 1/13/24. +// + +import Foundation + +private func remove_damus_uri_prefix(_ s: String) -> String { + var uri = s.replacingOccurrences(of: "https://damus.io/r/", with: "") + uri = uri.replacingOccurrences(of: "https://damus.io/", with: "") + uri = uri.replacingOccurrences(of: "/", with: "") + + return uri +} + +func remove_nostr_uri_prefix(_ s: String) -> String { + if s.starts(with: "https://damus.io/") { + return remove_damus_uri_prefix(s) + } + + var uri = s + uri = uri.replacingOccurrences(of: "nostr://", with: "") + uri = uri.replacingOccurrences(of: "nostr:", with: "") + + // Fix for non-latin characters resulting in second colon being encoded + uri = uri.replacingOccurrences(of: "damus:t%3A", with: "t:") + + uri = uri.replacingOccurrences(of: "damus://", with: "") + uri = uri.replacingOccurrences(of: "damus:", with: "") + + return uri +} diff --git a/damus/Views/Search/SearchingEventView.swift b/damus/Views/Search/SearchingEventView.swift @@ -18,6 +18,7 @@ enum SearchType: Equatable { case event(NoteId) case profile(Pubkey) case nip05(String) + case naddr(NAddr) } @MainActor @@ -35,6 +36,8 @@ struct SearchingEventView: View { return "Profile" case .event: return "Note" + case .naddr: + return "Naddr" } } @@ -89,6 +92,14 @@ struct SearchingEventView: View { } self.search_state = .found_profile(pubkey) } + case .naddr(let naddr): + naddrLookup(damus_state: state, naddr: naddr) { res in + guard let res = res else { + self.search_state = .not_found + return + } + self.search_state = .found(res) + } } } diff --git a/damus/Views/SearchResultsView.swift b/damus/Views/SearchResultsView.swift @@ -20,6 +20,9 @@ enum Search: Identifiable { case nip05(String) case hex(Data) case multi(MultiSearch) + case nevent(NEvent) + case naddr(NAddr) + case nprofile(NProfile) var id: String { switch self { @@ -30,6 +33,9 @@ enum Search: Identifiable { case .nip05: return "nip05" case .hex: return "hex" case .multi: return "multi" + case .nevent: return "nevent" + case .naddr: return "naddr" + case .nprofile: return "nprofile" } } } @@ -62,27 +68,25 @@ struct InnerSearchResults: View { switch search { case .profiles(let results): ProfilesSearch(results) - case .hashtag(let ht): HashtagSearch(ht) - case .nip05(let addr): SearchingEventView(state: damus_state, search_type: .nip05(addr)) - case .profile(let pubkey): SearchingEventView(state: damus_state, search_type: .profile(pubkey)) - case .hex(let h): - VStack(spacing: 10) { SearchingEventView(state: damus_state, search_type: .event(NoteId(h))) - SearchingEventView(state: damus_state, search_type: .profile(Pubkey(h))) - } - + } case .note(let nid): SearchingEventView(state: damus_state, search_type: .event(nid)) - + case .nevent(let nevent): + SearchingEventView(state: damus_state, search_type: .event(nevent.noteid)) + case .nprofile(let nprofile): + SearchingEventView(state: damus_state, search_type: .profile(nprofile.author)) + case .naddr(let naddr): + SearchingEventView(state: damus_state, search_type: .naddr(naddr)) case .multi(let multi): VStack { HashtagSearch(multi.hashtag) @@ -142,21 +146,35 @@ func search_for_string<Y>(profiles: Profiles, search new: String, txn: NdbTxn<Y> return .hashtag(make_hashtagable(new)) } - if let new = hex_decode_id(new) { + let searchQuery = remove_nostr_uri_prefix(new) + + if let new = hex_decode_id(searchQuery) { return .hex(new) } - if new.starts(with: "npub") { - if let decoded = bech32_pubkey_decode(new) { + if searchQuery.starts(with: "npub") { + if let decoded = bech32_pubkey_decode(searchQuery) { return .profile(decoded) } } - if new.starts(with: "note"), let decoded = try? bech32_decode(new) { + if searchQuery.starts(with: "note"), let decoded = try? bech32_decode(searchQuery) { return .note(NoteId(decoded.data)) } - let multisearch = MultiSearch(hashtag: make_hashtagable(new), profiles: search_profiles(profiles: profiles, search: new, txn: txn)) + if searchQuery.starts(with: "nevent"), case let .nevent(nevent) = Bech32Object.parse(searchQuery) { + return .nevent(nevent) + } + + if searchQuery.starts(with: "nprofile"), case let .nprofile(nprofile) = Bech32Object.parse(searchQuery) { + return .nprofile(nprofile) + } + + if searchQuery.starts(with: "naddr"), case let .naddr(naddr) = Bech32Object.parse(searchQuery) { + return .naddr(naddr) + } + + let multisearch = MultiSearch(hashtag: make_hashtagable(searchQuery), profiles: search_profiles(profiles: profiles, search: new, txn: txn)) return .multi(multisearch) }