""" Менеджер задач по стримерам. Простой список с возможностью изменять параметры на лету. """ import asyncio from dataclasses import dataclass, field from typing import Optional, List, Dict from datetime import datetime import random import logging logger = logging.getLogger(__name__) @dataclass class TaskParams: """Динамические параметры задачи.""" url: str = "" task_type: str = "visit" # Задержки min_delay: int = 10 max_delay: int = 30 min_reading: int = 5 max_reading: int = 15 # Лимиты max_visits: Optional[int] = None current_visit: int = 0 # Twitch параметры channel: str = "" target_username: str = "" visits_per_link: int = 5 monitor_minutes: int = 10 allowed_domains: Optional[List[str]] = None # Состояние paused: bool = False skip_next: bool = False force_delay: Optional[int] = None force_reading: Optional[int] = None # Статистика started_at: Optional[datetime] = None total_visits: int = 0 successful_visits: int = 0 links_found: int = 0 chat_id: Optional[int] = None user_id: Optional[int] = None # Telegram user_id, если задача создана пользователем completed: bool = False # Задача завершена stopped: bool = False # Задача остановлена пользователем @property def monitor_duration(self) -> int: return self.monitor_minutes * 60 def update(self, **kwargs): for key, value in kwargs.items(): if hasattr(self, key): setattr(self, key, value) def get_delay(self) -> int: if self.skip_next: self.skip_next = False return 0 if self.force_delay: delay = self.force_delay self.force_delay = None return delay return random.randint(self.min_delay, self.max_delay) def get_reading_time(self) -> int: if self.force_reading: reading = self.force_reading self.force_reading = None return reading return random.randint(self.min_reading, self.max_reading) def get_status_emoji(self) -> str: if self.stopped: return "🛑" if self.completed: return "✅" if self.paused: return "⏸️" return "🔄" def get_status_text(self) -> str: if self.stopped: return "🛑 Остановлена" if self.completed: return "✅ Завершена" if self.paused: return "⏸️ Пауза" return "🔄 Активна" def get_stats_text(self) -> str: """Текст статистики.""" if self.task_type == "twitch_irc": status = self.get_status_text() duration = "" if self.started_at and (self.completed or self.stopped): delta = datetime.now() - self.started_at duration = f"├ ⏱️ Длилась: {delta.seconds // 60} мин\n" # Домены domains_text = "" if self.allowed_domains: domains_text = f"├ 🌐 Домены: {', '.join(self.allowed_domains)}\n" else: domains_text = "├ 🌐 Домены: все\n" return ( f"📺 **{self.channel}**\n" f"├ Статус: {status}\n" f"├ 🔗 Ссылок найдено: {self.links_found}\n" f"├ 📊 Всего визитов: {self.total_visits}\n" f"├ ✅ Успешно: {self.successful_visits}\n" f"├ ⏱️ Задержка: {self.min_delay}-{self.max_delay}с\n" f"├ 📖 Чтение: {self.min_reading}-{self.max_reading}с\n" f"├ 🔗 На ссылку: {self.visits_per_link} визитов\n" f"├ ⏰ Мониторинг: {self.monitor_minutes} мин\n" f"{domains_text}" f"{duration}" ) else: return ( f"🌐 `{self.url[:50]}`\n" f"├ Статус: {self.get_status_text()}\n" f"├ 📊 Визитов: {self.total_visits}\n" f"├ ✅ Успешно: {self.successful_visits}\n" ) class TaskManager: """ Менеджер задач. Хранит список стримеров и позволяет управлять каждым. """ def __init__(self): self._tasks: Dict[str, TaskParams] = {} # task_id -> params self._lock = asyncio.Lock() async def add_task(self, task_id: str, params: TaskParams) -> None: """Добавляет задачу.""" async with self._lock: self._tasks[task_id] = params logger.info(f"➕ Задача: {task_id[:12]} ({params.channel or params.url})") async def remove_task(self, task_id: str) -> bool: """Удаляет задачу.""" async with self._lock: if task_id in self._tasks: del self._tasks[task_id] logger.info(f"➖ Задача удалена: {task_id[:12]}") return True return False async def get_task(self, task_id: str) -> Optional[TaskParams]: """Получает задачу.""" async with self._lock: return self._tasks.get(task_id) async def update_task(self, task_id: str, **kwargs) -> bool: """Обновляет параметры задачи.""" params = await self.get_task(task_id) if params: params.update(**kwargs) return True return False async def complete_task(self, task_id: str) -> bool: """Пометить задачу как завершенную.""" return await self.update_task(task_id, completed=True, paused=True) async def stop_task(self, task_id: str) -> bool: """Пометить задачу как остановленную.""" return await self.update_task(task_id, stopped=True, paused=True) async def get_active_tasks(self) -> Dict[str, TaskParams]: """Только активные задачи (не завершенные и не остановленные).""" return { tid: p for tid, p in self._tasks.items() if not p.completed and not p.stopped } async def get_completed_tasks(self) -> Dict[str, TaskParams]: """Завершенные задачи (для просмотра статистики).""" return { tid: p for tid, p in self._tasks.items() if p.completed or p.stopped } async def pause_task(self, task_id: str) -> bool: """Пауза задачи.""" return await self.update_task(task_id, paused=True) async def resume_task(self, task_id: str) -> bool: """Снять с паузы.""" return await self.update_task(task_id, paused=False) async def skip_next_task(self, task_id: str) -> bool: """Пропустить следующее действие.""" return await self.update_task(task_id, skip_next=True) async def restart_task(self, task_id: str) -> bool: """Сбросить счетчики для перезапуска.""" return await self.update_task( task_id, current_visit=0, total_visits=0, successful_visits=0, links_found=0 ) async def get_all_tasks(self) -> Dict[str, TaskParams]: """Все задачи.""" async with self._lock: return dict(self._tasks) async def get_tasks_by_type(self, task_type: str) -> Dict[str, TaskParams]: """Задачи по типу.""" return { tid: p for tid, p in self._tasks.items() if p.task_type == task_type } async def get_twitch_tasks(self) -> Dict[str, TaskParams]: """Только Twitch задачи.""" return await self.get_tasks_by_type("twitch_irc") async def get_visit_tasks(self) -> Dict[str, TaskParams]: """Только задачи посещения.""" return await self.get_tasks_by_type("visit") async def get_stats(self) -> dict: """Общая статистика.""" twitch = visit = paused = active = 0 for p in self._tasks.values(): if p.task_type == "twitch_irc": twitch += 1 elif p.task_type == "visit": visit += 1 if p.paused: paused += 1 else: active += 1 return { "total": len(self._tasks), "twitch": twitch, "visit": visit, "paused": paused, "active": active, } def _get_task_info(self, task_id: str, params: TaskParams) -> str: """Информация о задаче для отображения.""" status = params.get_status_emoji() if params.task_type == "twitch_irc": return ( f"{status} 📺 **{params.channel}**\n" f" `{task_id[:12]}...`\n" f" 👤 @{params.target_username} | " f"🔗{params.links_found} | " f"📊{params.total_visits}\n" ) else: return ( f"{status} 🌐 `{params.url[:40]}`\n" f" `{task_id[:12]}...`\n" f" 📊{params.total_visits} визитов\n" ) async def format_task_list(self, max_items: int = 15) -> str: """Форматирует список задач для сообщения.""" async with self._lock: if not self._tasks: return "📊 Нет активных задач" # Сначала Twitch, потом визиты twitch = await self.get_twitch_tasks() visits = await self.get_visit_tasks() async with self._lock: total_tasks = len(self._tasks) text = f"📊 **ЗАДАЧИ: {total_tasks}**\n\n" if twitch: text += "📺 **Twitch мониторинг:**\n" for i, (task_id, params) in enumerate(list(twitch.items())[:5]): text += self._get_task_info(task_id, params) if len(twitch) > 5: text += f" _...и еще {len(twitch) - 5}_\n" text += "\n" if visits: text += "🌐 **Посещение страниц:**\n" for i, (task_id, params) in enumerate(list(visits.items())[:5]): text += self._get_task_info(task_id, params) if len(visits) > 5: text += f" _...и еще {len(visits) - 5}_\n" return text