damus

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

commit db2ec0a00a9fb473a5d4788c559b6696f8728b21
parent dc21b6139c21a3115ad7692b07068d5af8484fe5
Author: William Casarin <jb55@jb55.com>
Date:   Tue, 11 Jul 2023 09:05:45 -0700

Fix npub mention bugs, fix slowness when parsing large posts

Switch the post parser to use the same code as the content parser. This
was causing many issues, including performance issues.

Changelog-Fixed: Fix lag when creating large posts
Changelog-Fixed: Fix npub mentions failing to parse in some cases
Changelog-Added: Add r tag when mentioning a url
Changelog-Removed: Remove old @ and & hex key mentions

Diffstat:
Mdamus-c/damus.c | 10+++++-----
Mdamus-c/nostr_bech32.c | 11+++++++++++
Mdamus-c/nostr_bech32.h | 6++++++
Mdamus/Models/Mentions.swift | 129+++++++++++++++++++++----------------------------------------------------------
Mdamus/Models/Post.swift | 29++---------------------------
Mdamus/Nostr/NostrEvent.swift | 4++++
Mdamus/Util/Keys.swift | 10++++++----
MdamusTests/HashtagTests.swift | 28+++++++++++++++++++++++++---
MdamusTests/Models/DamusParseContentTests.swift | 2+-
MdamusTests/ReplyTests.swift | 236++++++++++---------------------------------------------------------------------
MdamusTests/damusTests.swift | 4+++-
11 files changed, 127 insertions(+), 342 deletions(-)

diff --git a/damus-c/damus.c b/damus-c/damus.c @@ -180,9 +180,9 @@ static int parse_invoice(struct cursor *cur, struct note_block *block) { static int parse_mention_bech32(struct cursor *cur, struct note_block *block) { u8 *start = cur->p; - if (!parse_str(cur, "nostr:")) - return 0; - + parse_char(cur, '@'); + parse_str(cur, "nostr:"); + block->block.str.start = (const char *)cur->p; if (!parse_nostr_bech32(cur, &block->block.mention_bech32.bech32)) { @@ -231,7 +231,7 @@ int damus_parse_content(struct note_blocks *blocks, const char *content) { } pre_mention = cur.p; - if (cp == -1 || is_whitespace(cp) || c == '#') { + if (cp == -1 || is_boundary(cp) || c == '#') { if (c == '#' && (parse_mention_index(&cur, &block) || parse_hashtag(&cur, &block))) { if (!add_text_then_block(&cur, blocks, block, &start, pre_mention)) return 0; @@ -244,7 +244,7 @@ int damus_parse_content(struct note_blocks *blocks, const char *content) { if (!add_text_then_block(&cur, blocks, block, &start, pre_mention)) return 0; continue; - } else if (c == 'n' && parse_mention_bech32(&cur, &block)) { + } else if ((c == 'n' || c == '@') && parse_mention_bech32(&cur, &block)) { if (!add_text_then_block(&cur, blocks, block, &start, pre_mention)) return 0; continue; diff --git a/damus-c/nostr_bech32.c b/damus-c/nostr_bech32.c @@ -91,6 +91,9 @@ static int parse_nostr_bech32_type(const char *prefix, enum nostr_bech32_type *t } else if (strcmp(prefix, "npub") == 0) { *type = NOSTR_BECH32_NPUB; return 1; + } else if (strcmp(prefix, "nsec") == 0) { + *type = NOSTR_BECH32_NSEC; + return 1; } else if (strcmp(prefix, "nprofile") == 0) { *type = NOSTR_BECH32_NPROFILE; return 1; @@ -116,6 +119,10 @@ static int parse_nostr_bech32_npub(struct cursor *cur, struct bech32_npub *npub) return pull_bytes(cur, 32, &npub->pubkey); } +static int parse_nostr_bech32_nsec(struct cursor *cur, struct bech32_nsec *nsec) { + return pull_bytes(cur, 32, &nsec->nsec); +} + static int tlvs_to_relays(struct nostr_tlvs *tlvs, struct relays *relays) { struct nostr_tlv *tlv; struct str_block *str; @@ -268,6 +275,10 @@ int parse_nostr_bech32(struct cursor *cur, struct nostr_bech32 *obj) { if (!parse_nostr_bech32_npub(&bcur, &obj->data.npub)) goto fail; break; + case NOSTR_BECH32_NSEC: + if (!parse_nostr_bech32_nsec(&bcur, &obj->data.nsec)) + goto fail; + break; case NOSTR_BECH32_NEVENT: if (!parse_nostr_bech32_nevent(&bcur, &obj->data.nevent)) goto fail; diff --git a/damus-c/nostr_bech32.h b/damus-c/nostr_bech32.h @@ -26,6 +26,7 @@ enum nostr_bech32_type { NOSTR_BECH32_NEVENT = 4, NOSTR_BECH32_NRELAY = 5, NOSTR_BECH32_NADDR = 6, + NOSTR_BECH32_NSEC = 7, }; struct bech32_note { @@ -36,6 +37,10 @@ struct bech32_npub { const u8 *pubkey; }; +struct bech32_nsec { + const u8 *nsec; +}; + struct bech32_nevent { struct relays relays; const u8 *event_id; @@ -65,6 +70,7 @@ typedef struct nostr_bech32 { union { struct bech32_note note; struct bech32_npub npub; + struct bech32_nsec nsec; struct bech32_nevent nevent; struct bech32_nprofile nprofile; struct bech32_naddr naddr; diff --git a/damus/Models/Mentions.swift b/damus/Models/Mentions.swift @@ -25,6 +25,14 @@ struct Mention: Equatable { let index: Int? let type: MentionType let ref: ReferencedId + + static func note(_ id: String) -> Mention { + return Mention(index: nil, type: .event, ref: .e(id)) + } + + static func pubkey(_ pubkey: String) -> Mention { + return Mention(index: nil, type: .pubkey, ref: .p(pubkey)) + } } typealias Invoice = LightningInvoice<Amount> @@ -114,12 +122,12 @@ enum Block: Equatable { return mention.type == .event } - - var is_mention: Bool { - if case .mention = self { - return true + + var is_mention: Mention? { + if case .mention(let m) = self { + return m } - return false + return nil } } @@ -332,7 +340,13 @@ func convert_mention_bech32_block(_ b: mention_bech32_block) -> Block? 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)) - + + 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)) + case NOSTR_BECH32_NPROFILE: let nprofile = b.bech32.data.nprofile let pubkey = hex_encode(Data(bytes: nprofile.pubkey, count: 32)) @@ -394,65 +408,6 @@ func convert_mention_index_block(ind: Int32, tags: [[String]]) -> Block? return .mention(Mention(index: ind, type: mention_type, ref: ref)) } -func parse_while(_ p: Parser, match: (Character) -> Bool) -> String? { - var i: Int = 0 - let sub = substring(p.str, start: p.pos, end: p.str.count) - let start = p.pos - for c in sub { - if match(c) { - p.pos += 1 - } else { - break - } - i += 1 - } - - let end = start + i - if start == end { - return nil - } - return String(substring(p.str, start: start, end: end)) -} - -func is_hashtag_char(_ c: Character) -> Bool { - return (c.isLetter || c.isNumber || c.isASCII) && (!c.isPunctuation && !c.isWhitespace) -} - -func prev_char(_ p: Parser, n: Int) -> Character? { - if p.pos - n < 0 { - return nil - } - - let ind = p.str.index(p.str.startIndex, offsetBy: p.pos - n) - return p.str[ind] -} - -func is_punctuation(_ c: Character) -> Bool { - return c.isWhitespace || c.isPunctuation -} - -func parse_hashtag(_ p: Parser) -> String? { - let start = p.pos - - if !parse_char(p, "#") { - return nil - } - - if let prev = prev_char(p, n: 2) { - // we don't allow adjacent hashtags - if !is_punctuation(prev) { - return nil - } - } - - guard let str = parse_while(p, match: is_hashtag_char) else { - p.pos = start - return nil - } - - return str -} - func find_tag_ref(type: String, id: String, tags: [[String]]) -> Int? { var i: Int = 0 for tag in tags { @@ -483,47 +438,31 @@ func parse_mention_type(_ c: String) -> MentionType? { } /// Convert -func make_post_tags(post_blocks: [PostBlock], tags: [[String]], silent_mentions: Bool) -> PostTags { +func make_post_tags(post_blocks: [Block], tags: [[String]], silent_mentions: Bool) -> PostTags { var new_tags = tags - var blocks: [Block] = [] - + for post_block in post_blocks { switch post_block { - case .ref(let ref): - guard let mention_type = parse_mention_type(ref.key) else { - continue - } - + case .mention(let mention): + let mention_type = mention.type + if silent_mentions || mention_type == .event { - let mention = Mention(index: nil, type: mention_type, ref: ref) - let block = Block.mention(mention) - blocks.append(block) continue } - - if find_tag_ref(type: ref.key, id: ref.ref_id, tags: tags) != nil { - // Mention index is nil because indexed mentions from NIP-08 is deprecated. - // It has been replaced with NIP-27 text note references with nostr: prefixed URIs. - let mention = Mention(index: nil, type: mention_type, ref: ref) - let block = Block.mention(mention) - blocks.append(block) - } else { - new_tags.append(refid_to_tag(ref)) - // Mention index is nil because indexed mentions from NIP-08 is deprecated. - // It has been replaced with NIP-27 text note references with nostr: prefixed URIs. - let mention = Mention(index: nil, type: mention_type, ref: ref) - let block = Block.mention(mention) - blocks.append(block) - } + + new_tags.append(refid_to_tag(mention.ref)) case .hashtag(let hashtag): new_tags.append(["t", hashtag.lowercased()]) - blocks.append(.hashtag(hashtag)) - case .text(let txt): - blocks.append(Block.text(txt)) + case .text: break + case .invoice: break + case .relay: break + case .url(let url): + new_tags.append(["r", url.absoluteString]) + break } } - return PostTags(blocks: blocks, tags: new_tags) + return PostTags(blocks: post_blocks, tags: new_tags) } func post_to_event(post: NostrPost, privkey: String, pubkey: String) -> NostrEvent { diff --git a/damus/Models/Post.swift b/damus/Models/Post.swift @@ -109,32 +109,7 @@ func parse_post_bech32_mention(_ p: Parser) -> ReferencedId? { } /// Return a list of tags -func parse_post_blocks(content: String) -> [PostBlock] { - let p = Parser(pos: 0, str: content) - var blocks: [PostBlock] = [] - var starting_from: Int = 0 - - if content.count == 0 { - return [] - } - - while p.pos < content.count { - let pre_mention = p.pos - if let reference = parse_post_reference(p) { - blocks.append(parse_post_textblock(str: p.str, from: starting_from, to: pre_mention)) - blocks.append(.ref(reference)) - starting_from = p.pos - } else if let hashtag = parse_hashtag(p) { - blocks.append(parse_post_textblock(str: p.str, from: starting_from, to: pre_mention)) - blocks.append(.hashtag(hashtag)) - starting_from = p.pos - } else { - p.pos += 1 - } - } - - blocks.append(parse_post_textblock(str: content, from: starting_from, to: content.count)) - - return blocks +func parse_post_blocks(content: String) -> [Block] { + return parse_mentions(content: content, tags: []).blocks } diff --git a/damus/Nostr/NostrEvent.swift b/damus/Nostr/NostrEvent.swift @@ -36,6 +36,10 @@ struct ReferencedId: Identifiable, Hashable, Equatable { static func e(_ id: String, relay_id: String? = nil) -> ReferencedId { return ReferencedId(ref_id: id, relay_id: relay_id, key: "e") } + + static func p(_ pk: String, relay_id: String? = nil) -> ReferencedId { + return ReferencedId(ref_id: pk, relay_id: relay_id, key: "p") + } } class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Hashable, Comparable { diff --git a/damus/Util/Keys.swift b/damus/Util/Keys.swift @@ -93,16 +93,18 @@ func generate_new_keypair() -> Keypair { return Keypair(pubkey: pubkey, privkey: privkey) } -func privkey_to_pubkey(privkey: String) -> String? { - guard let sec = hex_decode(privkey) else { - return nil - } +func privkey_to_pubkey_raw(sec: [UInt8]) -> String? { guard let key = try? secp256k1.Signing.PrivateKey(rawRepresentation: sec) else { return nil } return hex_encode(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 save_pubkey(pubkey: String) { UserDefaults.standard.set(pubkey, forKey: "pubkey") } diff --git a/damusTests/HashtagTests.swift b/damusTests/HashtagTests.swift @@ -39,15 +39,37 @@ final class HashtagTests: XCTestCase { } func testHashtagWithEmoji() { - let parsed = parse_mentions(content: "some hashtag #bitcoin☕️ cool", tags: []).blocks - + let content = "some hashtag #bitcoin☕️ cool" + let parsed = parse_mentions(content: content, tags: []).blocks + let post_blocks = parse_post_blocks(content: content) + XCTAssertNotNil(parsed) XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed[0].is_text, "some hashtag ") XCTAssertEqual(parsed[1].is_hashtag, "bitcoin☕️") XCTAssertEqual(parsed[2].is_text, " cool") + + XCTAssertEqual(post_blocks.count, 3) + XCTAssertEqual(post_blocks[0].is_text, "some hashtag ") + XCTAssertEqual(post_blocks[1].is_hashtag, "bitcoin☕️") + XCTAssertEqual(post_blocks[2].is_text, " cool") } - + + func testPowHashtag() { + let content = "pow! #ぽわ〜" + let parsed = parse_mentions(content: content, tags: []).blocks + let post_blocks = parse_post_blocks(content: content) + + XCTAssertNotNil(parsed) + XCTAssertEqual(parsed.count, 2) + XCTAssertEqual(parsed[0].is_text, "pow! ") + XCTAssertEqual(parsed[1].is_hashtag, "ぽわ〜") + + XCTAssertEqual(post_blocks.count, 2) + XCTAssertEqual(post_blocks[0].is_text, "pow! ") + XCTAssertEqual(post_blocks[1].is_hashtag, "ぽわ〜") + } + func testHashtagWithAccents() { let parsed = parse_mentions(content: "hello from #türkiye", tags: []).blocks diff --git a/damusTests/Models/DamusParseContentTests.swift b/damusTests/Models/DamusParseContentTests.swift @@ -52,7 +52,7 @@ class DamusParseContentTests: XCTestCase { return } - if currentBlock.is_mention { + if currentBlock.is_mention != nil { XCTAssert(isMentionBlockSet.contains(i)) } else { XCTAssert(!isMentionBlockSet.contains(i)) diff --git a/damusTests/ReplyTests.swift b/damusTests/ReplyTests.swift @@ -39,8 +39,10 @@ class ReplyTests: XCTestCase { let content = "this is my link: https://jb55.com/index.html#buybitcoin this is not a hashtag!" let blocks = parse_post_blocks(content: content) - XCTAssertEqual(blocks.count, 1) - XCTAssertEqual(blocks[0].is_text != nil, true) + XCTAssertEqual(blocks.count, 3) + XCTAssertEqual(blocks[0].is_text, "this is my link: ") + XCTAssertEqual(blocks[1].is_url, URL(string: "https://jb55.com/index.html#buybitcoin")!) + XCTAssertEqual(blocks[2].is_text, " this is not a hashtag!") } func testLinkIsNotAHashtag() { @@ -49,8 +51,10 @@ class ReplyTests: XCTestCase { let content = "my \(link) link" let blocks = parse_post_blocks(content: content) - XCTAssertEqual(blocks.count, 1) - XCTAssertEqual(blocks[0].is_text, content) + XCTAssertEqual(blocks.count, 3) + XCTAssertEqual(blocks[0].is_text, "my ") + XCTAssertEqual(blocks[1].is_url, URL(string: link)!) + XCTAssertEqual(blocks[2].is_text, " link") } func testAtAtEnd() { @@ -74,23 +78,17 @@ class ReplyTests: XCTestCase { func testHashtagAtStartWorks() { let content = "#hashtag" let blocks = parse_post_blocks(content: content) - XCTAssertEqual(blocks.count, 3) - XCTAssertEqual(blocks[1].is_hashtag, "hashtag") + XCTAssertEqual(blocks.count, 1) + XCTAssertEqual(blocks[0].is_hashtag, "hashtag") } func testGroupOfHashtags() { let content = "#hashtag#what#nope" let blocks = parse_post_blocks(content: content) XCTAssertEqual(blocks.count, 3) - XCTAssertEqual(blocks[1].is_hashtag, "hashtag") - XCTAssertEqual(blocks[2].is_text, "#what#nope") - - switch blocks[1] { - case .hashtag(let htag): - XCTAssertEqual(htag, "hashtag") - default: - break - } + XCTAssertEqual(blocks[0].is_hashtag, "hashtag") + XCTAssertEqual(blocks[1].is_hashtag, "what") + XCTAssertEqual(blocks[2].is_hashtag, "nope") } func testRootReplyWithMention() throws { @@ -124,32 +122,12 @@ class ReplyTests: XCTestCase { XCTAssertEqual(post_tags.tags.count, 0) XCTAssertEqual(post_blocks.count, 1) } - - func testManyPostMentions() throws { - let content = """ -@38bc54a8f675564058b987056fc27fe3d40ca34404586933a115d9e0baeaccb9 -@774734fad6c318799149c35008c356352b8bfc1791d9e41c803bd412b23143be -@d64266d4bbf3cbcb773d074ee5ffe9ae557425cce0521e102dfde88a7223fb4c -@9f936cfb57374c95c4b8f2d5e640d978e4c59ccbe7783d434f434a8cc69bfa07 -@29080a53a6cef22b28dd8c9a25684cb9c2691f8f0c98651d20c65e1a2cd5cef1 -@dcdc52ec631c4034b0766a49865ec2e7fc0cdb2ba071aff4050eba343e7ba0fe -@136f15a6e4c5f046a71ddaf014bbca51408041d5d0ec2a0154be4b089e6f0249 -@5d994e704a4d3edf0163a708f69cb821f5a9caefeb79c17c1507e11e8a238f36 -@d76951e648f1b00715fe55003fcfb6fe91a7bf73fca5b6fd3e5bbe6845a5a0b1 -@3e999f94e2cb34ef44a64b351141ac4e51b5121b2d31aed4a6c84602a1144692 -""" - //let tags: [[String]] = [] - let blocks = parse_post_blocks(content: content) - - let mentions = blocks.filter { $0.is_ref != nil } - XCTAssertEqual(mentions.count, 10) - } - + func testManyMentions() throws { let content = "#[10]" let tags: [[String]] = [[],[],[],[],[],[],[],[],[],[],["p", "3e999f94e2cb34ef44a64b351141ac4e51b5121b2d31aed4a6c84602a1144692"]] let blocks = parse_mentions(content: content, tags: tags).blocks - let mentions = blocks.filter { $0.is_mention } + let mentions = blocks.filter { $0.is_mention != nil } XCTAssertEqual(mentions.count, 1) } @@ -213,11 +191,10 @@ class ReplyTests: XCTestCase { let content = "@\(pk) hello there" let blocks = parse_post_blocks(content: content) - XCTAssertEqual(blocks.count, 3) - XCTAssertEqual(blocks[0].is_text, "") - XCTAssertEqual(blocks[1].is_ref, ReferencedId(ref_id: hex_pk, relay_id: nil, key: "p")) - XCTAssertEqual(blocks[2].is_text, " hello there") - + XCTAssertEqual(blocks.count, 2) + XCTAssertEqual(blocks[0].is_mention, .pubkey(hex_pk)) + XCTAssertEqual(blocks[1].is_text, " hello there") + } func testBech32MentionAtEnd() throws { @@ -226,11 +203,9 @@ class ReplyTests: XCTestCase { let content = "this is a @\(pk)" let blocks = parse_post_blocks(content: content) - XCTAssertEqual(blocks.count, 3) - XCTAssertEqual(blocks[1].is_ref, ReferencedId(ref_id: hex_pk, relay_id: nil, key: "p")) + XCTAssertEqual(blocks.count, 2) + XCTAssertEqual(blocks[1].is_mention, .pubkey(hex_pk)) XCTAssertEqual(blocks[0].is_text, "this is a ") - XCTAssertEqual(blocks[2].is_text, "") - } func testNpubMention() throws { @@ -245,27 +220,10 @@ class ReplyTests: XCTestCase { XCTAssertEqual(ev.tags.count, 2) XCTAssertEqual(blocks.count, 3) - XCTAssertEqual(blocks[1].is_ref, ReferencedId(ref_id: hex_pk, relay_id: nil, key: "p")) + XCTAssertEqual(blocks[1].is_mention, .pubkey(hex_pk)) XCTAssertEqual(ev.content, "this is a nostr:npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s mention") } - func testNoteMention() throws { - let evid = "0000000000000000000000000000000000000000000000000000000000000005" - let pk = "note154fwmp6hdxqnmqdzkt5jeay8l4kxdsrpn02vw9kp4gylkxxur5fsq3ckpy" - let hex_note_id = "a552ed875769813d81a2b2e92cf487fd6c66c0619bd4c716c1aa09fb18dc1d13" - let content = "this is a @\(pk) &\(pk) mention" - let reply_ref = ReferencedId(ref_id: evid, relay_id: nil, key: "e") - let blocks = parse_post_blocks(content: content) - let post = NostrPost(content: content, references: [reply_ref]) - let ev = post_to_event(post: post, privkey: evid, pubkey: pk) - - XCTAssertEqual(ev.tags.count, 1) - XCTAssertEqual(blocks.count, 5) - XCTAssertEqual(blocks[1].is_ref, ReferencedId(ref_id: hex_note_id, relay_id: nil, key: "e")) - XCTAssertEqual(blocks[3].is_ref, ReferencedId(ref_id: hex_note_id, relay_id: nil, key: "e")) - XCTAssertEqual(ev.content, "this is a nostr:\(pk) nostr:\(pk) mention") - } - func testNsecMention() throws { let evid = "0000000000000000000000000000000000000000000000000000000000000005" let pk = "nsec1jmzdz7d0ldqctdxwm5fzue277ttng2pk28n2u8wntc2r4a0w96ssnyukg7" @@ -278,49 +236,25 @@ class ReplyTests: XCTestCase { XCTAssertEqual(ev.tags.count, 2) XCTAssertEqual(blocks.count, 3) - XCTAssertEqual(blocks[1].is_ref, ReferencedId(ref_id: hex_pk, relay_id: nil, key: "p")) + XCTAssertEqual(blocks[1].is_mention, .pubkey(hex_pk)) XCTAssertEqual(ev.content, "this is a nostr:npub1enu46e5x2qtcmm72ttzsx6fmve5wkauftassz78l3mvluh8efqhqejf3v4 mention") } - func testPostWithMentions() throws { - let evid = "0000000000000000000000000000000000000000000000000000000000000005" - let pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245" - let content = "this is a @\(pk) mention" - let reply_ref = ReferencedId(ref_id: evid, relay_id: nil, key: "e") - let post = NostrPost(content: content, references: [reply_ref]) - let ev = post_to_event(post: post, privkey: evid, pubkey: pk) - - XCTAssertEqual(ev.tags.count, 2) - XCTAssertEqual(ev.content, "this is a nostr:npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s mention") - } - - func testPostTags() throws { - let pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245" - let content = "this is a @\(pk) mention" - let parsed = parse_post_blocks(content: content) - let post_tags = make_post_tags(post_blocks: parsed, tags: [], silent_mentions: false) - - XCTAssertEqual(post_tags.blocks.count, 3) - XCTAssertEqual(post_tags.tags.count, 1) - XCTAssertEqual(post_tags.tags[0].count, 2) - XCTAssertEqual(post_tags.tags[0][0], "p") - XCTAssertEqual(post_tags.tags[0][1], pk) - } - func testReplyMentions() throws { let privkey = "0fc2092231f958f8d57d66f5e238bb45b6a2571f44c0ce024bbc6f3a9c8a15fe" let pubkey = "30c6d1dc7f7c156794fa15055e651b758a61b99f50fcf759de59386050bf6ae2" - + let npub = "npub1xrrdrhrl0s2k0986z5z4uegmwk9xrwvl2r70wkw7tyuxq59ldt3qh09eay" + let refs = [ ReferencedId(ref_id: "thread_id", relay_id: nil, key: "e"), ReferencedId(ref_id: "reply_id", relay_id: nil, key: "e"), ReferencedId(ref_id: pubkey, relay_id: nil, key: "p"), ] - let post = NostrPost(content: "this is a (@\(pubkey)) mention", references: refs) + let post = NostrPost(content: "this is a (@\(npub)) mention", references: refs) let ev = post_to_event(post: post, privkey: privkey, pubkey: pubkey) - XCTAssertEqual(ev.content, "this is a (nostr:npub1xrrdrhrl0s2k0986z5z4uegmwk9xrwvl2r70wkw7tyuxq59ldt3qh09eay) mention") + XCTAssertEqual(ev.content, "this is a (nostr:\(npub)) mention") XCTAssertEqual(ev.tags[2][1], pubkey) } @@ -347,38 +281,6 @@ class ReplyTests: XCTestCase { XCTAssertEqual(txt, content) } - func testFunnyUriReference() throws { - let id = "6fec2ee6cfff779fe8560976b3d9df782b74577f0caefa7a77c0ed4c3749b5de" - let content = "this is a nostr:&\(id):\(id) event mention" - let parsed = parse_post_blocks(content: content) - - XCTAssertNotNil(parsed) - XCTAssertEqual(parsed.count, 3) - XCTAssertEqual(parsed[0].is_text, "this is a nostr:") - XCTAssertTrue(parsed[1].is_ref != nil) - XCTAssertEqual(parsed[2].is_text, ":\(id) event mention") - - guard case .ref(let ref) = parsed[1] else { - XCTAssertTrue(false) - return - } - XCTAssertEqual(ref.ref_id, id) - XCTAssertEqual(ref.key, "e") - XCTAssertNil(ref.relay_id) - - guard case .text(let t1) = parsed[0] else { - XCTAssertTrue(false) - return - } - XCTAssertEqual(t1, "this is a nostr:") - - guard case .text(let t2) = parsed[2] else { - XCTAssertTrue(false) - return - } - XCTAssertEqual(t2, ":\(id) event mention") - } - func testInvalidUriReference() throws { let id = "6fec2ee6cfff779fe8560976b3d9df782b74577f0caefa7a77c0ed4c3749b5de" let content = "this is a nostr:z:\(id) event mention" @@ -403,17 +305,9 @@ class ReplyTests: XCTestCase { XCTAssertNotNil(parsed) XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed[0].is_text, "this is a ") - XCTAssertNotNil(parsed[1].is_ref) + XCTAssertEqual(parsed[1].is_mention, .pubkey(id)) XCTAssertEqual(parsed[2].is_text, " event mention") - guard case .ref(let ref) = parsed[1] else { - XCTAssertTrue(false) - return - } - XCTAssertEqual(ref.ref_id, id) - XCTAssertEqual(ref.key, "p") - XCTAssertNil(ref.relay_id) - guard case .text(let t1) = parsed[0] else { XCTAssertTrue(false) return @@ -435,48 +329,9 @@ class ReplyTests: XCTestCase { XCTAssertNotNil(parsed) XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed[0].is_text, "this is a ") - XCTAssertNotNil(parsed[1].is_ref) + XCTAssertEqual(parsed[1].is_mention, .note(id)) XCTAssertEqual(parsed[2].is_text, " event mention") - - guard case .ref(let ref) = parsed[1] else { - XCTAssertTrue(false) - return - } - XCTAssertEqual(ref.ref_id, id) - XCTAssertEqual(ref.key, "e") - XCTAssertNil(ref.relay_id) - - guard case .text(let t1) = parsed[0] else { - XCTAssertTrue(false) - return - } - XCTAssertEqual(t1, "this is a ") - - guard case .text(let t2) = parsed[2] else { - XCTAssertTrue(false) - return - } - XCTAssertEqual(t2, " event mention") - } - - func testParsePostEventReference() throws { - let pk = "6fec2ee6cfff779fe8560976b3d9df782b74577f0caefa7a77c0ed4c3749b5de" - let parsed = parse_post_blocks(content: "this is a &\(pk) event mention") - - XCTAssertNotNil(parsed) - XCTAssertEqual(parsed.count, 3) - XCTAssertEqual(parsed[0].is_text, "this is a ") - XCTAssertNotNil(parsed[1].is_ref) - XCTAssertEqual(parsed[2].is_text, " event mention") - - guard case .ref(let ref) = parsed[1] else { - XCTAssertTrue(false) - return - } - XCTAssertEqual(ref.ref_id, pk) - XCTAssertEqual(ref.key, "e") - XCTAssertNil(ref.relay_id) - + guard case .text(let t1) = parsed[0] else { XCTAssertTrue(false) return @@ -490,37 +345,6 @@ class ReplyTests: XCTestCase { XCTAssertEqual(t2, " event mention") } - func testParsePostPubkeyReference() throws { - let pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245" - let parsed = parse_post_blocks(content: "this is a @\(pk) mention") - - XCTAssertNotNil(parsed) - XCTAssertEqual(parsed.count, 3) - XCTAssertEqual(parsed[0].is_text, "this is a ") - XCTAssertNotNil(parsed[1].is_ref) - XCTAssertEqual(parsed[2].is_text, " mention") - - guard case .ref(let ref) = parsed[1] else { - XCTAssertTrue(false) - return - } - XCTAssertEqual(ref.ref_id, pk) - XCTAssertEqual(ref.key, "p") - XCTAssertNil(ref.relay_id) - - guard case .text(let t1) = parsed[0] else { - XCTAssertTrue(false) - return - } - XCTAssertEqual(t1, "this is a ") - - guard case .text(let t2) = parsed[2] else { - XCTAssertTrue(false) - return - } - XCTAssertEqual(t2, " mention") - } - func testParseInvalidMention() throws { let parsed = parse_mentions(content: "this is #[0] a mention", tags: []).blocks diff --git a/damusTests/damusTests.swift b/damusTests/damusTests.swift @@ -74,8 +74,10 @@ class damusTests: XCTestCase { let parsed = parse_mentions(content: md, tags: []).blocks XCTAssertNotNil(parsed) - XCTAssertEqual(parsed.count, 1) + XCTAssertEqual(parsed.count, 3) XCTAssertNotNil(parsed[0].is_text) + XCTAssertNotNil(parsed[1].is_url) + XCTAssertNotNil(parsed[2].is_text) } func testParseUrlUpper() {