Spaces:
Running
Running
File size: 12,859 Bytes
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 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 | """/mediainfo in the chat: reply to a file, get a Telegraph link."""
import io
import shutil
from datetime import datetime, timezone
from pathlib import Path
import pytest
from bot import app as appmod
from bot.telegraph import Telegraph, TelegraphError
from .appkit import OWNER, STRANGER, say, send_doc
from .conftest import needs_ffmpeg
from .faketelegraph import serve_fake_telegraph
from .tgfakes import FakeMessage, document_message
URL = "https://telegra.ph/Show-05-10-21"
HINT = "Reply to a file or video with /mediainfo"
class StubTelegraph:
"""What the bot talks to instead of Telegraph: remembers the pages it was asked for."""
def __init__(self, url=URL, error=None):
self.url, self.error, self.calls = url, error, []
async def create_page(self, title, nodes):
self.calls.append((title, nodes))
if self.error is not None:
raise self.error
return self.url
def blocks(nodes):
"""{heading: text of its block} for a page's nodes."""
return {n["children"][0]: nodes[i + 1]["children"][0] for i, n in enumerate(nodes) if n["tag"] == "h4"}
def notes(nodes):
return [n["children"][0]["children"][0] for n in nodes if n["tag"] == "p"]
def serve(monkeypatch, media_dir, name="sample.mkv"):
"""Make transfer.download copy a real sample file; returns the list of calls."""
calls = []
async def fake_download(client, message, dest, **kw):
calls.append(kw)
shutil.copy(media_dir / name, dest)
monkeypatch.setattr(appmod.transfer, "download", fake_download)
return calls
def serve_junk(monkeypatch, content=b"\x00" * 4096):
calls = []
async def fake_download(client, message, dest, **kw):
calls.append(kw)
Path(dest).write_bytes(content)
monkeypatch.setattr(appmod.transfer, "download", fake_download)
return calls
def leftovers(ctx):
root = Path(ctx.cfg.temp_dir)
return list(root.glob("minfo*")) if root.exists() else []
def use_stub(ctx, **kw):
ctx.app.telegraph = StubTelegraph(**kw)
return ctx.app.telegraph
def real_size(media_dir, name="sample.mkv"):
return (media_dir / name).stat().st_size
# ---------------------------------------------------------------------------
# the flow
# ---------------------------------------------------------------------------
@needs_ffmpeg
async def test_reply_to_a_video_and_the_telegraph_link_comes_back(ctx, media_dir, monkeypatch):
calls = serve(monkeypatch, media_dir)
tg = use_stub(ctx)
video = document_message("Show - 05.mkv", real_size(media_dir), mid=70, caption="Dual Audio")
video.date = datetime(2026, 10, 19, 21, 30, 12, tzinfo=timezone.utc)
video.document.dc_id = 4
ev = await say(ctx, OWNER, "/mediainfo", reply=video)
assert ev.texts == ["🔎 Reading the file…"]
entity, text, kw = ctx.client.edited[-1]
assert entity.id == 1001 # the "Reading…" message became the answer
assert "🔎 <b>MediaInfo</b> · Show - 05.mkv" in text and URL in text and "Matroska" in text and "1 video · 3 audio · 2 subtitle" in text
assert kw["parse_mode"] == "html" and kw["link_preview"] is False
(button,), = kw["buttons"]
assert button.type.url == URL and button.text.startswith("📄")
(title, nodes), = tg.calls
assert title == "MediaInfo – Show - 05.mkv"
page = blocks(nodes)
assert list(page) == ["General", "Video", "Audio #1", "Audio #2", "Audio #3", "Text #1", "Text #2", "Menu", "Telegram"]
assert "Show - 05.mkv" in page["General"] and "probe.mkv" not in page["General"] # the file's own name, not the temp copy
telegram = page["Telegram"]
for line in ("File name : Show - 05.mkv", "MIME type : video/x-matroska", "Sent as : Video",
"Sent on : 2026-10-19 21:30:12 UTC", "Data center : 4", "Caption : Dual Audio"):
assert line in telegram, line
assert len(calls) == 1 and calls[0]["max_bytes"] == appmod.TRACKS_PROBE_BYTES # the start of the file was enough
assert leftovers(ctx) == []
async def test_without_a_file_there_is_a_hint_and_nothing_is_downloaded(ctx, monkeypatch):
async def no_download(*a, **kw):
raise AssertionError("there is nothing to read")
monkeypatch.setattr(appmod.transfer, "download", no_download)
tg = use_stub(ctx)
for reply in (None, FakeMessage("just some text")):
ev = await say(ctx, OWNER, "/mediainfo", reply=reply)
assert HINT in ev.texts[0] and "Telegraph" in ev.texts[0]
assert tg.calls == [] and ctx.client.edited == []
@needs_ffmpeg
async def test_a_file_sent_with_the_command_as_its_caption_is_read_not_renamed(ctx, media_dir, monkeypatch):
serve(monkeypatch, media_dir)
tg = use_stub(ctx)
for mid, caption in ((71, "/mediainfo"), (72, "/mediainfo@my_rename_bot")):
ev = await send_doc(ctx, OWNER, "Show.mkv", size=real_size(media_dir), caption=caption, mid=mid)
assert ev.texts == ["🔎 Reading the file…"] # no "Queued" message from the auto-renamer
assert ctx.app.queue.waiting == 0 and len(tg.calls) == 2
assert "Caption" not in blocks(tg.calls[0][1])["Telegram"] # the caption was only the command
async def test_other_captions_still_rename_the_file(ctx):
ev = await send_doc(ctx, OWNER, "Show.mkv", size=5 * 1024 * 1024, caption="/mediainfoX", mid=73)
assert ev.texts[0].startswith("📥 <b>Queued</b>") and ctx.app.queue.waiting == 1
@needs_ffmpeg
async def test_a_video_whose_index_is_at_the_end_is_read_from_the_whole_file(ctx, media_dir, monkeypatch):
calls = []
async def fake_download(client, message, dest, **kw):
calls.append(kw)
if "max_bytes" in kw:
Path(dest).write_bytes((media_dir / "sample.mp4").read_bytes()[:3000]) # an mp4 cut before its index
else:
shutil.copy(media_dir / "sample.mp4", dest)
monkeypatch.setattr(appmod.transfer, "download", fake_download)
tg = use_stub(ctx)
await say(ctx, OWNER, "/mediainfo", reply=document_message("Movie.mp4", 40 * 1024 * 1024, mid=74, mime="video/mp4"))
assert len(calls) == 2 and "max_bytes" in calls[0] and "max_bytes" not in calls[1]
assert any("needs the whole file" in text for _e, text, _k in ctx.client.edited)
(_title, nodes), = tg.calls
assert list(blocks(nodes))[:2] == ["General", "Video"] and "No tracks" not in " ".join(notes(nodes))
assert URL in ctx.client.edited[-1][1] and leftovers(ctx) == []
@pytest.mark.parametrize("size, why", [(2 * 1024 * 1024, "may be damaged"), (900 * 1024 * 1024, "too big for me to download completely")])
async def test_a_video_with_no_readable_tracks_still_gets_a_page_that_says_so(ctx, monkeypatch, size, why):
calls = serve_junk(monkeypatch)
tg = use_stub(ctx)
await say(ctx, OWNER, "/mediainfo", reply=document_message("Broken.mkv", size, mid=75))
assert len(calls) == 1 # too small to need more, or too big to fetch whole
(_title, nodes), = tg.calls
assert any(why in note for note in notes(nodes))
assert list(blocks(nodes)) == ["General", "Telegram"] and URL in ctx.client.edited[-1][1]
assert leftovers(ctx) == []
async def test_a_file_that_is_not_media_is_read_from_its_start_only(ctx, monkeypatch):
calls = serve_junk(monkeypatch, b"PK\x03\x04" + b"\x00" * 4000)
tg = use_stub(ctx)
await say(ctx, OWNER, "/mediainfo", reply=document_message("archive.zip", 50 * 1024 * 1024, mid=76, video=False, mime="application/zip"))
assert len(calls) == 1 and "max_bytes" in calls[0]
(_title, nodes), = tg.calls
page = blocks(nodes)
assert list(page) == ["General", "Telegram"] and "archive.zip" in page["General"] and "50.0 MiB" in page["General"]
assert "Sent as : Document" in page["Telegram"] and "No tracks" not in " ".join(notes(nodes))
assert any("Only the start of the file was read" in note for note in notes(nodes))
async def test_a_download_that_fails_is_reported_and_cleaned_up(ctx, monkeypatch):
async def fail(client, message, dest, **kw):
Path(dest).write_bytes(b"partial")
raise ConnectionError("connection reset")
monkeypatch.setattr(appmod.transfer, "download", fail)
tg = use_stub(ctx)
await say(ctx, OWNER, "/mediainfo", reply=document_message("Show.mkv", 5 * 1024 * 1024, mid=77))
assert ctx.client.edited[-1][1] == "❌ I couldn't read that file." and tg.calls == [] and leftovers(ctx) == []
# ---------------------------------------------------------------------------
# when Telegraph does not work
# ---------------------------------------------------------------------------
@needs_ffmpeg
async def test_when_telegraph_fails_the_report_comes_as_a_text_file(ctx, media_dir, monkeypatch):
serve(monkeypatch, media_dir)
use_stub(ctx, error=TelegraphError("Telegraph asks to wait 5 seconds", "FLOOD_WAIT_5"))
await say(ctx, OWNER, "/mediainfo", reply=document_message("Show: 05?.mkv", real_size(media_dir), mid=78))
(entity, file, kw), = [f for f in ctx.client.files if isinstance(f[1], io.BytesIO)]
assert entity == OWNER and file.name == "Show_ 05_ - MediaInfo.txt" and kw["force_document"] is True
assert "Telegraph asks to wait 5 seconds, so here it is as a text file." in kw["caption"] and kw["parse_mode"] == "html"
text = file.getvalue().decode()
assert text.startswith("General\n") and "\nVideo\n" in text and "\nTelegram\nFile name" in text and "Show: 05?.mkv" in text
assert (OWNER, [1001]) in ctx.client.deleted # the "Reading…" message goes
assert leftovers(ctx) == []
async def test_another_telegraph_failure_does_not_show_its_details(ctx, monkeypatch):
serve_junk(monkeypatch)
use_stub(ctx, error=RuntimeError("secret token abc123 rejected"))
await say(ctx, OWNER, "/mediainfo", reply=document_message("Show.mkv", 2 * 1024 * 1024, mid=79))
(_e, _file, kw), = [f for f in ctx.client.files if isinstance(f[1], io.BytesIO)]
assert "Telegraph is not available, so here it is as a text file." in kw["caption"] and "abc123" not in kw["caption"]
async def test_an_unexpected_error_does_not_leave_the_reading_message_hanging(ctx, monkeypatch):
def broken(message, caption=True):
raise RuntimeError("boom")
monkeypatch.setattr(appmod, "telegram_facts", broken)
ev = await say(ctx, OWNER, "/mediainfo", reply=document_message("Show.mkv", 2 * 1024 * 1024, mid=80))
assert ev.texts == ["🔎 Reading the file…"]
assert ctx.client.edited[-1][1] == "⚠️ Something went wrong while reading that file."
# ---------------------------------------------------------------------------
# access and plumbing
# ---------------------------------------------------------------------------
async def test_only_allowed_users_can_use_it(ctx, monkeypatch):
tg = use_stub(ctx)
ev = await say(ctx, STRANGER, "/mediainfo", reply=document_message("Show.mkv", 1024, mid=81))
assert ev.out == [] and tg.calls == []
async def test_the_command_is_in_the_menu():
commands = dict(appmod.COMMANDS)
assert "mediainfo" in commands and 3 <= len(commands["mediainfo"]) <= 256
async def test_the_telegraph_token_lives_in_the_database(ctx):
assert await ctx.app._load_telegraph_token() is None
await ctx.app._save_telegraph_token("abc")
assert await ctx.app._load_telegraph_token() == "abc" and await ctx.db.kv_get("telegraph_token") == "abc"
await ctx.app._save_telegraph_token("") # what the client does when Telegraph rejects the token
assert await ctx.db.kv_get("telegraph_token") is None
@needs_ffmpeg
async def test_the_whole_chain_with_the_real_client_and_a_fake_telegraph(ctx, media_dir, monkeypatch):
serve(monkeypatch, media_dir, "sample.mp4")
fake = await serve_fake_telegraph()
try:
ctx.app.telegraph = Telegraph(ctx.app._load_telegraph_token, ctx.app._save_telegraph_token, api=fake.url)
ctx.app.telegraph.set_author("Rename Bot", "https://t.me/rename_bot")
await say(ctx, OWNER, "/mediainfo", reply=document_message("Movie.mp4", real_size(media_dir, "sample.mp4"), mid=82, mime="video/mp4"))
await say(ctx, OWNER, "/mediainfo", reply=document_message("Movie 2.mp4", real_size(media_dir, "sample.mp4"), mid=83, mime="video/mp4"))
assert fake.accounts == 1 and len(fake.pages) == 2 # one account, kept for the second page
assert await ctx.db.kv_get("telegraph_token") == "token1"
page = fake.pages[0]
assert page["title"] == "MediaInfo – Movie.mp4" and page["author_name"] == "Rename Bot" and page["author_url"] == "https://t.me/rename_bot"
assert "Audio #2" in page["content"] and "Movie.mp4" in page["content"]
assert "https://telegra.ph/Page-1" in ctx.client.edited[-2][1] and "https://telegra.ph/Page-2" in ctx.client.edited[-1][1]
finally:
await fake.server.close()
|