feat(bot): add admin notification via Telegram bot

This commit is contained in:
2026-07-29 20:52:01 +05:00
parent 0047f30629
commit 299fb63936
3 changed files with 174 additions and 9 deletions
+116
View File
@@ -0,0 +1,116 @@
import logging
from aiogram import Bot
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.client.default import DefaultBotProperties
from config import Config
from typing import Dict, Any, List
logger = logging.getLogger(__name__)
class NotificationBot:
def __init__(self, config: Config):
self.config = config
try:
self.bot: Bot = Bot(
token=config.bot_token,
default=DefaultBotProperties(
parse_mode="HTML",
disable_notification=True,
link_preview_is_disabled=True,
),
)
except:
raise
async def _notify(self, message: str, reply_to: int, link: str = None):
try:
keyboard = None
if link:
keyboard = InlineKeyboardMarkup(
inline_keyboard=[[InlineKeyboardButton(text=f"Перейти")]]
)
if not reply_to:
sent_message = await self.bot.send_message(
chat_id=self.config.admin_id, text=message, reply_markup=keyboard
)
else:
sent_message = await self.bot.send_message(
chat_id=self.config.admin_id,
text=message,
reply_to_message_id=reply_to,
reply_markup=keyboard,
)
return sent_message.message_id
except Exception as e:
logger.error(f"Ошибка отправки уведомления: {e}")
async def new_post(self, post: Dict[str, Any], links: List[str]):
try:
thread = post.get("thread")
thread_title = thread.get("thread_title")
permalink = post.get("links").get("permalink")
thread_link = f'<a href="{permalink}">{thread_title}</a>'
poster_username = post.get("poster_username")
poster_user_id = post.get("poster_user_id")
poster_link = f"https://lolz.team/members/{poster_user_id}"
user_link = f'<a href="{poster_link}">{poster_username}</a>'
links_msg = f"<blockquote>\n"
for link in links:
links_msg += f"{link}\n"
links_msg += "</blockquote>"
message = (
f"🚩 Новый ответ в теме {thread_link}:\n"
f"▪️ Пользователь: {user_link}\n"
f"▪️ Ссылки: {links_msg}"
)
post_message_id = await self._notify(message=message, link=permalink)
return post_message_id
except:
return None
async def success(self, link: str, post_message_id: int):
try:
message = (
f"🌟 Звезда отправлена:\n"
f"▪️ Количество: {self.config.stars_count} ⭐️\n"
f"▪️ Ссылка на сообщение: {link}"
)
success_message_id = await self._notify(
message=message, reply_to=post_message_id, link=link
)
return success_message_id
except:
return None
async def failure(self, post: Dict[str, Any], reason: str, post_message_id: int):
try:
permalink = post.get("links").get("permalink")
message = (
f"❌ Не удалось отправить звезду:\n"
f"▪️ Причина: <code>{reason}</code>"
)
success_message_id = await self._notify(
message=message, reply_to=post_message_id, link=permalink
)
return success_message_id
except:
return None
+58 -9
View File
@@ -11,6 +11,7 @@ from typing import List, Optional, Dict, Any
from config import Config from config import Config
from modules.lolz import Lolz from modules.lolz import Lolz
from modules.bot import NotificationBot
from modules.misc import ProcessedPostsManager from modules.misc import ProcessedPostsManager
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -57,6 +58,10 @@ class TelegramStarsBot:
self.client: Optional[Client] = None self.client: Optional[Client] = None
self.notification_bot = None
if config.bot_token and config.admin_id and config.notify_admin:
self.notification_bot = NotificationBot(config)
async def parse_existing_posts(self): async def parse_existing_posts(self):
logger.info("Начинаю парсинг всех существующих постов в теме...") logger.info("Начинаю парсинг всех существующих постов в теме...")
all_posts = await self.lolz_api.get_all_thread_posts( all_posts = await self.lolz_api.get_all_thread_posts(
@@ -137,8 +142,11 @@ class TelegramStarsBot:
return False return False
async def _process_single_post(self, post: Dict[str, Any]): async def _process_single_post(self, post: Dict[str, Any]):
post_message_id = None
post_id = post.get("post_id") post_id = post.get("post_id")
poster_user_id = post.get("poster_user_id") poster_user_id = post.get("poster_user_id")
poster_username = post.get("poster_username", "Неизвестный пользователь")
if not post_id or self.processed_manager.is_processed(post_id): if not post_id or self.processed_manager.is_processed(post_id):
if post_id: if post_id:
@@ -147,7 +155,9 @@ class TelegramStarsBot:
logger.warning("Пост без post_id, пропускаю.") logger.warning("Пост без post_id, пропускаю.")
return return
logger.info(f"Найден новый пост для обработки: ID {post_id}") logger.info(
f"Найден новый пост для обработки: ID {post_id} (от {poster_username})"
)
if self.config.skip_commented: if self.config.skip_commented:
if await self.lolz_api.has_comments(post=post): if await self.lolz_api.has_comments(post=post):
@@ -155,21 +165,47 @@ class TelegramStarsBot:
f"Пост {post_id} уже имеет комментарии. Пропускаю обработку." f"Пост {post_id} уже имеет комментарии. Пропускаю обработку."
) )
self.processed_manager.mark_processed(post_id) self.processed_manager.mark_processed(post_id)
if self.notification_bot:
self.notification_bot.failure(
post=post,
reason=f"пост уже имеет комментарии",
post_message_id=post_message_id,
)
return return
post_content = post.get("post_body_html") or post.get("post_body") post_content = post.get("post_body_html") or post.get("post_body")
if not post_content: if not post_content:
logger.warning(f"У поста {post_id} отсутствует содержимое. Пропускаем.") logger.warning(f"У поста {post_id} отсутствует содержимое. Пропускаем.")
self.processed_manager.mark_processed(post_id) self.processed_manager.mark_processed(post_id)
if self.notification_bot:
self.notification_bot.failure(
post=post,
reason=f"у поста отсутствует содержимое",
post_message_id=post_message_id,
)
return return
links = TelegramLinkExtractor.extract(post_content) links = TelegramLinkExtractor.extract(post_content)
if not links: if not links:
logger.info(f"В посте {post_id} не найдено ссылок Telegram.") logger.info(f"В посте {post_id} не найдено ссылок Telegram.")
self.processed_manager.mark_processed(post_id) self.processed_manager.mark_processed(post_id)
if self.notification_bot:
self.notification_bot.failure(
post=post,
reason=f"в посте не найдено ссылок Telegram",
post_message_id=post_message_id,
)
return return
logger.info(f"В посте {post_id} найдено ссылок: {len(links)}.") logger.info(f"В посте {post_id} найдено ссылок: {len(links)}.")
if self.notification_bot:
post_message_id = self.notification_bot.new_post(post=post, links=links)
successful_reactions = 0 successful_reactions = 0
for link in links: for link in links:
@@ -194,15 +230,36 @@ class TelegramStarsBot:
reply_message = f"[userids={poster_user_id};align=left]{random.choice(reply_options)}[/userids]" reply_message = f"[userids={poster_user_id};align=left]{random.choice(reply_options)}[/userids]"
await self.lolz_api.create_comment(post_id, reply_message) await self.lolz_api.create_comment(post_id, reply_message)
if self.notification_bot:
self.notification_bot.success(
link=parsed_link, post_message_id=post_message_id
)
else: else:
logger.warning( logger.warning(
f"Пост {post_id}: poster_user_id отсутствует, комментарий не оставлен." f"Пост {post_id}: poster_user_id отсутствует, комментарий не оставлен."
) )
if self.notification_bot:
self.notification_bot.failure(
post=post,
reason=f"poster_user_id отсутствует",
post_message_id=post_message_id,
)
elif successful_reactions == 0: elif successful_reactions == 0:
logger.warning( logger.warning(
f"Пост {post_id}: ни одна звезда не была отправлена успешно ({len(links)} ссылок)." f"Пост {post_id}: ни одна звезда не была отправлена успешно ({len(links)} ссылок)."
) )
if self.notification_bot:
self.notification_bot.failure(
post=post,
reason=f"ни одна звезда не была отправлена успешно",
post_message_id=post_message_id,
)
logger.info(f"Пост {post_id} полностью обработан.") logger.info(f"Пост {post_id} полностью обработан.")
self.processed_manager.mark_processed(post_id) self.processed_manager.mark_processed(post_id)
@@ -277,16 +334,8 @@ class TelegramStarsBot:
logger.info("Пожалуйста, перезапустите скрипт для начала работы.") logger.info("Пожалуйста, перезапустите скрипт для начала работы.")
return return
logger.info("=" * 40)
logger.info(
f"Комментарии на форуме: {'ВКЛЮЧЕНЫ' if self.config.enable_reply else 'ВЫКЛЮЧЕНЫ'}"
)
logger.info(
f"Пропуск постов с комментариями: {'ВКЛЮЧЕН' if self.config.skip_commented else 'ВЫКЛЮЧЕН'}"
)
logger.info(f"Проверка начинается со страницы: {self.start_page}") logger.info(f"Проверка начинается со страницы: {self.start_page}")
logger.info("Бот в работе. Для остановки нажмите Ctrl+C.") logger.info("Бот в работе. Для остановки нажмите Ctrl+C.")
logger.info("=" * 40)
await self._main_loop() await self._main_loop()
finally: finally:
BIN
View File
Binary file not shown.