""" farmer.py — запускает Pyrogram-клиенты для аккаунтов, зарегистрированных через меню controller.py (/addaccount). Никакого ручного config.json — все данные (api_id/api_hash/session_name/цель/команда/интервал) в БД. Раз в POLL_NEW_ACCOUNTS секунд проверяет базу на новые готовые аккаунты (session_ready=1) и запускает для них клиент "на лету", без рестарта. """ import asyncio import logging import os import random import time from pyrogram import Client, filters from pyrogram.handlers import MessageHandler from pyrogram.errors import FloodWait import db logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", ) log = logging.getLogger("farmer") CHECK_INTERVAL = 30 # проверка паузы/таймера конкретного аккаунта, сек POLL_NEW_ACCOUNTS = 60 # как часто искать новые аккаунты в базе, сек MIN_JITTER = 5 # случайная задержка перед отправкой, сек MAX_JITTER = 120 def make_response_handler(name): async def handler(client, message): state = db.get_state(name) if not state: return target = state["target_chat"] chat_username = f"@{message.chat.username}" if message.chat.username else None chat_id = str(message.chat.id) if target and target in (chat_username, chat_id): text = message.text or message.caption or "" if text: db.save_response(name, text) return handler async def account_loop(app: Client, name: str): while True: try: state = db.get_state(name) if state is None: log.warning("Аккаунт %s исчез из базы, останавливаю цикл", name) return if state["paused"] or not state["target_chat"] or not state["farm_command"]: await asyncio.sleep(CHECK_INTERVAL) continue interval_seconds = state["interval_hours"] * 3600 elapsed = time.time() - state["last_farm_ts"] if elapsed >= interval_seconds: jitter = random.uniform(MIN_JITTER, MAX_JITTER) log.info("[%s] Пора фармить, жду %.0f сек (jitter)", name, jitter) await asyncio.sleep(jitter) await app.send_message(state["target_chat"], state["farm_command"]) db.update_last_farm(name, time.time()) log.info("[%s] Отправлено '%s' в %s", name, state["farm_command"], state["target_chat"]) await asyncio.sleep(CHECK_INTERVAL) except FloodWait as e: log.warning("[%s] FloodWait: жду %s сек", name, e.value) await asyncio.sleep(e.value + 5) except Exception: log.exception("[%s] Ошибка в цикле фарма, повтор через 60 сек", name) await asyncio.sleep(60) async def start_account(name: str, state: dict) -> Client: app = Client( name=state["session_name"], api_id=state["api_id"], api_hash=state["api_hash"], workdir=os.path.dirname(__file__), ) await app.start() app.add_handler(MessageHandler(make_response_handler(name), filters.incoming)) log.info("Аккаунт %s запущен", name) return app async def main(): db.init_db() running = {} # name -> asyncio.Task log.info("Farmer запущен, ищу зарегистрированные аккаунты...") while True: try: states = db.get_all_states() for state in states: name = state["name"] if not state["session_ready"]: continue # регистрация ещё не завершена (нет кода/2FA) if name in running and not running[name].done(): continue try: app = await start_account(name, state) except Exception: log.exception("Не удалось запустить аккаунт %s, попробую позже", name) continue running[name] = asyncio.create_task(account_loop(app, name)) except Exception: log.exception("Ошибка при опросе новых аккаунтов") await asyncio.sleep(POLL_NEW_ACCOUNTS) if __name__ == "__main__": asyncio.run(main())