Фиксы: автозакрытие браузера после отправки, README, вторая модалка подтверждения hh.ru #2

Open
apuc wants to merge 3 commits from fix/recent into main
pull from: fix/recent
2 changed files with 94 additions and 16 deletions

No files matched your search

+38 -8
View File
@@ -1,9 +1,9 @@
# Автоотклик на hh.ru (полуавтоматический, безопасный) # Автоотклик на hh.ru (полуавтоматический)
Скрипт ищет вакансии на hh.ru по ключевым словам, генерирует персонализированный Скрипт ищет вакансии на hh.ru по ключевым словам, генерирует персонализированный
текст отклика через ИИ (на основе вашего резюме и описания вакансии), открывает текст отклика через ИИ (на основе вашего резюме и описания вакансии), открывает
страницу в браузере и подставляет текст. **Кнопку «Отправить» всегда нажимаете вы** — страницу в браузере, подставляет текст и резюме. Отправка — в один клик
это не нарушает правила hh.ru и не рискует аккаунтом. («Отправить отклик»), либо с проверкой текста перед отправкой.
## Установка ## Установка
@@ -25,17 +25,30 @@ PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.npmmirror.com/binaries/playwright .venv/bin
.venv/bin/python webui.py .venv/bin/python webui.py
``` ```
Откройте **http://localhost:5000** — там можно: Откройте **http://localhost:18091** (порт меняется через `PORT`) — там можно:
- регистрироваться и входить (открытая регистрация, отключается `ALLOW_REGISTRATION=0`)
- создавать аккаунты (имя, регион, поисковые запросы, стиль) - создавать аккаунты (имя, регион, поисковые запросы, стиль)
- загружать резюме файлом (**txt, md, docx, pdf**) - загружать резюме файлом (**txt, md, docx, pdf**)
- сохранять **ключ сессии hh.ru** (cookies из браузера) — нужен для поиска и списка резюме - сохранять **ключ сессии hh.ru** (cookies из браузера) — нужен для поиска и списка резюме
- выбирать **резюме на hh.ru** — оно автоматически подставляется в модалке отклика - выбирать **резюме на hh.ru** — оно автоматически подставляется в модалке отклика
- искать вакансии и видеть их списком (название, компания, зарплата, опыт) - искать вакансии и видеть их списком (название, компания, зарплата, опыт)
- генерировать тексты откликов через ИИ (редактируются перед отправкой) - генерировать тексты откликов через ИИ (редактируются перед отправкой)
- отправлять отклик в один клик: кнопка «Отправить отклик» в колонке действий —
браузер открывает вакансию, вставляет текст, жмёт «Отправить» и закрывается
(скриншот результата показывается в модалке)
- открывать вакансию в браузере с уже вставленным текстом и выбранным резюме - открывать вакансию в браузере с уже вставленным текстом и выбранным резюме
для ручной проверки перед отправкой
- отмечать отправленные — они больше не предлагаются - отмечать отправленные — они больше не предлагаются
### Регистрация
При первом запуске откройте `/register` и создайте логин с паролем (минимум
6 символов) — вы сразу войдёте. Каждый пользователь работает со своими
аккаунтами и лимитом генераций. Открытая регистрация отключается
`ALLOW_REGISTRATION=0` (пользователей тогда создаёт администратор через
`manage_users.py`).
### Ключ сессии hh.ru ### Ключ сессии hh.ru
Чтобы скрипт мог искать вакансии и видеть ваши резюме, нужны cookies из браузера: Чтобы скрипт мог искать вакансии и видеть ваши резюме, нужны cookies из браузера:
@@ -89,13 +102,30 @@ accounts/
1. Скрипт парсит страницу поиска hh.ru по вашим запросам 1. Скрипт парсит страницу поиска hh.ru по вашим запросам
2. Для каждой новой вакансии получает описание 2. Для каждой новой вакансии получает описание
3. ИИ генерирует текст отклика (вакансия + ваше резюме + стиль) 3. ИИ генерирует текст отклика (вакансия + ваше резюме + стиль)
4. Браузер открывает вакансию, жмёт «Откликнуться», вставляет текст 4. **Один клик**: кнопка «Отправить отклик» — браузер открывает вакансию,
5. **Вы проверяете текст и нажимаете «Отправить»** жмёт «Откликнуться», вставляет текст, выбирает резюме, жмёт «Отправить»
6. Отмечаете отправленное — скрипт переходит к следующей и автоматически закрывается (скриншот результата — в модалке)
5. **С проверкой**: «Открыть в браузере» — вы проверяете текст и отправляете
сами, либо жмёте «Отправить отклик» в модалке скриншота
6. Отправленные вакансии отмечаются и больше не предлагаются
## Переменные окружения (.env)
| Переменная | По умолчанию | Описание |
|---|---|---|
| `LLM_API_KEY` | — | Ключ API для генерации текстов откликов |
| `SESSION_SECRET` | `dev-secret-change-me` | Секрет сессий Flask (обязательно сменить) |
| `PORT` | `18091` | Порт веб-интерфейса |
| `HOST` | `127.0.0.1` | Адрес прослушивания |
| `ALLOW_REGISTRATION` | `1` | Открытая регистрация (`0` — выключить) |
| `REGISTER_LLM_LIMIT` | `50` | Лимит генераций для новых пользователей |
| `HH_HEADLESS` | `0` | `1` — браузер без окна (для сервера без дисплея) |
| `PROXY_URL` | — | Прокси для браузера: `http://LOGIN:PASS@HOST:PORT` |
## Важно ## Важно
- Скрипт не нарушает правила hh.ru: финальное действие всегда за человеком - Отправка в один клик автоматически жмёт «Отправить» — используйте с осторожностью
и не чаще, чем позволяет hh.ru (паузы между страницами уже стоят)
- Не запускайте поиск слишком часто — между страницами есть паузы - Не запускайте поиск слишком часто — между страницами есть паузы
- Если hh.ru покажет капчу — скрипт остановится, решите капчу вручную и запустите снова - Если hh.ru покажет капчу — скрипт остановится, решите капчу вручную и запустите снова
- `.env` содержит API-ключ — не коммитьте его в git (уже добавлен в `.gitignore`) - `.env` содержит API-ключ — не коммитьте его в git (уже добавлен в `.gitignore`)
+56 -8
View File
@@ -37,6 +37,15 @@ SUBMIT_BUTTONS = [
'[data-qa="vacancy-response-popup-form"] button[type="submit"]', '[data-qa="vacancy-response-popup-form"] button[type="submit"]',
'button:has-text("Отправить")', 'button:has-text("Отправить")',
] ]
# Кнопки подтверждения во ВТОРОЙ модалке hh.ru: после клика «Отправить»
# иногда появляется модалка «Отклик будет отправлен» с кнопкой
# «Отправить отклик» — её нужно нажать ещё раз.
CONFIRM_BUTTONS = [
'button:has-text("Отправить отклик")',
'button:has-text("Подтвердить")',
'button:has-text("Да, отправить")',
'[data-qa="vacancy-response-confirm"]',
]
# Живые браузерные контексты и страницы: {(user, account): ...} # Живые браузерные контексты и страницы: {(user, account): ...}
# Доступны ТОЛЬКО из браузерного потока. # Доступны ТОЛЬКО из браузерного потока.
@@ -199,6 +208,26 @@ def _save_screenshot(page, screenshot_dir, vid):
return None return None
def _click_second_confirm(page):
"""После клика «Отправить» hh.ru иногда показывает вторую модалку
подтверждения («Отклик будет отправлен») — жмём и её.
Возвращает True, если вторая кнопка найдена и нажата."""
try:
page.wait_for_timeout(1500)
except Exception:
return False
for sel in CONFIRM_BUTTONS:
try:
btn = page.locator(sel).first
if btn.is_visible(timeout=1500):
btn.click()
print(" ✓ Вторая модалка подтверждения — кнопка нажата")
return True
except Exception:
continue
return False
def open_response_page(account_dir, user, account, vacancy, message, def open_response_page(account_dir, user, account, vacancy, message,
resume_id=None, screenshot_dir=None, submit=False): resume_id=None, screenshot_dir=None, submit=False):
"""Открывает вакансию, жмёт «Откликнуться», выбирает резюме, вставляет текст. """Открывает вакансию, жмёт «Откликнуться», выбирает резюме, вставляет текст.
@@ -277,11 +306,21 @@ def open_response_page(account_dir, user, account, vacancy, message,
if not submitted: if not submitted:
shot = _save_screenshot(page, screenshot_dir, vacancy["id"]) shot = _save_screenshot(page, screenshot_dir, vacancy["id"])
return False, "Не нашёл кнопку «Отправить» — проверьте скриншот", shot return False, "Не нашёл кнопку «Отправить» — проверьте скриншот", shot
# Ждём подтверждение и снимаем скриншот результата # hh.ru иногда показывает вторую модалку подтверждения — жмём и её
page.wait_for_timeout(2500) try:
shot = _save_screenshot(page, screenshot_dir, vacancy["id"]) _click_second_confirm(page)
# Отклик отправлен — закрываем браузер автоматически except Exception:
_close_context(key) pass
# Ждём подтверждение и снимаем скриншот результата.
# После отправки hh.ru может закрыть модалку/перезагрузить страницу —
# ловим любые ошибки, но браузер закрываем ВСЕГДА (finally).
try:
page.wait_for_timeout(2500)
shot = _save_screenshot(page, screenshot_dir, vacancy["id"])
except Exception:
shot = None
finally:
_close_context(key)
return True, "Отклик отправлен", shot return True, "Отклик отправлен", shot
shot = _save_screenshot(page, screenshot_dir, vacancy["id"]) shot = _save_screenshot(page, screenshot_dir, vacancy["id"])
@@ -299,9 +338,18 @@ def confirm_send(user, account, vid):
btn = page.locator(sel).first btn = page.locator(sel).first
if btn.is_visible(timeout=3000): if btn.is_visible(timeout=3000):
btn.click() btn.click()
page.wait_for_timeout(1500) try:
# Отклик отправлен — закрываем браузер автоматически # Вторая модалка подтверждения (если появилась)
_close_context(key) _click_second_confirm(page)
except Exception:
pass
try:
page.wait_for_timeout(1500)
except Exception:
pass
finally:
# Отклик отправлен — закрываем браузер автоматически
_close_context(key)
return True, "Кнопка «Отправить» нажата. Проверьте статус отклика на hh.ru." return True, "Кнопка «Отправить» нажата. Проверьте статус отклика на hh.ru."
except Exception: except Exception:
continue continue