"""A caption keeps the formatting it was sent with (bold, italic, links...): nobody has to type HTML.
The caption of the auto format is edited on that format's screen in /formats (the Caption button), so ``autofmt`` is where it is read back."""
from telethon.extensions import html as telegram_html
from bot.pipeline import make_caption
from .appkit import OWNER, autofmt, click, data_of, edit_auto, ensure_auto, say, say_formatted
from .test_formats_app import make_format
FORMATTED = "[b]Watch[/b] {title} [i]episode {episode}[/i] on [a=https://t.me/Anime_X]our channel[/a]"
STORED = 'Watch {title} episode {episode} on our channel'
async def set_caption(ctx, markup, *, formatted=True):
"""Tap Caption on the auto format's screen and send the caption (``markup`` as ``say_formatted`` takes it, or typed as it is
with ``formatted=False``).
Returns the message sent and the text the prompt's message turned into once the caption was taken (empty when it was refused)."""
auto = await ensure_auto(ctx.db)
edited = len(ctx.client.edited)
await click(ctx, OWNER, f"fm:in:{auto['id']}:caption", msg_id=500)
ev = await (say_formatted if formatted else say)(ctx, OWNER, markup)
return ev, (ctx.client.edited[-1][1] if len(ctx.client.edited) > edited else "")
# ---------------------------------------------------------------------------
# the Caption button of the auto format, then the caption
# ---------------------------------------------------------------------------
async def test_the_caption_prompt_keeps_the_formatting(ctx):
auto = await ensure_auto(ctx.db)
cb = await click(ctx, OWNER, f"fm:in:{auto['id']}:caption", msg_id=500)
assert "No HTML needed" in cb.edits[-1][0]
ev = await say_formatted(ctx, OWNER, FORMATTED, mid=60)
assert ev.deleted and OWNER not in ctx.app.pending
assert (await autofmt(ctx))["caption"] == STORED
(chat, text, kw), = ctx.client.edited
assert kw["message"] == 500 and "Caption template saved" in text and "Unknown" not in text
assert STORED in text and "<b>" not in text # the format's screen shows the caption formatted
assert f"fm:in:{auto['id']}:caption" in data_of(kw["buttons"])
async def test_the_format_screen_shows_the_caption_with_its_formatting(ctx):
auto = await edit_auto(ctx, caption=STORED)
text = (await click(ctx, OWNER, f"fm:v:{auto['id']}")).edits[-1][0]
assert STORED in text # shown as the person wrote it, not as tags to read
assert "<b>" not in text and "<a href" not in text
async def test_a_caption_may_have_several_lines(ctx):
await set_caption(ctx, "[b]{filename}[/b]\nsecond [i]line[/i] {size}")
assert (await autofmt(ctx))["caption"] == "{filename}\nsecond line {size}"
async def test_emoji_before_the_formatted_words_do_not_shift_it(ctx):
await set_caption(ctx, "🔥🔥 [b]{filename}[/b] 👨👩👧 [i]{size}[/i]")
assert (await autofmt(ctx))["caption"] == "🔥🔥 {filename} 👨👩👧 {size}"
async def test_html_typed_by_hand_still_works_when_nothing_is_formatted(ctx):
await set_caption(ctx, "{filename} · join", formatted=False)
assert (await autofmt(ctx))["caption"] == '{filename} · join'
async def test_what_telegram_finds_by_itself_is_not_formatting(ctx):
"""A #hashtag or an @mention is an entity too, but nothing was formatted: typed tags are still taken as typed."""
await set_caption(ctx, "{filename} [tag]#anime[/tag] [at]@channel[/at]")
assert (await autofmt(ctx))["caption"] == "{filename} #anime @channel"
async def test_once_words_are_formatted_the_message_is_taken_as_written(ctx):
await set_caption(ctx, "{filename} [i]x[/i]")
assert (await autofmt(ctx))["caption"] == "<b>{filename}</b> x" # the tags the person typed are just text
async def test_a_spoiler_is_not_kept_so_its_words_stay_plain(ctx):
await set_caption(ctx, "[spoiler]secret[/spoiler] {title}")
assert (await autofmt(ctx))["caption"] == "secret {title}"
async def test_reset_still_resets_when_it_was_made_bold(ctx):
await edit_auto(ctx, caption="custom {filename}")
_ev, text = await set_caption(ctx, "[b]reset[/b]")
assert (await autofmt(ctx))["caption"] == "{filename}" and "Caption template saved" in text
async def test_a_variable_cut_by_the_formatting_is_still_a_whole_variable(ctx):
"""Double-tapping {filename} selects the word without its braces: the bold covers the whole variable all the same."""
_ev, text = await set_caption(ctx, "{[b]filename[/b]} · {siz[i]e} left[/i]")
assert (await autofmt(ctx))["caption"] == "{filename} · {size} left"
assert "Unknown" not in text
async def test_unknown_variables_are_still_reported(ctx):
_ev, text = await set_caption(ctx, "[b]{filename}[/b] {nonsense}")
assert "Unknown variables" in text and "{nonsense}" in text
assert (await autofmt(ctx))["caption"] == "{filename} {nonsense}"
async def test_a_caption_that_is_too_long_with_its_link_addresses_is_refused(ctx):
ev, text = await set_caption(ctx, f"[a=https://example.com/{'a' * 800}]link[/a] {{filename}}")
assert "too long" in ev.texts[0] and "link addresses" in ev.texts[0] and text == ""
assert (await autofmt(ctx))["caption"] == "{filename}" # nothing was saved
async def test_the_caption_prompt_still_takes_typed_html_and_reset(ctx):
auto = await ensure_auto(ctx.db)
await click(ctx, OWNER, f"fm:in:{auto['id']}:caption")
await say(ctx, OWNER, "{filename} {size}")
assert (await autofmt(ctx))["caption"] == "{filename} {size}"
await click(ctx, OWNER, f"fm:in:{auto['id']}:caption")
await say_formatted(ctx, OWNER, "[b]reset[/b]")
assert (await autofmt(ctx))["caption"] == "{filename}"
async def test_formatting_sent_to_another_prompt_is_not_turned_into_html(ctx):
"""Only captions are rich text: a template or a title is plain text, whatever it was formatted with."""
auto = await ensure_auto(ctx.db)
await click(ctx, OWNER, f"fm:in:{auto['id']}:template")
await say_formatted(ctx, OWNER, "[b]{title}[/b] [i]{quality}[/i]")
assert (await autofmt(ctx))["template"] == "{title} {quality}"
async def test_a_prompt_that_is_refused_stays_open_for_another_try(ctx):
auto = await ensure_auto(ctx.db)
await click(ctx, OWNER, f"fm:in:{auto['id']}:caption")
ev = await say_formatted(ctx, OWNER, f"[a=https://example.com/{'a' * 800}]link[/a]")
assert "too long" in ev.texts[0] and "Try again" in ev.texts[0] and OWNER in ctx.app.pending
await say_formatted(ctx, OWNER, "[b]{filename}[/b] ok")
assert OWNER not in ctx.app.pending and (await autofmt(ctx))["caption"] == "{filename} ok"
# ---------------------------------------------------------------------------
# a saved format's own caption
# ---------------------------------------------------------------------------
async def test_a_formats_caption_prompt_keeps_the_formatting(ctx):
await edit_auto(ctx, caption="{filename}") # the auto format in use has a caption of its own
fmt = await make_format(ctx)
i = fmt["id"]
cb = await click(ctx, OWNER, f"fm:in:{i}:caption", msg_id=710)
assert "No HTML needed" in cb.edits[-1][0]
ev = await say_formatted(ctx, OWNER, FORMATTED)
assert ev.deleted and OWNER not in ctx.app.pending
assert (await ctx.db.get_format(OWNER, i))["caption"] == STORED
assert (await autofmt(ctx))["caption"] == "{filename}" # the auto format in use was never touched
chat, text, kw = ctx.client.edited[-1]
assert kw["message"] == 710 and "Caption template saved" in text
assert "Caption ▸ " + STORED in text and "· default" not in text # the format's screen shows it formatted
assert f"fm:cr:{i}" in data_of(kw["buttons"])
async def test_a_formats_caption_made_bold_exactly_like_the_default_counts_as_the_default(ctx):
fmt = await make_format(ctx, caption="{filename}")
await click(ctx, OWNER, f"fm:in:{fmt['id']}:caption")
await say_formatted(ctx, OWNER, "[b]{filename}[/b]")
chat, text, kw = ctx.client.edited[-1]
assert (await ctx.db.get_format(OWNER, fmt["id"]))["caption"] == "{filename}"
assert "· default" in text and f"fm:cr:{fmt['id']}" not in data_of(kw["buttons"])
async def test_a_format_caption_reset_works_in_bold_and_other_fields_stay_plain(ctx):
fmt = await make_format(ctx, caption="{filename}")
i = fmt["id"]
await click(ctx, OWNER, f"fm:in:{i}:caption")
await say_formatted(ctx, OWNER, "[b]reset[/b]")
assert (await ctx.db.get_format(OWNER, i))["caption"] == "{filename}"
await click(ctx, OWNER, f"fm:in:{i}:title")
await say_formatted(ctx, OWNER, "[b]One Piece[/b]")
assert (await ctx.db.get_format(OWNER, i))["meta"]["title"] == "One Piece" # metadata is not rich text
# ---------------------------------------------------------------------------
# a link in the caption must not put a link preview on the settings screens
# ---------------------------------------------------------------------------
async def test_a_link_in_a_caption_gets_no_link_preview_on_any_screen_that_shows_it(ctx):
auto = await edit_auto(ctx, caption=STORED)
cb = await click(ctx, OWNER, f"fm:v:{auto['id']}")
assert STORED in cb.edits[-1][0] and cb.edit_kwargs[-1]["link_preview"] is False
await click(ctx, OWNER, f"fm:in:{auto['id']}:caption", msg_id=500)
await say_formatted(ctx, OWNER, FORMATTED)
assert STORED in ctx.client.edited[-1][1] and ctx.client.edited[-1][2]["link_preview"] is False
fmt = await make_format(ctx, caption=STORED)
cb = await click(ctx, OWNER, f"fm:v:{fmt['id']}")
assert STORED in cb.edits[-1][0] and cb.edit_kwargs[-1]["link_preview"] is False
await click(ctx, OWNER, f"fm:in:{fmt['id']}:caption", msg_id=710)
await say_formatted(ctx, OWNER, FORMATTED)
assert STORED in ctx.client.edited[-1][1] and ctx.client.edited[-1][2]["link_preview"] is False
cb = await click(ctx, OWNER, f"fm:sh:{fmt['id']}")
assert cb.edit_kwargs and all(kw.get("link_preview") is False for kw in cb.edit_kwargs)
# ---------------------------------------------------------------------------
# what the renamed file gets
# ---------------------------------------------------------------------------
async def test_the_formatting_follows_the_values_that_replace_the_variables(ctx):
await set_caption(ctx, "[b]{filename}[/b] · [i]{size}[/i] · [a=https://t.me/x]{original}[/a]")
caption = make_caption((await autofmt(ctx))["caption"], new_name="One Piece - E5.mkv", orig_name="1147 - One Piece [Dual].mkv",
ext=".mkv", size=1536, duration=0)
text, entities = telegram_html.parse(caption) # what Telegram makes of it when it is sent with parse_mode="html"
assert text == "One Piece - E5 · 1.50 KB · 1147 - One Piece [Dual].mkv"
shown = {type(e).__name__: (text[e.offset:e.offset + e.length], getattr(e, "url", None)) for e in entities}
assert shown == {
"MessageEntityBold": ("One Piece - E5", None),
"MessageEntityItalic": ("1.50 KB", None),
"MessageEntityTextUrl": ("1147 - One Piece [Dual].mkv", "https://t.me/x"),
}
async def test_a_name_with_html_characters_cannot_break_the_formatting(ctx):
await set_caption(ctx, "[b]{filename}[/b] [i]{original}[/i]")
caption = make_caption((await autofmt(ctx))["caption"], new_name="Tom & .mkv", orig_name="a&c.mkv", ext=".mkv", size=1, duration=0)
text, entities = telegram_html.parse(caption)
assert text == "Tom & a&c.mkv"
assert [(type(e).__name__, e.offset, e.length) for e in entities] == [("MessageEntityBold", 0, 13), ("MessageEntityItalic", 14, 10)]