diff --git a/src/config.py b/src/config.py index 31efbcc..979d61a 100644 --- a/src/config.py +++ b/src/config.py @@ -55,11 +55,11 @@ class Settings(BaseSettings): # Media & Video Limits video_max_size_mb_cloud: int = 49 - video_max_size_mb_local: int = 500 - video_max_duration_sec: int = 600 + video_max_size_mb_local: int = 2000 + video_max_duration_sec: int = 7200 video_max_height: int = 720 - media_download_timeout_sec: int = 60 - yt_dlp_timeout_sec: int = 300 + media_download_timeout_sec: int = 120 + yt_dlp_timeout_sec: int = 600 # Text Styling & Decoration header_text: str = "" diff --git a/src/max_poster.py b/src/max_poster.py index 833c1c7..542e8e1 100644 --- a/src/max_poster.py +++ b/src/max_poster.py @@ -243,15 +243,8 @@ class MAXPoster: logger.warning("MAX bot token or chat ID is not set; skipping MAX post.") return [], None - link_only = [m for m in media_items if m.is_link_only] - link_text = "" - if link_only: - lines = [f"- {m.media_type}: {m.original_url}" for m in link_only] - link_text = "\n\nМедиа по ссылке:\n" + "\n".join(lines) - - full_raw = (raw_text + link_text).strip() formatted_text = format_post_text( - full_raw, + raw_text, parse_mode="html", bold_first_line=settings.format_first_line_bold, vk_url=vk_url, diff --git a/src/media_processor.py b/src/media_processor.py index d55acbf..af1661d 100644 --- a/src/media_processor.py +++ b/src/media_processor.py @@ -131,6 +131,7 @@ class MediaProcessor: f"best[height<={settings.video_max_height}][filesize<{max_size}]" f"/best[height<={settings.video_max_height}]" f"/bestvideo[height<={settings.video_max_height}][filesize<{max_size}]+bestaudio/best" + f"/bestvideo[height<={settings.video_max_height}]+bestaudio/best" f"/best[filesize<{max_size}]" f"/best" ), diff --git a/src/text_formatter.py b/src/text_formatter.py index a2e3543..08b5b3e 100644 --- a/src/text_formatter.py +++ b/src/text_formatter.py @@ -43,6 +43,35 @@ def strip_trailing_hashtags(text: str) -> str: return "\n".join(lines).rstrip() +def clean_vk_wiki_links(text: str, parse_mode: str = "html") -> str: + """ + Converts VK wiki links to clickable links: + - [club12345|Name] -> Name + - [id12345|Name] -> Name + - [public12345|Name] -> Name + - [event12345|Name] -> Name + - [https://example.com|Title] -> Title + """ + def _replace_vk(match: re.Match) -> str: + prefix = match.group(1) + obj_id = match.group(2) + title = match.group(3) + if parse_mode == "html": + return f'{html.escape(title)}' + return f"[{title}](https://vk.com/{prefix}{obj_id})" + + def _replace_url(match: re.Match) -> str: + url = match.group(1) + title = match.group(2) + if parse_mode == "html": + return f'{html.escape(title)}' + return f"[{title}]({url})" + + text = re.sub(r"\[(club|id|public|event)(\d+)\|([^\]]+)\]", _replace_vk, text) + text = re.sub(r"\[(https?://[^\s\|]+)\|([^\]]+)\]", _replace_url, text) + return text + + def format_post_text( raw_text: str, *, @@ -68,31 +97,30 @@ def format_post_text( formatted_lines: list[str] = [] for idx, line in enumerate(lines): - if idx == title_idx and bold_first_line and line.strip(): + cleaned_line = clean_vk_wiki_links(line, parse_mode=parse_mode) if parse_mode == "html" else line + if idx == title_idx and bold_first_line and cleaned_line.strip(): if parse_mode == "html": - escaped = html.escape(line.strip()) - formatted_lines.append(f"{escaped}") + # If clean_vk_wiki_links was run, keep existing tags safe + formatted_lines.append(f"{cleaned_line.strip()}") else: - formatted_lines.append(f"**{line.strip()}**") + formatted_lines.append(f"**{cleaned_line.strip()}**") # Blank line after header if next line is not empty if idx + 1 < len(lines) and lines[idx + 1].strip() != "": formatted_lines.append("") else: - if parse_mode == "html": - formatted_lines.append(html.escape(line)) - else: - formatted_lines.append(line) + formatted_lines.append(cleaned_line) - normalized_body = re.sub(r"\n{3,}", "\n\n", "\n".join(formatted_lines)).strip() + raw_body = "\n".join(formatted_lines) + normalized_body = re.sub(r"\n{3,}", "\n\n", raw_body).strip() parts: list[str] = [] if header: - parts.append(html.escape(header) if parse_mode == "html" else header) + parts.append(html.escape(header) if parse_mode == "html" and "<" not in header else header) if normalized_body: parts.append(normalized_body) if footer: - parts.append(html.escape(footer) if parse_mode == "html" else footer) + parts.append(html.escape(footer) if parse_mode == "html" and "<" not in footer else footer) if tags: - parts.append(html.escape(tags) if parse_mode == "html" else tags) + parts.append(html.escape(tags) if parse_mode == "html" and "<" not in tags else tags) return "\n\n".join(part for part in parts if part).strip() diff --git a/src/tg_poster.py b/src/tg_poster.py index f7e8807..5acbb16 100644 --- a/src/tg_poster.py +++ b/src/tg_poster.py @@ -371,16 +371,8 @@ class TelegramPoster: 3. Tries sendRichMessage first. 4. If unavailable, falls back to legacy media groups / single media / text. """ - # Append link-only media notice if any videos/photos couldn't be downloaded - link_only = [m for m in media_items if m.is_link_only] - link_text = "" - if link_only: - lines = [f"- {m.media_type}: ссылка" for m in link_only] - link_text = "\n\nМедиа по ссылке:\n" + "\n".join(lines) - - full_raw = (raw_text + link_text).strip() formatted_text = format_post_text( - full_raw, + raw_text, parse_mode="html", bold_first_line=settings.format_first_line_bold, vk_url=vk_url,