diff --git a/bot/handlers.py b/bot/handlers.py index 47012dc..52413e0 100644 --- a/bot/handlers.py +++ b/bot/handlers.py @@ -1,9 +1,8 @@ -from typing import Optional from pathlib import Path import httpx +from typing import Optional from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import ContextTypes - from .config import DOWNLOAD_DIR, COOKIES_FILE, BROWSER, CONNECT_TIMEOUT, READ_TIMEOUT from .utils import is_instagram_story_url, VIDEO_SUFFIXES, IMAGE_SUFFIXES, extract_instagram_username from .ytdlp_client import build_opts, run @@ -276,33 +275,33 @@ class Handlers: self.sending_active[chat_id] = False async def _download_file(self, url: str, into: Path) -> Path: - print(f"[DBG] _download_file: url={url}, into={into}") # DBG - into.parent.mkdir(parents=True, exist_ok=True) - timeout = httpx.Timeout(30.0) - async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client: - r = await client.get(url) - r.raise_for_status() - ctype = r.headers.get('content-type', '') - print(f"[DBG] _download_file: content-type={ctype}") # DBG + print(f"[DBG] _download_file: url={url}, into={into}") # DBG + into.parent.mkdir(parents=True, exist_ok=True) + timeout = httpx.Timeout(30.0) + async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client: + r = await client.get(url) + r.raise_for_status() + ctype = r.headers.get('content-type', '') + print(f"[DBG] _download_file: content-type={ctype}") # DBG - ext = '' - if 'image/jpeg' in ctype: - ext = '.jpg' - elif 'image/png' in ctype: - ext = '.png' - elif 'image/webp' in ctype: - ext = '.webp' - elif 'image/avif' in ctype: - ext = '.avif' - elif 'image/heic' in ctype or 'image/heif' in ctype: - ext = '.heic' + ext = '' + if 'image/jpeg' in ctype: + ext = '.jpg' + elif 'image/png' in ctype: + ext = '.png' + elif 'image/webp' in ctype: + ext = '.webp' + elif 'image/avif' in ctype: + ext = '.avif' + elif 'image/heic' in ctype or 'image/heif' in ctype: + ext = '.heic' - if not into.suffix: - into = into.with_suffix(ext or '.jpg') + if not into.suffix: + into = into.with_suffix(ext or '.jpg') - into.write_bytes(r.content) - print(f"[DBG] _download_file: saved {into}") # DBG - return into + into.write_bytes(r.content) + print(f"[DBG] _download_file: saved {into}") # DBG + return into def _pick_path(self, entry) -> Optional[Path]: if isinstance(entry, dict): @@ -313,6 +312,7 @@ class Handlers: p = Path(fp) if p.exists(): return p + fn = entry.get('_filename') if fn: p = Path(fn) @@ -320,14 +320,19 @@ class Handlers: return p return None - async def _send_images_from_playlist_with_fallback(self, info: dict | None, message, chat_id: int) -> int: + async def _send_images_from_playlist_with_fallback( + self, info: Optional[dict], message, chat_id: int + ) -> int: if not isinstance(info, dict): return 0 + entries = info.get('entries') or [] sent = 0 + for idx, entry in enumerate(entries): fp = self._pick_path(entry) temp_file: Optional[Path] = None + try: if fp and fp.exists(): suffix = (fp.suffix or '').lower() @@ -343,10 +348,12 @@ class Handlers: urls = [t.get('url') for t in thumbs if isinstance(t, dict) and t.get('url')] if urls: image_url = max(urls, key=len) + if not image_url and isinstance(entry, dict): cand = entry.get('url') or entry.get('thumbnail') if isinstance(cand, str) and cand.startswith(('http://', 'https://')): image_url = cand + if image_url: temp_file = await self._download_file(image_url, DOWNLOAD_DIR / f"img_{idx}") await self._send_photo_safe(message, temp_file, chat_id) @@ -356,72 +363,73 @@ class Handlers: temp_file.unlink(missing_ok=True) if fp: fp.unlink(missing_ok=True) + return sent - async def _send_playlist_mixed_with_images(self, info: dict | None, message, chat_id: int) -> int: - if not isinstance(info, dict): - print("[DBG] _send_playlist_mixed_with_images: info is not dict") # DBG - return 0 + async def _send_playlist_mixed_with_images( + self, info: Optional[dict], message, chat_id: int + ) -> int: + if not isinstance(info, dict): + print("[DBG] _send_playlist_mixed_with_images: info is not dict") + return 0 - entries = info.get('entries') or [] - print(f"[DBG] playlist entries={len(entries)}") # DBG + entries = info.get('entries') or [] + print(f"[DBG] playlist entries={len(entries)}") - sent = 0 - for idx, entry in enumerate(entries): - print(f"[DBG] entry[{idx}] keys={list(entry.keys()) if isinstance(entry, dict) else type(entry)}") # DBG - fp = self._pick_path(entry) - temp_file: Path | None = None + sent = 0 + for idx, entry in enumerate(entries): + print(f"[DBG] entry[{idx}] keys={list(entry.keys()) if isinstance(entry, dict) else type(entry)}") + fp = self._pick_path(entry) + temp_file: Optional[Path] = None - try: - if fp and fp.exists(): - suffix = (fp.suffix or '').lower() - print(f"[DBG] entry[{idx}] local file={fp}, suffix={suffix}") # DBG - - if suffix in IMAGE_SUFFIXES: - print(f"[DBG] entry[{idx}] send as photo") # DBG - await self._send_photo_safe(message, fp, chat_id) - sent += 1 - elif suffix in VIDEO_SUFFIXES: - print(f"[DBG] entry[{idx}] transcode/send video") # DBG - out_mp4 = await transcode_to_mobile_mp4(fp) - try: - await self._send_video_safe(message, out_mp4, chat_id) + try: + if fp and fp.exists(): + suffix = (fp.suffix or '').lower() + print(f"[DBG] entry[{idx}] local file={fp}, suffix={suffix}") + if suffix in IMAGE_SUFFIXES: + print(f"[DBG] entry[{idx}] send as photo") + await self._send_photo_safe(message, fp, chat_id) + sent += 1 + elif suffix in VIDEO_SUFFIXES: + print(f"[DBG] entry[{idx}] transcode/send video") + out_mp4 = await transcode_to_mobile_mp4(fp) + try: + await self._send_video_safe(message, out_mp4, chat_id) + sent += 1 + finally: + out_mp4.unlink(missing_ok=True) + else: + print(f"[DBG] entry[{idx}] send as document") + await self._send_document_safe(message, fp, chat_id) sent += 1 - finally: - out_mp4.unlink(missing_ok=True) else: - print(f"[DBG] entry[{idx}] send as document") # DBG - await self._send_document_safe(message, fp, chat_id) - sent += 1 - else: - print(f"[DBG] entry[{idx}] no local file, try thumbnails/url") # DBG - image_url = None - thumbs = entry.get('thumbnails') if isinstance(entry, dict) else None + print(f"[DBG] entry[{idx}] no local file, try thumbnails/url") + image_url = None + thumbs = entry.get('thumbnails') if isinstance(entry, dict) else None - if isinstance(thumbs, list) and thumbs: - urls = [t.get('url') for t in thumbs if isinstance(t, dict) and t.get('url')] - if urls: - image_url = max(urls, key=len) - print(f"[DBG] entry[{idx}] thumbnail url={image_url}") # DBG + if isinstance(thumbs, list) and thumbs: + urls = [t.get('url') for t in thumbs if isinstance(t, dict) and t.get('url')] + if urls: + image_url = max(urls, key=len) + print(f"[DBG] entry[{idx}] thumbnail url={image_url}") - if not image_url and isinstance(entry, dict): - cand = entry.get('url') or entry.get('thumbnail') - if isinstance(cand, str) and cand.startswith(('http://', 'https://')): - image_url = cand - print(f"[DBG] entry[{idx}] direct url={image_url}") # DBG + if not image_url and isinstance(entry, dict): + cand = entry.get('url') or entry.get('thumbnail') + if isinstance(cand, str) and cand.startswith(('http://', 'https://')): + image_url = cand + print(f"[DBG] entry[{idx}] direct url={image_url}") - if image_url: - temp_file = await self._download_file(image_url, DOWNLOAD_DIR / f"story_img_{idx}") - await self._send_photo_safe(message, temp_file, chat_id) - sent += 1 - else: - print(f"[DBG] entry[{idx}] no image url found") # DBG + if image_url: + temp_file = await self._download_file(image_url, DOWNLOAD_DIR / f"story_img_{idx}") + await self._send_photo_safe(message, temp_file, chat_id) + sent += 1 + else: + print(f"[DBG] entry[{idx}] no image url found") + finally: + if temp_file: + temp_file.unlink(missing_ok=True) + if fp: + fp.unlink(missing_ok=True) - finally: - if temp_file: - temp_file.unlink(missing_ok=True) - if fp: - fp.unlink(missing_ok=True) - - print(f"[DBG] _send_playlist_mixed_with_images: sent={sent}") # DBG - return sent + print(f"[DBG] _send_playlist_mixed_with_images: sent={sent}") + return sent