fix command ana params
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(pip install *)",
|
||||
"Bash(python -c \"import psutil; print\\('psutil ok, version:', psutil.__version__\\)\")",
|
||||
"Bash(python -c ' *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
+342
-57
@@ -4,6 +4,8 @@
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict
|
||||
@@ -71,6 +73,7 @@ class BotInterface:
|
||||
self._chat_history: Dict[int, list] = {}
|
||||
self._bot_ref = bot_ref
|
||||
self._waiting_password: Dict[int, str] = {} # user_id -> "register" or "login"
|
||||
self._start_time = datetime.now()
|
||||
|
||||
async def _update_commands(self):
|
||||
if self._bot_ref:
|
||||
@@ -251,6 +254,31 @@ class BotInterface:
|
||||
await interface._show_status(callback.message, edit=True)
|
||||
await callback.answer()
|
||||
|
||||
# === Перезапуск ===
|
||||
@dp.callback_query(F.data == "bot_restart_confirm")
|
||||
async def cb_restart_confirm(callback: CallbackQuery):
|
||||
if not await require_admin(callback):
|
||||
return
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="✅ Да, перезапустить", callback_data="bot_restart_do"),
|
||||
InlineKeyboardButton(text="❌ Отмена", callback_data="menu_status"),
|
||||
)
|
||||
await callback.message.edit_text(
|
||||
"⚠️ *Перезапустить бота?*\n\n"
|
||||
"Все задачи будут сохранены и восстановлены автоматически.",
|
||||
reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
@dp.callback_query(F.data == "bot_restart_do")
|
||||
async def cb_restart_do(callback: CallbackQuery):
|
||||
if not await require_admin(callback):
|
||||
return
|
||||
await callback.message.edit_text("🔄 Перезапуск бота...")
|
||||
await callback.answer()
|
||||
asyncio.create_task(interface._do_restart())
|
||||
|
||||
# === Стоп всё ===
|
||||
@dp.message(F.text == "🛑 Остановить всё")
|
||||
@dp.callback_query(F.data == "stop_all")
|
||||
@@ -405,11 +433,42 @@ class BotInterface:
|
||||
}
|
||||
|
||||
prompts = {
|
||||
"delay": "⏱️ Задержка (сек): `10-30` или `15`",
|
||||
"reading": "📖 Чтение (сек): `10-30` или `20`",
|
||||
"perlink": "🔗 Посещений на ссылку: `5`",
|
||||
"minutes": "⏰ Длительность (мин): `30`",
|
||||
"domains": "🌐 Домены через запятую: `site1.com, site2.ru`\nИли `all` для всех",
|
||||
"delay": (
|
||||
"⏱️ *Пауза между переходами*\n"
|
||||
"Сколько секунд ждать перед следующим кликом.\n"
|
||||
"Диапазон `30-60` — каждый раз рандом от 30 до 60.\n"
|
||||
"Одно число `45` — всегда фиксированно."
|
||||
),
|
||||
"reading": (
|
||||
"📖 *Время на странице*\n"
|
||||
"Сколько секунд браузер будет листать страницу (имитация чтения).\n"
|
||||
"Диапазон `45-90` или фиксированно `60`."
|
||||
),
|
||||
"perlink": (
|
||||
"🔗 *Резервное кол-во кликов*\n"
|
||||
"Используется только если Twitch GQL недоступен.\n"
|
||||
"В норме: клики = зрители × CTR.\n"
|
||||
"Пример: `5`"
|
||||
),
|
||||
"ctr": (
|
||||
"📊 *CTR — процент от зрителей*\n"
|
||||
"Формула: клики = зрители × CTR%.\n"
|
||||
"Например: 120 зрителей × 1% = 1 клик.\n"
|
||||
"Диапазон `0.8-1` — случайно в этом диапазоне.\n"
|
||||
"Одно число `1` — фиксированно."
|
||||
),
|
||||
"minutes": (
|
||||
"⏰ *Длительность мониторинга*\n"
|
||||
"Как долго следить за чатом стримера (в минутах).\n"
|
||||
"`0` — мониторить бесконечно.\n"
|
||||
"Пример: `30`"
|
||||
),
|
||||
"domains": (
|
||||
"🌐 *Разрешённые домены*\n"
|
||||
"Кликать только по ссылкам с этих сайтов.\n"
|
||||
"Несколько через запятую: `twitch.tv, youtube.com`\n"
|
||||
"`all` — любые ссылки без фильтра."
|
||||
),
|
||||
}
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
@@ -748,11 +807,66 @@ class BotInterface:
|
||||
await callback.answer("Задача не найдена", show_alert=True)
|
||||
return
|
||||
|
||||
if params.task_type == "user_visit":
|
||||
cur = (
|
||||
f"{params.min_series}–{params.max_series}"
|
||||
if params.min_series != params.max_series
|
||||
else str(params.min_series)
|
||||
)
|
||||
perlink_label = (
|
||||
f"🔢 *Кликов в одной серии*\n"
|
||||
f"Сколько кликов подряд делать перед паузой.\n"
|
||||
f"Текущее: {cur}\n"
|
||||
f"Диапазон `2-5` или фиксированно `3`."
|
||||
)
|
||||
delay_label = (
|
||||
f"⏱️ *Пауза между сериями*\n"
|
||||
f"Сколько секунд ждать после каждой серии кликов.\n"
|
||||
f"Текущая: {params.min_delay}–{params.max_delay} сек\n"
|
||||
f"Диапазон `30-60` или фиксированно `45`."
|
||||
)
|
||||
else:
|
||||
perlink_label = (
|
||||
f"🔗 *Резервное кол-во кликов*\n"
|
||||
f"Используется только если не удаётся получить зрителей.\n"
|
||||
f"В норме клики = зрители × CTR.\n"
|
||||
f"Текущее: {params.visits_per_link}\n"
|
||||
f"Пример: `5`"
|
||||
)
|
||||
delay_label = (
|
||||
f"⏱️ *Пауза между переходами*\n"
|
||||
f"Сколько секунд ждать перед следующим кликом.\n"
|
||||
f"Текущая: {params.min_delay}–{params.max_delay} сек\n"
|
||||
f"Диапазон `30-60` или фиксированно `45`."
|
||||
)
|
||||
prompts = {
|
||||
"delay": f"Задержка (сек): например 30-60 или 45\nТекущая: {params.min_delay}-{params.max_delay}",
|
||||
"reading": f"Время чтения (сек): например 45-90 или 60\nТекущее: {params.min_reading}-{params.max_reading}",
|
||||
"percent": f"% зрителей (0 = отключить): например 5\nТекущее: {params.visits_percent}%",
|
||||
"perlink": f"Кол-во переходов на ссылку: например 3\nТекущее: {params.visits_per_link}",
|
||||
"delay": delay_label,
|
||||
"reading": (
|
||||
f"📖 *Время на странице*\n"
|
||||
f"Сколько секунд браузер листает страницу (имитация чтения).\n"
|
||||
f"Текущее: {params.min_reading}–{params.max_reading} сек\n"
|
||||
f"Диапазон `45-90` или фиксированно `60`."
|
||||
),
|
||||
"percent": (
|
||||
f"👥 *Кликов = % от зрителей*\n"
|
||||
f"Количество кликов рассчитывается как % от числа зрителей стрима.\n"
|
||||
f"Текущий: {params.visits_percent}%\n"
|
||||
f"`0` — отключить, использовать фиксированное кол-во.\n"
|
||||
f"Пример: `5` = 5 кликов на 100 зрителей."
|
||||
),
|
||||
"perlink": perlink_label,
|
||||
"clickdelay": (
|
||||
f"⏳ *Задержка между кликами в серии*\n"
|
||||
f"Сколько секунд ждать между каждым кликом внутри одной серии.\n"
|
||||
f"Текущая: {params.min_click_delay}–{params.max_click_delay} сек\n"
|
||||
f"Диапазон `5-15` или фиксированно `10`."
|
||||
),
|
||||
"ctr": (
|
||||
f"📊 *CTR — процент от зрителей*\n"
|
||||
f"Формула: клики = зрители × CTR%.\n"
|
||||
f"Текущий: {params.min_ctr}%–{params.max_ctr}%\n"
|
||||
f"Диапазон `0.8-1` или фиксированно `1`."
|
||||
),
|
||||
}
|
||||
prompt = prompts.get(param, f"Введите значение для {param}:")
|
||||
|
||||
@@ -1187,7 +1301,7 @@ class BotInterface:
|
||||
params = TaskParams(
|
||||
task_type="twitch_irc",
|
||||
channel=channel,
|
||||
target_username="*", # Все пользователи
|
||||
target_username="*",
|
||||
visits_per_link=vl,
|
||||
monitor_minutes=mm,
|
||||
min_delay=mind, max_delay=maxd,
|
||||
@@ -1216,7 +1330,7 @@ class BotInterface:
|
||||
f"✅ Мониторинг запущен\n\n"
|
||||
f"📺 Канал: `{channel}`\n"
|
||||
f"👤 Отслеживание: все пользователи\n"
|
||||
f"🔗 На ссылку: {vl} визитов\n"
|
||||
f"🔗 На ссылку: {vl} кликов\n"
|
||||
f"⏱️ Мониторинг: {mm} мин\n"
|
||||
f"⏱️ Задержка: {format_range(mind, maxd)}с\n"
|
||||
f"📖 Чтение: {format_range(minr, maxr)}с\n"
|
||||
@@ -1454,23 +1568,66 @@ class BotInterface:
|
||||
builder.row(InlineKeyboardButton(text="🔙 В меню", callback_data="menu_main"))
|
||||
await self._edit_or_send(message, text, builder.as_markup(), edit)
|
||||
|
||||
def _get_sys_info(self) -> str:
|
||||
try:
|
||||
import psutil
|
||||
proc = psutil.Process(os.getpid())
|
||||
cpu = psutil.cpu_percent(interval=None)
|
||||
vm = psutil.virtual_memory()
|
||||
proc_mb = proc.memory_info().rss / 1024 / 1024
|
||||
ram_used = vm.used / 1024 / 1024 / 1024
|
||||
ram_total = vm.total / 1024 / 1024 / 1024
|
||||
return (
|
||||
f"💻 CPU: {cpu:.1f}%\n"
|
||||
f"🖥️ RAM: {ram_used:.1f}/{ram_total:.1f} GB ({vm.percent:.0f}%)\n"
|
||||
f"🤖 Процесс: {proc_mb:.1f} MB\n"
|
||||
)
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
async def _show_status(self, message: Message, edit: bool = False):
|
||||
"""Статус."""
|
||||
stats = await self.task_manager.get_stats()
|
||||
proxy_stats = self.proxy_manager.get_stats()
|
||||
bg_count = self.background_tasks.active_count
|
||||
|
||||
delta = datetime.now() - self._start_time
|
||||
h, rem = divmod(int(delta.total_seconds()), 3600)
|
||||
m, s = divmod(rem, 60)
|
||||
uptime = f"{h}ч {m}м {s}с" if h else f"{m}м {s}с"
|
||||
|
||||
sys_info = self._get_sys_info()
|
||||
|
||||
text = (
|
||||
"📈 Статус\n\n"
|
||||
f"📺 Twitch: {stats['twitch']}\n"
|
||||
f"🌐 Визиты: {stats['visit']}\n"
|
||||
f"⏸️ Пауза: {stats['paused']}\n"
|
||||
f"🔄 Активно: {stats['active']}\n\n"
|
||||
f"🔌 Прокси: {proxy_stats['total']} всего, {proxy_stats['available']} доступно"
|
||||
"📈 *Статус бота*\n\n"
|
||||
f"⏱️ Аптайм: {uptime}\n\n"
|
||||
f"📊 *Задачи:*\n"
|
||||
f"├ 📺 Twitch: {stats['twitch']}\n"
|
||||
f"├ 🌐 Визиты: {stats['visit']}\n"
|
||||
f"├ ⏸️ Пауза: {stats['paused']}\n"
|
||||
f"└ 🔄 Активно: {stats['active']}\n\n"
|
||||
f"🔌 Прокси: {proxy_stats['total']} / {proxy_stats['available']} доступно\n"
|
||||
f"🔁 Фоновых задач: {bg_count}\n\n"
|
||||
f"{sys_info}"
|
||||
)
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔄 Обновить", callback_data="menu_status"))
|
||||
builder.row(InlineKeyboardButton(text="🔁 Перезапустить бота", callback_data="bot_restart_confirm"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 В меню", callback_data="menu_main"))
|
||||
await self._edit_or_send(message, text, builder.as_markup(), edit)
|
||||
|
||||
async def _do_restart(self):
|
||||
"""Сохраняет задачи и перезапускает процесс."""
|
||||
import sys
|
||||
try:
|
||||
if self.storage:
|
||||
await self.storage.save_tasks(self.task_manager._tasks)
|
||||
await self.background_tasks.cancel_all()
|
||||
except Exception as e:
|
||||
logger.error(f"Restart cleanup error: {e}")
|
||||
finally:
|
||||
await asyncio.sleep(0.5)
|
||||
os.execv(sys.executable, [sys.executable] + sys.argv)
|
||||
|
||||
# =========================================================================
|
||||
# УПРАВЛЕНИЕ ЗАДАЧЕЙ
|
||||
# =========================================================================
|
||||
@@ -1493,15 +1650,16 @@ class BotInterface:
|
||||
builder.row(InlineKeyboardButton(text="⏸️ Пауза", callback_data=f"tpause_{task_id}"))
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="⏱️ Задержка", callback_data=f"tset_{task_id}_delay"),
|
||||
InlineKeyboardButton(text="📖 Чтение", callback_data=f"tset_{task_id}_reading")
|
||||
InlineKeyboardButton(text="⏱️ Пауза (сек)", callback_data=f"tset_{task_id}_delay"),
|
||||
InlineKeyboardButton(text="📖 Время на стр.", callback_data=f"tset_{task_id}_reading")
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔗 На ссылку", callback_data=f"tset_{task_id}_perlink"),
|
||||
InlineKeyboardButton(text="⏰ Минуты", callback_data=f"tset_{task_id}_minutes")
|
||||
InlineKeyboardButton(text="🔗 Резерв кликов", callback_data=f"tset_{task_id}_perlink"),
|
||||
InlineKeyboardButton(text="📊 CTR %", callback_data=f"tset_{task_id}_ctr"),
|
||||
InlineKeyboardButton(text="⏰ Длительность", callback_data=f"tset_{task_id}_minutes")
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🌐 Домены", callback_data=f"tset_{task_id}_domains"),
|
||||
InlineKeyboardButton(text="🌐 Фильтр доменов", callback_data=f"tset_{task_id}_domains"),
|
||||
InlineKeyboardButton(text="⏭️ Пропустить", callback_data=f"tskip_{task_id}")
|
||||
)
|
||||
builder.row(
|
||||
@@ -1568,9 +1726,32 @@ class BotInterface:
|
||||
await message.answer(f"✅ Чтение: {format_range(mn, mx)}с")
|
||||
|
||||
elif param == "perlink":
|
||||
params_obj = await self.task_manager.get_task(task_id)
|
||||
if params_obj and params_obj.task_type == "user_visit":
|
||||
if "-" in value:
|
||||
mn, mx = parse_range(value, default_min=1)
|
||||
else:
|
||||
mn = mx = int(value)
|
||||
await self.task_manager.update_task(task_id, min_series=mn, max_series=mx)
|
||||
lbl = f"{mn}–{mx}" if mn != mx else str(mn)
|
||||
await message.answer(f"✅ Серия: {lbl} кликов")
|
||||
else:
|
||||
await self.task_manager.update_task(task_id, visits_per_link=int(value))
|
||||
await message.answer(f"✅ На ссылку: {value}")
|
||||
|
||||
elif param == "ctr":
|
||||
if "-" in value:
|
||||
parts_v = value.split("-", 1)
|
||||
mn_c = float(parts_v[0].replace(",", "."))
|
||||
mx_c = float(parts_v[1].replace(",", "."))
|
||||
else:
|
||||
mn_c = mx_c = float(value.replace(",", "."))
|
||||
if not (0 < mn_c <= mx_c <= 100):
|
||||
raise ValueError("CTR должен быть от 0 до 100, например 0.8-1")
|
||||
await self.task_manager.update_task(task_id, min_ctr=mn_c, max_ctr=mx_c)
|
||||
lbl = f"{mn_c}%–{mx_c}%" if mn_c != mx_c else f"{mn_c}%"
|
||||
await message.answer(f"✅ CTR: {lbl}")
|
||||
|
||||
elif param == "minutes":
|
||||
await self.task_manager.update_task(task_id, monitor_minutes=int(value))
|
||||
await message.answer(f"✅ Мониторинг: {value} мин")
|
||||
@@ -1591,6 +1772,27 @@ class BotInterface:
|
||||
await self.task_manager.update_task(task_id, visits_percent=pct)
|
||||
await message.answer(f"✅ % зрителей: {pct}%" if pct > 0 else "✅ % зрителей отключён")
|
||||
|
||||
elif param == "clickdelay":
|
||||
if "-" in value:
|
||||
mn, mx = parse_range(value, default_min=0)
|
||||
else:
|
||||
mn = mx = int(value)
|
||||
await self.task_manager.update_task(task_id, min_click_delay=mn, max_click_delay=mx)
|
||||
await message.answer(f"✅ Задержка между кликами: {format_range(mn, mx)}с")
|
||||
|
||||
elif param == "ctr":
|
||||
if "-" in value:
|
||||
parts_v = value.split("-", 1)
|
||||
mn_c = float(parts_v[0].replace(",", "."))
|
||||
mx_c = float(parts_v[1].replace(",", "."))
|
||||
else:
|
||||
mn_c = mx_c = float(value.replace(",", "."))
|
||||
if not (0 < mn_c <= mx_c <= 100):
|
||||
raise ValueError("CTR должен быть от 0 до 100, например 0.8-1")
|
||||
await self.task_manager.update_task(task_id, min_ctr=mn_c, max_ctr=mx_c)
|
||||
lbl = f"{mn_c}%–{mx_c}%" if mn_c != mx_c else f"{mn_c}%"
|
||||
await message.answer(f"✅ CTR: {lbl}")
|
||||
|
||||
else:
|
||||
await message.answer(f"❌ Неизвестный параметр: {param}")
|
||||
|
||||
@@ -1599,32 +1801,57 @@ class BotInterface:
|
||||
|
||||
async def _show_user_task_detail(self, callback: CallbackQuery, task_id: str, params: TaskParams):
|
||||
if params.task_type == "user_visit":
|
||||
series_range = (
|
||||
f"{params.min_series}–{params.max_series}"
|
||||
if params.min_series != params.max_series
|
||||
else str(params.min_series)
|
||||
)
|
||||
info = (
|
||||
f"🔗 {params.url[:50]}\n"
|
||||
f"Прогресс: {params.successful_visits}/{params.max_visits}\n"
|
||||
f"⏱ Задержка: {params.min_delay}-{params.max_delay} сек\n"
|
||||
f"📖 Чтение: {params.min_reading}-{params.max_reading} сек"
|
||||
f"📊 Выполнено: {params.successful_visits} из {params.max_visits} переходов\n\n"
|
||||
f"🔢 Кликов в серии: {series_range}\n"
|
||||
f"⏳ Задержка между кликами: {params.min_click_delay}–{params.max_click_delay} сек\n"
|
||||
f"⏱ Пауза между сериями: {params.min_delay}–{params.max_delay} сек\n"
|
||||
f"📖 Время на странице: {params.min_reading}–{params.max_reading} сек"
|
||||
)
|
||||
else:
|
||||
domains = ", ".join(params.allowed_domains) if params.allowed_domains else "все"
|
||||
pct = f"{params.visits_percent}% зрителей" if params.visits_percent > 0 else f"{params.visits_per_link} фикс."
|
||||
pct = (
|
||||
f"{params.visits_percent}% от зрителей"
|
||||
if params.visits_percent > 0
|
||||
else f"фиксированно {params.visits_per_link}"
|
||||
)
|
||||
ctr_str = (
|
||||
f"{params.min_ctr}%–{params.max_ctr}%"
|
||||
if params.min_ctr != params.max_ctr
|
||||
else f"{params.min_ctr}%"
|
||||
)
|
||||
info = (
|
||||
f"📺 {params.channel} | 🌐 {domains}\n"
|
||||
f"🔗 {params.links_found} ссылок | ✅ {params.successful_visits} визитов\n"
|
||||
f"⏱ Задержка: {params.min_delay}-{params.max_delay} сек\n"
|
||||
f"📖 Чтение: {params.min_reading}-{params.max_reading} сек\n"
|
||||
f"👥 Переходов: {pct}"
|
||||
f"📺 Стример: {params.channel}\n"
|
||||
f"🌐 Домены: {domains}\n\n"
|
||||
f"🔗 Ссылок найдено: {params.links_found}\n"
|
||||
f"✅ Успешных кликов: {params.successful_visits}\n\n"
|
||||
f"🔗 Кликов на ссылку: {pct}\n"
|
||||
f"📊 CTR: {ctr_str}\n"
|
||||
f"⏱ Пауза между кликами: {params.min_delay}–{params.max_delay} сек\n"
|
||||
f"📖 Время на странице: {params.min_reading}–{params.max_reading} сек"
|
||||
)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="⏱️ Задержка", callback_data=f"uset_{task_id}_delay"),
|
||||
InlineKeyboardButton(text="📖 Чтение", callback_data=f"uset_{task_id}_reading"),
|
||||
InlineKeyboardButton(text="⏱️ Пауза (сек)", callback_data=f"uset_{task_id}_delay"),
|
||||
InlineKeyboardButton(text="📖 Время на стр.", callback_data=f"uset_{task_id}_reading"),
|
||||
)
|
||||
if params.task_type == "twitch_irc":
|
||||
if params.task_type == "user_visit":
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔢 Размер серии", callback_data=f"uset_{task_id}_perlink"),
|
||||
InlineKeyboardButton(text="⏳ Между кликами", callback_data=f"uset_{task_id}_clickdelay"),
|
||||
)
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="👥 % зрителей", callback_data=f"uset_{task_id}_percent"),
|
||||
InlineKeyboardButton(text="🔗 На ссылку", callback_data=f"uset_{task_id}_perlink"),
|
||||
InlineKeyboardButton(text="🔗 Резерв кликов", callback_data=f"uset_{task_id}_perlink"),
|
||||
InlineKeyboardButton(text="📊 CTR %", callback_data=f"uset_{task_id}_ctr"),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🌐 Домены", callback_data=f"uedit_{task_id}"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 К задачам", callback_data="my_tasks"))
|
||||
@@ -1728,11 +1955,33 @@ class BotInterface:
|
||||
|
||||
|
||||
async def _run_url_visits(self, task_id: str, params: TaskParams, bot):
|
||||
"""Посещает URL фиксированное число раз с балансом."""
|
||||
"""Посещает URL сериями: sleep(межсерийная) → N кликов(+sleep между ними) → повтор."""
|
||||
total_done = 0
|
||||
max_v = params.max_visits or 1
|
||||
|
||||
async def _sleep_interruptible(seconds: int):
|
||||
for _ in range(max(0, seconds)):
|
||||
if params.stopped:
|
||||
return
|
||||
while params.paused and not params.stopped:
|
||||
await asyncio.sleep(2)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
try:
|
||||
for i in range(params.max_visits or 1):
|
||||
while total_done < max_v and not params.stopped:
|
||||
# --- пауза перед серией ---
|
||||
await _sleep_interruptible(params.get_delay())
|
||||
if params.stopped:
|
||||
break
|
||||
|
||||
# размер серии выбирается случайно каждый раз
|
||||
series_size = params.get_series_size()
|
||||
|
||||
# --- серия кликов ---
|
||||
for click_num in range(series_size):
|
||||
if params.stopped or total_done >= max_v:
|
||||
break
|
||||
|
||||
while params.paused and not params.stopped:
|
||||
await asyncio.sleep(2)
|
||||
if params.stopped:
|
||||
@@ -1741,13 +1990,14 @@ class BotInterface:
|
||||
balance = await self.balance_storage.get_balance(params.user_id)
|
||||
if balance <= 0:
|
||||
await self.task_manager.stop_task(task_id)
|
||||
await send_message_safe(bot, params.chat_id, "Баланс исчерпан — задача остановлена")
|
||||
break
|
||||
await send_message_safe(bot, params.chat_id, "⚠️ Баланс исчерпан — задача остановлена")
|
||||
return
|
||||
|
||||
reading = params.get_reading_time()
|
||||
try:
|
||||
result = await self.browser_service.visit_page(params.url, reading)
|
||||
params.total_visits += 1
|
||||
total_done += 1
|
||||
if result.success:
|
||||
params.successful_visits += 1
|
||||
await self.balance_storage.deduct(params.user_id, 1)
|
||||
@@ -1755,21 +2005,21 @@ class BotInterface:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"URL visit error: {e}")
|
||||
total_done += 1
|
||||
|
||||
if i < (params.max_visits or 1) - 1 and not params.stopped:
|
||||
await asyncio.sleep(params.get_delay())
|
||||
# задержка между кликами внутри серии (кроме последнего)
|
||||
if click_num < series_size - 1 and not params.stopped and total_done < max_v:
|
||||
await _sleep_interruptible(params.get_click_delay())
|
||||
|
||||
await self.task_manager.complete_task(task_id)
|
||||
remaining = await self.balance_storage.get_balance(params.user_id)
|
||||
msg = await send_message_safe(
|
||||
await send_message_safe(
|
||||
bot, params.chat_id,
|
||||
f"✅ Задача завершена\n"
|
||||
f"🔗 {params.url[:50]}\n"
|
||||
f"Выполнено: {params.successful_visits}/{params.max_visits}\n"
|
||||
f"Переходов осталось: {remaining}"
|
||||
)
|
||||
if msg and params.user_id:
|
||||
await self._track_msg(params.user_id, msg.message_id)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
finally:
|
||||
@@ -1793,13 +2043,15 @@ class BotInterface:
|
||||
)
|
||||
return
|
||||
|
||||
# Вычисляем количество переходов: по % зрителей или фиксировано
|
||||
if params.visits_percent > 0:
|
||||
# visits = viewers × CTR; fallback на visits_per_link если GQL недоступен
|
||||
ctr = random.uniform(params.min_ctr, params.max_ctr)
|
||||
viewers = await get_viewer_count(params.channel)
|
||||
visits_count = max(1, int(viewers * params.visits_percent / 100))
|
||||
logger.info(f"Viewer-based visits: {viewers} × {params.visits_percent}% = {visits_count}")
|
||||
if viewers and viewers > 0:
|
||||
visits_count = max(1, round(viewers * ctr / 100))
|
||||
logger.info(f"visits: {viewers} viewers × {ctr:.2f}% = {visits_count}")
|
||||
else:
|
||||
visits_count = params.visits_per_link
|
||||
visits_count = max(1, round(params.visits_per_link * ctr / 100))
|
||||
logger.info(f"visits: fallback {params.visits_per_link} × {ctr:.2f}% = {visits_count}")
|
||||
|
||||
logger.info(f"🔗 @{username}: {url}")
|
||||
await send_message_safe(
|
||||
@@ -1989,7 +2241,7 @@ class BotInterface:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="❌ Отмена", callback_data="cancel_new_task"))
|
||||
sent = await message.answer(
|
||||
f"🔗 {text[:50]}\n\nШаг 2/2 — введите количество переходов:",
|
||||
f"🔗 {text[:50]}\n\nШаг 2/3 — введите количество переходов:",
|
||||
reply_markup=builder.as_markup()
|
||||
)
|
||||
self._menu_msg[user_id] = sent.message_id
|
||||
@@ -2003,12 +2255,7 @@ class BotInterface:
|
||||
await self._send_temp(message, "Введите целое число больше 0")
|
||||
return
|
||||
|
||||
del self._user_task_state[user_id]
|
||||
await self._clean_prev(message)
|
||||
|
||||
balance = await self.balance_storage.get_balance(user_id)
|
||||
url = state["url"]
|
||||
|
||||
if balance <= 0:
|
||||
await self._send_temp(message, "Недостаточно переходов")
|
||||
return
|
||||
@@ -2016,11 +2263,47 @@ class BotInterface:
|
||||
await self._send_temp(message, f"Недостаточно переходов: нужно {count}, доступно {balance}")
|
||||
return
|
||||
|
||||
if user_id in self._menu_msg:
|
||||
await self._safe_delete(message.bot, message.chat.id, self._menu_msg.pop(user_id))
|
||||
self._user_task_state[user_id] = {**state, "step": "batch", "count": count}
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="❌ Отмена", callback_data="cancel_new_task"))
|
||||
sent = await message.answer(
|
||||
f"✅ Переходов: {count}\n\n"
|
||||
"Шаг 3/3 — сколько кликов в одной серии?\n\n"
|
||||
"Бот делает N кликов подряд, затем ждёт паузу и начинает следующую серию.\n\n"
|
||||
"Диапазон `2-5` — каждая серия будет случайной длины от 2 до 5.\n"
|
||||
"Одно число `3` — всегда ровно 3 клика.",
|
||||
reply_markup=builder.as_markup()
|
||||
)
|
||||
self._menu_msg[user_id] = sent.message_id
|
||||
|
||||
elif state["step"] == "batch":
|
||||
try:
|
||||
if "-" in text:
|
||||
mn, mx = parse_range(text, default_min=1)
|
||||
else:
|
||||
mn = mx = int(text)
|
||||
if mn < 1:
|
||||
raise ValueError
|
||||
except Exception:
|
||||
await self._send_temp(message, "Введите число или диапазон, например: `3` или `2-5`")
|
||||
return
|
||||
|
||||
del self._user_task_state[user_id]
|
||||
await self._clean_prev(message)
|
||||
|
||||
url = state["url"]
|
||||
count = state["count"]
|
||||
series_label = f"{mn}–{mx}" if mn != mx else str(mn)
|
||||
|
||||
task_id = f"u{user_id}_{extract_domain(url)[:12]}_{datetime.now().strftime('%H%M%S')}"
|
||||
params = TaskParams(
|
||||
url=url,
|
||||
task_type="user_visit",
|
||||
max_visits=count,
|
||||
min_series=mn,
|
||||
max_series=mx,
|
||||
user_id=user_id,
|
||||
min_delay=settings.DEFAULT_MIN_DELAY,
|
||||
max_delay=settings.DEFAULT_MAX_DELAY,
|
||||
@@ -2043,7 +2326,9 @@ class BotInterface:
|
||||
await self.storage.save_task(task_id, params)
|
||||
await self._send_temp(
|
||||
message,
|
||||
f"✅ Задача создана\n🔗 {url[:50]}\n🔢 Переходов: {count}",
|
||||
f"✅ Задача создана\n🔗 {url[:50]}\n"
|
||||
f"🔢 Переходов: {count} | Серия: {series_label} кликов\n"
|
||||
f"⏱ Пауза между сериями: {settings.DEFAULT_MIN_DELAY}–{settings.DEFAULT_MAX_DELAY} сек",
|
||||
delay=6
|
||||
)
|
||||
await self._show_user_menu(message, user_id=user_id)
|
||||
|
||||
@@ -46,6 +46,12 @@ class TaskStorage:
|
||||
"chat_id": params.chat_id,
|
||||
"user_id": params.user_id,
|
||||
"visits_percent": params.visits_percent,
|
||||
"min_click_delay": params.min_click_delay,
|
||||
"max_click_delay": params.max_click_delay,
|
||||
"min_series": params.min_series,
|
||||
"max_series": params.max_series,
|
||||
"min_ctr": params.min_ctr,
|
||||
"max_ctr": params.max_ctr,
|
||||
"started_at": params.started_at.isoformat() if params.started_at else None,
|
||||
}
|
||||
|
||||
@@ -73,6 +79,12 @@ class TaskStorage:
|
||||
chat_id=data.get("chat_id"),
|
||||
user_id=data.get("user_id"),
|
||||
visits_percent=data.get("visits_percent", 0.0),
|
||||
min_click_delay=data.get("min_click_delay", 5),
|
||||
max_click_delay=data.get("max_click_delay", 15),
|
||||
min_series=data.get("min_series", data.get("visits_per_link", 1)),
|
||||
max_series=data.get("max_series", data.get("visits_per_link", 1)),
|
||||
min_ctr=data.get("min_ctr", 0.8),
|
||||
max_ctr=data.get("max_ctr", 1.0),
|
||||
)
|
||||
if data.get("started_at"):
|
||||
try:
|
||||
|
||||
+44
-16
@@ -24,6 +24,8 @@ class TaskParams:
|
||||
max_delay: int = 30
|
||||
min_reading: int = 5
|
||||
max_reading: int = 15
|
||||
min_click_delay: int = 5 # задержка между кликами внутри серии
|
||||
max_click_delay: int = 15
|
||||
|
||||
# Лимиты
|
||||
max_visits: Optional[int] = None
|
||||
@@ -37,6 +39,10 @@ class TaskParams:
|
||||
allowed_domains: Optional[List[str]] = None
|
||||
|
||||
visits_percent: float = 0.0 # % от зрителей (0 = использовать visits_per_link)
|
||||
min_series: int = 1 # для user_visit: мин. кликов в серии
|
||||
max_series: int = 3 # для user_visit: макс. кликов в серии
|
||||
min_ctr: float = 0.8 # для twitch_irc: мин. коэф. эффективности клика
|
||||
max_ctr: float = 1.0 # для twitch_irc: макс. коэф. эффективности клика
|
||||
|
||||
# Состояние
|
||||
paused: bool = False
|
||||
@@ -83,6 +89,12 @@ class TaskParams:
|
||||
return reading
|
||||
return random.randint(self.min_reading, self.max_reading)
|
||||
|
||||
def get_click_delay(self) -> int:
|
||||
return random.randint(self.min_click_delay, self.max_click_delay)
|
||||
|
||||
def get_series_size(self) -> int:
|
||||
return random.randint(self.min_series, self.max_series)
|
||||
|
||||
def get_status_emoji(self) -> str:
|
||||
if self.stopped:
|
||||
return "🛑"
|
||||
@@ -109,34 +121,50 @@ class TaskParams:
|
||||
duration = ""
|
||||
if self.started_at and (self.completed or self.stopped):
|
||||
delta = datetime.now() - self.started_at
|
||||
duration = f"├ ⏱️ Длилась: {delta.seconds // 60} мин\n"
|
||||
duration = f"├ 🕐 Длилась: {delta.seconds // 60} мин\n"
|
||||
|
||||
# Домены
|
||||
domains_text = ""
|
||||
if self.allowed_domains:
|
||||
domains_text = f"├ 🌐 Домены: {', '.join(self.allowed_domains)}\n"
|
||||
else:
|
||||
domains_text = "├ 🌐 Домены: все\n"
|
||||
domains_text = (
|
||||
f"├ 🌐 Домены: {', '.join(self.allowed_domains)}\n"
|
||||
if self.allowed_domains else
|
||||
"├ 🌐 Домены: все (без фильтра)\n"
|
||||
)
|
||||
ctr_str = (
|
||||
f"{self.min_ctr}%–{self.max_ctr}%"
|
||||
if self.min_ctr != self.max_ctr
|
||||
else f"{self.min_ctr}%"
|
||||
)
|
||||
ctr_line = f"├ 📊 CTR: {ctr_str} (зрители × CTR% = кликов)"
|
||||
monitor = (
|
||||
f"{self.monitor_minutes} мин" if self.monitor_minutes > 0 else "бесконечно"
|
||||
)
|
||||
|
||||
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"├ 🔗 Ссылок найдено в чате: {self.links_found}\n"
|
||||
f"├ 📊 Всего кликов: {self.total_visits}\n"
|
||||
f"├ ✅ Успешных кликов: {self.successful_visits}\n"
|
||||
f"{ctr_line}\n"
|
||||
f"├ 🔗 Резерв (без GQL): {self.visits_per_link}\n"
|
||||
f"├ ⏱️ Пауза между переходами: {self.min_delay}–{self.max_delay} сек\n"
|
||||
f"├ 📖 Время на странице: {self.min_reading}–{self.max_reading} сек\n"
|
||||
f"├ ⏰ Длительность мониторинга: {monitor}\n"
|
||||
f"{domains_text}"
|
||||
f"{duration}"
|
||||
)
|
||||
else:
|
||||
series_range = (
|
||||
f"{self.min_series}" if self.min_series == self.max_series
|
||||
else f"{self.min_series}–{self.max_series}"
|
||||
)
|
||||
return (
|
||||
f"🌐 `{self.url[:50]}`\n"
|
||||
f"├ Статус: {self.get_status_text()}\n"
|
||||
f"├ 📊 Визитов: {self.total_visits}\n"
|
||||
f"├ ✅ Успешно: {self.successful_visits}\n"
|
||||
f"├ 📊 Выполнено: {self.successful_visits}/{self.max_visits} переходов\n"
|
||||
f"├ 🔢 Кликов в серии: {series_range}\n"
|
||||
f"├ ⏳ Задержка между кликами: {self.min_click_delay}–{self.max_click_delay} сек\n"
|
||||
f"├ ⏱️ Пауза между сериями: {self.min_delay}–{self.max_delay} сек\n"
|
||||
f"├ 📖 Время на странице: {self.min_reading}–{self.max_reading} сек\n"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -8,3 +8,4 @@ aiofiles>=24.1.0
|
||||
playwright-captcha>=0.1.0
|
||||
playwright>=1.49.0
|
||||
camoufox[geoip]>=0.4.0
|
||||
psutil>=5.9.0
|
||||
|
||||
Reference in New Issue
Block a user