1
0
mirror of git://jb55.com/damus synced 2024-09-16 02:03:45 +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) {
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;

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) {
*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;

View File

@ -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;

View File

@ -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 {

View File

@ -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
}

View File

@ -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 {

View File

@ -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")
}

View File

@ -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

View File

@ -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))

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 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,17 +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
@ -459,68 +345,6 @@ class ReplyTests: XCTestCase {
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 {
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
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() {