Metadata-Version: 2.4
Name: hh-applicant-tool
Version: 2.0.1
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]
> [Группа поддержки в Телеграме (для всех моих утилит и просто для общения)](https://t.me/s3rgeym_chat)

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

<details>
<summary>Еще куча важной информации</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, натравив на тот нейронку
> (я половину бреда чатагпт вычистил вручную).

> [!IMPORTANT]
> Сайт с одноименным названием (НН Applicant Tool) к этой утилите отношения не имеет, то какой-то кабан кабаныч решил за счет мусорной поисковой выдачи клиентов залутать.

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

> [!WARNING]
> Мой проект полностью бесплатен для личного пользования, для коммерческого использования ОН ЗАПРЕЩЕН, однако находятся отдельные ублюдки, которые используют МОЮ РАЗРАБОТКУ в коммерческих целях. Просто первые строки из гугла, все эти хуИИ помощники для рассылки откликов. Как спалились? - Мой скрипт аналоговнентный и как только я в нем реализовал мультиакки, так сразу они появились у этих козлов, сделал автоматическое решение тестов, у них они тоже появились. Эти гандоны или пиздят мой код частично, либо тупо утилиту встроили в свои говносервисы. Я их проклинаю в веках и желаю им всего плохого. Они жулики и воры! Если где увидите где подобных, в отзывах каких, то кидайте ссылку на этот скрипт и пишите, что эти мошенники продают ХАЛЯВУ.

> [!WARNING]
> Нет НЕЙРОХУЙНЕ!!! Прекратите слать код, который сами не понимаете. Мне лень просматривать высеры нейродурака, поэтому скопилось приличное количество нейрошлака... В 2026 программисты умерли, остались лишь операторы промптов... из-за которых даже я не могу найти никакую работу, так как любая чмоха возомнила себя погромистом, если в состоянии составить запрос к нейродураку, но тут скорее рынок сдох, да и овчар-шаболды сменили алгоритм отбора с активного на пассивный

</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` (спасибо за донаты — это единственное, что двигает разработку, так что не стесняйтесь и шлите их чаще, вы ЭКОНОМИТЕ КУЧУ ДЕНЕГ с учетом жадности владельцев сервисов для откликов)

---

## Функционал

- 🤖 Поддержка ИИ
- 📤 Рассылка откликов
- ✍️ Автоматическая генерация сопроводительных
- 🧩 Решение тестов при отклике
- 💬 Автоответчик в чате с работодателем, в тч для общения с ботом рекрутером
- 📧 Рассылка писем на Email работодателям
- 👥 Поддержка нескольких аккаунтов через профили
- 📇 Сохранение контактов работодателей и информации о вакансиях
- 🔒 Данные хранятся локально, никаких скрытых зондов и отправки данных на сторонние сервера
- 📖 Открытый исходный код
- 🖥️ Может запускаться на сервере
- 🎨 Наличие простого UI
- 🎁 И другой функционал абсолютно бесплатно!

---

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

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

---

## Запуск через 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`.

Информацию об ошибках можно посмотреть в файле `config/log.txt`, а контакты работодателей — в `config/data` с помощью `sqlite3`. В `config/config.toml` хранятся токены, дающие доступ к аккаунту.

Также советую отредактировать файл `letter.txt`.

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

```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 \
  -l letter.txt \
  --excluded-filter "fullstack|junior|php" # укажите любые аргументы
```

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

---

## Стандартная установка

### Установка утилиты

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

# Лучше использовать pipx
# Чтобы выводить капчу через sixel нужен pillow
$ 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 '<ваш телефон>'
```

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

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

В случае успешной авторизации токены будут сохранены в `config.toml`.

Токен доступа выдается на две недели. Он обновляется автоматически. Для его ручного обновления нужно выполнить:

```bash
hh-applicant-tool refresh-token
```

Помните, что у `refresh_token` тоже есть время жизни, поэтому может потребоваться полная авторизация.

---

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

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

```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;'
count(*)
--------
228

# Экспорт контактов в 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 -vv api /areas | jq -r '.[] | select(.name == "Беларусь")'
```

Документация для работы с 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>

---

## Использование AI

Для генерации сопроводительных писем при откликах и ответа в чаты работодателей (`reply-employers`) можно использовать OpenAI (ChatGPT).

Пример рассылки откликов с генерированным письмом:

```sh
hh-applicant-tool --ai apply-vacancies -f
```

Генерацию сопроводительных писем в откликах я считаю лишней, так как их никто не читает. Экономнее воспользоваться [шаблонами сообщений](#шаблоны-сообщений).

### AI-фильтрация вакансий

Функция AI-фильтрации позволяет автоматически отсеивать неподходящие вакансии перед откликом. AI анализирует ваше резюме и сравнивает его с требованиями вакансии.

#### Режимы фильтрации

> [!IMPORTANT]
> Я эту хуйню выкину, так как никто нейрослоп править не хочет

`heavy` - должность, навыки, опыт работы, задачи, технологи

`light` - быстрая оценка по названию резюме и ключевым навыкам

`custom` - полный контекст вакансии и резюме, но со своим системным промптом через `--ai-filter-prompt` (резюме добавляется в конец промпта блоком «Кандидат»)

#### Примеры использования

```sh
# Полный анализ вакансий
hh-applicant-tool --ai apply-vacancies --ai-filter heavy

# Быстрый анализ
hh-applicant-tool --ai apply-vacancies --ai-filter light

# С кастомным rate limit
hh-applicant-tool --ai apply-vacancies --ai-filter heavy --ai-rate-limit 20

# С собственным системным промптом
hh-applicant-tool --ai apply-vacancies --ai-filter custom --ai-filter-prompt "Откликайся только на вакансии для Python-разработчика"
```

#### Параметры фильтрации

> [!CAUTION]
> Эти параметры я планирую удалить в рамках борьбы с нейрослопом в коде

- `--ai-filter` — режим фильтрации: `heavy`, `light` или `custom`
- `--ai-rate-limit` — ограничение запросов к AI в минуту (по умолчанию 40)
- `--ai-filter-prompt` — системный промпт для AI-фильтра. Используется только в режиме `custom`

### OpenAI/ChatGPT

> [!IMPORTANT]
> То что тут в примерах были указаны эндпоинты Chat GPT НЕ ОЗНАЧАЕТ, ЧТО УТИЛИТА
> НЕ МОЖЕТ РАБОТАТЬ С ДРУГИМИ ПРОВАЙДЕРАМИ! ОНА РАБОТАЕТ С ЛЮБЫМИ!!!

Отредактируйте конфиг:

```sh
hh-applicant-tool config -e
```

#### Раздельная конфигурация

> [!CAUTION]
> В рамках борьбы с нейрослопом формат конфигов может быть изменен.

Универсальный конфиг OpenAI задается в секции `openai` с полями `api_key`, `base_url`, `model` и тп.

Для разных задач можно задать отдельные секции конфига. Доступные: `openai_cover_letter`, `openai_captcha`, `openai_chat`, `openai_vacancy_filter`. В них можно переопределить модель, ссылку, ключ API и тд. Если в них не хватает какого-то поля, то оно берется из `openai`.

```toml
[openai]
api_key = "ВАШ_API_КЛЮЧ"
base_url = "https://api.openai.com/v1/chat/completions"
model = "gpt-4o-mini"

[openai_cover_letter]
temperature = 0.7
max_completion_tokens = 1000
rate_limit = 40

[openai_vacancy_filter]
temperature = 0.1
max_completion_tokens = 100
rate_limit = 60

[openai_captcha]
temperature = 0.0
max_completion_tokens = 20
rate_limit = 40
```

> `api.openai.com` указан в качестве примера. Утилита работает с любыми провайдерами хуИИ в тч с локальынми
> При использовании Docker нужно указывать IP хоста вместо `localhost`, например `http://192.168.1.100:11434/v1/chat/completions`

Таймауты задаются полями `timeout` (весь запрос: соединение и чтение ответа, по умолчанию 30 секунд) и `connect_timeout` (только соединение, по умолчанию 5 секунд). Их можно указать в любой из секций выше, сразу для всех — в секции `openai`, либо флагами `--openai-timeout` и `--openai-connect-timeout`. Если модель отвечает медленно, увеличивайте `timeout`.

### Автоматическое решение капчи

> Для ручного ввода капчи используйте флаг --manual.

Утилита поддерживает автоматическое распознавание капчи через Vision AI.

#### Настройка решения капчи

Добавьте секцию `openai_captcha` (специализированная LLM для распознания текста на изображениях) или `openai` (универсальная для всех ai) в конфиг:

```toml
[openai_captcha]
api_key = "ВАШ_API_КЛЮЧ"
base_url = "https://api.openai.com/v1/chat/completions"
model = "gpt-4o-mini"
temperature = 0.0
max_completion_tokens = 20
rate_limit = 40
```

- **model** обязательно с поддержкой Vision!

#### Проблемы распознания

Разработчики сайта хуй-хуй сру нашли довольно своеобразное решение для борьбы с автоматическим распознанием капчи — в качестве капчи для распознания они используют текст с намерянными ошибками. В чем проблема? — В принципе работы нейродурака. Он оперирует не символами, н словами, а токенами, которые могут быть как отдельными символами, так и целыми предложениями... Короче, он вместо текста с ошибками на картинке выдает его исправленную версию... А вы думали он такой грамотный просто так? Эксперименты с OCR для распознания текста не увенчались успехом: процент ошибок еще выше, так как последние "обучались" распознавать текст на бумаге, максимум на номерах автомобилях и уличных адресах, где плоскость под углом повернута, хотя те как раз отдельные символы распознают, а не токены, что и нужно...

---

## Решение тестов при отклике

На некоторые вакансии HeadHunter требует пройти тест перед откликом. В REST API нет
отдельного метода для решения тестов, поэтому утилита выполняет их через веб-версию
сайта: открывает страницу отклика, разбирает встроенный JSON с вопросами
и отправляет ответы обратно веб-формой.

Ответы на тест генерируются двумя способами:

- **Быстрый (по умолчанию):** без AI. Выбирается вариант «Да», если он есть, иначе —
  средний вариант (надеемся что правильный ответ находится посередине).
  Открытые вопросы без ссылок отвечаются «Да», со ссылками — отказным текстом.
- **Через AI:** при запуске с `--use-ai` и настроенной секцией `openai_cover_letter`
  каждый вопрос отправляется моделью, которая возвращает ID правильного ответа.

Управление поведением:

- `--skip-tests` — пропускать вакансии с тестами и не решать их вовсе.
- `--dry-run` — «решить» тест виртуально, без реальной отправки.

Если тест не удалось разобрать, утилита откатывается к обычному отклику через API.

---

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

Команды `apply-vacancies` и `reply-employers` поддерживают специальный формат сообщений.

Также в сообщении можно использовать плейсхолдеры:

- **`%(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.
```

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

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

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

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

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

---

## Данные приложения

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

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

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

Главный конфиг называется `config.toml`. Он содержит токены. Полный путь до конфигурационного файла можно вывести с помощью команды:

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

Через конфиг можно задать дополнительные настройки. Значения вложенных полей
указываются через точку (например, `config --set smtp.host smtp.yandex.ru`):

| Имя атрибута             | Описание                                                                                                                       |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| `proxy_url`              | Прокси, используемый для всех запросов, например, `socks5h://localhost:1080`                                                   |
| `api_delay`              | Минимальная задержка между отправкой запросов к API HH                                                                         |
| `reply_message`          | Сообщение для ответа работодателю при отклике на вакансии, см. формат сообщений                                                |
| `user_agent`             | Кастомный юзерагент, передаваемый при каждом запросе. По умолчанию используется от Android                                     |
| `client_id`              | Идентификатор клиента, используемый для авторизации. По умолчанию используется от Android                                      |
| `client_secret`          | Секретный ключ клиента, используемый для авторизации. По умолчанию используется от Android                                     |
| `openai_cover_letter`    | Конфигурация AI для генерации сопроводительных писем                                                                           |
| `openai_vacancy_filter`  | Конфигурация AI для фильтрации вакансий                                                                                        |
| `openai_captcha`         | Конфигурация AI для распознавания капчи                                                                                        |
| `token`                  | Объект с OAuth-токенами: `access_token`, `refresh_token`, `access_expires_at`.                                                 |
| `openai.proxy_url`       | Отдельный прокси только для запросов к AI. Переопределяется флагом `--openai-proxy`; если не задан — используется общий прокси |
| `openai.timeout`         | Таймаут запроса к AI: соединение и чтение ответа. Переопределяется флагом `--openai-timeout`, по умолчанию 30 секунд           |
| `openai.connect_timeout` | Таймаут соединения с AI. Переопределяется флагом `--openai-connect-timeout`, по умолчанию 5 секунд                             |
| `smtp.host`              | SMTP-сервер для отправки писем при отклике (обязателен вместе с `port`)                                                        |
| `smtp.port`              | Порт SMTP-сервера (обязателен вместе с `host`)                                                                                 |
| `smtp.user`              | Логин SMTP (используется для авторизации и как `From`, если `smtp.from` не задан)                                              |
| `smtp.password`          | Пароль SMTP                                                                                                                    |
| `smtp.ssl`               | Использовать `SMTP_SSL` (по умолчанию `false`)                                                                                 |
| `smtp.starttls`          | Выполнять `STARTTLS` при незашифрованном подключении (по умолчанию `true`)                                                     |
| `smtp.from`              | Заголовок `From` письма; если не задан — берётся из `smtp.user`                                                                |
| `apply_mail_subject`     | Шаблон темы письма при флаге `--send-email`, см. формат сообщений                                                              |
| `apply_mail_body`        | Шаблон тела письма при флаге `--send-email`, см. формат сообщений                                                              |

Настройки секций AI (`openai_cover_letter`, `openai_vacancy_filter`, `openai_captcha`) —
см. раздел «Использование AI».

Если вы залогинитесь под другим аккаунтом, то данные не исчезнут.

При использовании профиля, отличного от дефолтного, данные хранятся в подкаталоге.

### Логи

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

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

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

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

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

#### Пропущенные вакансии

При использовании AI-фильтрации или exclude-фильтра вакансии сохраняются в таблицу `skipped_vacancies` с указанием причины:

| Причина           | Описание                  |
| ----------------- | ------------------------- |
| `ai_rejected`     | Отклонена AI-фильтром     |
| `excluded_filter` | Попала под exclude-фильтр |

Для управления пропущенными вакансиями используется команда `clear-skipped`:

```sh
# Показать количество пропущенных вакансий
hh-applicant-tool clear-skipped --dry-run

# Очистить все пропущенные вакансии
hh-applicant-tool clear-skipped

# Очистить только отклонённые AI
hh-applicant-tool clear-skipped --reason ai_rejected

# Очистить только исключённые фильтром
hh-applicant-tool clear-skipped --reason excluded_filter
```

Для произвольных запросов к базе используйте команду `query`:

```sh
# Сколько вакансий отклонено по каждой причине
hh-applicant-tool query "SELECT reason, COUNT(*) FROM skipped_vacancies GROUP BY reason"

# Последние отклонённые вакансии
hh-applicant-tool query "SELECT * FROM skipped_vacancies ORDER BY created_at DESC LIMIT 10"
```

### cookies.txt

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

---

## Отправка писем при отклике

Для рассылки писем на почту рекрутера используйте флаг `--send-email` и добавьте в конфиг:

```toml
apply_mail_subject = "{Отклик|Резюме} на вакансию %(vacancy_name)s"
apply_mail_body = """
{Здравствуйте|Добрый день}!

Меня заинтересовала ваша вакансия %(vacancy_name)s.
Мое резюме: %(resume_url)s

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

[smtp]
host = "smtp.yandex.ru"
port = 465
user = "your_login@yandex.ru"
password = "your_app_password"
ssl = true
from = "Иван Иванов <your_login@yandex.ru>"
```

---

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

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

```python
from hh_applicant_tool import HHApplicantTool

tool = HHApplicantTool([
    # Передаем глобальные настройки как обычно
    "--proxy-url", "socks5://localhost:1080",
    "--config-path", "/path/to/config"
])

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(['auth']).run()
...
📨 Код был отправлен. Проверьте почту или SMS.
📩 Введите полученный код:
```

---

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

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

```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-прокси                                                           |
| `HH_AI_BASE_URL`              | Ссылка на API                                                                                                                          |
| `HH_AI_API_KEY`               | Ключ доступа к API                                                                                                                     |
| `HH_AI_MODEL`                 | Модель по умолчанию                                                                                                                    |

---

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

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

> [!NOTE]
> Утилита для генерации сопроводительного письма может использовать AI, в т. ч. ChatGPT. Подробное описание ниже

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

```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">`. Внутри него заключен JSON `{"redirectConfig":....}`, в котором разделители заменены на HTML-сущности. В нем содержатся данные, необходимые для рендеринга страницы. Если нужно спарсить какие-то данные с веб-версии, то искать их стоит там. Там, кстати, много лишнего хранится: от ссылки на бота в Telegram для сотрудников поддержки сайт до страниц каких-то инфоцыган (этот уебский сайт ставку сделал на зарабатывание денег на соискателях, я это осуждаю в отличии от доения тамошних работодятлов с их тупорылыми херками).

Для решения капчи нужен `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 обычно ничего не вставляют и не выбирают из выпадающего списка, а "печатают"...

Далее этот объект пропускается через `JSON.stringify` и шифруется с помощью публичного ключа:

```js
return useCallback(async () => {
  // 1. Ожидается готовность импортированного CryptoKey (из прошлых шагов)
  const cryptoKey = await _0xb4057b

  // 2. Если ключа нет, возвращается null
  if (!cryptoKey) return null

  // 3. Собирается итоговый объект payload с добавлением метки времени ts
  const payload = {
    ..._0xa54263,
    ts: Date.now(),
  }

  try {
    // 4. Объект превращается в JSON-строку
    const jsonString = JSON.stringify(payload)

    // 5. Вызывается функция шифрования _0x2993e2
    return await _0x2993e2(jsonString, cryptoKey)
  } catch (err) {
    return null
  }
}, [_0xb4057b, _0xa54263])
```

> Claude, который к слову отказался помогать обходить авторизацию на ХХ,
> любезно объяснил код

Используется ассимитричное шифрование `RSA-OAEP` с хешированием `SHA-256` и кодированием в base64. Без этой подписи запроса авторизоваться не получится. Много я видел реализации авторизации на ХХ, и везде используется для этого браузер либо вообще расширение какое, чтобы не авторизоваться... Но для этого не нужен браузер...

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

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

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

Таким образом моя утилита работает в гибридном режиме: через 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-auto-apply)

---

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

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

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

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

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

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

