Files
redairsoft_vk_gt_max_sender/src/admin_notifier.py
T
exostring 20135263c4 Add multi-route donor->recipient support, fix VK hashtag truncation
Posts can now be sourced from multiple VK groups, each routed to its own
Telegram/MAX destination(s) with independent on/off switches, configured
via data/routes.json (supports // line comments). Falls back to a single
route auto-generated from the legacy VK_SOURCE/TG_CHAT_ID/MAX_CHAT_ID env
vars if routes.json doesn't exist yet, so existing deployments keep working.

Routes are processed strictly sequentially within a cycle (no concurrency)
to keep flood control on VK/TG/MAX correct, since bot tokens are shared
across routes. DB schema gains route_id in the posts uniqueness key so the
same VK donor can safely feed multiple routes without status collisions.

Also removes the trailing-hashtag-stripping logic in text_formatter, which
was silently deleting VK posts' own hashtags whenever COMMON_TAGS wasn't
configured (it always wasn't) - posts are now forwarded unchanged.
2026-08-18 14:48:22 +05:00

103 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from loguru import logger
from datetime import datetime
from typing import Any, Optional
from aiogram import Bot
try:
from .config import settings
from .text_formatter import split_message_chunks
except (ImportError, ValueError):
from config import settings
from text_formatter import split_message_chunks
class AdminNotifier:
def __init__(self, bot: Bot) -> None:
self.bot = bot
self.admin_ids = settings.admin_id_list
async def send_to_all(self, text: str) -> None:
if not self.admin_ids:
return
for chunk in split_message_chunks(text, 4000):
for admin_id in self.admin_ids:
try:
await self.bot.send_message(
chat_id=admin_id,
text=chunk,
parse_mode="HTML",
disable_web_page_preview=True,
)
except Exception as exc:
logger.warning("Failed to send report to admin {}: {}", admin_id, exc)
def _route_section(self, route_name: str, vk_url: str, found_posts: list[dict[str, Any]], error: Optional[str]) -> Optional[str]:
if error:
return (
f"⚠️ <b>{route_name}</b> (<a href=\"{vk_url}\">VK</a>) — ошибка проверки:\n"
f"<code>{error}</code>"
)
if not found_posts:
if not settings.report_empty_runs:
return None
return f"ℹ️ <b>{route_name}</b> (<a href=\"{vk_url}\">VK</a>) — новых постов не обнаружено."
lines = [f"🚀 <b>{route_name}</b> (<a href=\"{vk_url}\">VK</a>) — опубликовано: {len(found_posts)}"]
for idx, p in enumerate(found_posts, 1):
post_id = p.get("vk_post_id")
vk_post_url = p.get("vk_post_url")
tg_url = p.get("tg_url")
max_url = p.get("max_url")
tg_status = p.get("tg_status")
max_status = p.get("max_status")
tg_err = p.get("tg_error")
max_err = p.get("max_error")
lines.append(f"\n<b>{idx}. Пост #{post_id}</b>")
lines.append(f"• <a href=\"{vk_post_url}\">Оригинал в VK</a>")
if tg_status == "published":
tg_link = f"<a href=\"{tg_url}\">Ссылка</a>" if tg_url else "Опубликовано"
lines.append(f"• Telegram: ✅ {tg_link}")
elif tg_err:
lines.append(f"• Telegram: ❌ Ошибка: <code>{tg_err[:100]}</code>")
if max_status == "published":
max_link = f"<a href=\"{max_url}\">Ссылка</a>" if max_url else "Опубликовано"
lines.append(f"• MAX: ✅ {max_link}")
elif max_err:
lines.append(f"• MAX: ❌ Ошибка: <code>{max_err[:100]}</code>")
return "\n".join(lines)
async def notify_cycle_result(self, route_results: list[dict[str, Any]]) -> None:
"""route_results: one entry per route this cycle -
{"route_name": str, "vk_url": str, "reports": list[dict], "error": Optional[str]}
Sent as a single aggregated report per cycle (chunked if too long) rather
than one message per route, to avoid flooding admins when there are several
donor->recipient routes.
"""
if not self.admin_ids:
return
sections: list[str] = []
for rr in route_results:
section = self._route_section(
rr.get("route_name", rr.get("route_id", "?")),
rr.get("vk_url", ""),
rr.get("reports", []),
rr.get("error"),
)
if section:
sections.append(section)
if not sections:
return
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
text = f"<b>[VK Poster] Итоги проверки</b> — <code>{now_str}</code>\n\n" + "\n\n".join(sections)
await self.send_to_all(text)