damus

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

commit f30f93f65cb78a508731a682100485dc62908f97
parent 725548170567a9911fb6cc8059994e633806d2d5
Author: William Casarin <jb55@jb55.com>
Date:   Fri, 25 Aug 2023 19:03:19 -0700

Revert "Move the Block helper type to its own file"

This fixes the broken tests

This reverts commit 286ae68fd63754fbf5a9f4b271c207e9d2df1728.

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 4----
Mdamus/ContentParsing.swift | 2+-
Mdamus/Models/Mentions.swift | 227+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Ddamus/Types/Block.swift | 214-------------------------------------------------------------------------------
Mdamus/Util/Zap.swift | 2+-
Mdamus/Views/DMChatView.swift | 4+---
Mdamus/Views/NoteContentView.swift | 10+---------
MdamusTests/LongPostTests.swift | 2+-
Mnostrdb/NdbNote.swift | 10+---------
9 files changed, 230 insertions(+), 245 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -375,7 +375,6 @@ 643EA5C8296B764E005081BB /* RelayFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643EA5C7296B764E005081BB /* RelayFilterView.swift */; }; 647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; }; 64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; }; - 7527271E2A93FF0100214108 /* Block.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7527271D2A93FF0100214108 /* Block.swift */; }; 7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C60CAEE298471A1009C80D6 /* CoreSVG.swift */; }; 7C902AE32981D55B002AB16E /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */; }; 7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */; }; @@ -934,7 +933,6 @@ 643EA5C7296B764E005081BB /* RelayFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterView.swift; sourceTree = "<group>"; }; 647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; }; 64FBD06E296255C400D9D3B2 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; }; - 7527271D2A93FF0100214108 /* Block.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Block.swift; sourceTree = "<group>"; }; 7C60CAEE298471A1009C80D6 /* CoreSVG.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreSVG.swift; sourceTree = "<group>"; }; 7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomableScrollView.swift; sourceTree = "<group>"; }; 7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KFOptionSetter+.swift"; sourceTree = "<group>"; }; @@ -1607,7 +1605,6 @@ isa = PBXGroup; children = ( 4CC14FED2A73FCBB007AEB17 /* Ids */, - 7527271D2A93FF0100214108 /* Block.swift */, ); path = Types; sourceTree = "<group>"; @@ -2423,7 +2420,6 @@ 7CFF6317299FEFE5005D382A /* SelectableText.swift in Sources */, 4CA352A82A76B37E003BB08B /* NewMutesNotify.swift in Sources */, 4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */, - 7527271E2A93FF0100214108 /* Block.swift in Sources */, 4C54AA0729A540BA003E4487 /* NotificationsModel.swift in Sources */, 4C12536C2A76D4B00004F4B8 /* RepostedNotify.swift in Sources */, 4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */, diff --git a/damus/ContentParsing.swift b/damus/ContentParsing.swift @@ -27,7 +27,7 @@ func parsed_blocks_finish(bs: inout note_blocks, tags: TagsSequence?) -> Blocks while (i < bs.num_blocks) { let block = bs.blocks[i] - if let converted = Block(block, tags: tags) { + if let converted = convert_block(block, tags: tags) { out.append(converted) } diff --git a/damus/Models/Mentions.swift b/damus/Models/Mentions.swift @@ -123,11 +123,147 @@ struct LightningInvoice<T> { } } +enum Block: Equatable { + static func == (lhs: Block, rhs: Block) -> Bool { + switch (lhs, rhs) { + case (.text(let a), .text(let b)): + return a == b + case (.mention(let a), .mention(let b)): + return a == b + case (.hashtag(let a), .hashtag(let b)): + return a == b + case (.url(let a), .url(let b)): + return a == b + case (.invoice(let a), .invoice(let b)): + return a.string == b.string + case (_, _): + return false + } + } + + case text(String) + case mention(Mention<MentionRef>) + case hashtag(String) + case url(URL) + case invoice(Invoice) + case relay(String) + + var is_invoice: Invoice? { + if case .invoice(let invoice) = self { + return invoice + } + return nil + } + + var is_hashtag: String? { + if case .hashtag(let htag) = self { + return htag + } + return nil + } + + var is_url: URL? { + if case .url(let url) = self { + return url + } + + return nil + } + + var is_text: String? { + if case .text(let txt) = self { + return txt + } + return nil + } + + var is_note_mention: Bool { + if case .mention(let mention) = self, + case .note = mention.ref { + return true + } + return false + } + + var is_mention: Mention<MentionRef>? { + if case .mention(let m) = self { + return m + } + return nil + } +} + +func render_blocks(blocks: [Block]) -> String { + return blocks.reduce("") { str, block in + switch block { + case .mention(let m): + if let idx = m.index { + return str + "#[\(idx)]" + } + + switch m.ref { + case .pubkey(let pk): return str + "nostr:\(pk.npub)" + case .note(let note_id): return str + "nostr:\(note_id.bech32)" + } + case .relay(let relay): + return str + relay + case .text(let txt): + return str + txt + case .hashtag(let htag): + return str + "#" + htag + case .url(let url): + return str + url.absoluteString + case .invoice(let inv): + return str + inv.string + } + } +} + struct Blocks: Equatable { let words: Int let blocks: [Block] } +func strblock_to_string(_ s: str_block_t) -> String? { + let len = s.end - s.start + let bytes = Data(bytes: s.start, count: len) + return String(bytes: bytes, encoding: .utf8) +} + +func convert_block(_ b: block_t, tags: TagsSequence?) -> Block? { + if b.type == BLOCK_HASHTAG { + guard let str = strblock_to_string(b.block.str) else { + return nil + } + return .hashtag(str) + } else if b.type == BLOCK_TEXT { + guard let str = strblock_to_string(b.block.str) else { + return nil + } + return .text(str) + } else if b.type == BLOCK_MENTION_INDEX { + return convert_mention_index_block(ind: Int(b.block.mention_index), tags: tags) + } else if b.type == BLOCK_URL { + return convert_url_block(b.block.str) + } else if b.type == BLOCK_INVOICE { + return convert_invoice_block(b.block.invoice) + } else if b.type == BLOCK_MENTION_BECH32 { + return convert_mention_bech32_block(b.block.mention_bech32) + } + + return nil +} + +func convert_url_block(_ b: str_block) -> Block? { + guard let str = strblock_to_string(b) else { + return nil + } + guard let url = URL(string: str) else { + return .text(str) + } + return .url(url) +} + func maybe_pointee<T>(_ p: UnsafeMutablePointer<T>!) -> T? { guard p != nil else { return nil @@ -190,6 +326,75 @@ func format_msats(_ msat: Int64, locale: Locale = Locale.current) -> String { return String(format: format, locale: locale, sats.decimalValue as NSDecimalNumber, formattedSats) } +func convert_invoice_block(_ b: invoice_block) -> Block? { + guard let invstr = strblock_to_string(b.invstr) else { + return nil + } + + guard var b11 = maybe_pointee(b.bolt11) else { + return nil + } + + guard let description = convert_invoice_description(b11: b11) else { + return nil + } + + let amount: Amount = maybe_pointee(b11.msat).map { .specific(Int64($0.millisatoshis)) } ?? .any + let payment_hash = Data(bytes: &b11.payment_hash, count: 32) + let created_at = b11.timestamp + + tal_free(b.bolt11) + return .invoice(Invoice(description: description, amount: amount, string: invstr, expiry: b11.expiry, payment_hash: payment_hash, created_at: created_at)) +} + +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 note_id = NoteId(Data(bytes: note.event_id, count: 32)) + return .mention(.any(.note(note_id))) + + case NOSTR_BECH32_NEVENT: + let nevent = b.bech32.data.nevent; + let note_id = NoteId(Data(bytes: nevent.event_id, count: 32)) + return .mention(.any(.note(note_id))) + + case NOSTR_BECH32_NPUB: + let npub = b.bech32.data.npub + let pubkey = Pubkey(Data(bytes: npub.pubkey, count: 32)) + return .mention(.any(.pubkey(pubkey))) + + case NOSTR_BECH32_NSEC: + let nsec = b.bech32.data.nsec + let privkey = Privkey(Data(bytes: nsec.nsec, count: 32)) + guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil } + return .mention(.any(.pubkey(pubkey))) + + case NOSTR_BECH32_NPROFILE: + let nprofile = b.bech32.data.nprofile + let pubkey = Pubkey(Data(bytes: nprofile.pubkey, count: 32)) + return .mention(.any(.pubkey(pubkey))) + + case NOSTR_BECH32_NRELAY: + let nrelay = b.bech32.data.nrelay + guard let relay_str = strblock_to_string(nrelay.relay) else { + return nil + } + return .relay(relay_str) + + case NOSTR_BECH32_NADDR: + // TODO: wtf do I do with this + guard let naddr = strblock_to_string(b.str) else { + return nil + } + return .text("nostr:" + naddr) + + default: + return nil + } +} + func convert_invoice_description(b11: bolt11) -> InvoiceDescription? { if let desc = b11.description { return .description(String(cString: desc)) @@ -202,6 +407,24 @@ func convert_invoice_description(b11: bolt11) -> InvoiceDescription? { return nil } +func convert_mention_index_block(ind: Int, tags: TagsSequence?) -> Block? +{ + guard let tags, + ind >= 0, + ind + 1 <= tags.count + else { + return .text("#[\(ind)]") + } + + let tag = tags[ind] + + guard let mention = MentionRef.from_tag(tag: tag) else { + return .text("#[\(ind)]") + } + + return .mention(.any(mention, index: ind)) +} + func find_tag_ref(type: String, id: String, tags: [[String]]) -> Int? { var i: Int = 0 for tag in tags { @@ -251,9 +474,7 @@ func post_to_event(post: NostrPost, keypair: FullKeypair) -> NostrEvent? { let tags = post.references.map({ r in r.tag }) + post.tags let post_blocks = parse_post_blocks(content: post.content) let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags) - let content = post_tags.blocks - .map(\.asString) - .joined(separator: "") + let content = render_blocks(blocks: post_tags.blocks) return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: post.kind.rawValue, tags: post_tags.tags) } diff --git a/damus/Types/Block.swift b/damus/Types/Block.swift @@ -1,214 +0,0 @@ -// -// Block.swift -// damus -// -// Created by Kyle Roucis on 2023-08-21. -// - -import Foundation - - -fileprivate extension String { - /// Failable initializer to build a Swift.String from a C-backed `str_block_t`. - init?(_ s: str_block_t) { - let len = s.end - s.start - let bytes = Data(bytes: s.start, count: len) - self.init(bytes: bytes, encoding: .utf8) - } -} - -/// Represents a block of data stored by the NOSTR protocol. This can be -/// simple text, a hashtag, a url, a relay reference, a mention ref and -/// potentially more in the future. -enum Block: Equatable { - static func == (lhs: Block, rhs: Block) -> Bool { - switch (lhs, rhs) { - case (.text(let a), .text(let b)): - return a == b - case (.mention(let a), .mention(let b)): - return a == b - case (.hashtag(let a), .hashtag(let b)): - return a == b - case (.url(let a), .url(let b)): - return a == b - case (.invoice(let a), .invoice(let b)): - return a.string == b.string - case (_, _): - return false - } - } - - case text(String) - case mention(Mention<MentionRef>) - case hashtag(String) - case url(URL) - case invoice(Invoice) - case relay(String) -} -extension Block { - /// Failable initializer for the C-backed type `block_t`. This initializer will inspect - /// the underlying block type and build the appropriate enum value as needed. - init?(_ block: block_t, tags: TagsSequence? = nil) { - switch block.type { - case BLOCK_HASHTAG: - guard let str = String(block.block.str) else { - return nil - } - self = .hashtag(str) - case BLOCK_TEXT: - guard let str = String(block.block.str) else { - return nil - } - self = .text(str) - case BLOCK_MENTION_INDEX: - guard let b = Block(index: Int(block.block.mention_index), tags: tags) else { - return nil - } - self = b - case BLOCK_URL: - guard let b = Block(block.block.str) else { - return nil - } - self = b - case BLOCK_INVOICE: - guard let b = Block(invoice: block.block.invoice) else { - return nil - } - self = b - case BLOCK_MENTION_BECH32: - guard let b = Block(bech32: block.block.mention_bech32) else { - return nil - } - self = b - default: - return nil - } - } -} -fileprivate extension Block { - /// Failable initializer for the C-backed type `str_block_t`. - init?(_ b: str_block_t) { - guard let str = String(b) else { - return nil - } - - if let url = URL(string: str) { - self = .url(url) - } - else { - self = .text(str) - } - } -} -fileprivate extension Block { - /// Failable initializer for a block index and a tag sequence. - init?(index: Int, tags: TagsSequence? = nil) { - guard let tags, - index >= 0, - index + 1 <= tags.count - else { - self = .text("#[\(index)]") - return - } - - let tag = tags[index] - - if let mention = MentionRef.from_tag(tag: tag) { - self = .mention(.any(mention, index: index)) - } - else { - self = .text("#[\(index)]") - } - } -} -fileprivate extension Block { - /// Failable initializer for the C-backed type `invoice_block_t`. - init?(invoice: invoice_block_t) { - guard let invstr = String(invoice.invstr) else { - return nil - } - - guard var b11 = maybe_pointee(invoice.bolt11) else { - return nil - } - - guard let description = convert_invoice_description(b11: b11) else { - return nil - } - - let amount: Amount = maybe_pointee(b11.msat).map { .specific(Int64($0.millisatoshis)) } ?? .any - let payment_hash = Data(bytes: &b11.payment_hash, count: 32) - let created_at = b11.timestamp - - tal_free(invoice.bolt11) - self = .invoice(Invoice(description: description, amount: amount, string: invstr, expiry: b11.expiry, payment_hash: payment_hash, created_at: created_at)) - } -} -fileprivate extension Block { - /// Failable initializer for the C-backed type `mention_bech32_block_t`. This initializer will inspect the - /// bech32 type code and build the appropriate enum type. - init?(bech32 b: mention_bech32_block_t) { - switch b.bech32.type { - case NOSTR_BECH32_NOTE: - let note = b.bech32.data.note; - let note_id = NoteId(Data(bytes: note.event_id, count: 32)) - self = .mention(.any(.note(note_id))) - case NOSTR_BECH32_NEVENT: - let nevent = b.bech32.data.nevent; - let note_id = NoteId(Data(bytes: nevent.event_id, count: 32)) - self = .mention(.any(.note(note_id))) - case NOSTR_BECH32_NPUB: - let npub = b.bech32.data.npub - let pubkey = Pubkey(Data(bytes: npub.pubkey, count: 32)) - self = .mention(.any(.pubkey(pubkey))) - case NOSTR_BECH32_NSEC: - let nsec = b.bech32.data.nsec - let privkey = Privkey(Data(bytes: nsec.nsec, count: 32)) - guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil } - self = .mention(.any(.pubkey(pubkey))) - case NOSTR_BECH32_NPROFILE: - let nprofile = b.bech32.data.nprofile - let pubkey = Pubkey(Data(bytes: nprofile.pubkey, count: 32)) - self = .mention(.any(.pubkey(pubkey))) - case NOSTR_BECH32_NRELAY: - let nrelay = b.bech32.data.nrelay - guard let relay_str = String(nrelay.relay) else { - return nil - } - self = .relay(relay_str) - case NOSTR_BECH32_NADDR: - // TODO: wtf do I do with this - guard let naddr = String(b.str) else { - return nil - } - self = .text("nostr:" + naddr) - default: - return nil - } - } -} -extension Block { - var asString: String { - switch self { - case .mention(let m): - if let idx = m.index { - return "#[\(idx)]" - } - - switch m.ref { - case .pubkey(let pk): return "nostr:\(pk.npub)" - case .note(let note_id): return "nostr:\(note_id.bech32)" - } - case .relay(let relay): - return relay - case .text(let txt): - return txt - case .hashtag(let htag): - return "#" + htag - case .url(let url): - return url.absoluteString - case .invoice(let inv): - return inv.string - } - } -} diff --git a/damus/Util/Zap.swift b/damus/Util/Zap.swift @@ -393,7 +393,7 @@ func decode_bolt11(_ s: String) -> Invoice? { let block = bs.blocks[0] - guard let converted = Block(block) else { + guard let converted = convert_block(block, tags: nil) else { blocks_free(&bs) return nil } diff --git a/damus/Views/DMChatView.swift b/damus/Views/DMChatView.swift @@ -130,9 +130,7 @@ struct DMChatView: View, KeyboardReadable { func send_message() { let tags = [["p", pubkey.hex()]] let post_blocks = parse_post_blocks(content: dms.draft) - let content = post_blocks - .map(\.asString) - .joined(separator: "") + let content = render_blocks(blocks: post_blocks) guard let dm = create_dm(content, to_pk: pubkey, tags: tags, keypair: damus_state.keypair) else { print("error creating dm") diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift @@ -445,15 +445,7 @@ func render_blocks(blocks bs: Blocks, profiles: Profiles) -> NoteArtifactsSepara let blocks = bs.blocks let one_note_ref = blocks - .filter({ - if case .mention(let mention) = $0, - case .note = mention.ref { - return true - } - else { - return false - } - }) + .filter({ $0.is_note_mention }) .count == 1 var ind: Int = -1 diff --git a/damusTests/LongPostTests.swift b/damusTests/LongPostTests.swift @@ -34,7 +34,7 @@ final class LongPostTests: XCTestCase { XCTAssertEqual(subid, "subid") XCTAssertTrue(ev.should_show_event) XCTAssertTrue(!ev.too_big) - XCTAssertTrue(should_show_event(contacts: contacts, ev: ev)) + XCTAssertTrue(should_show_event(privkey: test_keypair.privkey, hellthreads: test_damus_state().muted_threads, contacts: contacts, ev: ev)) XCTAssertTrue(validate_event(ev: ev) == .ok ) } diff --git a/nostrdb/NdbNote.swift b/nostrdb/NdbNote.swift @@ -415,15 +415,7 @@ extension NdbNote { // Rely on Apple's NLLanguageRecognizer to tell us which language it thinks the note is in // and filter on only the text portions of the content as URLs and hashtags confuse the language recognizer. let originalBlocks = self.blocks(privkey).blocks - let originalOnlyText = originalBlocks.compactMap { - if case .text(let txt) = $0 { - return txt - } - else { - return nil - } - } - .joined(separator: " ") + let originalOnlyText = originalBlocks.compactMap { $0.is_text }.joined(separator: " ") // Only accept language recognition hypothesis if there's at least a 50% probability that it's accurate. let languageRecognizer = NLLanguageRecognizer()