Support full 2GB/2h video downloads via Local Bot API, clean VK wiki links and remove fallback links
This commit is contained in:
+4
-4
@@ -55,11 +55,11 @@ class Settings(BaseSettings):
|
|||||||
|
|
||||||
# Media & Video Limits
|
# Media & Video Limits
|
||||||
video_max_size_mb_cloud: int = 49
|
video_max_size_mb_cloud: int = 49
|
||||||
video_max_size_mb_local: int = 500
|
video_max_size_mb_local: int = 2000
|
||||||
video_max_duration_sec: int = 600
|
video_max_duration_sec: int = 7200
|
||||||
video_max_height: int = 720
|
video_max_height: int = 720
|
||||||
media_download_timeout_sec: int = 60
|
media_download_timeout_sec: int = 120
|
||||||
yt_dlp_timeout_sec: int = 300
|
yt_dlp_timeout_sec: int = 600
|
||||||
|
|
||||||
# Text Styling & Decoration
|
# Text Styling & Decoration
|
||||||
header_text: str = ""
|
header_text: str = ""
|
||||||
|
|||||||
+1
-8
@@ -243,15 +243,8 @@ class MAXPoster:
|
|||||||
logger.warning("MAX bot token or chat ID is not set; skipping MAX post.")
|
logger.warning("MAX bot token or chat ID is not set; skipping MAX post.")
|
||||||
return [], None
|
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(
|
formatted_text = format_post_text(
|
||||||
full_raw,
|
raw_text,
|
||||||
parse_mode="html",
|
parse_mode="html",
|
||||||
bold_first_line=settings.format_first_line_bold,
|
bold_first_line=settings.format_first_line_bold,
|
||||||
vk_url=vk_url,
|
vk_url=vk_url,
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ class MediaProcessor:
|
|||||||
f"best[height<={settings.video_max_height}][filesize<{max_size}]"
|
f"best[height<={settings.video_max_height}][filesize<{max_size}]"
|
||||||
f"/best[height<={settings.video_max_height}]"
|
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}][filesize<{max_size}]+bestaudio/best"
|
||||||
|
f"/bestvideo[height<={settings.video_max_height}]+bestaudio/best"
|
||||||
f"/best[filesize<{max_size}]"
|
f"/best[filesize<{max_size}]"
|
||||||
f"/best"
|
f"/best"
|
||||||
),
|
),
|
||||||
|
|||||||
+40
-12
@@ -43,6 +43,35 @@ def strip_trailing_hashtags(text: str) -> str:
|
|||||||
return "\n".join(lines).rstrip()
|
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] -> <a href="https://vk.com/club12345">Name</a>
|
||||||
|
- [id12345|Name] -> <a href="https://vk.com/id12345">Name</a>
|
||||||
|
- [public12345|Name] -> <a href="https://vk.com/public12345">Name</a>
|
||||||
|
- [event12345|Name] -> <a href="https://vk.com/event12345">Name</a>
|
||||||
|
- [https://example.com|Title] -> <a href="https://example.com">Title</a>
|
||||||
|
"""
|
||||||
|
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'<a href="https://vk.com/{prefix}{obj_id}">{html.escape(title)}</a>'
|
||||||
|
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'<a href="{html.escape(url, quote=True)}">{html.escape(title)}</a>'
|
||||||
|
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(
|
def format_post_text(
|
||||||
raw_text: str,
|
raw_text: str,
|
||||||
*,
|
*,
|
||||||
@@ -68,31 +97,30 @@ def format_post_text(
|
|||||||
|
|
||||||
formatted_lines: list[str] = []
|
formatted_lines: list[str] = []
|
||||||
for idx, line in enumerate(lines):
|
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":
|
if parse_mode == "html":
|
||||||
escaped = html.escape(line.strip())
|
# If clean_vk_wiki_links was run, keep existing <a> tags safe
|
||||||
formatted_lines.append(f"<b>{escaped}</b>")
|
formatted_lines.append(f"<b>{cleaned_line.strip()}</b>")
|
||||||
else:
|
else:
|
||||||
formatted_lines.append(f"**{line.strip()}**")
|
formatted_lines.append(f"**{cleaned_line.strip()}**")
|
||||||
# Blank line after header if next line is not empty
|
# Blank line after header if next line is not empty
|
||||||
if idx + 1 < len(lines) and lines[idx + 1].strip() != "":
|
if idx + 1 < len(lines) and lines[idx + 1].strip() != "":
|
||||||
formatted_lines.append("")
|
formatted_lines.append("")
|
||||||
else:
|
else:
|
||||||
if parse_mode == "html":
|
formatted_lines.append(cleaned_line)
|
||||||
formatted_lines.append(html.escape(line))
|
|
||||||
else:
|
|
||||||
formatted_lines.append(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] = []
|
parts: list[str] = []
|
||||||
if header:
|
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:
|
if normalized_body:
|
||||||
parts.append(normalized_body)
|
parts.append(normalized_body)
|
||||||
if footer:
|
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:
|
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()
|
return "\n\n".join(part for part in parts if part).strip()
|
||||||
|
|||||||
+1
-9
@@ -371,16 +371,8 @@ class TelegramPoster:
|
|||||||
3. Tries sendRichMessage first.
|
3. Tries sendRichMessage first.
|
||||||
4. If unavailable, falls back to legacy media groups / single media / text.
|
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}: <a href=\"{m.original_url}\">ссылка</a>" for m in link_only]
|
|
||||||
link_text = "\n\n<i>Медиа по ссылке:</i>\n" + "\n".join(lines)
|
|
||||||
|
|
||||||
full_raw = (raw_text + link_text).strip()
|
|
||||||
formatted_text = format_post_text(
|
formatted_text = format_post_text(
|
||||||
full_raw,
|
raw_text,
|
||||||
parse_mode="html",
|
parse_mode="html",
|
||||||
bold_first_line=settings.format_first_line_bold,
|
bold_first_line=settings.format_first_line_bold,
|
||||||
vk_url=vk_url,
|
vk_url=vk_url,
|
||||||
|
|||||||
Reference in New Issue
Block a user