You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
104 lines
3.6 KiB
104 lines
3.6 KiB
"""NIP-30 custom emoji: ``emoji`` tags + ``:shortcode:`` in note text.""" |
|
|
|
import tempfile |
|
from pathlib import Path |
|
|
|
from imwald.core.database import Database |
|
from imwald.core.md_render import markdown_html_fragment, markdown_plain_summary |
|
from imwald.core.nip30_emoji import ( |
|
nip30_emoji_urls_from_tags, |
|
parse_kind30030_a_coordinate, |
|
preprocess_nip30_emoji_markdown, |
|
) |
|
from imwald.core.nostr_crypto import build_signed_event, pubkey_hex_from_secret |
|
|
|
|
|
def test_urls_from_tags() -> None: |
|
tags = [ |
|
["emoji", "100percent", "https://example.com/a.png"], |
|
["emoji", "bad", "ftp://x/y.png"], |
|
["emoji", "", "https://example.com/z.png"], |
|
["t", "nostr"], |
|
] |
|
m = nip30_emoji_urls_from_tags(tags) |
|
assert m == {"100percent": "https://example.com/a.png"} |
|
|
|
|
|
def test_preprocess_skips_fenced_blocks() -> None: |
|
urls = {"x": "https://example.com/x.png"} |
|
md = "```\n:x:\n```\nline :x: end" |
|
out = preprocess_nip30_emoji_markdown(md, urls) |
|
assert ":x:" in out.split("```")[1] |
|
assert '<img src="https://example.com/x.png"' in out |
|
|
|
|
|
def test_markdown_html_fragment_nip30() -> None: |
|
tags = [["emoji", "100percent", "https://example.com/e.png"]] |
|
html = markdown_html_fragment("Hello :100percent: world", nip30_tags=tags) |
|
assert 'class="nip30-emoji"' in html |
|
assert 'src="https://example.com/e.png"' in html |
|
assert "Hello" in html and "world" in html |
|
|
|
|
|
def test_parse_kind30030_a_coordinate() -> None: |
|
pk = "a" * 64 |
|
assert parse_kind30030_a_coordinate(f"30030:{pk}:my-pack") == (pk, "my-pack") |
|
assert parse_kind30030_a_coordinate("1:xx:yy") is None |
|
assert parse_kind30030_a_coordinate("nope") is None |
|
|
|
|
|
def test_author_kind0_inventory_resolves_shortcode_in_markdown() -> None: |
|
"""Jumble-style: shortcodes in the note resolve from the author's kind 0 ``emoji`` tags.""" |
|
sk = bytes.fromhex("3501454135014541350145413501453fefb02227e449e57cf4d3a3ce05378683") |
|
pk = pubkey_hex_from_secret(sk) |
|
k0 = build_signed_event( |
|
sk, |
|
created_at=1, |
|
kind=0, |
|
tags=[["emoji", "chadyes_sm", "https://example.com/chad.png"]], |
|
content="{}", |
|
) |
|
with tempfile.TemporaryDirectory() as td: |
|
db = Database(Path(td) / "nip30.sqlite") |
|
db.connect() |
|
db.upsert_event(k0) |
|
html = markdown_html_fragment( |
|
"Hi :chadyes_sm: bye", |
|
db=db, |
|
nip30_tags=None, |
|
nip30_author_pubkey=pk, |
|
) |
|
assert "nip30-emoji" in html |
|
assert "example.com/chad.png" in html |
|
|
|
|
|
def test_event_emoji_tags_override_author_inventory() -> None: |
|
sk = bytes.fromhex("3501454135014541350145413501453fefb02227e449e57cf4d3a3ce05378683") |
|
pk = pubkey_hex_from_secret(sk) |
|
k0 = build_signed_event( |
|
sk, |
|
created_at=1, |
|
kind=0, |
|
tags=[["emoji", "x", "https://example.com/from0.png"]], |
|
content="{}", |
|
) |
|
with tempfile.TemporaryDirectory() as td: |
|
db = Database(Path(td) / "nip30b.sqlite") |
|
db.connect() |
|
db.upsert_event(k0) |
|
html = markdown_html_fragment( |
|
":x:", |
|
db=db, |
|
nip30_tags=[["emoji", "x", "https://example.com/from_note.png"]], |
|
nip30_author_pubkey=pk, |
|
) |
|
assert "from_note.png" in html |
|
assert "from0.png" not in html |
|
|
|
|
|
def test_plain_summary_strips_inline_emoji_img() -> None: |
|
"""List previews strip HTML; NIP-30 ``<img>`` does not leave the shortcode in plain text.""" |
|
tags = [["emoji", "x", "https://example.com/x.png"]] |
|
s = markdown_plain_summary("Hi :x: bye", max_len=200, nip30_tags=tags) |
|
assert "Hi" in s and "bye" in s |
|
assert ":x:" not in s
|
|
|