173 lines
5.0 KiB
Python
173 lines
5.0 KiB
Python
from typing import Any
|
|
|
|
from pyrogram import Client
|
|
from pyrogram.errors import (
|
|
FileIdInvalid,
|
|
FileReferenceExpired,
|
|
FileReferenceInvalid,
|
|
FloodPremiumWait,
|
|
FloodWait,
|
|
)
|
|
from pyrogram.types import Message
|
|
|
|
from userbot.modules.capture import repository
|
|
from userbot.modules.capture.context import CaptureContext
|
|
from userbot.modules.download import Downloadable, download_bytes
|
|
from utils.logging import logger
|
|
from utils.policy.models import CaptureToggles
|
|
|
|
STALE_FILE_ID = (FileIdInvalid, FileReferenceExpired, FileReferenceInvalid)
|
|
|
|
_MEDIA_ATTRS = (
|
|
"photo",
|
|
"video",
|
|
"voice",
|
|
"video_note",
|
|
"document",
|
|
"audio",
|
|
"animation",
|
|
"sticker",
|
|
)
|
|
|
|
|
|
_WEB_PAGE_ATTRS = ("photo", "video", "animation", "document", "audio")
|
|
|
|
|
|
def media_object(message: Message) -> tuple[str | None, Any]:
|
|
for attr in _MEDIA_ATTRS:
|
|
obj = getattr(message, attr, None)
|
|
if obj is not None:
|
|
return attr, obj
|
|
web_page = getattr(message, "web_page", None)
|
|
if web_page is not None:
|
|
for attr in _WEB_PAGE_ATTRS:
|
|
obj = getattr(web_page, attr, None)
|
|
if obj is not None:
|
|
return attr, obj
|
|
return None, None
|
|
|
|
|
|
def self_destruct_ttl(message: Message) -> int | None:
|
|
_, obj = media_object(message)
|
|
return getattr(obj, "ttl_seconds", None) if obj is not None else None
|
|
|
|
|
|
def media_unique_id(message: Message) -> str | None:
|
|
_, obj = media_object(message)
|
|
return getattr(obj, "file_unique_id", None) if obj is not None else None
|
|
|
|
|
|
def _download_target(message: Message) -> Downloadable | None:
|
|
kind, obj = media_object(message)
|
|
if obj is None:
|
|
return None
|
|
return message if getattr(message, kind or "", None) is obj else obj
|
|
|
|
|
|
async def _fresh_target(
|
|
client: Client, chat_id: int, message_id: int
|
|
) -> Downloadable | None:
|
|
message = await client.get_messages(chat_id, message_id)
|
|
if not isinstance(message, Message) or message.empty:
|
|
return None
|
|
return _download_target(message)
|
|
|
|
|
|
async def _download(
|
|
client: Client, target: Downloadable | None, chat_id: int, message_id: int
|
|
) -> bytes | None:
|
|
if target is None:
|
|
return None
|
|
try:
|
|
return await download_bytes(client, target)
|
|
except TimeoutError:
|
|
logger.warning(
|
|
f"[yellow]Media download timed out for {chat_id}/{message_id}.[/]"
|
|
)
|
|
return None
|
|
except STALE_FILE_ID:
|
|
fresh = await _fresh_target(client, chat_id, message_id)
|
|
if fresh is None:
|
|
return None
|
|
try:
|
|
return await download_bytes(client, fresh)
|
|
except (TimeoutError, *STALE_FILE_ID):
|
|
logger.warning(
|
|
f"[yellow]Media download failed after refetch "
|
|
f"for {chat_id}/{message_id}.[/]"
|
|
)
|
|
return None
|
|
|
|
|
|
async def capture_media( # noqa: PLR0913
|
|
client: Client,
|
|
message: Message,
|
|
ctx: CaptureContext,
|
|
chat_id: int,
|
|
message_id: int,
|
|
toggles: CaptureToggles,
|
|
*,
|
|
force: bool = False,
|
|
) -> bool:
|
|
kind, obj = media_object(message)
|
|
if obj is None:
|
|
return True
|
|
unique_id = getattr(obj, "file_unique_id", None)
|
|
ttl = getattr(obj, "ttl_seconds", None)
|
|
want = toggles.self_destruct_media if ttl else toggles.media
|
|
file_size = getattr(obj, "file_size", None)
|
|
mime = getattr(obj, "mime_type", None)
|
|
storage_key: str | None = None
|
|
downloaded = False
|
|
evicted = False
|
|
flood: FloodWait | FloodPremiumWait | None = None
|
|
if want:
|
|
existing = await repository.current_media(
|
|
ctx.pool, ctx.account_id, chat_id, message_id
|
|
)
|
|
if (
|
|
existing is not None
|
|
and existing["downloaded"]
|
|
and existing["unique_id"] == unique_id
|
|
and existing["storage_key"] is not None
|
|
):
|
|
storage_key = existing["storage_key"]
|
|
file_size = existing["file_size"]
|
|
downloaded = True
|
|
elif force or not await repository.is_evicted(ctx.pool, unique_id):
|
|
target = _download_target(message)
|
|
try:
|
|
data = await _download(client, target, chat_id, message_id)
|
|
except (FloodWait, FloodPremiumWait) as exc:
|
|
flood = exc
|
|
data = None
|
|
if data is not None:
|
|
storage_key = ctx.storage.put(data)
|
|
file_size = len(data)
|
|
downloaded = True
|
|
else:
|
|
evicted = True
|
|
await repository.insert_media(
|
|
ctx.pool,
|
|
ctx.account_id,
|
|
chat_id,
|
|
message_id,
|
|
kind or "unknown",
|
|
storage_key,
|
|
file_size,
|
|
mime,
|
|
ttl,
|
|
unique_id,
|
|
downloaded=downloaded,
|
|
evicted=evicted,
|
|
)
|
|
if (
|
|
force
|
|
and storage_key is not None
|
|
and await repository.unevict(ctx.pool, unique_id, storage_key, file_size)
|
|
):
|
|
ctx.storage.purge(storage_key)
|
|
if flood is not None:
|
|
raise flood
|
|
return downloaded or not want
|