File size: 11,984 Bytes
6c3a31d
 
b737d7a
21cde0c
 
 
 
b737d7a
21cde0c
 
 
 
 
 
d55a0af
b737d7a
 
d55a0af
 
b737d7a
d55a0af
b737d7a
d55a0af
 
 
 
21cde0c
b737d7a
21cde0c
 
 
b737d7a
 
 
d55a0af
 
 
6c3a31d
d55a0af
b737d7a
 
 
21cde0c
 
b737d7a
 
 
 
21cde0c
 
 
d55a0af
 
6c3a31d
21cde0c
 
 
d55a0af
6c3a31d
21cde0c
 
 
d55a0af
6c3a31d
21cde0c
 
 
 
d55a0af
6c3a31d
21cde0c
 
 
d55a0af
6c3a31d
21cde0c
 
 
d55a0af
6c3a31d
21cde0c
 
 
d55a0af
 
 
21cde0c
 
 
 
d55a0af
6c3a31d
d55a0af
21cde0c
 
 
d55a0af
 
6c3a31d
21cde0c
 
 
d55a0af
 
b737d7a
21cde0c
 
b737d7a
 
 
21cde0c
6c3a31d
b737d7a
21cde0c
6c3a31d
21cde0c
 
 
 
b737d7a
 
21cde0c
6c3a31d
21cde0c
 
 
b737d7a
 
21cde0c
 
 
6c3a31d
21cde0c
 
 
 
 
 
 
 
d55a0af
21cde0c
 
 
 
 
 
 
6c3a31d
21cde0c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b737d7a
 
21cde0c
 
b737d7a
21cde0c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d55a0af
6c3a31d
21cde0c
 
 
 
 
 
 
 
 
 
 
 
d55a0af
6c3a31d
21cde0c
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
"""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 = '<b>Watch</b> {title} <i>episode {episode}</i> on <a href="https://t.me/Anime_X">our channel</a>'


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 "&lt;b&gt;" 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 "&lt;b&gt;" not in text and "&lt;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"] == "<b>{filename}</b>\nsecond <i>line</i> {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"] == "🔥🔥 <b>{filename}</b> 👨‍👩‍👧 <i>{size}</i>"


async def test_html_typed_by_hand_still_works_when_nothing_is_formatted(ctx):
    await set_caption(ctx, "<i>{filename}</i> · <a href=\"https://t.me/x\">join</a>", formatted=False)
    assert (await autofmt(ctx))["caption"] == '<i>{filename}</i> · <a href="https://t.me/x">join</a>'


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, "<i>{filename}</i> [tag]#anime[/tag] [at]@channel[/at]")
    assert (await autofmt(ctx))["caption"] == "<i>{filename}</i> #anime @channel"


async def test_once_words_are_formatted_the_message_is_taken_as_written(ctx):
    await set_caption(ctx, "<b>{filename}</b> [i]x[/i]")
    assert (await autofmt(ctx))["caption"] == "&lt;b&gt;{filename}&lt;/b&gt; <i>x</i>"  # 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"] == "<b>{filename}</b>" 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"] == "<b>{filename}</b> · <i>{size} left</i>"
    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"] == "<b>{filename}</b> {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"] == "<b>{filename}</b>"  # 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, "<u>{filename}</u> {size}")
    assert (await autofmt(ctx))["caption"] == "<u>{filename}</u> {size}"
    await click(ctx, OWNER, f"fm:in:{auto['id']}:caption")
    await say_formatted(ctx, OWNER, "[b]reset[/b]")
    assert (await autofmt(ctx))["caption"] == "<b>{filename}</b>"


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"] == "<b>{filename}</b> ok"


# ---------------------------------------------------------------------------
# a saved format's own caption
# ---------------------------------------------------------------------------


async def test_a_formats_caption_prompt_keeps_the_formatting(ctx):
    await edit_auto(ctx, caption="<u>{filename}</u>")  # 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"] == "<u>{filename}</u>"  # 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 "<b>Caption</b> ▸ " + 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="<u>{filename}</u>")
    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"] == "<b>{filename}</b>"
    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="<u>{filename}</u>")
    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"] == "<b>{filename}</b>"
    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 & <Jerry>.mkv", orig_name="a<b>&c.mkv", ext=".mkv", size=1, duration=0)
    text, entities = telegram_html.parse(caption)
    assert text == "Tom & <Jerry> a<b>&c.mkv"
    assert [(type(e).__name__, e.offset, e.length) for e in entities] == [("MessageEntityBold", 0, 13), ("MessageEntityItalic", 14, 10)]