93 lines
3.5 KiB
Python
93 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Работа с браузером: открытие вакансии, клик «Откликнуться», вставка текста."""
|
|
import os
|
|
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
# Селекторы hh.ru (с запасными вариантами)
|
|
RESPONSE_BUTTONS = [
|
|
'[data-qa="vacancy-response-link-top"]',
|
|
'[data-qa="vacancy-response-link"]',
|
|
'button:has-text("Откликнуться")',
|
|
]
|
|
MESSAGE_FIELDS = [
|
|
'textarea[data-qa="vacancy-response-letter"]',
|
|
'textarea[data-qa="vacancy-response-popup-form"] textarea',
|
|
'textarea',
|
|
]
|
|
|
|
# Живые браузерные контексты по аккаунтам: {account_name: context}
|
|
_browsers = {}
|
|
|
|
|
|
def launch_context(account_dir):
|
|
"""Запускает браузер с профилем аккаунта (видимое окно)."""
|
|
p = sync_playwright().start()
|
|
context = p.chromium.launch_persistent_context(
|
|
user_data_dir=os.path.join(account_dir, "profile"),
|
|
headless=False,
|
|
viewport={"width": 1400, "height": 900},
|
|
)
|
|
return context
|
|
|
|
|
|
def get_context(account_dir, account_name):
|
|
"""Возвращает живой контекст аккаунта, создавая при необходимости."""
|
|
if account_name not in _browsers:
|
|
_browsers[account_name] = launch_context(account_dir)
|
|
return _browsers[account_name]
|
|
|
|
|
|
def close_context(account_name):
|
|
if account_name in _browsers:
|
|
try:
|
|
_browsers[account_name].close()
|
|
except Exception:
|
|
pass
|
|
_browsers.pop(account_name, None)
|
|
|
|
|
|
def open_response_page(context, vacancy, message, resume_id=None):
|
|
"""Открывает вакансию, жмёт «Откликнуться», выбирает резюме и вставляет текст.
|
|
Возвращает (ok, note): ok=True если текст вставлен автоматически."""
|
|
page = context.new_page()
|
|
page.goto(vacancy["url"], timeout=45000)
|
|
page.wait_for_timeout(2500)
|
|
|
|
# Кнопка «Откликнуться»
|
|
clicked = False
|
|
for sel in RESPONSE_BUTTONS:
|
|
try:
|
|
btn = page.locator(sel).first
|
|
if btn.is_visible(timeout=3000):
|
|
btn.click()
|
|
clicked = True
|
|
break
|
|
except Exception:
|
|
continue
|
|
if not clicked:
|
|
return False, "Не нашёл кнопку «Откликнуться» — сделайте это вручную"
|
|
|
|
# Ждём модалку
|
|
page.wait_for_timeout(2000)
|
|
|
|
# Выбор резюме (радио-кнопка name="resumeId" value="{hash}")
|
|
if resume_id:
|
|
try:
|
|
radio = page.locator(f'input[name="resumeId"][value="{resume_id}"]').first
|
|
if radio.is_visible(timeout=3000):
|
|
radio.check(force=True)
|
|
print(f" ✓ Резюме {resume_id} выбрано")
|
|
except Exception:
|
|
print(" ⚠ Не нашёл радио-кнопку резюме — выберите вручную")
|
|
|
|
# Вставляем текст
|
|
for sel in MESSAGE_FIELDS:
|
|
try:
|
|
field = page.locator(sel).first
|
|
if field.is_visible(timeout=3000):
|
|
field.fill(message)
|
|
return True, "Текст вставлен. Проверьте и нажмите «Отправить»"
|
|
except Exception:
|
|
continue
|
|
return False, "Не нашёл поле сообщения — вставьте текст вручную" |