Files
Click/managers/task_manager.py
T
2026-05-14 16:15:58 +07:00

303 lines
11 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.
"""
Менеджер задач по стримерам.
Простой список с возможностью изменять параметры на лету.
"""
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
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_tasks = await self.get_twitch_tasks()
visit_tasks = await self.get_visit_tasks()
return {
"total": len(self._tasks),
"twitch": len(twitch_tasks),
"visit": len(visit_tasks),
"paused": sum(1 for p in self._tasks.values() if p.paused),
"active": sum(1 for p in self._tasks.values() if not p.paused),
}
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