""" vprok.ru Windows Playwright Client ==================================== Каждые 30 секунд проверяет сервер-реле на наличие задания. Если задание есть — открывает vprok.ru и добавляет товары в корзину. Требования: pip install playwright requests playwright install chromium """ import time import requests from playwright.sync_api import sync_playwright, TimeoutError as PwTimeout # ─── Настройки ─────────────────────────────────────────────────────────────── SERVER_URL = "http://185.130.212.192:5000" API_KEY = "vprok2024secret" POLL_INTERVAL = 30 # секунды между проверками # ───────────────────────────────────────────────────────────────────────────── HEADERS = {"X-Api-Key": API_KEY, "Content-Type": "application/json"} def log(msg: str): ts = time.strftime("%H:%M:%S") print(f"[{ts}] {msg}", flush=True) def fetch_task(): """Забрать задание с сервера. Возвращает dict или None.""" try: r = requests.get(f"{SERVER_URL}/task", headers=HEADERS, timeout=10) if r.status_code == 204: return None if r.status_code == 200: return r.json() log(f"⚠️ Неожиданный статус от сервера: {r.status_code}") return None except Exception as e: log(f"❌ Ошибка подключения к серверу: {e}") return None def report_done(status: str, message: str): """Отправить результат на сервер.""" try: requests.post( f"{SERVER_URL}/task/done", headers=HEADERS, json={"status": status, "message": message}, timeout=10 ) log(f"✅ Результат отправлен: [{status}] {message}") except Exception as e: log(f"❌ Не удалось отправить результат: {e}") def add_item_to_cart(page, query: str, qty: int) -> tuple[bool, str]: """ Ищет товар на vprok.ru и добавляет qty раз в корзину. Возвращает (success, message). """ log(f"🔍 Ищу: «{query}» (кол-во: {qty})") try: # Переходим на главную и кликаем поиск page.goto("https://www.vprok.ru/", wait_until="domcontentloaded", timeout=30000) page.wait_for_timeout(1500) # Находим поле поиска и вводим запрос search_input = page.locator( "input[placeholder*='оиск'], input[type='search'], [data-testid='search-input'], .search__input" ).first search_input.click() search_input.fill("") search_input.type(query, delay=60) search_input.press("Enter") log(f" ⏳ Жду результаты поиска...") page.wait_for_load_state("domcontentloaded") page.wait_for_timeout(2500) # Ищем кнопки добавления — берём первый доступный товар # vprok.ru использует разные классы, пробуем несколько вариантов add_button_selectors = [ "button:has-text('Добавить'):not([disabled])", "button:has-text('В корзину'):not([disabled])", "[class*='add-to-cart']:not([disabled])", "[data-testid*='add']:not([disabled])", ] add_btn = None for selector in add_button_selectors: btns = page.locator(selector) count = btns.count() if count > 0: # Проверяем что рядом нет текста "Нет в наличии" for i in range(min(count, 5)): btn = btns.nth(i) try: # Ищем ближайший контейнер товара product_card = btn.locator("xpath=ancestor::*[contains(@class,'product') or contains(@class,'item') or contains(@class,'card')][1]") card_text = product_card.inner_text(timeout=1000) if product_card.count() > 0 else "" if "нет в наличии" in card_text.lower() or "недоступен" in card_text.lower(): log(f" ⏭️ Товар #{i+1} недоступен, пропускаю") continue add_btn = btn break except Exception: add_btn = btn break if add_btn: break if add_btn is None: return False, f"Кнопка 'Добавить' не найдена для «{query}»" # Добавляем нужное количество раз for n in range(qty): try: add_btn.scroll_into_view_if_needed() add_btn.click(timeout=5000) log(f" ➕ Добавлено ({n+1}/{qty})") page.wait_for_timeout(800) # После первого клика кнопка может смениться на +/- if n < qty - 1: plus_btn = page.locator( "button:has-text('+'), [aria-label*='увеличить'], [class*='plus']:not([disabled])" ).first if plus_btn.count() > 0 and plus_btn.is_visible(): add_btn = plus_btn except PwTimeout: log(f" ⚠️ Таймаут при клике на кнопку (попытка {n+1})") return True, f"«{query}» добавлен(о) ×{qty}" except Exception as e: return False, f"Ошибка при обработке «{query}»: {e}" def process_task(task: dict): """Обрабатывает задание: открывает браузер и добавляет все товары.""" items = task.get("items", []) task_id = task.get("id", "?") log(f"📦 Задание #{task_id} получено. Товаров: {len(items)}") results = [] errors = [] with sync_playwright() as pw: log("🌐 Запускаю браузер...") browser = pw.chromium.launch( headless=False, args=["--start-maximized"] ) context = browser.new_context( viewport=None, # использовать размер окна locale="ru-RU", timezone_id="Europe/Moscow" ) page = context.new_page() for item in items: query = item.get("query", "").strip() qty = int(item.get("qty", 1)) if not query: continue success, msg = add_item_to_cart(page, query, qty) results.append(msg) if not success: errors.append(msg) log(f" {'✅' if success else '❌'} {msg}") time.sleep(1) log("🛒 Все товары обработаны. Перехожу в корзину...") try: # Пытаемся открыть корзину cart_link = page.locator("a[href*='cart'], a[href*='basket'], [data-testid*='cart']").first if cart_link.count() > 0: cart_link.click() page.wait_for_load_state("domcontentloaded") else: page.goto("https://www.vprok.ru/cart", wait_until="domcontentloaded", timeout=15000) except Exception: pass log("👁️ Браузер оставлен открытым — проверьте корзину!") log(" (закройте браузер вручную когда будете готовы)") # Ждём пока пользователь не закроет браузер try: page.wait_for_event("close", timeout=0) # бесконечно except Exception: pass # Отправляем результат if errors: status_str = "partial" message = f"Выполнено с ошибками: {'; '.join(errors)}" else: status_str = "ok" message = f"Все {len(results)} товар(ов) добавлены: {', '.join(results)}" report_done(status_str, message) try: browser.close() except Exception: pass def main(): log("🚀 vprok.ru клиент запущен") log(f" Сервер: {SERVER_URL}") log(f" Интервал проверки: {POLL_INTERVAL} сек") log(" Нажмите Ctrl+C для остановки\n") while True: task = fetch_task() if task: process_task(task) else: log(f"💤 Нет заданий, жду {POLL_INTERVAL} сек...") time.sleep(POLL_INTERVAL) if __name__ == "__main__": try: main() except KeyboardInterrupt: log("\n👋 Остановка по запросу пользователя")