File size: 9,765 Bytes
5f400f8
 
 
 
 
 
 
 
 
6c3a31d
5f400f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6c3a31d
5f400f8
 
 
 
 
6c3a31d
5f400f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6c3a31d
5f400f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
226
"""/resize: pick a size, send an image, get it back resized."""
import io
import time

from PIL import Image

from bot import app as appmod
from bot import panels

from .appkit import OWNER, STRANGER, auto_thumb, autofmt, click, data_of, jpeg, say, send_doc
from .tgfakes import FakeEvent, FakeMessage, dispatch

FULL_HD = (1920, 1080)


def results(ctx):
    return [(entity, file, kw) for entity, file, kw in ctx.client.files if isinstance(file, io.BytesIO)]


def size_of(file):
    return Image.open(io.BytesIO(file.getvalue())).size


async def send_photo(ctx, mid=50, data=None, sender=OWNER):
    ctx.client.media[mid] = data if data is not None else jpeg(FULL_HD)
    ev = FakeEvent(sender, message=FakeMessage("", photo=True, mid=mid), photo_bytes=jpeg())
    await dispatch(ctx.client, ev)
    return ev


async def send_image_file(ctx, name="poster.png", mid=51, data=None, size=200 * 1024):
    ctx.client.media[mid] = data if data is not None else jpeg(FULL_HD)
    return await send_doc(ctx, OWNER, name, size=size, video=False, mime="image/png", mid=mid)


# ---------------------------------------------------------------------------
# choosing the size
# ---------------------------------------------------------------------------


async def test_resize_offers_the_sizes(ctx):
    ev = await say(ctx, OWNER, "/resize")
    text, buttons = ev.texts[0], data_of(ev.out[0][1])
    assert "RESIZE IMAGE" in text and "200 KB" in text and "/resize 320x180" in text
    assert buttons == ["rz:s:320", "rz:s:640", "rz:s:1280", "rz:custom", "x:close"]
    assert OWNER not in ctx.app.modes  # nothing is waiting for an image until a size is chosen


async def test_a_preset_button_makes_the_bot_wait_for_the_image(ctx):
    cb = await click(ctx, OWNER, "rz:s:320")
    assert "Size: <b>320 px</b>" in cb.edits[-1][0] and "one image at a time" in cb.edits[-1][0]
    assert data_of(cb.edits[-1][1]) == ["rz:menu", "rz:stop"]
    mode = ctx.app.modes[OWNER]
    assert mode.kind == "resize" and mode.data == {"box": (320, 320)} and mode.expires > time.time() + 500
    await click(ctx, OWNER, "rz:s:1280")
    assert ctx.app.modes[OWNER].data["box"] == (1280, 1280)


async def test_unknown_presets_are_ignored(ctx):
    cb = await click(ctx, OWNER, "rz:s:999")
    assert cb.edits == [] and OWNER not in ctx.app.modes


async def test_the_size_can_be_given_with_the_command(ctx):
    ev = await say(ctx, OWNER, "/resize 320x180")
    assert "<b>320×180</b>" in ev.texts[0] and ctx.app.modes[OWNER].data["box"] == (320, 180)
    ev = await say(ctx, OWNER, "/resize 640")
    assert "<b>640 px</b>" in ev.texts[0] and ctx.app.modes[OWNER].data["box"] == (640, 640)
    for bad in ("tiny", "10x10", "99999", "320 px"):
        ev = await say(ctx, OWNER, f"/resize {bad}")
        assert "❌" in ev.texts[0] and "/resize 320x180" in ev.texts[0], bad
    assert ctx.app.modes[OWNER].data["box"] == (640, 640)  # a bad size does not disturb the one that was set


async def test_a_custom_size_is_typed_in(ctx):
    cb = await click(ctx, OWNER, "rz:custom", msg_id=500)
    p = ctx.app.pending[OWNER]
    assert p.kind == "resize:size" and p.msg_id == 500 and cb.edits[-1][0] == panels.RESIZE_CUSTOM_PROMPT
    assert data_of(cb.edits[-1][1]) == ["rz:menu"]

    ev = await say(ctx, OWNER, "nonsense", mid=60)
    assert "couldn't read that size" in ev.texts[0] and "Try again" in ev.texts[0] and OWNER in ctx.app.pending

    ev = await say(ctx, OWNER, "300 x 200", mid=61)
    assert ev.deleted and OWNER not in ctx.app.pending
    assert ctx.app.modes[OWNER].data["box"] == (300, 200)
    (chat, text, kw), = ctx.client.edited
    assert kw["message"] == 500 and "<b>300×200</b>" in text and data_of(kw["buttons"]) == ["rz:menu", "rz:stop"]


async def test_cancelling_the_custom_prompt_goes_back_to_the_sizes(ctx):
    await click(ctx, OWNER, "rz:custom")
    cb = await click(ctx, OWNER, "rz:menu")
    assert OWNER not in ctx.app.pending and "RESIZE IMAGE" in cb.edits[-1][0]


async def test_change_size_and_stop_buttons(ctx):
    await click(ctx, OWNER, "rz:s:320")
    cb = await click(ctx, OWNER, "rz:menu")
    assert "RESIZE IMAGE" in cb.edits[-1][0] and OWNER not in ctx.app.modes
    await click(ctx, OWNER, "rz:s:320")
    cb = await click(ctx, OWNER, "rz:stop")
    assert "Stopped" in cb.edits[-1][0] and OWNER not in ctx.app.modes


# ---------------------------------------------------------------------------
# resizing
# ---------------------------------------------------------------------------


async def test_a_photo_comes_back_resized(ctx):
    await say(ctx, OWNER, "/resize 320")
    await send_photo(ctx)
    (entity, file, kw), = results(ctx)
    assert entity == OWNER and file.name == "resized.jpg" and size_of(file) == (320, 180)
    assert len(file.getvalue()) <= 200 * 1024
    assert "<b>320×180</b>" in kw["caption"] and "was 1920×1080" in kw["caption"] and kw["parse_mode"] == "html"
    assert kw["force_document"] is False
    assert OWNER not in ctx.app.modes
    assert ctx.client.downloads == [(50, bytes, None)]


async def test_an_image_sent_as_a_file_works_too(ctx):
    await say(ctx, OWNER, "/resize 640x360")
    ev = await send_image_file(ctx)
    assert ev.out == [] and ctx.app.queue.waiting == 0  # not renamed
    (_e, file, _kw), = results(ctx)
    assert size_of(file) == (640, 360)


async def test_what_is_not_an_image_does_not_use_up_the_request(ctx):
    await say(ctx, OWNER, "/resize 320")
    ev = await send_doc(ctx, OWNER, "Episode.mkv", mid=52)
    assert "isn't an image" in ev.texts[0] and ctx.app.queue.waiting == 0 and results(ctx) == []
    assert ctx.app.modes[OWNER].kind == "resize"
    await send_photo(ctx)
    assert len(results(ctx)) == 1


async def test_it_handles_one_image_per_command_and_extra_photos_do_not_replace_the_thumbnail(ctx):
    await say(ctx, OWNER, "/resize 320")
    await send_photo(ctx, mid=50)
    extra = await send_photo(ctx, mid=51)
    assert "/resize handles one file at a time" in extra.texts[0]
    assert (await send_photo(ctx, mid=52)).out == []
    assert len(results(ctx)) == 1
    assert await autofmt(ctx) is None  # none of the extra photos became a thumbnail (that would have made the auto format)
    doc = await send_image_file(ctx, mid=53)
    assert doc.out == [] and ctx.app.queue.waiting == 0  # an image file right after is skipped as well, not auto-renamed

    ctx.app._recent[OWNER][1] -= appmod.ONE_SHOT_GRACE + 1
    await send_photo(ctx, mid=54)
    assert await auto_thumb(ctx)  # after the grace a photo is the thumbnail of the auto format again


async def test_large_results_are_sent_as_files_so_telegram_does_not_shrink_them(ctx):
    await say(ctx, OWNER, "/resize 2000")
    await send_photo(ctx, data=jpeg((3000, 2000)))
    (_e, file, kw), = results(ctx)
    assert size_of(file) == (2000, 1333) and kw["force_document"] is True


async def test_resize_as_a_reply_works_at_once(ctx):
    ctx.client.media[40] = jpeg(FULL_HD)
    reply = FakeMessage("", photo=True, mid=40)
    await say(ctx, OWNER, "/resize", reply=reply, mid=41)
    (_e, file, _kw), = results(ctx)
    assert size_of(file) == (320, 180) and OWNER not in ctx.app.modes  # thumbnail size unless told otherwise
    await say(ctx, OWNER, "/resize 640x640", reply=reply, mid=42)
    assert size_of(results(ctx)[-1][1]) == (640, 360)


async def test_a_picture_that_cannot_be_read_gets_an_honest_answer(ctx):
    await say(ctx, OWNER, "/resize 320")
    ev = await send_photo(ctx, data=b"not a picture at all")
    assert "couldn't read that image" in ev.texts[0] and results(ctx) == []
    ctx.client.media[50] = None
    await say(ctx, OWNER, "/resize 320")
    ev = await send_photo(ctx, data=b"")
    assert "couldn't download" in ev.texts[0]


async def test_very_large_files_are_refused_before_downloading(ctx):
    await say(ctx, OWNER, "/resize 320")
    ev = await send_image_file(ctx, size=appmod.RESIZE_MAX_BYTES + 1)
    assert "I can resize images up to 20.00 MB" in ev.texts[0] and ctx.client.downloads == []


async def test_it_expires_and_cancel_ends_it(ctx):
    await say(ctx, OWNER, "/resize 320")
    ctx.app.modes[OWNER].expires = time.time() - 1
    ev = await send_doc(ctx, OWNER, "A.mkv", mid=60)
    assert "Queued" in ev.texts[0]

    await say(ctx, OWNER, "/resize 320")
    ev = await say(ctx, OWNER, "/cancel")
    assert "Cancelled" in ev.texts[0] and OWNER not in ctx.app.modes
    await send_photo(ctx)
    assert results(ctx) == [] and await auto_thumb(ctx)


async def test_a_format_thumbnail_prompt_takes_priority_over_a_waiting_resize(ctx):
    fmt = await ctx.db.create_format(OWNER, "One Piece")
    await say(ctx, OWNER, "/resize 320")
    await click(ctx, OWNER, f"fm:th:{fmt['id']}")  # opened later: the user is setting the format's thumbnail now
    await dispatch(ctx.client, FakeEvent(OWNER, message=FakeMessage("", photo=True, mid=70), photo_bytes=jpeg()))
    assert (await ctx.db.get_format(OWNER, fmt["id"]))["has_thumb"] and results(ctx) == []


async def test_strangers_cannot_use_it(ctx):
    ev = await say(ctx, STRANGER, "/resize 320")
    assert ev.out == [] and STRANGER not in ctx.app.modes


async def test_starting_it_ends_a_sequence_and_says_so(ctx):
    await say(ctx, OWNER, "/sequence")
    await send_doc(ctx, OWNER, "Show - 01.mkv", mid=92)
    ev = await say(ctx, OWNER, "/resize 320")
    assert "1 file(s) was discarded" in ev.texts[0] and ctx.app.modes[OWNER].kind == "resize"
    cb = await click(ctx, OWNER, "rz:s:640")  # via the menu too
    assert "discarded" not in cb.edits[-1][0]  # the sequence was already dropped by the first command


async def test_both_commands_are_in_the_command_menu(ctx):
    assert "resize" in dict(appmod.COMMANDS) and "ethumb" in dict(appmod.COMMANDS)