fix view
This commit is contained in:
+84
-26
@@ -436,13 +436,12 @@ class BotInterface:
|
|||||||
"delay": (
|
"delay": (
|
||||||
"⏱️ *Пауза между переходами*\n"
|
"⏱️ *Пауза между переходами*\n"
|
||||||
"Сколько секунд ждать перед следующим кликом.\n"
|
"Сколько секунд ждать перед следующим кликом.\n"
|
||||||
"Диапазон `30-60` — каждый раз рандом от 30 до 60.\n"
|
"Диапазон `30-60` — каждый раз рандом от 30 до 60."
|
||||||
"Одно число `45` — всегда фиксированно."
|
|
||||||
),
|
),
|
||||||
"reading": (
|
"reading": (
|
||||||
"📖 *Время на странице*\n"
|
"📖 *Время на странице*\n"
|
||||||
"Сколько секунд браузер будет листать страницу (имитация чтения).\n"
|
"Сколько секунд браузер будет листать страницу (имитация чтения).\n"
|
||||||
"Диапазон `45-90` или фиксированно `60`."
|
"Диапазон `45-90`."
|
||||||
),
|
),
|
||||||
"perlink": (
|
"perlink": (
|
||||||
"🔗 *Резервное кол-во кликов*\n"
|
"🔗 *Резервное кол-во кликов*\n"
|
||||||
@@ -454,8 +453,7 @@ class BotInterface:
|
|||||||
"📊 *CTR — процент от зрителей*\n"
|
"📊 *CTR — процент от зрителей*\n"
|
||||||
"Формула: клики = зрители × CTR%.\n"
|
"Формула: клики = зрители × CTR%.\n"
|
||||||
"Например: 120 зрителей × 1% = 1 клик.\n"
|
"Например: 120 зрителей × 1% = 1 клик.\n"
|
||||||
"Диапазон `0.8-1` — случайно в этом диапазоне.\n"
|
"Диапазон `0.8-1` — случайно в этом диапазоне."
|
||||||
"Одно число `1` — фиксированно."
|
|
||||||
),
|
),
|
||||||
"minutes": (
|
"minutes": (
|
||||||
"⏰ *Длительность мониторинга*\n"
|
"⏰ *Длительность мониторинга*\n"
|
||||||
@@ -817,7 +815,7 @@ class BotInterface:
|
|||||||
f"🔢 *Кликов в одном цикле*\n"
|
f"🔢 *Кликов в одном цикле*\n"
|
||||||
f"Сколько кликов подряд делать перед паузой.\n"
|
f"Сколько кликов подряд делать перед паузой.\n"
|
||||||
f"Текущее: {cur}\n"
|
f"Текущее: {cur}\n"
|
||||||
f"Диапазон `2-5` или фиксированно `3`."
|
f"Диапазон `2-5`."
|
||||||
)
|
)
|
||||||
_d_mn = params.min_delay // 60
|
_d_mn = params.min_delay // 60
|
||||||
_d_mx = params.max_delay // 60
|
_d_mx = params.max_delay // 60
|
||||||
@@ -826,7 +824,7 @@ class BotInterface:
|
|||||||
f"⏱️ *Пауза между циклами*\n"
|
f"⏱️ *Пауза между циклами*\n"
|
||||||
f"Сколько минут ждать после каждого цикла кликов.\n"
|
f"Сколько минут ждать после каждого цикла кликов.\n"
|
||||||
f"Текущая: {_d_cur} мин\n"
|
f"Текущая: {_d_cur} мин\n"
|
||||||
f"Диапазон `2-5` или фиксированно `3`."
|
f"Диапазон `2-5`."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
perlink_label = (
|
perlink_label = (
|
||||||
@@ -840,7 +838,7 @@ class BotInterface:
|
|||||||
f"⏱️ *Пауза между переходами*\n"
|
f"⏱️ *Пауза между переходами*\n"
|
||||||
f"Сколько секунд ждать перед следующим кликом.\n"
|
f"Сколько секунд ждать перед следующим кликом.\n"
|
||||||
f"Текущая: {params.min_delay}–{params.max_delay} сек\n"
|
f"Текущая: {params.min_delay}–{params.max_delay} сек\n"
|
||||||
f"Диапазон `30-60` или фиксированно `45`."
|
f"Диапазон `30-60`."
|
||||||
)
|
)
|
||||||
prompts = {
|
prompts = {
|
||||||
"delay": delay_label,
|
"delay": delay_label,
|
||||||
@@ -848,13 +846,13 @@ class BotInterface:
|
|||||||
f"📖 *Время на странице*\n"
|
f"📖 *Время на странице*\n"
|
||||||
f"Сколько секунд браузер листает страницу (имитация чтения).\n"
|
f"Сколько секунд браузер листает страницу (имитация чтения).\n"
|
||||||
f"Текущее: {params.min_reading}–{params.max_reading} сек\n"
|
f"Текущее: {params.min_reading}–{params.max_reading} сек\n"
|
||||||
f"Диапазон `45-90` или фиксированно `60`."
|
f"Диапазон `45-90`."
|
||||||
),
|
),
|
||||||
"percent": (
|
"percent": (
|
||||||
f"👥 *Кликов = % от зрителей*\n"
|
f"👥 *Кликов = % от зрителей*\n"
|
||||||
f"Количество кликов рассчитывается как % от числа зрителей стрима.\n"
|
f"Количество кликов рассчитывается как % от числа зрителей стрима.\n"
|
||||||
f"Текущий: {params.visits_percent}%\n"
|
f"Текущий: {params.visits_percent}%\n"
|
||||||
f"`0` — отключить, использовать фиксированное кол-во.\n"
|
f"`0` — отключить.\n"
|
||||||
f"Пример: `5` = 5 кликов на 100 зрителей."
|
f"Пример: `5` = 5 кликов на 100 зрителей."
|
||||||
),
|
),
|
||||||
"perlink": perlink_label,
|
"perlink": perlink_label,
|
||||||
@@ -862,13 +860,13 @@ class BotInterface:
|
|||||||
f"⏳ *Задержка между кликами в цикле*\n"
|
f"⏳ *Задержка между кликами в цикле*\n"
|
||||||
f"Сколько секунд ждать между каждым кликом внутри одного цикла.\n"
|
f"Сколько секунд ждать между каждым кликом внутри одного цикла.\n"
|
||||||
f"Текущая: {params.min_click_delay}–{params.max_click_delay} сек\n"
|
f"Текущая: {params.min_click_delay}–{params.max_click_delay} сек\n"
|
||||||
f"Диапазон `5-15` или фиксированно `10`."
|
f"Диапазон `5-15`."
|
||||||
),
|
),
|
||||||
"ctr": (
|
"ctr": (
|
||||||
f"📊 *CTR — процент от зрителей*\n"
|
f"📊 *CTR — процент от зрителей*\n"
|
||||||
f"Формула: клики = зрители × CTR%.\n"
|
f"Формула: клики = зрители × CTR%.\n"
|
||||||
f"Текущий: {params.min_ctr}%–{params.max_ctr}%\n"
|
f"Текущий: {params.min_ctr}%–{params.max_ctr}%\n"
|
||||||
f"Диапазон `0.8-1` или фиксированно `1`."
|
f"Диапазон `0.8-1`."
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
prompt = prompts.get(param, f"Введите значение для {param}:")
|
prompt = prompts.get(param, f"Введите значение для {param}:")
|
||||||
@@ -1089,6 +1087,59 @@ class BotInterface:
|
|||||||
await callback.answer("🗑️ Задача удалена")
|
await callback.answer("🗑️ Задача удалена")
|
||||||
await interface._show_user_tasks(callback.message, uid, edit=True)
|
await interface._show_user_tasks(callback.message, uid, edit=True)
|
||||||
|
|
||||||
|
@dp.callback_query(F.data.startswith("urestart_"))
|
||||||
|
async def cb_user_restart_task(callback: CallbackQuery):
|
||||||
|
tid = callback.data.replace("urestart_", "", 1)
|
||||||
|
params, uid = await _get_user_task(callback, tid)
|
||||||
|
if not params:
|
||||||
|
return
|
||||||
|
|
||||||
|
balance = await interface.balance_storage.get_balance(uid)
|
||||||
|
if balance <= 0:
|
||||||
|
await callback.answer("❌ Недостаточно переходов", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Сбрасываем статистику и статус
|
||||||
|
await interface.task_manager.update_task(
|
||||||
|
tid,
|
||||||
|
completed=False,
|
||||||
|
stopped=False,
|
||||||
|
paused=False,
|
||||||
|
successful_visits=0,
|
||||||
|
total_visits=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
if params.task_type == "user_visit":
|
||||||
|
await interface.background_tasks.cancel_task(tid)
|
||||||
|
success = await interface.background_tasks.start_task(
|
||||||
|
task_id=tid,
|
||||||
|
coro=interface._run_url_visits(tid, params, callback.message.bot),
|
||||||
|
task_type="user_visit",
|
||||||
|
metadata={"type": "user_visit", "url": params.url, "chat_id": params.chat_id},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
from types import SimpleNamespace
|
||||||
|
msg_mock = SimpleNamespace(
|
||||||
|
chat=SimpleNamespace(id=params.chat_id),
|
||||||
|
bot=callback.message.bot,
|
||||||
|
)
|
||||||
|
await interface.background_tasks.cancel_task(tid)
|
||||||
|
success = await interface.background_tasks.start_task(
|
||||||
|
task_id=tid,
|
||||||
|
coro=interface._run_twitch(tid, params, msg_mock),
|
||||||
|
task_type="twitch_irc",
|
||||||
|
metadata={"type": "twitch_irc", "channel": params.channel, "chat_id": params.chat_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
if interface.storage:
|
||||||
|
await interface.storage.save_task(tid, params)
|
||||||
|
await callback.answer("🔄 Задача перезапущена")
|
||||||
|
else:
|
||||||
|
await callback.answer("❌ Ошибка перезапуска", show_alert=True)
|
||||||
|
|
||||||
|
await interface._show_user_tasks(callback.message, uid, edit=True)
|
||||||
|
|
||||||
@dp.callback_query(F.data.startswith("upause_"))
|
@dp.callback_query(F.data.startswith("upause_"))
|
||||||
async def cb_user_pause_task(callback: CallbackQuery):
|
async def cb_user_pause_task(callback: CallbackQuery):
|
||||||
tid = callback.data.replace("upause_", "", 1)
|
tid = callback.data.replace("upause_", "", 1)
|
||||||
@@ -2022,12 +2073,22 @@ class BotInterface:
|
|||||||
def is_active():
|
def is_active():
|
||||||
return not params.paused and not params.stopped and not params.stream_offline
|
return not params.paused and not params.stopped and not params.stream_offline
|
||||||
|
|
||||||
|
async def on_drain_fail():
|
||||||
|
await self.task_manager.pause_task(task_id)
|
||||||
|
await send_message_safe(
|
||||||
|
message.bot, params.chat_id,
|
||||||
|
f"⚠️ IRC #{params.channel}: ошибка соединения (3 попытки) — задача поставлена на паузу"
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(f"🚀 IRC monitor: {params.channel}")
|
logger.info(f"🚀 IRC monitor: {params.channel}")
|
||||||
|
|
||||||
watcher = asyncio.create_task(self._stream_watcher(task_id, params, message))
|
watcher = asyncio.create_task(self._stream_watcher(task_id, params, message))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await irc.listen_for_messages(on_url, params.monitor_duration, params.allowed_domains, is_active=is_active)
|
await irc.listen_for_messages(
|
||||||
|
on_url, params.monitor_duration, params.allowed_domains,
|
||||||
|
is_active=is_active, on_drain_fail=on_drain_fail
|
||||||
|
)
|
||||||
if params.monitor_minutes > 0:
|
if params.monitor_minutes > 0:
|
||||||
await self.task_manager.complete_task(task_id)
|
await self.task_manager.complete_task(task_id)
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
@@ -2044,7 +2105,6 @@ class BotInterface:
|
|||||||
|
|
||||||
async def _run_url_visits(self, task_id: str, params: TaskParams, bot):
|
async def _run_url_visits(self, task_id: str, params: TaskParams, bot):
|
||||||
"""Посещает URL циклами: sleep(межцикловая) → N кликов(+sleep между ними) → повтор."""
|
"""Посещает URL циклами: sleep(межцикловая) → N кликов(+sleep между ними) → повтор."""
|
||||||
total_done = 0
|
|
||||||
max_v = params.max_visits or 1
|
max_v = params.max_visits or 1
|
||||||
|
|
||||||
async def _sleep_interruptible(seconds: int):
|
async def _sleep_interruptible(seconds: int):
|
||||||
@@ -2056,7 +2116,7 @@ class BotInterface:
|
|||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while total_done < max_v and not params.stopped:
|
while params.successful_visits < max_v and not params.stopped:
|
||||||
# --- пауза перед циклом ---
|
# --- пауза перед циклом ---
|
||||||
await _sleep_interruptible(params.get_delay())
|
await _sleep_interruptible(params.get_delay())
|
||||||
if params.stopped:
|
if params.stopped:
|
||||||
@@ -2067,7 +2127,7 @@ class BotInterface:
|
|||||||
|
|
||||||
# --- цикл кликов ---
|
# --- цикл кликов ---
|
||||||
for click_num in range(series_size):
|
for click_num in range(series_size):
|
||||||
if params.stopped or total_done >= max_v:
|
if params.stopped or params.successful_visits >= max_v:
|
||||||
break
|
break
|
||||||
|
|
||||||
while params.paused and not params.stopped:
|
while params.paused and not params.stopped:
|
||||||
@@ -2085,7 +2145,6 @@ class BotInterface:
|
|||||||
try:
|
try:
|
||||||
result = await self.browser_service.visit_page(params.url, reading)
|
result = await self.browser_service.visit_page(params.url, reading)
|
||||||
params.total_visits += 1
|
params.total_visits += 1
|
||||||
total_done += 1
|
|
||||||
if result.success:
|
if result.success:
|
||||||
params.successful_visits += 1
|
params.successful_visits += 1
|
||||||
await self.balance_storage.deduct(params.user_id, 1)
|
await self.balance_storage.deduct(params.user_id, 1)
|
||||||
@@ -2093,10 +2152,10 @@ class BotInterface:
|
|||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"URL visit error: {e}")
|
logger.error(f"URL visit error: {e}")
|
||||||
total_done += 1
|
params.total_visits += 1
|
||||||
|
|
||||||
# задержка между кликами внутри цикла (кроме последнего)
|
# задержка между кликами внутри цикла (кроме последнего)
|
||||||
if click_num < series_size - 1 and not params.stopped and total_done < max_v:
|
if click_num < series_size - 1 and not params.stopped and params.successful_visits < max_v:
|
||||||
await _sleep_interruptible(params.get_click_delay())
|
await _sleep_interruptible(params.get_click_delay())
|
||||||
|
|
||||||
await self.task_manager.complete_task(task_id)
|
await self.task_manager.complete_task(task_id)
|
||||||
@@ -2290,10 +2349,11 @@ class BotInterface:
|
|||||||
InlineKeyboardButton(text="🗑️", callback_data=f"ustop_{tid}"),
|
InlineKeyboardButton(text="🗑️", callback_data=f"ustop_{tid}"),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
builder.row(InlineKeyboardButton(
|
builder.row(
|
||||||
text=f"🗑️ {label} (завершена)",
|
InlineKeyboardButton(text=label, callback_data=f"utask_{tid}"),
|
||||||
callback_data=f"ustop_{tid}"
|
InlineKeyboardButton(text="🔄", callback_data=f"urestart_{tid}"),
|
||||||
))
|
InlineKeyboardButton(text="🗑️", callback_data=f"ustop_{tid}"),
|
||||||
|
)
|
||||||
builder.row(InlineKeyboardButton(text="📝 Новая задача", callback_data="new_task"))
|
builder.row(InlineKeyboardButton(text="📝 Новая задача", callback_data="new_task"))
|
||||||
builder.row(InlineKeyboardButton(text="🔙 Мои задачи", callback_data="my_tasks"))
|
builder.row(InlineKeyboardButton(text="🔙 Мои задачи", callback_data="my_tasks"))
|
||||||
|
|
||||||
@@ -2417,8 +2477,7 @@ class BotInterface:
|
|||||||
f"✅ Переходов: {count}\n\n"
|
f"✅ Переходов: {count}\n\n"
|
||||||
"Шаг 3/4 — сколько кликов в одном цикле?\n\n"
|
"Шаг 3/4 — сколько кликов в одном цикле?\n\n"
|
||||||
"Бот делает N кликов подряд, затем ждёт паузу и начинает следующий цикл.\n\n"
|
"Бот делает N кликов подряд, затем ждёт паузу и начинает следующий цикл.\n\n"
|
||||||
"Диапазон `2-5` — каждый цикл будет случайной длины от 2 до 5.\n"
|
"Диапазон `2-5` — каждый цикл будет случайной длины от 2 до 5.",
|
||||||
"Одно число `3` — всегда ровно 3 клика.",
|
|
||||||
reply_markup=builder.as_markup()
|
reply_markup=builder.as_markup()
|
||||||
)
|
)
|
||||||
self._menu_msg[user_id] = sent.message_id
|
self._menu_msg[user_id] = sent.message_id
|
||||||
@@ -2444,7 +2503,6 @@ class BotInterface:
|
|||||||
sent = await message.answer(
|
sent = await message.answer(
|
||||||
f"✅ Цикл: {series_label} кликов\n\n"
|
f"✅ Цикл: {series_label} кликов\n\n"
|
||||||
"Шаг 4/4 — таймаут между циклами (в минутах)\n\n"
|
"Шаг 4/4 — таймаут между циклами (в минутах)\n\n"
|
||||||
"`3` — ровно 3 минуты.\n"
|
|
||||||
"`2-5` — случайно от 2 до 5 минут.",
|
"`2-5` — случайно от 2 до 5 минут.",
|
||||||
reply_markup=builder.as_markup()
|
reply_markup=builder.as_markup()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -97,16 +97,13 @@ class BotApplication:
|
|||||||
notify_users: dict = {} # user_id -> chat_id
|
notify_users: dict = {} # user_id -> chat_id
|
||||||
|
|
||||||
for task_id, params in saved_tasks.items():
|
for task_id, params in saved_tasks.items():
|
||||||
if params.stopped:
|
# Завершённые и остановленные — загружаем в память но не запускаем
|
||||||
|
if params.completed or params.stopped:
|
||||||
|
await self.interface.task_manager.add_task(task_id, params)
|
||||||
continue
|
continue
|
||||||
if params.completed and params.monitor_minutes > 0:
|
|
||||||
continue
|
|
||||||
if params.completed:
|
|
||||||
params.completed = False
|
|
||||||
|
|
||||||
# Все восстановленные задачи ставим на паузу
|
# Активные задачи ставим на паузу и перезапускаем
|
||||||
params.paused = True
|
params.paused = True
|
||||||
|
|
||||||
await self.interface.task_manager.add_task(task_id, params)
|
await self.interface.task_manager.add_task(task_id, params)
|
||||||
|
|
||||||
if params.task_type == "twitch_irc" and params.chat_id:
|
if params.task_type == "twitch_irc" and params.chat_id:
|
||||||
|
|||||||
@@ -135,6 +135,10 @@ class BrowserService:
|
|||||||
) -> VisitResult:
|
) -> VisitResult:
|
||||||
"""Выполняет просмотр страницы."""
|
"""Выполняет просмотр страницы."""
|
||||||
page = await browser.new_page()
|
page = await browser.new_page()
|
||||||
|
initial_url = url
|
||||||
|
final_url = url
|
||||||
|
redirect = False
|
||||||
|
reading_completed = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await page.set_viewport_size({
|
await page.set_viewport_size({
|
||||||
@@ -149,12 +153,22 @@ class BrowserService:
|
|||||||
await goto_task
|
await goto_task
|
||||||
|
|
||||||
initial_url = page.url
|
initial_url = page.url
|
||||||
final_url, redirect = await self._wait_redirect(page, initial_url)
|
|
||||||
|
try:
|
||||||
|
final_url, redirect = await self._wait_redirect(page, initial_url)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Redirect wait error (ignored): {e}")
|
||||||
|
final_url = page.url
|
||||||
|
|
||||||
await self._simulate_reading(page, reading_time)
|
await self._simulate_reading(page, reading_time)
|
||||||
|
reading_completed = True
|
||||||
|
|
||||||
screenshot_path = None
|
screenshot_path = None
|
||||||
if settings.SCREENSHOTS_DIR:
|
if settings.SCREENSHOTS_DIR:
|
||||||
screenshot_path = await self._take_screenshot(page, final_url)
|
try:
|
||||||
|
screenshot_path = await self._take_screenshot(page, final_url)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
return VisitResult(
|
return VisitResult(
|
||||||
url=url,
|
url=url,
|
||||||
@@ -166,12 +180,20 @@ class BrowserService:
|
|||||||
screenshot_path=screenshot_path,
|
screenshot_path=screenshot_path,
|
||||||
success=True
|
success=True
|
||||||
)
|
)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return VisitResult(url=url, reading_time=reading_time,
|
return VisitResult(
|
||||||
success=False, error=str(e))
|
url=url,
|
||||||
|
initial_url=initial_url,
|
||||||
|
final_url=final_url,
|
||||||
|
redirect_occurred=redirect,
|
||||||
|
proxy_info=proxy_info,
|
||||||
|
reading_time=reading_time,
|
||||||
|
success=reading_completed,
|
||||||
|
error=str(e) if not reading_completed else None
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
if page.url == url:
|
|
||||||
logger.error()
|
|
||||||
await page.close()
|
await page.close()
|
||||||
|
|
||||||
async def _move_mouse(self, page, task):
|
async def _move_mouse(self, page, task):
|
||||||
|
|||||||
+47
-26
@@ -165,16 +165,18 @@ class TwitchIRCClient:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def listen_for_messages(
|
async def listen_for_messages(
|
||||||
self,
|
self,
|
||||||
on_url_found: Callable[[str, str], Awaitable[None]],
|
on_url_found: Callable[[str, str], Awaitable[None]],
|
||||||
duration: int,
|
duration: int,
|
||||||
allowed_domains: Optional[List[str]] = None,
|
allowed_domains: Optional[List[str]] = None,
|
||||||
is_active: Callable[[], bool] = None, # Функция проверки активности
|
is_active: Callable[[], bool] = None,
|
||||||
) -> dict:
|
on_drain_fail: Callable[[], Awaitable[None]] = None,
|
||||||
|
) -> dict:
|
||||||
stats = {"links_found": 0, "messages": 0, "reconnects": 0}
|
stats = {"links_found": 0, "messages": 0, "reconnects": 0}
|
||||||
|
|
||||||
start_time = datetime.now()
|
start_time = datetime.now()
|
||||||
last_message_time = datetime.now()
|
last_message_time = datetime.now()
|
||||||
|
drain_errors = 0
|
||||||
|
|
||||||
if not await self.connect():
|
if not await self.connect():
|
||||||
raise ConnectionError(f"IRC connect failed for #{self.channel}")
|
raise ConnectionError(f"IRC connect failed for #{self.channel}")
|
||||||
@@ -195,26 +197,45 @@ class TwitchIRCClient:
|
|||||||
if (datetime.now() - last_message_time).seconds > 30:
|
if (datetime.now() - last_message_time).seconds > 30:
|
||||||
logger.info("🔄 Reconnecting (30s)...")
|
logger.info("🔄 Reconnecting (30s)...")
|
||||||
|
|
||||||
# Сначала читаем всё что осталось
|
# Сначала читаем всё что осталось (с retry)
|
||||||
try:
|
drain_ok = False
|
||||||
while True:
|
for attempt in range(3):
|
||||||
line = await asyncio.wait_for(self._reader.readline(), timeout=1.0)
|
try:
|
||||||
if not line:
|
while True:
|
||||||
break
|
line = await asyncio.wait_for(self._reader.readline(), timeout=1.0)
|
||||||
decoded = line.decode('utf-8', errors='ignore').strip()
|
if not line:
|
||||||
if 'PRIVMSG' in decoded:
|
break
|
||||||
msg = self._parse_message(decoded)
|
decoded = line.decode('utf-8', errors='ignore').strip()
|
||||||
if msg:
|
if 'PRIVMSG' in decoded:
|
||||||
stats["messages"] += 1
|
msg = self._parse_message(decoded)
|
||||||
for url in self._extract_urls(msg['message']):
|
if msg:
|
||||||
if self._is_domain_allowed(url, allowed_domains):
|
stats["messages"] += 1
|
||||||
stats["links_found"] += 1
|
for url in self._extract_urls(msg['message']):
|
||||||
try:
|
if self._is_domain_allowed(url, allowed_domains):
|
||||||
await on_url_found(url, msg['display_name'])
|
stats["links_found"] += 1
|
||||||
except Exception as e:
|
try:
|
||||||
logger.warning(f"URL callback error: {e}")
|
await on_url_found(url, msg['display_name'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error draining IRC data: {e}")
|
logger.warning(f"URL callback error: {e}")
|
||||||
|
drain_ok = True
|
||||||
|
break
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
drain_ok = True
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error draining IRC data (attempt {attempt + 1}/3): {e}")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
if not drain_ok:
|
||||||
|
drain_errors += 1
|
||||||
|
logger.error(f"IRC drain failed 3 times, total failures: {drain_errors}")
|
||||||
|
if drain_errors >= 3:
|
||||||
|
logger.error("IRC drain failed 3 consecutive times — pausing task")
|
||||||
|
if on_drain_fail:
|
||||||
|
await on_drain_fail()
|
||||||
|
return stats
|
||||||
|
else:
|
||||||
|
drain_errors = 0
|
||||||
|
|
||||||
# Переподключаемся
|
# Переподключаемся
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user