fix command ana params

This commit is contained in:
Yuriy Yuriev
2026-05-18 18:35:09 +07:00
parent 2f163aaa3e
commit ca8a386f82
5 changed files with 430 additions and 95 deletions
+9
View File
@@ -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
View File
@@ -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)
+12
View File
@@ -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
View File
@@ -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"
)
+1
View File
@@ -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