1
0
mirror of git://jb55.com/damus synced 2024-09-18 19:23:49 +00:00

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
This commit is contained in:
William Casarin 2023-07-11 09:05:45 -07:00
parent dc21b6139c
commit db2ec0a00a
11 changed files with 127 additions and 342 deletions

View File

@ -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) { static int parse_mention_bech32(struct cursor *cur, struct note_block *block) {
u8 *start = cur->p; u8 *start = cur->p;
if (!parse_str(cur, "nostr:")) parse_char(cur, '@');
return 0; parse_str(cur, "nostr:");
block->block.str.start = (const char *)cur->p; block->block.str.start = (const char *)cur->p;
if (!parse_nostr_bech32(cur, &block->block.mention_bech32.bech32)) { 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; 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 (c == '#' && (parse_mention_index(&cur, &block) || parse_hashtag(&cur, &block))) {
if (!add_text_then_block(&cur, blocks, block, &start, pre_mention)) if (!add_text_then_block(&cur, blocks, block, &start, pre_mention))
return 0; 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)) if (!add_text_then_block(&cur, blocks, block, &start, pre_mention))
return 0; return 0;
continue; 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)) if (!add_text_then_block(&cur, blocks, block, &start, pre_mention))
return 0; return 0;
continue; continue;

View File

@ -91,6 +91,9 @@ static int parse_nostr_bech32_type(const char *prefix, enum nostr_bech32_type *t
} else if (strcmp(prefix, "npub") == 0) { } else if (strcmp(prefix, "npub") == 0) {
*type = NOSTR_BECH32_NPUB; *type = NOSTR_BECH32_NPUB;
return 1; return 1;
} else if (strcmp(prefix, "nsec") == 0) {
*type = NOSTR_BECH32_NSEC;
return 1;
} else if (strcmp(prefix, "nprofile") == 0) { } else if (strcmp(prefix, "nprofile") == 0) {
*type = NOSTR_BECH32_NPROFILE; *type = NOSTR_BECH32_NPROFILE;
return 1; 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); 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) { static int tlvs_to_relays(struct nostr_tlvs *tlvs, struct relays *relays) {
struct nostr_tlv *tlv; struct nostr_tlv *tlv;
struct str_block *str; 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)) if (!parse_nostr_bech32_npub(&bcur, &obj->data.npub))
goto fail; goto fail;
break; break;
case NOSTR_BECH32_NSEC:
if (!parse_nostr_bech32_nsec(&bcur, &obj->data.nsec))
goto fail;
break;
case NOSTR_BECH32_NEVENT: case NOSTR_BECH32_NEVENT:
if (!parse_nostr_bech32_nevent(&bcur, &obj->data.nevent)) if (!parse_nostr_bech32_nevent(&bcur, &obj->data.nevent))
goto fail; goto fail;

View File

@ -26,6 +26,7 @@ enum nostr_bech32_type {
NOSTR_BECH32_NEVENT = 4, NOSTR_BECH32_NEVENT = 4,
NOSTR_BECH32_NRELAY = 5, NOSTR_BECH32_NRELAY = 5,
NOSTR_BECH32_NADDR = 6, NOSTR_BECH32_NADDR = 6,
NOSTR_BECH32_NSEC = 7,
}; };
struct bech32_note { struct bech32_note {
@ -36,6 +37,10 @@ struct bech32_npub {
const u8 *pubkey; const u8 *pubkey;
}; };
struct bech32_nsec {
const u8 *nsec;
};
struct bech32_nevent { struct bech32_nevent {
struct relays relays; struct relays relays;
const u8 *event_id; const u8 *event_id;
@ -65,6 +70,7 @@ typedef struct nostr_bech32 {
union { union {
struct bech32_note note; struct bech32_note note;
struct bech32_npub npub; struct bech32_npub npub;
struct bech32_nsec nsec;
struct bech32_nevent nevent; struct bech32_nevent nevent;
struct bech32_nprofile nprofile; struct bech32_nprofile nprofile;
struct bech32_naddr naddr; struct bech32_naddr naddr;

View File

@ -25,6 +25,14 @@ struct Mention: Equatable {
let index: Int? let index: Int?
let type: MentionType let type: MentionType
let ref: ReferencedId 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> typealias Invoice = LightningInvoice<Amount>
@ -114,12 +122,12 @@ enum Block: Equatable {
return mention.type == .event return mention.type == .event
} }
var is_mention: Bool { var is_mention: Mention? {
if case .mention = self { if case .mention(let m) = self {
return true 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 = hex_encode(Data(bytes: npub.pubkey, count: 32))
let pubkey_ref = ReferencedId(ref_id: pubkey, relay_id: nil, key: "p") let pubkey_ref = ReferencedId(ref_id: pubkey, relay_id: nil, key: "p")
return .mention(Mention(index: nil, type: .pubkey, ref: pubkey_ref)) 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: case NOSTR_BECH32_NPROFILE:
let nprofile = b.bech32.data.nprofile let nprofile = b.bech32.data.nprofile
let pubkey = hex_encode(Data(bytes: nprofile.pubkey, count: 32)) 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)) 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? { func find_tag_ref(type: String, id: String, tags: [[String]]) -> Int? {
var i: Int = 0 var i: Int = 0
for tag in tags { for tag in tags {
@ -483,47 +438,31 @@ func parse_mention_type(_ c: String) -> MentionType? {
} }
/// Convert /// 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 new_tags = tags
var blocks: [Block] = []
for post_block in post_blocks { for post_block in post_blocks {
switch post_block { switch post_block {
case .ref(let ref): case .mention(let mention):
guard let mention_type = parse_mention_type(ref.key) else { let mention_type = mention.type
continue
}
if silent_mentions || mention_type == .event { 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 continue
} }
if find_tag_ref(type: ref.key, id: ref.ref_id, tags: tags) != nil { new_tags.append(refid_to_tag(mention.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)
} 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)
}
case .hashtag(let hashtag): case .hashtag(let hashtag):
new_tags.append(["t", hashtag.lowercased()]) new_tags.append(["t", hashtag.lowercased()])
blocks.append(.hashtag(hashtag)) case .text: break
case .text(let txt): case .invoice: break
blocks.append(Block.text(txt)) 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 { func post_to_event(post: NostrPost, privkey: String, pubkey: String) -> NostrEvent {

View File

@ -109,32 +109,7 @@ func parse_post_bech32_mention(_ p: Parser) -> ReferencedId? {
} }
/// Return a list of tags /// Return a list of tags
func parse_post_blocks(content: String) -> [PostBlock] { func parse_post_blocks(content: String) -> [Block] {
let p = Parser(pos: 0, str: content) return parse_mentions(content: content, tags: []).blocks
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
} }

View File

@ -36,6 +36,10 @@ struct ReferencedId: Identifiable, Hashable, Equatable {
static func e(_ id: String, relay_id: String? = nil) -> ReferencedId { static func e(_ id: String, relay_id: String? = nil) -> ReferencedId {
return ReferencedId(ref_id: id, relay_id: relay_id, key: "e") 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 { class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Hashable, Comparable {

View File

@ -93,16 +93,18 @@ func generate_new_keypair() -> Keypair {
return Keypair(pubkey: pubkey, privkey: privkey) return Keypair(pubkey: pubkey, privkey: privkey)
} }
func privkey_to_pubkey(privkey: String) -> String? { func privkey_to_pubkey_raw(sec: [UInt8]) -> String? {
guard let sec = hex_decode(privkey) else {
return nil
}
guard let key = try? secp256k1.Signing.PrivateKey(rawRepresentation: sec) else { guard let key = try? secp256k1.Signing.PrivateKey(rawRepresentation: sec) else {
return nil return nil
} }
return hex_encode(Data(key.publicKey.xonly.bytes)) 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) { func save_pubkey(pubkey: String) {
UserDefaults.standard.set(pubkey, forKey: "pubkey") UserDefaults.standard.set(pubkey, forKey: "pubkey")
} }

View File

@ -39,15 +39,37 @@ final class HashtagTests: XCTestCase {
} }
func testHashtagWithEmoji() { 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) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
XCTAssertEqual(parsed[0].is_text, "some hashtag ") XCTAssertEqual(parsed[0].is_text, "some hashtag ")
XCTAssertEqual(parsed[1].is_hashtag, "bitcoin☕") XCTAssertEqual(parsed[1].is_hashtag, "bitcoin☕")
XCTAssertEqual(parsed[2].is_text, " cool") 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() { func testHashtagWithAccents() {
let parsed = parse_mentions(content: "hello from #türkiye", tags: []).blocks let parsed = parse_mentions(content: "hello from #türkiye", tags: []).blocks

View File

@ -52,7 +52,7 @@ class DamusParseContentTests: XCTestCase {
return return
} }
if currentBlock.is_mention { if currentBlock.is_mention != nil {
XCTAssert(isMentionBlockSet.contains(i)) XCTAssert(isMentionBlockSet.contains(i))
} else { } else {
XCTAssert(!isMentionBlockSet.contains(i)) XCTAssert(!isMentionBlockSet.contains(i))

View File

@ -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 content = "this is my link: https://jb55.com/index.html#buybitcoin this is not a hashtag!"
let blocks = parse_post_blocks(content: content) let blocks = parse_post_blocks(content: content)
XCTAssertEqual(blocks.count, 1) XCTAssertEqual(blocks.count, 3)
XCTAssertEqual(blocks[0].is_text != nil, true) 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() { func testLinkIsNotAHashtag() {
@ -49,8 +51,10 @@ class ReplyTests: XCTestCase {
let content = "my \(link) link" let content = "my \(link) link"
let blocks = parse_post_blocks(content: content) let blocks = parse_post_blocks(content: content)
XCTAssertEqual(blocks.count, 1) XCTAssertEqual(blocks.count, 3)
XCTAssertEqual(blocks[0].is_text, content) XCTAssertEqual(blocks[0].is_text, "my ")
XCTAssertEqual(blocks[1].is_url, URL(string: link)!)
XCTAssertEqual(blocks[2].is_text, " link")
} }
func testAtAtEnd() { func testAtAtEnd() {
@ -74,23 +78,17 @@ class ReplyTests: XCTestCase {
func testHashtagAtStartWorks() { func testHashtagAtStartWorks() {
let content = "#hashtag" let content = "#hashtag"
let blocks = parse_post_blocks(content: content) let blocks = parse_post_blocks(content: content)
XCTAssertEqual(blocks.count, 3) XCTAssertEqual(blocks.count, 1)
XCTAssertEqual(blocks[1].is_hashtag, "hashtag") XCTAssertEqual(blocks[0].is_hashtag, "hashtag")
} }
func testGroupOfHashtags() { func testGroupOfHashtags() {
let content = "#hashtag#what#nope" let content = "#hashtag#what#nope"
let blocks = parse_post_blocks(content: content) let blocks = parse_post_blocks(content: content)
XCTAssertEqual(blocks.count, 3) XCTAssertEqual(blocks.count, 3)
XCTAssertEqual(blocks[1].is_hashtag, "hashtag") XCTAssertEqual(blocks[0].is_hashtag, "hashtag")
XCTAssertEqual(blocks[2].is_text, "#what#nope") XCTAssertEqual(blocks[1].is_hashtag, "what")
XCTAssertEqual(blocks[2].is_hashtag, "nope")
switch blocks[1] {
case .hashtag(let htag):
XCTAssertEqual(htag, "hashtag")
default:
break
}
} }
func testRootReplyWithMention() throws { func testRootReplyWithMention() throws {
@ -124,32 +122,12 @@ class ReplyTests: XCTestCase {
XCTAssertEqual(post_tags.tags.count, 0) XCTAssertEqual(post_tags.tags.count, 0)
XCTAssertEqual(post_blocks.count, 1) 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 { func testManyMentions() throws {
let content = "#[10]" let content = "#[10]"
let tags: [[String]] = [[],[],[],[],[],[],[],[],[],[],["p", "3e999f94e2cb34ef44a64b351141ac4e51b5121b2d31aed4a6c84602a1144692"]] let tags: [[String]] = [[],[],[],[],[],[],[],[],[],[],["p", "3e999f94e2cb34ef44a64b351141ac4e51b5121b2d31aed4a6c84602a1144692"]]
let blocks = parse_mentions(content: content, tags: tags).blocks 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) XCTAssertEqual(mentions.count, 1)
} }
@ -213,11 +191,10 @@ class ReplyTests: XCTestCase {
let content = "@\(pk) hello there" let content = "@\(pk) hello there"
let blocks = parse_post_blocks(content: content) let blocks = parse_post_blocks(content: content)
XCTAssertEqual(blocks.count, 3) XCTAssertEqual(blocks.count, 2)
XCTAssertEqual(blocks[0].is_text, "") XCTAssertEqual(blocks[0].is_mention, .pubkey(hex_pk))
XCTAssertEqual(blocks[1].is_ref, ReferencedId(ref_id: hex_pk, relay_id: nil, key: "p")) XCTAssertEqual(blocks[1].is_text, " hello there")
XCTAssertEqual(blocks[2].is_text, " hello there")
} }
func testBech32MentionAtEnd() throws { func testBech32MentionAtEnd() throws {
@ -226,11 +203,9 @@ class ReplyTests: XCTestCase {
let content = "this is a @\(pk)" let content = "this is a @\(pk)"
let blocks = parse_post_blocks(content: content) let blocks = parse_post_blocks(content: content)
XCTAssertEqual(blocks.count, 3) XCTAssertEqual(blocks.count, 2)
XCTAssertEqual(blocks[1].is_ref, ReferencedId(ref_id: hex_pk, relay_id: nil, key: "p")) XCTAssertEqual(blocks[1].is_mention, .pubkey(hex_pk))
XCTAssertEqual(blocks[0].is_text, "this is a ") XCTAssertEqual(blocks[0].is_text, "this is a ")
XCTAssertEqual(blocks[2].is_text, "")
} }
func testNpubMention() throws { func testNpubMention() throws {
@ -245,27 +220,10 @@ class ReplyTests: XCTestCase {
XCTAssertEqual(ev.tags.count, 2) XCTAssertEqual(ev.tags.count, 2)
XCTAssertEqual(blocks.count, 3) 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") 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 { func testNsecMention() throws {
let evid = "0000000000000000000000000000000000000000000000000000000000000005" let evid = "0000000000000000000000000000000000000000000000000000000000000005"
let pk = "nsec1jmzdz7d0ldqctdxwm5fzue277ttng2pk28n2u8wntc2r4a0w96ssnyukg7" let pk = "nsec1jmzdz7d0ldqctdxwm5fzue277ttng2pk28n2u8wntc2r4a0w96ssnyukg7"
@ -278,49 +236,25 @@ class ReplyTests: XCTestCase {
XCTAssertEqual(ev.tags.count, 2) XCTAssertEqual(ev.tags.count, 2)
XCTAssertEqual(blocks.count, 3) 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") 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 { func testReplyMentions() throws {
let privkey = "0fc2092231f958f8d57d66f5e238bb45b6a2571f44c0ce024bbc6f3a9c8a15fe" let privkey = "0fc2092231f958f8d57d66f5e238bb45b6a2571f44c0ce024bbc6f3a9c8a15fe"
let pubkey = "30c6d1dc7f7c156794fa15055e651b758a61b99f50fcf759de59386050bf6ae2" let pubkey = "30c6d1dc7f7c156794fa15055e651b758a61b99f50fcf759de59386050bf6ae2"
let npub = "npub1xrrdrhrl0s2k0986z5z4uegmwk9xrwvl2r70wkw7tyuxq59ldt3qh09eay"
let refs = [ let refs = [
ReferencedId(ref_id: "thread_id", relay_id: nil, key: "e"), ReferencedId(ref_id: "thread_id", relay_id: nil, key: "e"),
ReferencedId(ref_id: "reply_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"), 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) 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) XCTAssertEqual(ev.tags[2][1], pubkey)
} }
@ -347,38 +281,6 @@ class ReplyTests: XCTestCase {
XCTAssertEqual(txt, content) 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 { func testInvalidUriReference() throws {
let id = "6fec2ee6cfff779fe8560976b3d9df782b74577f0caefa7a77c0ed4c3749b5de" let id = "6fec2ee6cfff779fe8560976b3d9df782b74577f0caefa7a77c0ed4c3749b5de"
let content = "this is a nostr:z:\(id) event mention" let content = "this is a nostr:z:\(id) event mention"
@ -403,17 +305,9 @@ class ReplyTests: XCTestCase {
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
XCTAssertEqual(parsed[0].is_text, "this is a ") 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") 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 { guard case .text(let t1) = parsed[0] else {
XCTAssertTrue(false) XCTAssertTrue(false)
return return
@ -435,17 +329,9 @@ class ReplyTests: XCTestCase {
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
XCTAssertEqual(parsed[0].is_text, "this is a ") 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") 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 { guard case .text(let t1) = parsed[0] else {
XCTAssertTrue(false) XCTAssertTrue(false)
return return
@ -459,68 +345,6 @@ class ReplyTests: XCTestCase {
XCTAssertEqual(t2, " event mention") 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
}
XCTAssertEqual(t1, "this is a ")
guard case .text(let t2) = parsed[2] else {
XCTAssertTrue(false)
return
}
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 { func testParseInvalidMention() throws {
let parsed = parse_mentions(content: "this is #[0] a mention", tags: []).blocks let parsed = parse_mentions(content: "this is #[0] a mention", tags: []).blocks

View File

@ -74,8 +74,10 @@ class damusTests: XCTestCase {
let parsed = parse_mentions(content: md, tags: []).blocks let parsed = parse_mentions(content: md, tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 1) XCTAssertEqual(parsed.count, 3)
XCTAssertNotNil(parsed[0].is_text) XCTAssertNotNil(parsed[0].is_text)
XCTAssertNotNil(parsed[1].is_url)
XCTAssertNotNil(parsed[2].is_text)
} }
func testParseUrlUpper() { func testParseUrlUpper() {