Metadata-Version: 2.4
Name: hh-applicant-tool
Version: 2.0.12
Summary: HH-Applicant-Tool: An automation utility for HeadHunter (hh.ru) designed to streamline the job search process by auto-applying to relevant vacancies and periodically refreshing resumes to stay at the top of recruiter searches.
Author: Senior YAML Developer
Author-email: yamldeveloper@proton.me
Requires-Python: >=3.11,<4.0
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Provides-Extra: ui
Requires-Dist: brotli (>=1.2.0,<2.0.0)
Requires-Dist: cryptography (>=50.0.2,<51.0.0)
Requires-Dist: pillow (>=12.3.0,<13.0.0)
Requires-Dist: pywebview (>=5.0) ; extra == "ui"
Requires-Dist: requests[socks] (>=2.32.3,<3.0.0)
Requires-Dist: tomli-w (>=1.2.0,<2.0.0)
Project-URL: Homepage, https://github.com/s3rgeym/hh-applicant-tool
Project-URL: Repository, https://github.com/s3rgeym/hh-applicant-tool
Description-Content-Type: text/markdown

# HH Applicant Tool

![Publish to PyPI](https://github.com/s3rgeym/hh-applicant-tool/actions/workflows/publish.yml/badge.svg)
[![PyPi Version](https://img.shields.io/pypi/v/hh-applicant-tool)](<>)
[![Python Versions](https://img.shields.io/pypi/pyversions/hh-applicant-tool.svg)](<>)
[![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/s3rgeym/hh-applicant-tool)](<>)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/hh-applicant-tool)](<>)
[![Total Downloads](https://static.pepy.tech/badge/hh-applicant-tool)](<>)

> [!IMPORTANT]
> После перехода на версию 2.0.0 часть функционала может не работать. Если
> что-то не работает, делаете fork, выполняете git clone, берете Claude/Chat GPT/Gemini просите
> его поправить код, в pyproject.toml повышаете версию, делаете push, потом
> создаете PR (Pull Request), и помогаете всем тысячам пользователей утилиты...
> Только реквест должен быть не большим. Не присылайте исправления на тысячи
> строк. Они часто ломают логику. Ну и, конечно, вы должны сами понимать, что
> сгенерированный код делает, я только недавно из проекта вычистил 5000 строк
> нейробреда.

> [!IMPORTANT]
> [Группа поддержки в Телеграме (для всех моих утилит и просто для общения)](https://t.me/s3rgeym_chat)

> [!IMPORTANT]
> Ищу почасовую или проектную [@feedback_s3rgeym_bot](https://t.me/feedback_s3rgeym_bot) (Python, Vue.js, DevOps). Не пишите туда с вопросами по утилите. Для них есть канал!!! Так же буду благодарен, если посоветуете меня в качестве сись-пись админа, погромиста и девжопса своим знакомым, начальству... Имею огромный опыт фриланса (это может и не нравится тупопездым идиотинам, мнящим себя дохуя специалистками по подбору), но наебизнес ценит тех, кто умеет решать проблемы быстро, не задавая лишние вопросы и без надзирателей... С овчарами общаться желания нет (не знаю как другим, но мне тошнит от них).

> [!IMPORTANT]
> Проект запрещено использовать в коммерческих проектах, а так же любых проектах
> с закрытым исходным кодом.
> Остерегайтесь мошенников! Существует мошеннический сайт HH Applicant Tool,
> который использует название проекта для привлечения клиентов. К проекту
> отношения он не имеет!!! Все барыги торгующие моей утилитой прокляты в веках!

<details>
<summary>Прочтите обращение <s>основателя Wikipedia</s> разработчика</summary>
> [!IMPORTANT]
> Прежде чем задавать долбоебские вопросы, прочитай весь текст ниже, особенно пункт [ответы на заебавшие вопросы](#ответы-на-заебавшие-вопросы).

> [!NOTE]
> Советую попробовать [HH AI Responder](https://github.com/s3rgeym/hh-ai-responder/tree/main) — это
> программа, которая не требует установки и зависимостей (просто скачал и
> запусти), написанная на Go под Linux, Mac, Windows и Android, которая
> автоматически рассылает отклики и общается с хрюшами и кабанычами через ХуИИ.
> Так же можете дописать [autoresponder](./src/hh_applicant_tool/operations/autoresponder.py), чтобы
> добавить автоответчик по чатам и в HH Applicant Tool, натравив на тот нейронку
> (я половину бреда чатагпт вычистил вручную).

> [!NOTE]
> Настоящие погромисты пишут только в <s>блокноте</s> <s>vim</s> neovim. ЗАЦЕНИТЕ МОЙ [КОНФИГ NEOVIM](https://github.com/s3rgeym/nvim-config) НА КОТОРОМ Я ФАКТИЧЕСКИ РЕДАКТИРУЮ ВЕСЬ КОД, УДАЛИВ ДАВНО VS CODE И ПР ЗАЛУПУ!!!

</details>

<div align="center">
  <img src="https://github.com/user-attachments/assets/29d91490-2c83-4e3f-a573-c7a6182a4044" width="500">
</div>

> Авторские права на изображение принадлежат Кандинскому... нет, не человеку, а
> боту от говносбера

### ☕ Поддержать проект

[![Donate BTC](https://img.shields.io/badge/Donate-BTC-orange?style=for-the-badge&logo=bitcoin&logoColor=white)](bitcoin:BC1QWQXZX6D5Q0J5QVGH2VYXTFXX9Y6EPPGCW3REHS?label=%D0%94%D0%BB%D1%8F%20%D0%BF%D0%BE%D0%B6%D0%B5%D1%80%D1%82%D0%B2%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B9)

**BTC Address:** `BC1QWQXZX6D5Q0J5QVGH2VYXTFXX9Y6EPPGCW3REHS` (спасибо за донаты — это единственное, что двигает разработку, так что не стесняйтесь и шлите их чаще, вы ЭКОНОМИТЕ КУЧУ ДЕНЕГ с учетом жадности владельцев сервисов для откликов)

---

## Особенности и функционал

- 🐍 Утилита написана на Python, а значит может быть запущена на любой системе,
  куда его можно установить (даже на Android)
- 🤖 Поддержка ИИ (далее по тексту хуИИ)
- 📤 Рассылка откликов
- ✍️ Автоматическая генерация сопроводительных
- 🧩 Решение тестов при отклике и распознание капчи
- 💬 Автоответчик в чате с работодателем, в тч для общения с ботом рекрутером
- 📧 Дополнительная рассылка писем на Email работодателям
- 👥 Поддержка нескольких аккаунтов через профили
- 📇 Сохранение контактов работодателей и информации о вакансиях
- 🔒 Данные хранятся локально, никаких скрытых зондов и отправки данных на
  сторонние сервера, а значит ваши данные не утекут в результате взлома или не будут переданы различным мошенникам
- 📖 Открытый исходный код
- 🖥️ Может запускаться на сервере. В отличии от других реализаций не использует Headless Chromium, поэтому потребляет минимальное количество ресурсов, что критично для бюджетных серверов. Хромиум сильно грузит процессор, потребляет сотни мегабайт оперативной памяти при открытии hh и требует 1-2 гигабайт свободного места. Так что утилите хватит и VDS за пару сотен.
- 🎨 Наличие простого UI
- 🎁 И другой функционал абсолютно бесплатно!

---

## Содержание

- [HH Applicant Tool](#hh-applicant-tool)
  - [☕ Поддержать проект](#-поддержать-проект)
  - [Особенности и функционал](#особенности-и-функционал)
  - [Содержание](#содержание)
  - [Установка](#установка)
  - [Авторизация](#авторизация)
  - [Описание команд](#описание-команд)
  - [Капча](#капча)
  - [Запуск через Docker](#запуск-через-docker)
  - [Настройки и данные приложения](#настройки-и-данные-приложения)
    - [Конфигурационный файл](#конфигурационный-файл)
    - [auth.json](#authjson)
    - [cookies.txt](#cookiestxt)
    - [Логи](#логи)
    - [База данных](#база-данных)
  - [Дополнительные настройки](#дополнительные-настройки)
    - [Переменные окружения](#переменные-окружения)
  - [Шаблоны сообщений](#шаблоны-сообщений)
  - [Интеграция и использование в скриптах](#интеграция-и-использование-в-скриптах)
  - [Принцип работы](#принцип-работы)
  - [Разработка](#разработка)
  - [Ответы на заебавшие вопросы](#ответы-на-заебавшие-вопросы)
  - [Связанные проекты](#связанные-проекты)
  - [Лицензионное соглашение (Limited Non-Commercial License)](#лицензионное-соглашение-limited-non-commercial-license)

---

## Установка

Утилиту можно установить из `PyPi` или скачать из репозитория. Из остальных мест ее скачивать не рекомендуется.

Установка:

```bash
# Стандартный pip (в Linux только в виртуальное окружение получится установить)
$ pip install hh-applicant-tool

# Чтобы утилита поддерживала команду ui
$ pip install 'hh-applicant-tool[ui]'

# Но лучше использовать pipx
$ pipx install hh-applicant-tool

# Последнюю версию можно установить через git
$ pipx install "git+https://github.com/s3rgeym/hh-applicant-tool"

# Для обновления до новой версии
$ pipx upgrade hh-applicant-tool
```

pipx добавляет исполняемый файл `hh-applicant-tool` в `~/.local/bin`, делая эту команду доступной. Путь до `~/.local/bin` должен быть в `$PATH` (в большинстве дистрибутивов он добавлен).

Использование виртуальных окружений в Linux/Mac:

```sh
mkdir -p ~/.venvs
python -m venv ~/.venvs/hh-applicant-tool
# Это придется делать постоянно, чтобы команда hh-applicant-tool стала доступна
.  ~/.venvs/hh-applicant-tool/bin/activate
pip install hh-applicant-tool
```

Отдельно я распишу процесс установки в **Windows** в подробностях:

- Для начала поставьте последнюю версию **Python 3** любым удобным способом.
- Запустите **PowerShell** (не `CMD.EXE`, блять, а именно поверщель) от Администратора и выполните:

  ```ps
  Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted
  ```

  Данная политика разрешает текущему пользователю (от которого зашли) запускать скрипты. Без нее не будут работать виртуальные окружения.

Далее можно поставить `pipx` и вернуться к инструкции вверху раздела:

- Всё так же от администратора выполните:

  ```ps
  python -m pip install --user pipx
  ```

  А затем:

  ```ps
  python -m pipx ensurepath
  ```

- Перезапускаем Terminal/Powershell и проверяем:

  ```ps
  pipx -h
  ```

С использованием вирт. окружений:

- Создайте и активируйте виртуальное окружение:

  ```ps
  PS> python -m venv hh-applicant-venv
  PS> .\hh-applicant-venv\Scripts\activate
  ```

- Поставьте все пакеты в виртуальное окружение `hh-applicant-venv`:

  ```ps
  (hh-applicant-venv) PS> pip install hh-applicant-tool
  ```

- Проверьте, работает ли оно:

  ```ps
  (hh-applicant-venv) PS> hh-applicant-tool -h
  ```

- В случае неудачи вернитесь к первому шагу.
- Для последующих запусков сначала активируйте виртуальное окружение.

---

## Авторизация

Авторизация по коду:

```bash
$ hh-applicant-tool authorize 79012345678
📱 Введите полученный код из SMS: 1488
🎉 Авторизация прошла успешно!
```

Проверка авторизации:

```bash
$ hh-applicant-tool whoami
🆔 27405918 Кузнецов Андрей Владимирович [ 📄 1 | 👁️ +115 | ✉️ +28 ]
```

В случае успешной авторизации токены будут сохранены в [auth.json](#authjson):

Токен доступа выдается на две недели.  <s>Для его обновления нужно выполнить:

```bash
hh-applicant-tool refresh-token
```
</s> Он обновляется автоматически. 

Помните, что у `refresh_token` тоже есть время жизни, поэтому может потребоваться полная авторизация, если долго не запускать утилиту + время жизни cookies может быть меньше времени жизни токенов, поэтому часть функционала, требующая авторизации на сайте может отваливаться, поэтому в любой непонятной ситуации следует попробовать авторизоваться по-новой.

---

## Описание команд

Примеры команд:

```bash
# Общий вид: сначала глобальные настройки, затем команда и её аргументы
$ hh-applicant-tool [options] <operation> [args]

# Справка по глобальным флагам и список операций
$ hh-applicant-tool -h

# Справка по операции
$ hh-applicant-tool authorize -h

# Авторизуемся
$ hh-applicant-tool authorize

# Авторизация с использованием другого профиля
$ hh-applicant-tool --profile profile123 authorize

# Рассылаем заявки
$ hh-applicant-tool apply-vacancies

# Для тестирования поисковой строки и других параметров, используйте --dry-run.
# С ним отклики не отправляются, а лишь выводятся сообщения
$ hh-applicant-tool -vv apply-vacancies --search "Python программист" --per-page 3 --total-pages 1 --dry-run

# Фильтруем вакансии по исключаемым словам (fullstack, junior, php и т.п.)
$ hh-applicant-tool apply-vacancies --search "Python backend" --excluded-filter "fullstack,junior,php,java" --dry-run

# Поднимаем резюме
$ hh-applicant-tool update-resumes

# Ответить работодателям
$ hh-applicant-tool reply-employers

# Просмотр лога в реальном времени
$ hh-applicant-tool log -f

# Посмотреть содержимое конфига
$ hh-applicant-tool config

# Редактировать конфиг в стандартном редакторе
$ hh-applicant-tool config -e

# Вывести значение из конфига
$ hh-applicant-tool config -k token.access_token

# Установить значение в конфиге, например, socks-прокси
# Учтите, что при таком способе изменения конфига, исчезнут все комментарии в нем!!!
$ hh-applicant-tool config -s proxy_url socks5h://localhost:1080

# Удалить значение из конфига
# Эта операция тоже форматирует конфиг, удаляя из него комментарии
$ hh-applicant-tool config -u proxy_url

# Утилита все данные об откликах хранит в SQLite
$ hh-applicant-tool query 'select count(*) from vacancy_contacts;'

# Экспорт контактов в csv
$ hh-applicant-tool query 'select * from vacancy_contacts' --csv -o
contacts.csv

# Выполнение запросов в интерактивном режиме
$ hh-applicant-tool query

# Чистим отказы
$ hh-applicant-tool clear-negotiations

# При обновлении может сломаться схема БД, для ее починки нужно выполнить
# поочерёдно все миграции, добавленные после выхода последней версии
$ hh-applicant-tool migrate
List of migrations:

  [1]: 2026-01-07

Choose migration [1] (Keep empty to exit): 1
✅ Success!

# Вывести все настройки
$ hh-applicant-tool settings

# Получить значение по ключу
$ hh-applicant-tool settings auth.username

# Установить email, используемый для автологина
$ hh-applicant-tool settings auth.username 'user@example.com'
```

Глобальные настройки:

- `-v` используется для вывода отладочной информации. Два таких флага, например, выводят запросы к **API**.
- `-c <path>` путь до каталога, где хранятся конфигурации.
- `--profile <profile-id>` можно указать профиль, данные которого будут храниться в подкаталоге.
- `--use-kitty`/`--use-sixel` использовать опр протокол для отображения капчи
  в терминале. Если не указывать, то капча будет отображена через `tkinter`, если тот установлен....
- `--use-ai`/`--ai` использовать автоматизацию распознания капч и прочих
  действий через ИИ.

| Операция                           | Описание                                                                                                                                                                                                                                                                                                |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **authorize**, **auth**            | Авторизация на hh.ru. Введенные логин и пароль "запоминаются" и будут использованы при следующем вызове команды.                                                                                                                                                                                        |
| **logout**                         | Выйти из профиля (отозвать токен)                                                                                                                                                                                                                                                                       |
| **ui**                             | Запуск UI для рассылки и управления откликами                                                                                                                                                                                                                                                           |
| **whoami**, **id**                 | Выводит информацию об авторизованном пользователе                                                                                                                                                                                                                                                       |
| **list-resumes**, **list**, **ls** | Список резюме                                                                                                                                                                                                                                                                                           |
| **update-resumes**, **update**     | Обновить все резюме. Аналогично нажатию кнопки «Обновить дату».                                                                                                                                                                                                                                         |
| **clone-resume**                   | Клонировать резюме                                                                                                                                                                                                                                                                                      |
| **apply-vacancies**, **apply**     | Откликнуться на подходящие вакансии. Если указана строка для поиска (`--search`), то поиск будет произведен по вакансиям, иначе по списку рекомендованных вакансий. Лимит = 200 в день. На HH есть спам-фильтры, так что лучше не рассылайте отклики со ссылками, иначе рискуете попасть в теневой бан. |
| **reply-employers**, **reply**     | Ответить во все чаты с работодателями, где нет ответа либо не прочитали ваш предыдущий ответ                                                                                                                                                                                                            |
| **autoresponder**                  | ИИ-автоответчик, который отвечает в чатах с работодателями в тч на тесты (к удалению, так как некому его дописать)                                                                                                                                                                                      |
| **clear-negotiations**             | Отмена откликов                                                                                                                                                                                                                                                                                         |
| **clear-skipped**                  | Очистка базы пропущенных вакансий. С флагом `--reason` можно указать конкретную причину (`ai_rejected`, `excluded_filter`).                                                                                                                                                                             |
| **call-api**, **api**              | Вызов произвольного метода API с выводом результата.                                                                                                                                                                                                                                                    |
| **refresh-token**, **refresh**     | Обновляет access_token.                                                                                                                                                                                                                                                                                 |
| **config**                         | Показывает содержимое конфига. С флагом -e открывает его для редактирования.                                                                                                                                                                                                                            |
| **settings**                       | Просмотр и управление настройками в базе данных. Простое key-value хранилище.                                                                                                                                                                                                                           |
| **install**                        | Устанавливает зависимости, такие как браузер Chromium, необходимые для авторизации.                                                                                                                                                                                                                     |
| **uninstall**                      | Удаляет браузер Chromium, используемый для авторизации.                                                                                                                                                                                                                                                 |
| **check-proxy**                    | Проверяет используемые прокси                                                                                                                                                                                                                                                                           |
| **migrate**                        | Починить базу                                                                                                                                                                                                                                                                                           |
| **query**                          | Выполнение SQL-запросов к базе. Схема БД находится в файле [schema.sql](./src//hh_applicant_tool/storage/queries/schema.sql). Если скормить ее [DeepSeek](https://chat.deepseek.com), то он поможет написать любой запрос.                                                                              |
| **log**                            | Просмотр файла-лога. С флагом -f будет следить за изменениями. В логах частично скрыты идентификаторы в целях безопасности.                                                                                                                                                                             |

> [!IMPORTANT]
> Почитайте про [язык для поисковых запросов](https://hh.ru/article/1175). Он позволяет отсеивать мусор при поиске подходящих вакансий, например, `(Go OR Golang) NOT PHP NOT JavaScript`.

Утилита использует систему плагинов. Все они лежат в [operations](https://github.com/s3rgeym/hh-applicant-tool/tree/main/src/hh_applicant_tool/operations). Модули, расположенные там, автоматически добавляются как доступные команды. За основу для своего плагина можно взять [whoami.py](https://github.com/s3rgeym/hh-applicant-tool/tree/main/src/hh_applicant_tool/operations/whoami.py).

Для тестирования запросов к API используйте команду `call-api` совместно с `jq` для обработки JSON.

Пример поиска работодателей:

```bash
$ hh-applicant-tool call-api /employers text="IT" only_with_vacancies=true | jq -r '.items[].alternate_url'
https://hh.ru/employer/1966364
https://hh.ru/employer/4679771
...
```

Синтаксис `call-api` немного похож на `httpie` или `curlie`:

```sh
hh-applicant-tool call-api [{GET|POST|PUT|DELETE}] <endpoint> [<key=value> ...]
```

Если используется метод `GET` или `DELETE` (или ничего не указано), то параметры будут переданы как query string. Во всех остальных случаях параметры передаются как `application/x-www-form-urlencoded` в теле запроса.

Для поиска вакансий по региону в `apply-vacancies` нужно указать параметр `area`. Можно его указывать как для страны (каз - 40, бел - 2238), так и отдельного города (мск - 1).

Пример поиска `area id` по стране и названию:

```sh
$ hh-applicant-tool api /areas | jq -r '.[] | select(.name == "Беларусь") | .. | objects | select(.name == "Барановичи")'
{
  "id": "2239",
  "parent_id": "2233",
  "name": "Барановичи",
  "areas": [],
  "utc_offset": "+03:00",
  "lat": 53.16592,
  "lng": 29.00806
}
```

Документация для работы с API соискателей была удалена с ха-ха.сру и его корпоративного репозитория. Можете не искать, они затерли даже историю репозитория. Но я через веб-архив выкачал документацию. Чтобы ее посмотреть, клонируйте этот репозиторий и откройте файл `./docs/hhapi/openapi.yml`, например, с помощью [Swagger Viewer](https://marketplace.visualstudio.com/items?itemName=Arjun.swagger-viewer).

<img width="740" height="768" alt="image" src="https://github.com/user-attachments/assets/597fa31e-8bab-48c8-8601-ab9dfc9075b1" />

Также существуют CLI-утилиты:

```sh
sudo pacman -S openapi-tui

openapi-tui --input docs/hhapi/openapi.yml
```

Для тех, кто любит просматривать документацию в браузере:

```sh
npx @redocly/cli preview -d docs/hhapi
```

Потом нужно открыть в браузере [http://localhost:4000](http://localhost:4000).

> [!NOTE]
> Отдельные замечания у меня к API HH. Оно пиздец какое кривое. Например, при создании заявки возвращается пустой ответ либо редирект, хотя по логике должен возвращаться созданный объект. Также в ответах сервера нет `Content-Length`. Из-за этого нельзя узнать, есть ли тело у ответа сервера, нужно его пробовать прочитать. Я так понял, там какой-то прокси оборачивает все запросы и отдает всегда `Transfer-Encoding: Chunked`. А еще он возвращает 502 ошибку, когда бэкенд на Java падает либо долго отвечает (таймаут).
> По сути, никакие дополнительные команды, кроме имеющихся, не нужны. Вы можете сделать что угодно с помощью `call-api`, но если хочется чего-то особенного, можно добавить свои команды.

---

## Капча

Если вызов какого-то метода API генерирует ошибку капчи, то она перехватывается, а далее происходит следующее:

- при флаге `--use-ai` капча будет распознаваться нейронкой
- иначе:
  - капча отображается в терминале, если есть флаги `--use-kitty`/`--use-sixel` с именами используемых протоколов для отображения изображений (первый поддерживает даже Windows Terminal)
  - в противном случае капча будет отображена через всплывающее окно `tkinter`

Некоторые модели плохо распознают русский язык, английский для можно установить с помощью передачи `--captcha-lang EN`.

<p align="center">
  <img
    width="406"
    height="345"
    src="https://github.com/user-attachments/assets/3eb291ef-e8b4-40f2-abc0-eca7dd85be46"
    alt="Окно ввода капчи"
  >
  <br>
  <em>Диалог для ввода капчи</em>
</p>

---

## Запуск через Docker

Это рекомендованный разработчиком способ. Если не работает стандартная установка, то используйте его. Также это самый простой способ запуска и использования утилиты, требующий скопипастить 5 команд.

Для начала нужно установить `docker` и `docker-compose`:

```sh
sudo apt install docker.io docker-compose-v2
```

Выкачиваем репозиторий и переходим в каталог:

```sh
git clone https://github.com/s3rgeym/hh-applicant-tool
cd hh-applicant-tool
```

> [!IMPORTANT]
> Команды с docker compose нужно запускать строго, находясь в данном каталоге!

Теперь авторизуемся:

```sh
# Чтобы ввести капчу в терминале укажите флаги --use-sixel/-s или --use-kitty/-k
# Это названия протоколов, которые исп-ся терминалом для отображения изображений
# Sixel поддерживается большинством терминалов
# А вот Windows Terminal поддерживает только Kitty
docker compose run -u docker -it hh_applicant_tool \
  hh-applicant-tool -s -vv auth <username>
```

Если использовать флаг `--ai`, то капча будет разгадана нейронкой и останется только ввести код подтверждения из SMS/Email.

Подробно про авторизацию можно почитать [здесь](#авторизация).

В случае успешной авторизации можно запускать рассылку откликов по крону:

```sh
docker compose up -d
```

Что будет делать?

- Рассылать отклики со всех опубликованных резюме.
- Поднимать резюме.

Просмотр логов `cron`:

```sh
docker compose logs -f
```

Чтобы прекратить просмотр логов, нажмите `Ctrl-C`.

Запущенные сервисы докер стартуют автоматически после перезагрузки. Остановить их можно выполнив:

```sh
docker compose down
```

Чтобы обновить утилиту, в большинстве случаев достаточно в каталоге выполнить:

```sh
git pull
```

В редких случаях нужно пересобрать все:

```sh
docker compose up -d --build
```

Чтобы рассылать отклики с нескольких аккаунтов, нужно переписать `docker-compose.yml`:

```yaml
services:
  # Не меняем ничего тут
  hh_applicant_tool:
    # ...

  # Добавляем новые строки

  # Просто копипастим, меняя имя сервиса, container_name и значение HH_PROFILE_ID
  hh_second:
    extends: hh_applicant_tool
    container_name: hh_second
    environment:
      - HH_PROFILE_ID=second

  hh_third:
    extends: hh_applicant_tool
    container_name: hh_third
    environment:
      - HH_PROFILE_ID=third

  # Общий шаблон для новых профилей
  уникальное_имя_сервиса:
    extends: hh_applicant_tool
    # может совпадать с именем сервиса
    container_name: уникальное_имя_контейнера
    environment:
      - HH_PROFILE_ID=название_профиля
```

> [!IMPORTANT]
> В этом файле важны отступы!

Обратите внимание на `HH_PROFILE_ID` — его значение указывается при авторизации, если профиль отличен от дефолтного. Далее нужно авторизоваться в каждом профиле:

```sh
# Авторизуемся со второго профиля
docker-compose exec -u docker -it hh_applicant_tool \
  hh-applicant-tool --profile-id second -k auth

# Авторизуемся с третьего профиля
docker-compose exec -u docker -it hh_applicant_tool \
  hh-applicant-tool --profile-id third -k auth

# И так далее
```

Ну и выполнить `docker-compose up -d`, чтобы запустить новые сервисы.

[Команды](#описание-команд) можно потестировать в запущенном контейнере:

```sh
$ docker-compose exec -u docker -it hh_applicant_tool bash
docker@1897bdd7c80b:/app$ hh-applicant-tool config -p
/app/config/config.toml
docker@1897bdd7c80b:/app$ hh-applicant-tool refresh-token
ℹ️ Токен не истек, обновление не требуется.
docker@1897bdd7c80b:/app$
```

> [!IMPORTANT]
> Обратите внимание, что `docker-compose exec`/`docker-compose run` запускаются с аргументами `-u docker`. Только для пользователя `docker` установлен `chromium`, необходимый для авторизации, а также это избавляет от проблем с правами, когда созданные файлы для изменения требуют root-права.

Если хотите команду `apply-vacancies` вызывать с какими-то аргументами, то создайте в корне файл `apply-vacancies.sh`:

```sh
#!/bin/bash

# Пример с фильтрацией по исключаемым словам
/usr/local/bin/python -m hh_applicant_tool apply-vacancies \
  --excluded-filter "fullstack|junior|php" # укажите любые аргументы
```

В файлах `startup.sh` и `crontab` замените `/usr/local/bin/python -m hh_applicant_tool apply-vacancies` на `/bin/sh /app/apply-vacancies.sh`.

## Настройки и данные приложения

Настройки и данные приложения хранятся по следующим путям:

| OS          | Путь                                                     |
| ----------- | -------------------------------------------------------- |
| **Windows** | `C:\Users\%username%\AppData\Roaming\hh-applicant-tool\` |
| **macOS**   | `~/Library/Application Support/hh-applicant-tool/`       |
| **Linux**   | `~/.config/hh-applicant-tool/`                           |

### Конфигурационный файл

Конфигурационный называется `config.toml`. Он содержит основные настройки.

TOML удобен в отличии от JSON для хранения многострочного текста:

```toml
setting_name = """
Тут начало рассказа о себе
... Далее идут 100500 строк
И поэтому я считаю, что все п*идорасы
"""
```

Самый быстрый способ открыть в стандартном редакторе конфиг:

```bash
hh-applicant-tool config -e
```

Путь до него можно найти так:

```bash
hh-applicant-tool config -p

# Можно сразу открыть в любимом редакторе
code $(hh-applicant-tool config -p)
```

> TODO: придумать как сохранять комментарии в этом файле при его программном сохранении

Формат файла:

```toml
# Прокси для запросов к hh.ru
# Приоритет: флаг --proxy-url, затем этот параметр, затем переменные окружения HTTP_PROXY / HTTPS_PROXY
proxy_url = "http://user:pass@127.0.0.1:8080"

cover_letter = """
{Здравствуйте|Добрый день}, меня зовут %(first_name)s.

{Прошу|Предлагаю} рассмотреть {мою кандидатуру|мое резюме «%(resume_title)s»} на вакансию «%(vacancy_name)s».

У меня есть релевантный опыт работы и интерес к задачам вашей компании.

С уважением,
%(first_name)s
"""

# Тема и текст письма, отправляемого на email компании
apply_mail_subject = "{Отклик|Резюме} на вакансию %(vacancy_name)s"
apply_mail_body = """
{Здравствуйте|Добрый день},

{прошу рассмотреть|пожалуйста рассмотрите} мое резюме %(resume_url)s на вакансию %(vacancy_name)s.

Буду рад ответить на вопросы и пройти собеседование.
"""

#Клиент для раб_оты с API ХХ
# delay может быть переопределен флагом --api-delay
[api_client]
base_url = "https://api.hh.ru/"  # адрес API, должен заканчиваться на "/"
delay = 0.345                    # пауза между запросами, сек
captcha_cooldown = 3.0           # пауза после решения капчи, сек
max_retries = 3                  # число попыток при ошибках соединения

# Отправка писем на email при откликах (--send-email)
[smtp]
host = "smtp.example.com"
port = 465
user = "me@example.com"
password = "secret"
from = "Иван Иванов <me@example.com>"  # отправитель, по умолчанию = user
ssl = true                             # SSL-соединение (обычно порт 465), по умолчанию false
starttls = true                        # STARTTLS (обычно порт 587), по умолчанию true

# Настройки по умолчанию для работы с OpenAI API
[openai.default]
base_url = "https://api.openai.com/v1/chat/completions"  # полный URL эндпоинта chat completions
api_key = "sk-..."
model = "gpt-4o-mini"
proxy_url = "http://127.0.0.1:8080"  # отдельный прокси для AI
delay = 0.5                          # минимальный интервал между запросами, сек, по умолчанию 0.5
timeout = 60.0                       # таймаут запроса, сек, по умолчанию 60
connect_timeout = 10.0               # таймаут соединения, сек, по умолчанию 10
max_retries = 3                      # число повторов при сетевых ошибках и лимитах, по умолчанию 3
temperature = 0.0                    # по умолчанию 0.0
max_completion_tokens = 1000         # максимальная длина ответа, по умолчанию 1000

# Далее настройки для отдельных случаев
# Секции `openai.*` переопределяют дефолтные из `openai.default`
[openai.captcha]
# Какая-то другая модель лучше подходящая для распознания русского текста
base_url = "..."
api_key = "..."
model = "..."

# И пример передачи истории чата (контекста), которая будет передаваться с каждым вызовом complete
[openai.cover_letter]
system_prompt = "Ты помогаешь писать сопроводительные письма."
# Тут меняем температуру, чтобы сопроводительное письмо напоминало, написанное человеком
temperature = 0.8

[[openai.cover_letter.history]]
role = "user"
content = "Меня зовут Иван, я Python-разработчик."

[[openai.cover_letter.history]]
role = "assistant"
content = "Приятно познакомиться, Иван. Чем помочь?"

# ...

# Ну и далее указываем промпт для генерации сопроводительного
# В нем можно использовать плейсхолдеры, перечисленные в разделе
# шаблоны сообщений
cover_letter_prompt = """
Напиши сопроводительное для отклика на вакансию %(vacancy_name)s ...
"""
```

Часть настроек скрыта. Некоторые настройки передаются как аргумент конструктора, поэтому если увидите сообщение типа:

```sh
TypeError: ApiClient.__init__() got an unexpected keyword argument 'timeoutt'
```

Значит вы дебил и ебетесь в глаза, допустив в секции `api_client` ошибку в написании имени настройки...

Значения из конфигов имеют приоритет над флагами, если у флагов дефолтное значение как `None` отображается. Если значения и в конфиге нет, то будет задано внутренее дефолтное значение.

### auth.json

Хранит токены авторизации.

### cookies.txt

В `cookies.txt` хранятся куки, установленные при авторизации на сайте. Они нужны
для решения тестов при отклике на вакансию. Не редактируйте этот файл вручную.

Операция `logout` не только затирает токены, но очищает `cookies.txt`.

### Логи

В `log.txt` всегда можно посмотреть подробную информацию об ошибках. В целях безопасности токены, идентификаторы резюме и т. п. затираются. Для просмотра логов используйте операцию `log`.

### База данных

Данные о работодателях и их вакансиях хранятся в файле `data`. Это обычный с данными базы `sqlite3`, вы его можете просматривать через `DB Browser` и т.п., а так же выполнять запросы с помощью операции `query`.

Что хранится в базе:

- все просмотренные вакансии;
- профили работодателей;
- контакты работодателей;
- данные об откликах;
- пропущенные вакансии (отклонённые фильтром или AI);
- иные данные, важные для работы.

Если этот файл удалить, то сессия не слетит, вы потеряете лишь данные...

---

## Дополнительные настройки

Отключение проверки версии с выводом предупреждения:

```sh
hh-applicant-tool settings disable_version_check true
```

### Переменные окружения

Утилита читает следующие переменные окружения:

| Переменная                    | Описание                                                                                                                               |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `CONFIG_DIR`                  | Каталог, в котором хранятся данные приложения, вместо пути по умолчанию (см. «Данные приложения»). В Docker задаётся как `/app/config` |
| `HH_PROFILE_ID`               | Имя подкаталога-профиля внутри `CONFIG_DIR` (по умолчанию `.`). Позволяет держать несколько изолированных профилей                     |
| `HTTP_PROXY` / `http_proxy`   | Прокси для HTTP-запросов к API HH, если не задан `proxy_url` в конфиге                                                                 |
| `HTTPS_PROXY` / `https_proxy` | Прокси для HTTPS-запросов к API HH; если не задан — используется HTTP-прокси                                                           |

Первые нужны для удобства в `Docker`. Последние — де факто стандарт.

---

## Шаблоны сообщений

При откликах доступны сл плейсхолдеры (как это на русском?) шаблонов (актуальный список см в `apply-vacancies -h`):

- **`%(vacancy_name)s`**: Название вакансии.
- **`%(vacancy_url)s`**: Ссылка на вакансию.
- **`%(employer_name)s`**: Название работодателя.
- **`%(first_name)s`**: Имя пользователя.
- **`%(last_name)s`**: Фамилия пользователя.
- **`%(email)s`**: Email пользователя.
- **`%(phone)s`**: Телефон пользователя.
- **`%(resume_title)s`**: Название резюме.
- **`%(resume_hash)s`**: Идентификатор резюме.
- **`%(resume_url)s`**: Ссылка на резюме.
- ...

Например:

```
Меня заинтересовала ваша вакансия %(vacancy_name)s. Прошу рассмотреть мою кандидатуру. С уважением, %(first_name)s %(last_name)s.
```

Плейсхолдеры можно использовать в промптах, но не в системных, заданных в секциях `openai.*`.

Также можно делать текст уникальным с помощью `{}`. Внутри них через `|` перечисляются варианты, один из которых будет случайно выбран:

```
Увольте того {идиота|придурка}, который придумал этот {тупой|сраный|дурацкий} вопрос.
```

В итоге получится что-то вроде:

```
Увольте того идиота, который придумал этот сраный вопрос.
```

`{}` могут быть вложенными.

---

## Интеграция и использование в скриптах

Если хотите использовать `hh-applicant-tool` в своих скриптах, то можно это сделать так:

```python
from hh_applicant_tool import HHApplicantTool

tool = HHApplicantTool()
print(tool.api_client.get('/me'))

# Какую-то вспомогательную информацию можно сохранять в настройках
import datetime as dt

tool.storage.settings.set_value("_last_script_run", dt.datetime.now())

# Учтите, что в таком случае токены, которые могут обновиться при выполнении запросов,
# нужно сохранять вручную
tool.save_token()
```

Команды тоже можно вызывать:

```python
>>> from hh_applicant_tool import HHApplicantTool
>>> HHApplicantTool().run(["auth", "v.pupkin@mail.ru"])
```

Если же утилита интегрируется в графические приложения, то как правило должна быть возможность отменить текущую операцию:

```python
import multiprocessing as mp

from hh_applicant_tool import HHApplicantTool

def run_command(argv: list[str]):
    HHApplicantTool().run(argv)

p = mp.Process(target=run_command, args=(['apply-vacancies', ...],))
p.start()

# Убили процесс
p.terminate()

# Ждем пока не помрет
p.join()
```

---

## Принцип работы

> [!IMPORTANT]
> Данной утилите похуй на "запрет" доступа к API HH сторонним приложениям, так как она прикидывается официальным приложением под Android

Долгое время я делал массовые заявки с помощью консоли браузера:

```js
$$('[data-qa="vacancy-serp__vacancy_response"]').forEach((el) => el.click())
```

Оно работало, хоть и не идеально: например, при отклике на некоторые вакансии перебрасывало на другую страницу. Поэтому я пробовал автоматизировать рассылки через `p[yu]ppeteer`, пока не прочитал [документацию](https://github.com/hhru/api) и не обнаружил, что **API** (интерфейс) содержит все необходимые мне методы. Headhunter позволял создавать свое приложение, но там была ручная модерация, а палиться в нарушении правил пользования сайтом (автоматические отклики запрещены) не хотелось. И тогда я [декомпилировал](https://gist.github.com/s3rgeym/eee96bbf91b04f7eb46b7449f8884a00) официальное приложение для **Android**, получил **CLIENT_ID** и **CLIENT_SECRET**, необходимые для работы через **API**.

Позже оказалось, что работы только через **API** недостаточно. Например, в нем нет методов для решения тех же тестов. Поэтому для их решения приходится эмулировать работу через сайт. Точно так же обстоят дела с решением капчи. **API** буквально нам предлагает открыть страницу с капчей в браузере, решить ее, а потом вызвать нужный метод **API** снова.

Сам сайт ХХ использует React. На каждой странице есть тег `<template id="HH-Lux-InitialState"></template>`. Внутри него заключен JSON `{"redirectConfig":....}`, в котором разделители заменены на HTML-сущности. В нем содержатся данные, необходимые для рендеринга страницы. Если нужно спарсить какие-то данные с веб-версии (у них она называется десктопной), то искать их стоит там. Там, кстати, много лишнего хранится: от ссылки на бота в Telegram для сотрудников поддержки сайт до страниц каких-то инфоцыган (этот уебанский сайт ставку сделал на зарабатывание денег на соискателях, я это осуждаю в отличии от доения тамошних работодятлов с их тупорылыми херками).

Сами эндпоинты сайта работают довольно странно. Параметры могут передавать через тело, Query String, через куки и заголовки. Но не всегда, на отдельных эндпоинтах обязательна передача в теле запроса.

Так же при авторизации на сайте вам выдается `xsrf` токен, он используется во всех запросах, отправляемых через `XMLHttpRequest`...

Для решения капчи нужен `captchaState`. Его возвращают некоторые эндпоинты сайта к которым обращаются через `XMLHttpRequest`. Либо он содержится в ссылке. Далее нужно получить `captchaKey`, послав POST-запрос без тела на эндпоинт сайта `/captcha?lang=RU` (можно указать еще `EN`). После получения `captchaKey` мы грузим изображение `/captcha/picture?key=<captchaKey>`. Ну и далее решение капчи, `captchaState` и `captchaKey` отправляются на POST-запросом на `/account/captcha`. Этот процесс тут описан упрощенно. Там еще нужно отправлять специальные заголовки... А так сама по себе капча существует словно отдельно от сайта... Ну у них там и микрофронтенды используются... Там говнокодеры ебучие работают.

<p align="center">
  <img width="1016" height="999" alt="image" src="https://github.com/user-attachments/assets/2f25282b-aecd-4e0b-95f5-07c703f6287b" />
</p>

Самым сложным был "взлом" процесса авторизации. Мне пришлось два часа лазать искать как `loginTrustFlags` генерируются для "подписи" данных отправляемых при авторизации. Для этого мне пришлось во вкладке отладчика браузера `Sources` отрелактировать скрипты на сайте, вставив в нужные места `console.log`/`console.dir`/`console.table` перед `debugger;` (для этого нужно выбрать директорию для синхронизации, тогда станет доступна возможность редактировать скрипты "прямо на сайте"). Там на странице входа из объекта с `redirectConfig` берется `loginTrustFlagsSecret`. В нем содержится публичный сертификат, которым шифруется такой JSON:

```js
{"emailOrPhone":{"paste":false,"suggest":true},"ts":Date.now()}
```

По названиям полей несложно догадаться, что там указывается был ли логин вставлен в поле или выбран из выпадающего списка при вводе. Боты, которые работают через Headless Chromium обычно ничего не вставляют и не выбирают из выпадающего списка, а "печатают"...

> При авторизацию по паролю такое шифруется: {"password":{"suggest": false,"paste": false},"ts":...}

Далее этот объект пропускается через `JSON.stringify` и шифруется с помощью публичного ключа. Используется ассимитричное шифрование `RSA-OAEP` с хешированием `SHA-256` и кодированием в base64. Без этой подписи запроса авторизоваться не получится.

Без расстановки контрольных меток быстро данную функцию шифрования можно найти так: жмем `Ctrl-Shift-F` во вкладке `Sources` и вводим `await crypto[`, ниже будет что-то типа:

```js
// С каждой версией фронтенда имена переменных меняются
const _0x3cd982 = {
  ..._0x334204,
  // 'ts': Date['now']()
  '\x74\x73': Date[_0x50c3ac(0x13f, '\x51\x47\x76\x23')](),
};

// Добавляем и смотрим что шифруется в trustLoginFlags...
console.log('trust login: %o', _0x3cd982);
```

Большинство эндопоинтов шифрует лишь JSON с таймстермп наподобии такого: `{"ts":1791234567890}`. Я думаю, что кроме `ts` там обязательных полей нет.

В принципе больше нечего рассказать...

Таким образом моя утилита работает в гибридном режиме: через API и веб-версию. При этом она не использует Headless Chromium ни для авторизации, ни для решения капчи...

---

## Разработка

- `git clone ...`
- `poetry install .`
- **poetry** можно установить через **pipx**
- в дальнейшем можно запускать утилиту так `poetry run hh-applicant-tool authorize`
- либо можно активировать виртуальное окружение, которое создается в каталоге проекта (`source .venv/bin/activate`)
- после изменений ничего переустанавливать не надо (`poetry install` устанавливает проект в режиме редактирования, те все изменения в каталоге проекта "сразу применяются")

Для публикации новой версиии утилиты на `PyPi` нужно указывать версию. Она указывается в `pyproject.toml`. Когда делаете `Pull Request`, то увеличивайте эту версию на единичку (при малозначительных изменениях — последнее число).

---

## Ответы на заебавшие вопросы

> Утилита перестала откликаться на тесты ряяяяяяяяяяяяяя

- Обычно это происходит, когда истекают куки и слетает авторизация на самом сайте (вы долго не запускали отклики). При этом команды типа `whoami` могут продолжать работать. Нужно по новой авторизоваться.

> Ряяяя поиск возвращает не то, что я и ищу, утилита не работает

- Сайт хуй-хуй, созданный для овчарок, и поэтому имеющий красный логотип цвета менстуральных выделений, зарабатывает в тч на услугах по продвижению вакансий, когда вакансию показать некому, то он ее начинает подсовывать калдидатам, которых это "может" заинтересовать (хоть кому-то показать надо), так программистам предлагают вакансии учителей и наставников в онлайн-школах и прочую дичь. Ничего с этим не сделать кроме применения дополнительных фильтров для отсеивания поискового мусора...

<img width="792" height="198" alt="image" src="https://github.com/user-attachments/assets/c532afaf-8cde-478b-8bc2-63955133f502" />

- Как работало, так и работает. Меньше читайте всяких пиздаболов

---

## Связанные проекты

- [Приложение под Android](https://github.com/s3rgeym/hh-resume-automate)
- [Прога, не требующая установки (скачал и запустил), которая рассылает отклики и отвечает в чаты через хуИИ](https://github.com/s3rgeym/hh-ai-responder)

---

## Лицензионное соглашение (Limited Non-Commercial License)

Данное программное обеспечение распространяется на условиях **ограниченной некоммерческой лицензии**.

- **Разрешённое использование**
  - **Личное использование и изучение:** Разрешается копирование и модификация кода исключительно для личного ознакомления, обучения и отладки **без права публикации или распространения изменений**.
  - **Интеграция в программное обеспечение:** Разрешается включение данного кода в состав сторонних бесплатных некоммерческих продуктов **только при полном сохранении исходного кода в неизменном виде**.
  - **Атрибуция:** Любое распространение, демонстрация или использование кода (в том числе в составе других продуктов) допускается **только при обязательном указании автора и ссылки на оригинальный репозиторий**.

- **Ограничения на использование**
  **Строго запрещается** использование данного кода в любых коммерческих целях без предварительного письменного согласия автора, включая, но не ограничиваясь:
  - Интеграцией в платные сервисы, приложения, веб-сайты либо использованием в рамках оказания платных услуг.
  - Публикацией, распространением или передачей третьим лицам **модифицированных (изменённых) версий** кода.
  - Перепродажей, сублицензированием либо использованием кода в интересах коммерческих организаций.

- **Отказ от ответственности**
  Программное обеспечение предоставляется по принципу «как есть» (**AS IS**). Автор не несёт ответственности за любые прямые или косвенные убытки, включая, но не ограничиваясь, блокировкой аккаунтов, потерей данных или иными негативными последствиями, возникшими в результате использования данного программного обеспечения.

**© 2023-2026 Sergey M. Все права защищены.**

