hh-applicant-tool 0.6.12__py3-none-any.whl → 1.4.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. hh_applicant_tool/__init__.py +1 -0
  2. hh_applicant_tool/__main__.py +1 -1
  3. hh_applicant_tool/ai/base.py +2 -0
  4. hh_applicant_tool/ai/openai.py +24 -30
  5. hh_applicant_tool/api/client.py +82 -98
  6. hh_applicant_tool/api/errors.py +57 -8
  7. hh_applicant_tool/constants.py +0 -3
  8. hh_applicant_tool/datatypes.py +291 -0
  9. hh_applicant_tool/main.py +236 -82
  10. hh_applicant_tool/operations/apply_similar.py +268 -348
  11. hh_applicant_tool/operations/authorize.py +245 -70
  12. hh_applicant_tool/operations/call_api.py +18 -8
  13. hh_applicant_tool/operations/check_negotiations.py +102 -0
  14. hh_applicant_tool/operations/check_proxy.py +30 -0
  15. hh_applicant_tool/operations/config.py +119 -18
  16. hh_applicant_tool/operations/install.py +34 -0
  17. hh_applicant_tool/operations/list_resumes.py +24 -10
  18. hh_applicant_tool/operations/log.py +77 -0
  19. hh_applicant_tool/operations/migrate_db.py +65 -0
  20. hh_applicant_tool/operations/query.py +120 -0
  21. hh_applicant_tool/operations/refresh_token.py +14 -13
  22. hh_applicant_tool/operations/reply_employers.py +148 -167
  23. hh_applicant_tool/operations/settings.py +95 -0
  24. hh_applicant_tool/operations/uninstall.py +26 -0
  25. hh_applicant_tool/operations/update_resumes.py +21 -10
  26. hh_applicant_tool/operations/whoami.py +40 -7
  27. hh_applicant_tool/storage/__init__.py +4 -0
  28. hh_applicant_tool/storage/facade.py +24 -0
  29. hh_applicant_tool/storage/models/__init__.py +0 -0
  30. hh_applicant_tool/storage/models/base.py +169 -0
  31. hh_applicant_tool/storage/models/contact.py +16 -0
  32. hh_applicant_tool/storage/models/employer.py +12 -0
  33. hh_applicant_tool/storage/models/negotiation.py +16 -0
  34. hh_applicant_tool/storage/models/resume.py +19 -0
  35. hh_applicant_tool/storage/models/setting.py +6 -0
  36. hh_applicant_tool/storage/models/vacancy.py +36 -0
  37. hh_applicant_tool/storage/queries/migrations/.gitkeep +0 -0
  38. hh_applicant_tool/storage/queries/schema.sql +119 -0
  39. hh_applicant_tool/storage/repositories/__init__.py +0 -0
  40. hh_applicant_tool/storage/repositories/base.py +176 -0
  41. hh_applicant_tool/storage/repositories/contacts.py +19 -0
  42. hh_applicant_tool/storage/repositories/employers.py +13 -0
  43. hh_applicant_tool/storage/repositories/negotiations.py +12 -0
  44. hh_applicant_tool/storage/repositories/resumes.py +14 -0
  45. hh_applicant_tool/storage/repositories/settings.py +34 -0
  46. hh_applicant_tool/storage/repositories/vacancies.py +8 -0
  47. hh_applicant_tool/storage/utils.py +49 -0
  48. hh_applicant_tool/utils/__init__.py +31 -0
  49. hh_applicant_tool/utils/attrdict.py +6 -0
  50. hh_applicant_tool/utils/binpack.py +167 -0
  51. hh_applicant_tool/utils/config.py +55 -0
  52. hh_applicant_tool/utils/dateutil.py +19 -0
  53. hh_applicant_tool/{jsonc.py → utils/jsonc.py} +12 -6
  54. hh_applicant_tool/utils/jsonutil.py +61 -0
  55. hh_applicant_tool/utils/log.py +144 -0
  56. hh_applicant_tool/utils/misc.py +12 -0
  57. hh_applicant_tool/utils/mixins.py +220 -0
  58. hh_applicant_tool/utils/string.py +27 -0
  59. hh_applicant_tool/utils/terminal.py +19 -0
  60. hh_applicant_tool/utils/user_agent.py +17 -0
  61. hh_applicant_tool-1.4.7.dist-info/METADATA +628 -0
  62. hh_applicant_tool-1.4.7.dist-info/RECORD +67 -0
  63. hh_applicant_tool/ai/blackbox.py +0 -55
  64. hh_applicant_tool/color_log.py +0 -35
  65. hh_applicant_tool/mixins.py +0 -13
  66. hh_applicant_tool/operations/clear_negotiations.py +0 -113
  67. hh_applicant_tool/operations/delete_telemetry.py +0 -30
  68. hh_applicant_tool/operations/get_employer_contacts.py +0 -293
  69. hh_applicant_tool/telemetry_client.py +0 -106
  70. hh_applicant_tool/types.py +0 -45
  71. hh_applicant_tool/utils.py +0 -104
  72. hh_applicant_tool-0.6.12.dist-info/METADATA +0 -349
  73. hh_applicant_tool-0.6.12.dist-info/RECORD +0 -33
  74. {hh_applicant_tool-0.6.12.dist-info → hh_applicant_tool-1.4.7.dist-info}/WHEEL +0 -0
  75. {hh_applicant_tool-0.6.12.dist-info → hh_applicant_tool-1.4.7.dist-info}/entry_points.txt +0 -0
@@ -1,106 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import logging
5
- import os
6
- import time
7
- import warnings
8
- from functools import partialmethod
9
- from typing import Any, Dict, Optional
10
- from urllib.parse import urljoin
11
- import requests
12
- from .utils import Config
13
-
14
- # Сертификат на сервере давно истек, но его обновлять мне лень...
15
- warnings.filterwarnings("ignore", message="Unverified HTTPS request")
16
-
17
- logger = logging.getLogger(__package__)
18
-
19
-
20
- class TelemetryError(Exception):
21
- """Исключение, возникающее при ошибках в работе TelemetryClient."""
22
-
23
- pass
24
-
25
-
26
- class TelemetryClient:
27
- """Клиент для отправки телеметрии на сервер."""
28
-
29
- server_address: str = "https://hh-applicant-tool.mooo.com:54157/"
30
- default_delay: float = 0.334 # Задержка по умолчанию в секундах
31
-
32
- def __init__(
33
- self,
34
- telemetry_client_id: str,
35
- server_address: Optional[str] = None,
36
- *,
37
- session: Optional[requests.Session] = None,
38
- user_agent: str = "Mozilla/5.0 (HHApplicantTelemetry/1.0)",
39
- proxies: dict | None = None,
40
- delay: Optional[float] = None,
41
- ) -> None:
42
- self.send_telemetry_id = telemetry_client_id
43
- self.server_address = os.getenv(
44
- "TELEMETRY_SERVER", server_address or self.server_address
45
- )
46
- self.session = session or requests.Session()
47
- self.user_agent = user_agent
48
- self.proxies = proxies
49
- self.delay = delay if delay is not None else self.default_delay
50
- self.last_request_time = time.monotonic() # Время последнего запроса
51
-
52
- def request(
53
- self,
54
- method: str,
55
- endpoint: str,
56
- data: Dict[str, Any] | None = None,
57
- **kwargs: Any,
58
- ) -> Dict[str, Any]:
59
- method = method.upper()
60
- url = urljoin(self.server_address, endpoint)
61
- has_body = method in ["POST", "PUT", "PATCH"]
62
-
63
- # Вычисляем время, прошедшее с последнего запроса
64
- current_time = time.monotonic()
65
- time_since_last_request = current_time - self.last_request_time
66
-
67
- # Если прошло меньше времени, чем задержка, ждем оставшееся время
68
- if time_since_last_request < self.delay:
69
- time.sleep(self.delay - time_since_last_request)
70
-
71
- try:
72
- response = self.session.request(
73
- method,
74
- url,
75
- headers={
76
- "User-Agent": self.user_agent,
77
- "X-Telemetry-Client-ID": self.send_telemetry_id,
78
- },
79
- proxies=self.proxies,
80
- params=data if not has_body else None,
81
- json=data if has_body else None,
82
- verify=False, # Игнорирование истекшего сертификата
83
- **kwargs,
84
- )
85
- # response.raise_for_status()
86
- result = response.json()
87
- if 200 > response.status_code >= 300:
88
- raise TelemetryError(result)
89
- return result
90
-
91
- except (
92
- requests.exceptions.RequestException,
93
- json.JSONDecodeError,
94
- ) as ex:
95
- raise TelemetryError(str(ex)) from ex
96
- finally:
97
- # Обновляем время последнего запроса
98
- self.last_request_time = time.monotonic()
99
-
100
- get_telemetry = partialmethod(request, "GET")
101
- send_telemetry = partialmethod(request, "POST")
102
-
103
- @classmethod
104
- def create_from_config(cls, config: Config) -> "TelemetryClient":
105
- assert "telemetry_client_id" in config
106
- return cls(config["telemetry_client_id"])
@@ -1,45 +0,0 @@
1
- from typing import TypedDict, Literal
2
-
3
-
4
- class AccessToken(TypedDict):
5
- access_token: str
6
- refresh_token: str
7
- expires_in: int
8
- token_type: Literal["bearer"]
9
-
10
-
11
- class ApiListResponse(TypedDict):
12
- ...
13
- items: list
14
- found: int
15
- page: int
16
- pages: int
17
- per_page: int
18
-
19
-
20
- class VacancyItem(TypedDict):
21
- accept_incomplete_resumes: bool
22
- address: dict
23
- alternate_url: str
24
- apply_alternate_url: str
25
- area: dict
26
- contacts: dict
27
- counters: dict
28
- department: dict
29
- employer: dict
30
- has_test: bool
31
- id: int
32
- insider_interview: dict
33
- name: str
34
- professional_roles: list
35
- published_at: str
36
- relations: list
37
- response_letter_required: bool
38
- response_url: str | None
39
- salary: dict
40
- schedule: dict
41
- snippet: dict
42
- sort_point_distance: float
43
- type: dict
44
- url: str
45
- experience: dict
@@ -1,104 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import hashlib
4
- import json
5
- import platform
6
- import random
7
- import re
8
- import sys
9
- from datetime import datetime
10
- from functools import partial
11
- from os import getenv
12
- from pathlib import Path
13
- from threading import Lock
14
- from typing import Any
15
-
16
- from .constants import INVALID_ISO8601_FORMAT
17
- from .jsonc import parse_jsonc
18
-
19
- print_err = partial(print, file=sys.stderr, flush=True)
20
-
21
-
22
- def get_config_path() -> Path:
23
- match platform.system():
24
- case "Windows":
25
- return Path(getenv("APPDATA", Path.home() / "AppData" / "Roaming"))
26
- case "Darwin": # macOS
27
- return Path.home() / "Library" / "Application Support"
28
- case _: # Linux and etc
29
- return Path(getenv("XDG_CONFIG_HOME", Path.home() / ".config"))
30
-
31
-
32
- class AttrDict(dict):
33
- __getattr__ = dict.get
34
- __setattr__ = dict.__setitem__
35
- __delattr__ = dict.__delitem__
36
-
37
-
38
- # TODO: добавить defaults
39
- class Config(dict):
40
- def __init__(self, config_path: str | Path | None = None):
41
- self._config_path = Path(config_path or get_config_path())
42
- self._lock = Lock()
43
- self.load()
44
-
45
- def load(self) -> None:
46
- if self._config_path.exists():
47
- with self._lock:
48
- with self._config_path.open("r", encoding="utf-8", errors="replace") as f:
49
- self.update(json.load(f))
50
-
51
- def save(self, *args: Any, **kwargs: Any) -> None:
52
- self.update(*args, **kwargs)
53
- self._config_path.parent.mkdir(exist_ok=True, parents=True)
54
- with self._lock:
55
- with self._config_path.open("w+", encoding="utf-8", errors="replace") as fp:
56
- json.dump(
57
- self,
58
- fp,
59
- ensure_ascii=True,
60
- indent=2,
61
- sort_keys=True,
62
- )
63
-
64
- __getitem__ = dict.get
65
-
66
-
67
- def truncate_string(s: str, limit: int = 75, ellipsis: str = "…") -> str:
68
- return s[:limit] + bool(s[limit:]) * ellipsis
69
-
70
-
71
- def make_hash(data: str) -> str:
72
- # Вычисляем хеш SHA-256
73
- return hashlib.sha256(data.encode()).hexdigest()
74
-
75
-
76
- def parse_invalid_datetime(dt: str) -> datetime:
77
- return datetime.strptime(dt, INVALID_ISO8601_FORMAT)
78
-
79
-
80
- def fix_datetime(dt: str | None) -> str | None:
81
- return parse_invalid_datetime(dt).isoformat() if dt is not None else None
82
-
83
-
84
- def random_text(s: str) -> str:
85
- while (
86
- temp := re.sub(
87
- r"{([^{}]+)}",
88
- lambda m: random.choice(
89
- m.group(1).split("|"),
90
- ),
91
- s,
92
- )
93
- ) != s:
94
- s = temp
95
- return s
96
-
97
-
98
- def parse_interval(interval: str) -> tuple[float, float]:
99
- """Парсит строку интервала и возвращает кортеж с минимальным и максимальным значениями."""
100
- if "-" in interval:
101
- min_interval, max_interval = map(float, interval.split("-"))
102
- else:
103
- min_interval = max_interval = float(interval)
104
- return min(min_interval, max_interval), max(min_interval, max_interval)
@@ -1,349 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: hh-applicant-tool
3
- Version: 0.6.12
4
- Summary:
5
- Author: Senior YAML Developer
6
- Author-email: yamldeveloper@proton.me
7
- Requires-Python: >=3.10,<4.0
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Programming Language :: Python :: 3.10
10
- Classifier: Programming Language :: Python :: 3.11
11
- Classifier: Programming Language :: Python :: 3.12
12
- Classifier: Programming Language :: Python :: 3.13
13
- Classifier: Programming Language :: Python :: 3.14
14
- Provides-Extra: qt
15
- Requires-Dist: prettytable (>=3.6.0,<4.0.0)
16
- Requires-Dist: pyqt6 (==6.7.0) ; extra == "qt"
17
- Requires-Dist: pyqt6-webengine (==6.7.0) ; extra == "qt"
18
- Requires-Dist: requests[socks] (>=2.32.3,<3.0.0)
19
- Description-Content-Type: text/markdown
20
-
21
- ## HH Applicant Tool
22
-
23
- ![Publish to PyPI](https://github.com/s3rgeym/hh-applicant-tool/actions/workflows/publish.yml/badge.svg)
24
- [![PyPi Version](https://img.shields.io/pypi/v/hh-applicant-tool)]()
25
- [![Python Versions](https://img.shields.io/pypi/pyversions/hh-applicant-tool.svg)]()
26
- [![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/s3rgeym/hh-applicant-tool)]()
27
- [![PyPI - Downloads](https://img.shields.io/pypi/dm/hh-applicant-tool)]()
28
- [![Total Downloads](https://static.pepy.tech/badge/hh-applicant-tool)]()
29
-
30
- <div align="center">
31
- <img src="https://github.com/user-attachments/assets/29d91490-2c83-4e3f-a573-c7a6182a4044" width="500">
32
- </div>
33
-
34
- ### Описание
35
-
36
- > Утилита для генерации сопроводительного письма может использовать AI
37
-
38
- Утилита для успешных волчат и старых волков с опытом, служащая для автоматизации действий на HH.RU таких как рассылка откликов на подходящие вакансии и обновление всех резюме (бесплатный аналог услуги на HH). Утилита сохраняет контактные данные, всех кто вам писал, и позволяет вам по ним проводить поиск (название компании, сайт и тп). Это удобно, так как контакт сохранится даже, если вышлют отказ в дальнейшем. У утилиты есть канал в пашкограме: [HH Applicant Tool](https://t.me/hh_applicant_tool). Старый <s>[HH Resume Automate](https://t.me/hh_resume_automate)</s> был выпилен какими-то долбоебами, углядевшими во флаге Японии с двумя буквами h нарушение авторских прав...
39
-
40
- Работает с Python >= 3.10. Нужную версию Python можно поставить через
41
- asdf/pyenv/conda и что-то еще. В школотронской Manjaro и даже в последних Ubuntu
42
- версия Python новее.
43
-
44
- Данная утилита написана для Linux, но будет работать и на Ga..Mac OS, и в Windows, но с WSL не будет, так как для авторизации требуются оконный сервер X11 либо Wayland — только прямая установка пакета через pip в Windows. После авторизации вы можете перенести конфиг на сервер и запускать утилиту через systemd или cron. Столь странный процесс связан с тем, что на странице авторизации запускается море скриптов, которые шифруют данные на клиенте перед отправкой на сервер, а так же выполняется куча запросов чтобы проверить не бот ли ты. Хорошо, что после авторизации никаких проверок по факту нет, даже айпи не проверяется на соответсвие тому с какого была авторизация. В этой лапше мне лень разбираться. Так же при наличии рутованного телефона можно вытащить `access` и `refresh` токены из официального приложения и добавить их в конфиг.
45
-
46
- Пример работы:
47
-
48
- ![image](https://github.com/user-attachments/assets/a0cce1aa-884b-4d84-905a-3bb207eba4a3)
49
-
50
- > Если в веб-интерфейсе выставить фильтры, то они будут применяться в скрипте при отклике на подходящие
51
-
52
- ### Внимание!!!
53
-
54
- Если для Вас проблема установить данную утилиту, лень разбираться с ее настройкой, то Вы можете установить мое приложение под **Android** [HH Resume Automate](https://github.com/s3rgeym/hh-resume-automate/). Оно обладает минимальным функционалом: обновление резюме (одного) и рассылка откликов (чистить их и тп нельзя).
55
-
56
- ### Предыстория
57
-
58
- Долгое время я делал массовые заявки с помощью консоли браузера:
59
-
60
- ```js
61
- $$('[data-qa="vacancy-serp__vacancy_response"]').forEach((el) => el.click());
62
- ```
63
-
64
- Оно работает, хоть и не идеально. Я даже пробовал автоматизировать рассылки через `p[yu]ppeeter`, пока не прочитал [документацию](https://github.com/hhru/api). И не обнаружил, что **API** (интерфейс) содержит все необходимые мне методы. Headhunter позволяет создать свое приложение, но там ручная модерация, и наврядли кто-то разрешит мне создать приложение для спама заявками. Я [декомпилировал](https://gist.github.com/s3rgeym/eee96bbf91b04f7eb46b7449f8884a00) официальное приложение для **Android** и получил **CLIENT_ID** и **CLIENT_SECRET**, необходимые для работы через **API**.
65
-
66
- ### Установка
67
-
68
- Универсальный с использованием pipx (требует пакета `python-pipx` в Arch):
69
-
70
- ```bash
71
- # Версия с поддержкой авторизации через запуск окна с браузером (эта версия очень много весит)
72
- # Можно использовать обычный pip
73
- $ pipx install 'hh-applicant-tool[qt]'
74
-
75
- # Если хочется использовать самую последнюю версию, то можно установить ее через git
76
- $ pipx install git+https://github.com/s3rgeym/hh-applicant-tool
77
-
78
- # Для обновления до новой версии
79
- $ pipx upgrade hh-applicant-tool
80
- ```
81
-
82
- pipx добавляет исполняемый файл `hh-appplicant-tool` в `~/.local/bin`, делая эту команду доступной. Путь до `~/.local/bin` должен быть в `$PATH` (в большинстве дистрибутивов он добавлен).
83
-
84
- Традиционный способ для Linux/Mac:
85
-
86
- ```sh
87
- mkdir -p ~/.venvs
88
- python -m venv ~/.venvs/hh-applicant-tool
89
- # Это придется делать постоянно, чтобы команда hh-applicant-tool стала доступна
90
- . ~/.venvs/hh-applicant-tool/bin/activate
91
- pip install 'hh-applicant-tool[qt]'
92
- ```
93
-
94
- Отдельно я распишу процесс установки в **Windows** в подробностях:
95
-
96
- * Для начала поставьте последнюю версию **Python 3** любым удобным способом.
97
- * Запустите **Terminal** или **PowerShell** от Администратора и выполните:
98
- ```ps
99
- Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted
100
- ```
101
- Данная политика разрешает текущему пользователю (от которого зашли) запускать скрипты. Без нее не будут работать виртуальные окружения.
102
- * Создайте и активируйте виртуальное окружение:
103
- ```ps
104
- PS> python -m venv hh-applicant-venv
105
- PS> .\hh-applicant-venv\Scripts\activate
106
- ```
107
-
108
- * Поставьте все пакеты в виртуальное окружение `hh-applicant-venv`:
109
- ```ps
110
- (hh-applicant-venv) PS> pip install hh-applicant-tool[qt]
111
- ```
112
- * Проверьте работает ли оно:
113
- ```ps
114
- (hh-applicant-venv) PS> hh-applicant-tool -h
115
- ```
116
- * В случае неудачи вернитесь к первому шагу.
117
- * Для последующих запусков сначала активируйте виртуальное окружение.
118
-
119
- ### Авторизация
120
-
121
- ```bash
122
- $ hh-applicant-tool -vv authorize
123
- ```
124
-
125
- ![image](https://github.com/user-attachments/assets/88961e31-4ea3-478f-8c43-914d6785bc3b)
126
-
127
- > В Windows не забудьте разрешить доступ к сети (Allow access) в всплывающем окне.
128
-
129
- Проверка авторизации:
130
-
131
- ```bash
132
- $ hh-applicant-tool whoami
133
- {
134
- "auth_type": "applicant",
135
- "counters": {
136
- "new_resume_views": 1488,
137
- "resumes_count": 1,
138
- "unread_negotiations": 228
139
- },
140
- "email": "vasya.pupkin@gmail.com",
141
- "employer": null,
142
- "first_name": "Вася",
143
- "id": "1234567890",
144
- "is_admin": false,
145
- "is_anonymous": false,
146
- "is_applicant": true,
147
- "is_application": false,
148
- "is_employer": false,
149
- "is_in_search": true,
150
- "last_name": "Пупкин",
151
- "manager": null,
152
- "mid_name": null,
153
- "middle_name": null,
154
- "negotiations_url": "https://api.hh.ru/negotiations",
155
- "personal_manager": null,
156
- "phone": "79012345678",
157
- "profile_videos": {
158
- "items": []
159
- },
160
- "resumes_url": "https://api.hh.ru/resumes/mine"
161
- }
162
- ```
163
-
164
- В случае успешной авторизации токены будут сохранены в `config.json`:
165
-
166
- ```json
167
- {
168
- "token": {
169
- "access_token": "...",
170
- "created_at": 1678151427,
171
- "expires_in": 1209599,
172
- "refresh_token": "...",
173
- "token_type": "bearer"
174
- }
175
- }
176
- ```
177
-
178
- Токен доступа выдается на две недели. После его нужно обновить:
179
-
180
- ```bash
181
- $ hh-applicant-tool refresh-token
182
- ```
183
-
184
- ### Пути до файла config.json
185
-
186
- | OS | Путь |
187
- |----------------------------|---------------------------------------------------------------------|
188
- | **Windows** | `C:\Users\%username%\AppData\Roaming\hh-applicant-tool\config.json` |
189
- | **macOS** | `~/Library/Application Support/hh-applicant-tool/config.json` |
190
- | **Linux** | `~/.config/hh-applicant-tool/config.json` |
191
-
192
- Полный путь до конфигурационного файла можно вывести с помощью команды:
193
-
194
- ```bash
195
- hh-applicant-tool config -p
196
- ```
197
-
198
- Через конфиг можно задать дополнительные настройки:
199
-
200
- | Имя атрибута | Описание |
201
- | --- | --- |
202
- | `user_agent` | Кастомный юзерагент, передаваемый при кажом запросе. |
203
- | `proxy_url` | Прокси, используемый для всех запросов, например, `socks5h://127.0.0.1:9050` |
204
- | `reply_message` | Сообщение для ответа работодателю при отклике на вакансии, см. формат сообщений |
205
-
206
- ### Описание команд
207
-
208
- ```bash
209
- $ hh-applicant-tool [ GLOBAL_FLAGS ] [ OPERATION [ OPERATION_FLAGS ] ]
210
-
211
- # Справка по глобальным флагам и список операций
212
- $ hh-applicant-tool -h
213
-
214
- # Справка по операции
215
- $ hh-applicant-tool apply-similar -h
216
-
217
- # Авторизуемся
218
- $ hh-applicant-tool authorize
219
-
220
- # Рассылаем заявки
221
- $ hh-applicant-tool apply-similar
222
-
223
- # Поднимаем резюме
224
- $ hh-applicant-tool update-resumes
225
-
226
- # Чистим заявки и баним за отказы говноконторы
227
- $ hh-applicant-tool clear-negotiations --blacklist-discard
228
-
229
- # Экспортировать в HTML, контакты работодателей, которые когда-либо высылали вам
230
- # приглашение
231
- $ hh-applicant-tool get-employer-contacts --export -f html > report.html
232
- ```
233
-
234
- Можно вызвать любой метод API:
235
-
236
- ```bash
237
- $ hh-applicant-tool call-api /employers text="IT" only_with_vacancies=true | jq -r '.items[].alternate_url'
238
- https://hh.ru/employer/1966364
239
- https://hh.ru/employer/4679771
240
- https://hh.ru/employer/8932785
241
- https://hh.ru/employer/9451699
242
- https://hh.ru/employer/766478
243
- https://hh.ru/employer/4168187
244
- https://hh.ru/employer/9274777
245
- https://hh.ru/employer/1763330
246
- https://hh.ru/employer/5926815
247
- https://hh.ru/employer/1592535
248
- https://hh.ru/employer/9627641
249
- https://hh.ru/employer/4073857
250
- https://hh.ru/employer/2667859
251
- https://hh.ru/employer/4053700
252
- https://hh.ru/employer/5190600
253
- https://hh.ru/employer/607484
254
- https://hh.ru/employer/9386615
255
- https://hh.ru/employer/80660
256
- https://hh.ru/employer/6078902
257
- https://hh.ru/employer/1918903
258
- ```
259
-
260
- Данная возможность полезна для написания Bash-скриптов.
261
-
262
- Глобальные флаги:
263
-
264
- - `-v` используется для вывода отладочной информации. Два таких флага, например, выводят запросы к **API**.
265
- - `-c <path>` можно создать путь до конфига. С помощью этого флага можно одновременно использовать несколько профилей.
266
-
267
- | Операция | Описание |
268
- | ---------------------- | --------------------------------------------------------------------------------------------------- |
269
- | **authorize** | Открывает сайт hh.ru для авторизации и перехватывает перенаправление на `hhadnroid://oauthresponse` |
270
- | **whoami** | Выводит информацию об авторизованном пользователе |
271
- | **list-resumes** | Список резюме |
272
- | **update-resumes** | Обновить все резюме. Аналогично нажатию кнопки «Обновить дату». |
273
- | **apply-similar** | Откликнуться на все подходящие вакансии. Лимит = 200 в день. На HH есть спам-фильтры, так что лучше не рассылайте отклики со ссылками, иначе рискуете попасть в теневой бан. |
274
- | **reply-employers** | Ответить во все чаты с работодателями, где нет ответа либо не прочитали ваш предыдущий ответ |
275
- | **clear-negotiations** | Удаляет отказы и отменяет заявки, которые долго висят |
276
- | **call-api** | Вызов произвольного метода API с выводом результата. |
277
- | **refresh-token** | Обновляет access_token. |
278
- | **config** | Редактировать конфигурационный файл. |
279
- | **get-employer-contacts** | Получить список полученных вами контактов работодателей. Поддерживается так же экспорт в html/jsonl. Если хотите собирать контакты с нескольких акков, то укажите им одинаковый `client_telemetry_id` в конфигах. |
280
- | **delete-telemetry** | Удадяет телеметрию (контакты работодателей, которые вас пригласили), если та была включена. |
281
-
282
- ### Формат текста сообщений
283
-
284
- Команда `apply-similar` поддерживает специальный формат сообщений.
285
-
286
- Так же в сообщении можно использовать плейсхолдеры:
287
-
288
- - **`%(vacancy_name)s`**: Название вакансии.
289
- - **`%(employer_name)s`**: Название работодателя.
290
- - **`%(first_name)s`**: Имя пользователя.
291
- - **`%(last_name)s`**: Фамилия пользователя.
292
- - **`%(email)s`**: Email пользователя.
293
- - **`%(phone)s`**: Телефон пользователя.
294
-
295
- Эти плейсхолдеры могут быть использованы в сообщениях для отклика на вакансии, чтобы динамически подставлять соответствующие данные в текст сообщения. Например:
296
-
297
- ```
298
- Меня заинтересовала ваша вакансия %(vacancy_name)s. Прошу рассмотреть мою кандидатуру. С уважением, %(first_name)s %(last_name)s.
299
- ```
300
-
301
- Так же можно делать текст уникальным с помощью `{}`. Внутри них через `|` перечисляются варианты, один из которых будет случайно выбран:
302
-
303
- ```
304
- {Здоров|Привет}, {как {ты|сам}|что делаешь}?
305
- ```
306
-
307
- В итоге получится что-то типа:
308
-
309
- ```
310
- Привет, как ты?
311
- ```
312
-
313
- ### Использование AI для генерации сопроводительного письма
314
-
315
- * Перейдите на сайт [blackbox.ai](https://www.blackbox.ai) и создайте чат.
316
- * В первом сообщении опишите свой опыт и тп.
317
- * Далее откройте devtools, нажав `F12`.
318
- * Во вкладке `Network` последним должен быть POST-запрос на `https://www.blackbox.ai/api/chat`.
319
- * Запустите редактирование конфига:
320
- ```sh
321
- hh-applicant-tool config
322
- ```
323
- * Измените конфиг:
324
- ```json
325
- {
326
- // ...
327
- "blackbox": {
328
- "session_id": "<В заголовках запроса найдите Cookie, скопируйте сюда значение sessionId до ;>",
329
- "chat_payload": <Сюда вставьте тело запроса типа {"messages":[{"id":"IXqdOx9","content":"Я программист fullstack-разработчик...","role":"user"}],"id":"IXqdOx9","previewToken":null,"userId":null,...,"webSearchModePrompt":false,"deepSearchMode":false}>
330
- }
331
- }
332
- ```
333
- * Пример рассылки откликов с генерированным письмом:
334
- ```sh
335
- hh-applicant-tool apply-similar -f --ai
336
- ```
337
-
338
- ### Написание плагинов
339
-
340
- Утилита использует систему плагинов. Все они лежат в [operations](https://github.com/s3rgeym/hh-applicant-tool/tree/main/hh_applicant_tool/operations). Модули расположенные там автоматически добавляются как доступные операции. За основу для своего плагина можно взять [whoami.py](https://github.com/s3rgeym/hh-applicant-tool/tree/main/hh_applicant_tool/operations/whoami.py).
341
-
342
- Отдельные замечания у меня к API HH. Оно пиздец какое кривое. Например, при создании заявки возвращается пустой ответ либо редирект, хотя по логике должен возвраться созданный объект. Так же в ответах сервера нет `Content-Length`. Из-за этого нельзя узнать есть тело у ответа сервера нужно его пробовать прочитать. Я так понял там какой-то прокси оборачивает все запросы и отдает всегда `Transfer-Encoding: Chunked`. А еще он возвращает 502 ошибку, когда бекенд на Java падает либо долго отвечает (таймаут)? А вот [язык запросов](https://hh.ru/article/1175) мне понравился. Можно что-то типа этого использовать `NOT (!ID:123 OR !ID:456 OR !ID:789)` что бы отсеить какие-то вакансии.
343
-
344
- Для создания своих плагинов прочитайте документацию:
345
-
346
- * [HH.RU OpenAPI](https://api.hh.ru/openapi/redoc)
347
-
348
- Для тестирования запросов к API используйте команду `call-api` и `jq` для вывода JSON в удобочитаемом формате.
349
-
@@ -1,33 +0,0 @@
1
- hh_applicant_tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- hh_applicant_tool/__main__.py,sha256=cwKJAAML0RRKT9Qbzcwf07HHcuSd8oh7kx4P1apndWQ,84
3
- hh_applicant_tool/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- hh_applicant_tool/ai/blackbox.py,sha256=vqkEpsX7q7bgX49dmmifYJmrbuz_WBg5u9M9J9XdQlI,1670
5
- hh_applicant_tool/ai/openai.py,sha256=jEh8y4Y51rcuvZ41tkcq65EHKt8bdT6NahzbHJN5KhI,1682
6
- hh_applicant_tool/api/__init__.py,sha256=kgFSHibRaAugN2BA3U1djEa20qgKJUUVouwJzjEB0DU,84
7
- hh_applicant_tool/api/client.py,sha256=-nQMX_KY2zcV98CtBqq7BHyseDs5LglhHoMF1C7Oi4M,9895
8
- hh_applicant_tool/api/errors.py,sha256=Rd1XE2OTtZDa3GDqght2LtOnTHWtOx7Zsow87nn4x4A,1807
9
- hh_applicant_tool/color_log.py,sha256=gN6j1Ayy1G7qOMI_e3WvfYw_ublzeQbKgsVLhqGg_3s,823
10
- hh_applicant_tool/constants.py,sha256=KV_jowi21ToMp8yqF1vWolnVZb8nAC3rYRkcFJ71m-Q,759
11
- hh_applicant_tool/jsonc.py,sha256=QNS4gRHfi7SAeOFnffAIuhH7auC4Y4HAkmH12eX5PkI,4002
12
- hh_applicant_tool/main.py,sha256=A4YPkNXAdZY0GoGm0iigiQtzXTrpR3SaIGo54q9-Dd0,5652
13
- hh_applicant_tool/mixins.py,sha256=8VoyrNgdlljy6pLTSFGJOYd9kagWT3yFOZYIGR6MEbI,425
14
- hh_applicant_tool/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- hh_applicant_tool/operations/apply_similar.py,sha256=dS775_LJKpb7sHGLTno60KLJcIj4DnzY3Bu7yfzGadw,24916
16
- hh_applicant_tool/operations/authorize.py,sha256=NYrxe6oemUBcDHioT1t1lJmi9l45V4ZXzQPD_-nf6hk,3328
17
- hh_applicant_tool/operations/call_api.py,sha256=o3GZgtqk6w4zpCm-JTHVjFrKVOwW-vsu1HdRi-hqAjo,1423
18
- hh_applicant_tool/operations/clear_negotiations.py,sha256=FG_43P5GWmfKUggkKZqDznQ2_iBJ3zrZtv8yEI2XOXQ,4527
19
- hh_applicant_tool/operations/config.py,sha256=BzGWbHwNlXIpYHxnZUidDZTk1-7GZb8UL-asy8w4uN4,1390
20
- hh_applicant_tool/operations/delete_telemetry.py,sha256=JHdh_l7IJL_qy5AIIy8FQpUupmH60D3a6zjfEVKkT2U,986
21
- hh_applicant_tool/operations/get_employer_contacts.py,sha256=Sd-x3O08bmKm1OGVLtJ6rcPZ_j1jwjlqKV4z1n_G-38,9918
22
- hh_applicant_tool/operations/list_resumes.py,sha256=dILHyBCSEVqdNAvD8SML5f2Lau1R2AzTaKE9B4FG8Wg,1109
23
- hh_applicant_tool/operations/refresh_token.py,sha256=v_Fcw9mCfOdE6MLTCQjZQudhJPX0fup3k0BaIM394Qw,834
24
- hh_applicant_tool/operations/reply_employers.py,sha256=40rsTgpoEUaRQUq5ZQYgojKb42smYRkbDDD2S_7Vwtg,13512
25
- hh_applicant_tool/operations/update_resumes.py,sha256=_r7HA_vpYMs5DFY-mVP1ZRG9bggsv7ebKYrwteBmJ30,1053
26
- hh_applicant_tool/operations/whoami.py,sha256=pNWJMmEQLBk3U6eiGz4CHcX7eXzDXcfezFjX7zLjqyA,711
27
- hh_applicant_tool/telemetry_client.py,sha256=1EKZWc5kMx2yERW9SrR9vaf-OB6M_KKcMXeicH5YyY0,3834
28
- hh_applicant_tool/types.py,sha256=sQbt_vGXtWPRJ3UzcUkE87BTHOaGTsFxqdZa_qFghZE,864
29
- hh_applicant_tool/utils.py,sha256=3T4A2AykGqTwtGAttmYplIjHwFl3pNAcbWIVuA-OheQ,3080
30
- hh_applicant_tool-0.6.12.dist-info/METADATA,sha256=Bm4jg3-5ajaUefjiFHB9mIkQzw4MKMhy6fPv4TWZY84,21781
31
- hh_applicant_tool-0.6.12.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
32
- hh_applicant_tool-0.6.12.dist-info/entry_points.txt,sha256=Vb7M2YaYLMtKYJZh8chIrXZApMzSRFT1-rQw-U9r10g,65
33
- hh_applicant_tool-0.6.12.dist-info/RECORD,,