hh-applicant-tool 0.6.3__py3-none-any.whl → 0.6.12__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.
- hh_applicant_tool/ai/openai.py +67 -0
- hh_applicant_tool/operations/apply_similar.py +221 -15
- hh_applicant_tool/operations/clear_negotiations.py +5 -1
- hh_applicant_tool/operations/reply_employers.py +3 -2
- hh_applicant_tool/types.py +1 -0
- {hh_applicant_tool-0.6.3.dist-info → hh_applicant_tool-0.6.12.dist-info}/METADATA +21 -5
- {hh_applicant_tool-0.6.3.dist-info → hh_applicant_tool-0.6.12.dist-info}/RECORD +9 -8
- {hh_applicant_tool-0.6.3.dist-info → hh_applicant_tool-0.6.12.dist-info}/WHEEL +1 -1
- {hh_applicant_tool-0.6.3.dist-info → hh_applicant_tool-0.6.12.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__package__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OpenAIError(Exception):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OpenAIChat:
|
|
13
|
+
chat_endpoint: str = "https://api.openai.com/v1/chat/completions"
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
token: str,
|
|
18
|
+
model: str,
|
|
19
|
+
system_prompt: str,
|
|
20
|
+
proxies: dict[str, str] = {}
|
|
21
|
+
):
|
|
22
|
+
self.token = token
|
|
23
|
+
self.model = model
|
|
24
|
+
self.system_prompt = system_prompt
|
|
25
|
+
self.proxies = proxies
|
|
26
|
+
|
|
27
|
+
def default_headers(self) -> dict[str, str]:
|
|
28
|
+
return {
|
|
29
|
+
"Authorization": f"Bearer {self.token}",
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def send_message(self, message: str) -> str:
|
|
34
|
+
|
|
35
|
+
payload = {
|
|
36
|
+
"model": self.model,
|
|
37
|
+
"messages": [
|
|
38
|
+
{
|
|
39
|
+
"role": "system",
|
|
40
|
+
"content": self.system_prompt
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"role": "user",
|
|
44
|
+
"content": message
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"temperature": 0.7,
|
|
48
|
+
"max_completion_tokens": 1000
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
response = requests.post(
|
|
53
|
+
self.chat_endpoint,
|
|
54
|
+
json=payload,
|
|
55
|
+
headers=self.default_headers(),
|
|
56
|
+
proxies=self.proxies,
|
|
57
|
+
timeout=30
|
|
58
|
+
)
|
|
59
|
+
response.raise_for_status()
|
|
60
|
+
|
|
61
|
+
data = response.json()
|
|
62
|
+
assistant_message = data["choices"][0]["message"]["content"]
|
|
63
|
+
|
|
64
|
+
return assistant_message
|
|
65
|
+
|
|
66
|
+
except requests.exceptions.RequestException as ex:
|
|
67
|
+
raise OpenAIError(f"OpenAI API Error: {str(ex)}") from ex
|
|
@@ -4,11 +4,12 @@ import random
|
|
|
4
4
|
import time
|
|
5
5
|
from collections import defaultdict
|
|
6
6
|
from datetime import datetime, timedelta, timezone
|
|
7
|
-
from typing import TextIO
|
|
7
|
+
from typing import Any, TextIO
|
|
8
8
|
|
|
9
|
+
from ..ai.blackbox import BlackboxChat
|
|
10
|
+
from ..ai.openai import OpenAIChat
|
|
11
|
+
from ..api import ApiClient, ApiError
|
|
9
12
|
from ..api.errors import LimitExceeded
|
|
10
|
-
from ..ai.blackbox import BlackboxChat, BlackboxError
|
|
11
|
-
from ..api import ApiError, ApiClient
|
|
12
13
|
from ..main import BaseOperation
|
|
13
14
|
from ..main import Namespace as BaseNamespace
|
|
14
15
|
from ..mixins import GetResumeIdMixin
|
|
@@ -35,11 +36,47 @@ class Namespace(BaseNamespace):
|
|
|
35
36
|
page_interval: tuple[float, float]
|
|
36
37
|
order_by: str
|
|
37
38
|
search: str
|
|
39
|
+
schedule: str
|
|
38
40
|
dry_run: bool
|
|
41
|
+
# Пошли доп фильтры, которых не было
|
|
42
|
+
experience: str
|
|
43
|
+
employment: list[str] | None
|
|
44
|
+
area: list[str] | None
|
|
45
|
+
metro: list[str] | None
|
|
46
|
+
professional_role: list[str] | None
|
|
47
|
+
industry: list[str] | None
|
|
48
|
+
employer_id: list[str] | None
|
|
49
|
+
excluded_employer_id: list[str] | None
|
|
50
|
+
currency: str | None
|
|
51
|
+
salary: int | None
|
|
52
|
+
only_with_salary: bool
|
|
53
|
+
label: list[str] | None
|
|
54
|
+
period: int | None
|
|
55
|
+
date_from: str | None
|
|
56
|
+
date_to: str | None
|
|
57
|
+
top_lat: float | None
|
|
58
|
+
bottom_lat: float | None
|
|
59
|
+
left_lng: float | None
|
|
60
|
+
right_lng: float | None
|
|
61
|
+
sort_point_lat: float | None
|
|
62
|
+
sort_point_lng: float | None
|
|
63
|
+
no_magic: bool
|
|
64
|
+
premium: bool
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _bool(v: bool) -> str:
|
|
68
|
+
return str(v).lower()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _join_list(items: list[Any] | None) -> str:
|
|
72
|
+
return ",".join(f"{v}" for v in items) if items else ""
|
|
39
73
|
|
|
40
74
|
|
|
41
75
|
class Operation(BaseOperation, GetResumeIdMixin):
|
|
42
|
-
"""Откликнуться на все подходящие вакансии.
|
|
76
|
+
"""Откликнуться на все подходящие вакансии.
|
|
77
|
+
|
|
78
|
+
Описание фильтров для поиска вакансий: <https://api.hh.ru/openapi/redoc#tag/Poisk-vakansij-dlya-soiskatelya/operation/get-vacancies-similar-to-resume>
|
|
79
|
+
"""
|
|
43
80
|
|
|
44
81
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
45
82
|
parser.add_argument("--resume-id", help="Идентефикатор резюме")
|
|
@@ -100,12 +137,80 @@ class Operation(BaseOperation, GetResumeIdMixin):
|
|
|
100
137
|
type=str,
|
|
101
138
|
default=None,
|
|
102
139
|
)
|
|
140
|
+
|
|
141
|
+
parser.add_argument(
|
|
142
|
+
"--schedule",
|
|
143
|
+
help="Тип графика. Возможные значения: fullDay, shift, flexible, remote, flyInFlyOut для полного дня, сменного графика, гибкого графика, удаленной работы и вахтового метода",
|
|
144
|
+
type=str,
|
|
145
|
+
default=None,
|
|
146
|
+
)
|
|
103
147
|
parser.add_argument(
|
|
104
148
|
"--dry-run",
|
|
105
149
|
help="Не отправлять отклики, а только выводить параметры запроса",
|
|
106
150
|
default=False,
|
|
107
151
|
action=argparse.BooleanOptionalAction,
|
|
108
152
|
)
|
|
153
|
+
parser.add_argument(
|
|
154
|
+
"--experience",
|
|
155
|
+
help="Уровень опыта работы в вакансии. Возможные значения: noExperience, between1And3, between3And6, moreThan6",
|
|
156
|
+
type=str,
|
|
157
|
+
default=None,
|
|
158
|
+
)
|
|
159
|
+
parser.add_argument(
|
|
160
|
+
"--employment", nargs="+", help="Тип занятости (employment)"
|
|
161
|
+
)
|
|
162
|
+
parser.add_argument("--area", nargs="+", help="Регион (area id)")
|
|
163
|
+
parser.add_argument("--metro", nargs="+", help="Станции метро (metro id)")
|
|
164
|
+
parser.add_argument("--professional-role", nargs="+", help="Проф. роль (id)")
|
|
165
|
+
parser.add_argument("--industry", nargs="+", help="Индустрия (industry id)")
|
|
166
|
+
parser.add_argument("--employer-id", nargs="+", help="ID работодателей")
|
|
167
|
+
parser.add_argument(
|
|
168
|
+
"--excluded-employer-id", nargs="+", help="Исключить работодателей"
|
|
169
|
+
)
|
|
170
|
+
parser.add_argument("--currency", help="Код валюты (RUR, USD, EUR)")
|
|
171
|
+
parser.add_argument("--salary", type=int, help="Минимальная зарплата")
|
|
172
|
+
parser.add_argument(
|
|
173
|
+
"--only-with-salary", default=False, action=argparse.BooleanOptionalAction
|
|
174
|
+
)
|
|
175
|
+
parser.add_argument("--label", nargs="+", help="Метки вакансий (label)")
|
|
176
|
+
parser.add_argument("--period", type=int, help="Искать вакансии за N дней")
|
|
177
|
+
parser.add_argument("--date-from", help="Дата публикации с (YYYY-MM-DD)")
|
|
178
|
+
parser.add_argument("--date-to", help="Дата публикации по (YYYY-MM-DD)")
|
|
179
|
+
parser.add_argument("--top-lat", type=float, help="Гео: верхняя широта")
|
|
180
|
+
parser.add_argument("--bottom-lat", type=float, help="Гео: нижняя широта")
|
|
181
|
+
parser.add_argument("--left-lng", type=float, help="Гео: левая долгота")
|
|
182
|
+
parser.add_argument("--right-lng", type=float, help="Гео: правая долгота")
|
|
183
|
+
parser.add_argument(
|
|
184
|
+
"--sort-point-lat",
|
|
185
|
+
type=float,
|
|
186
|
+
help="Координата lat для сортировки по расстоянию",
|
|
187
|
+
)
|
|
188
|
+
parser.add_argument(
|
|
189
|
+
"--sort-point-lng",
|
|
190
|
+
type=float,
|
|
191
|
+
help="Координата lng для сортировки по расстоянию",
|
|
192
|
+
)
|
|
193
|
+
parser.add_argument(
|
|
194
|
+
"--no-magic",
|
|
195
|
+
default=False,
|
|
196
|
+
action=argparse.BooleanOptionalAction,
|
|
197
|
+
help="Отключить авторазбор текста запроса",
|
|
198
|
+
)
|
|
199
|
+
parser.add_argument(
|
|
200
|
+
"--premium",
|
|
201
|
+
default=False,
|
|
202
|
+
action=argparse.BooleanOptionalAction,
|
|
203
|
+
help="Только премиум вакансии",
|
|
204
|
+
)
|
|
205
|
+
parser.add_argument(
|
|
206
|
+
"--search-field", nargs="+", help="Поля поиска (name, company_name и т.п.)"
|
|
207
|
+
)
|
|
208
|
+
parser.add_argument(
|
|
209
|
+
"--clusters",
|
|
210
|
+
action=argparse.BooleanOptionalAction,
|
|
211
|
+
help="Включить кластеры (по умолчанию None)",
|
|
212
|
+
)
|
|
213
|
+
# parser.add_argument("--describe-arguments", action=argparse.BooleanOptionalAction, help="Вернуть описание параметров запроса")
|
|
109
214
|
|
|
110
215
|
def run(
|
|
111
216
|
self, args: Namespace, api_client: ApiClient, telemetry_client: TelemetryClient
|
|
@@ -138,6 +243,19 @@ class Operation(BaseOperation, GetResumeIdMixin):
|
|
|
138
243
|
chat_payload=config["chat_payload"],
|
|
139
244
|
proxies=self.api_client.proxies or {},
|
|
140
245
|
)
|
|
246
|
+
elif config := args.config.get("openai"):
|
|
247
|
+
model = "gpt-5.1"
|
|
248
|
+
system_prompt = "Напиши сопроводительное письмо для отклика на эту вакансию. Не используй placeholder'ы, твой ответ будет отправлен без обработки."
|
|
249
|
+
if "model" in config.keys():
|
|
250
|
+
model = config["model"]
|
|
251
|
+
if "system_prompt" in config.keys():
|
|
252
|
+
system_prompt = config["system_prompt"]
|
|
253
|
+
self.chat = OpenAIChat(
|
|
254
|
+
token=config["token"],
|
|
255
|
+
model=model,
|
|
256
|
+
system_prompt=system_prompt,
|
|
257
|
+
proxies=self.api_client.proxies or {},
|
|
258
|
+
)
|
|
141
259
|
|
|
142
260
|
self.pre_prompt = args.pre_prompt
|
|
143
261
|
|
|
@@ -147,7 +265,34 @@ class Operation(BaseOperation, GetResumeIdMixin):
|
|
|
147
265
|
self.force_message = args.force_message
|
|
148
266
|
self.order_by = args.order_by
|
|
149
267
|
self.search = args.search
|
|
268
|
+
self.schedule = args.schedule
|
|
150
269
|
self.dry_run = args.dry_run
|
|
270
|
+
self.experience = args.experience
|
|
271
|
+
self.search_field = args.search_field
|
|
272
|
+
self.employment = args.employment
|
|
273
|
+
self.area = args.area
|
|
274
|
+
self.metro = args.metro
|
|
275
|
+
self.professional_role = args.professional_role
|
|
276
|
+
self.industry = args.industry
|
|
277
|
+
self.employer_id = args.employer_id
|
|
278
|
+
self.excluded_employer_id = args.excluded_employer_id
|
|
279
|
+
self.currency = args.currency
|
|
280
|
+
self.salary = args.salary
|
|
281
|
+
self.only_with_salary = args.only_with_salary
|
|
282
|
+
self.label = args.label
|
|
283
|
+
self.period = args.period
|
|
284
|
+
self.date_from = args.date_from
|
|
285
|
+
self.date_to = args.date_to
|
|
286
|
+
self.top_lat = args.top_lat
|
|
287
|
+
self.bottom_lat = args.bottom_lat
|
|
288
|
+
self.left_lng = args.left_lng
|
|
289
|
+
self.right_lng = args.right_lng
|
|
290
|
+
self.sort_point_lat = args.sort_point_lat
|
|
291
|
+
self.sort_point_lng = args.sort_point_lng
|
|
292
|
+
self.clusters = args.clusters
|
|
293
|
+
# self.describe_arguments = args.describe_arguments
|
|
294
|
+
self.no_magic = args.no_magic
|
|
295
|
+
self.premium = args.premium
|
|
151
296
|
self._apply_similar()
|
|
152
297
|
|
|
153
298
|
def _get_application_messages(self, message_list: TextIO | None) -> list[str]:
|
|
@@ -267,10 +412,9 @@ class Operation(BaseOperation, GetResumeIdMixin):
|
|
|
267
412
|
|
|
268
413
|
if not do_apply:
|
|
269
414
|
logger.debug(
|
|
270
|
-
"
|
|
271
|
-
vacancy["alternate_url"],
|
|
415
|
+
"Останавливаем рассылку откликов, так как достигли лимита, попробуйте через сутки."
|
|
272
416
|
)
|
|
273
|
-
|
|
417
|
+
break
|
|
274
418
|
|
|
275
419
|
if relations:
|
|
276
420
|
logger.debug(
|
|
@@ -292,7 +436,7 @@ class Operation(BaseOperation, GetResumeIdMixin):
|
|
|
292
436
|
msg += message_placeholders["vacancy_name"]
|
|
293
437
|
logger.debug(msg)
|
|
294
438
|
msg = self.chat.send_message(msg)
|
|
295
|
-
except
|
|
439
|
+
except Exception as ex:
|
|
296
440
|
logger.error(ex)
|
|
297
441
|
continue
|
|
298
442
|
else:
|
|
@@ -352,16 +496,77 @@ class Operation(BaseOperation, GetResumeIdMixin):
|
|
|
352
496
|
except TelemetryError as ex:
|
|
353
497
|
logger.error(ex)
|
|
354
498
|
|
|
499
|
+
def _get_search_params(self, page: int, per_page: int) -> dict:
|
|
500
|
+
params = {
|
|
501
|
+
"page": page,
|
|
502
|
+
"per_page": per_page,
|
|
503
|
+
"order_by": self.order_by,
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if self.search:
|
|
507
|
+
params["text"] = self.search
|
|
508
|
+
if self.schedule:
|
|
509
|
+
params["schedule"] = self.schedule
|
|
510
|
+
if self.experience:
|
|
511
|
+
params["experience"] = self.experience
|
|
512
|
+
if self.currency:
|
|
513
|
+
params["currency"] = self.currency
|
|
514
|
+
if self.salary:
|
|
515
|
+
params["salary"] = self.salary
|
|
516
|
+
if self.period:
|
|
517
|
+
params["period"] = self.period
|
|
518
|
+
if self.date_from:
|
|
519
|
+
params["date_from"] = self.date_from
|
|
520
|
+
if self.date_to:
|
|
521
|
+
params["date_to"] = self.date_to
|
|
522
|
+
if self.top_lat:
|
|
523
|
+
params["top_lat"] = self.top_lat
|
|
524
|
+
if self.bottom_lat:
|
|
525
|
+
params["bottom_lat"] = self.bottom_lat
|
|
526
|
+
if self.left_lng:
|
|
527
|
+
params["left_lng"] = self.left_lng
|
|
528
|
+
if self.right_lng:
|
|
529
|
+
params["right_lng"] = self.right_lng
|
|
530
|
+
if self.sort_point_lat:
|
|
531
|
+
params["sort_point_lat"] = self.sort_point_lat
|
|
532
|
+
if self.sort_point_lng:
|
|
533
|
+
params["sort_point_lng"] = self.sort_point_lng
|
|
534
|
+
if self.search_field:
|
|
535
|
+
params["search_field"] = _join_list(self.search_field)
|
|
536
|
+
if self.employment:
|
|
537
|
+
params["employment"] = _join_list(self.employment)
|
|
538
|
+
if self.area:
|
|
539
|
+
params["area"] = _join_list(self.area)
|
|
540
|
+
if self.metro:
|
|
541
|
+
params["metro"] = _join_list(self.metro)
|
|
542
|
+
if self.professional_role:
|
|
543
|
+
params["professional_role"] = _join_list(self.professional_role)
|
|
544
|
+
if self.industry:
|
|
545
|
+
params["industry"] = _join_list(self.industry)
|
|
546
|
+
if self.employer_id:
|
|
547
|
+
params["employer_id"] = _join_list(self.employer_id)
|
|
548
|
+
if self.excluded_employer_id:
|
|
549
|
+
params["excluded_employer_id"] = _join_list(self.excluded_employer_id)
|
|
550
|
+
if self.label:
|
|
551
|
+
params["label"] = _join_list(self.label)
|
|
552
|
+
if self.only_with_salary is not None:
|
|
553
|
+
params["only_with_salary"] = _bool(self.only_with_salary)
|
|
554
|
+
if self.clusters is not None:
|
|
555
|
+
params["clusters"] = _bool(self.clusters)
|
|
556
|
+
if self.no_magic is not None:
|
|
557
|
+
params["no_magic"] = _bool(self.no_magic)
|
|
558
|
+
if self.premium is not None:
|
|
559
|
+
params["premium"] = _bool(self.premium)
|
|
560
|
+
# if self.responses_count_enabled is not None:
|
|
561
|
+
# params["responses_count_enabled"] = _bool(self.responses_count_enabled)
|
|
562
|
+
|
|
563
|
+
return params
|
|
564
|
+
|
|
355
565
|
def _get_vacancies(self, per_page: int = 100) -> list[VacancyItem]:
|
|
356
566
|
rv = []
|
|
567
|
+
# API отдает только 2000 результатов
|
|
357
568
|
for page in range(20):
|
|
358
|
-
params =
|
|
359
|
-
"page": page,
|
|
360
|
-
"per_page": per_page,
|
|
361
|
-
"order_by": self.order_by,
|
|
362
|
-
}
|
|
363
|
-
if self.search:
|
|
364
|
-
params["text"] = self.search
|
|
569
|
+
params = self._get_search_params(page, per_page)
|
|
365
570
|
res: ApiListResponse = self.api_client.get(
|
|
366
571
|
f"/resumes/{self.resume_id}/similar_vacancies", params
|
|
367
572
|
)
|
|
@@ -377,3 +582,4 @@ class Operation(BaseOperation, GetResumeIdMixin):
|
|
|
377
582
|
time.sleep(interval)
|
|
378
583
|
|
|
379
584
|
return rv
|
|
585
|
+
|
|
@@ -16,6 +16,7 @@ logger = logging.getLogger(__package__)
|
|
|
16
16
|
class Namespace(BaseNamespace):
|
|
17
17
|
older_than: int
|
|
18
18
|
blacklist_discard: bool
|
|
19
|
+
all: bool
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class Operation(BaseOperation):
|
|
@@ -93,7 +94,10 @@ class Operation(BaseOperation):
|
|
|
93
94
|
")",
|
|
94
95
|
)
|
|
95
96
|
if is_discard and args.blacklist_discard:
|
|
96
|
-
employer = vacancy
|
|
97
|
+
employer = vacancy.get("employer", {})
|
|
98
|
+
if not employer or 'id' not in employer:
|
|
99
|
+
# Работодатель удален или скрыт
|
|
100
|
+
continue
|
|
97
101
|
try:
|
|
98
102
|
r = api_client.put(f"/employers/blacklisted/{employer['id']}")
|
|
99
103
|
assert not r
|
|
@@ -61,7 +61,7 @@ class Operation(BaseOperation, GetResumeIdMixin):
|
|
|
61
61
|
"-m",
|
|
62
62
|
"--reply-message",
|
|
63
63
|
"--reply",
|
|
64
|
-
help="Отправить сообщение во все чаты, где ожидают ответа либо не прочитали ответ.
|
|
64
|
+
help="Отправить сообщение во все чаты, где ожидают ответа либо не прочитали ответ. Если не передать сообщение, то нужно будет вводить его в интерактивном режиме.",
|
|
65
65
|
)
|
|
66
66
|
parser.add_argument(
|
|
67
67
|
"-p",
|
|
@@ -104,10 +104,11 @@ class Operation(BaseOperation, GetResumeIdMixin):
|
|
|
104
104
|
|
|
105
105
|
def _get_blacklisted(self) -> list[str]:
|
|
106
106
|
rv = []
|
|
107
|
+
# В этом методе API страницы с 0 начинаются
|
|
107
108
|
for page in count(0):
|
|
108
109
|
r = self.api_client.get("/employers/blacklisted", page=page)
|
|
109
110
|
rv += [item["id"] for item in r["items"]]
|
|
110
|
-
if page >= r["pages"]:
|
|
111
|
+
if page + 1 >= r["pages"]:
|
|
111
112
|
break
|
|
112
113
|
return rv
|
|
113
114
|
|
hh_applicant_tool/types.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: hh-applicant-tool
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.12
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Senior YAML Developer
|
|
6
6
|
Author-email: yamldeveloper@proton.me
|
|
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
14
|
Provides-Extra: qt
|
|
14
15
|
Requires-Dist: prettytable (>=3.6.0,<4.0.0)
|
|
15
16
|
Requires-Dist: pyqt6 (==6.7.0) ; extra == "qt"
|
|
@@ -34,7 +35,7 @@ Description-Content-Type: text/markdown
|
|
|
34
35
|
|
|
35
36
|
> Утилита для генерации сопроводительного письма может использовать AI
|
|
36
37
|
|
|
37
|
-
Утилита для успешных волчат и старых волков с опытом, служащая для автоматизации действий на HH.RU таких как рассылка откликов на подходящие вакансии и обновление всех резюме (бесплатный аналог услуги на HH). Утилита сохраняет контактные данные, всех кто вам писал, и позволяет вам по ним проводить поиск (название компании, сайт и тп). Это удобно, так как контакт сохранится даже, если вышлют отказ в дальнейшем. У утилиты есть
|
|
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 нарушение авторских прав...
|
|
38
39
|
|
|
39
40
|
Работает с Python >= 3.10. Нужную версию Python можно поставить через
|
|
40
41
|
asdf/pyenv/conda и что-то еще. В школотронской Manjaro и даже в последних Ubuntu
|
|
@@ -64,6 +65,8 @@ $$('[data-qa="vacancy-serp__vacancy_response"]').forEach((el) => el.click());
|
|
|
64
65
|
|
|
65
66
|
### Установка
|
|
66
67
|
|
|
68
|
+
Универсальный с использованием pipx (требует пакета `python-pipx` в Arch):
|
|
69
|
+
|
|
67
70
|
```bash
|
|
68
71
|
# Версия с поддержкой авторизации через запуск окна с браузером (эта версия очень много весит)
|
|
69
72
|
# Можно использовать обычный pip
|
|
@@ -76,6 +79,18 @@ $ pipx install git+https://github.com/s3rgeym/hh-applicant-tool
|
|
|
76
79
|
$ pipx upgrade hh-applicant-tool
|
|
77
80
|
```
|
|
78
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
|
+
|
|
79
94
|
Отдельно я распишу процесс установки в **Windows** в подробностях:
|
|
80
95
|
|
|
81
96
|
* Для начала поставьте последнюю версию **Python 3** любым удобным способом.
|
|
@@ -88,7 +103,8 @@ $ pipx upgrade hh-applicant-tool
|
|
|
88
103
|
```ps
|
|
89
104
|
PS> python -m venv hh-applicant-venv
|
|
90
105
|
PS> .\hh-applicant-venv\Scripts\activate
|
|
91
|
-
```
|
|
106
|
+
```
|
|
107
|
+
|
|
92
108
|
* Поставьте все пакеты в виртуальное окружение `hh-applicant-venv`:
|
|
93
109
|
```ps
|
|
94
110
|
(hh-applicant-venv) PS> pip install hh-applicant-tool[qt]
|
|
@@ -261,7 +277,7 @@ https://hh.ru/employer/1918903
|
|
|
261
277
|
| **refresh-token** | Обновляет access_token. |
|
|
262
278
|
| **config** | Редактировать конфигурационный файл. |
|
|
263
279
|
| **get-employer-contacts** | Получить список полученных вами контактов работодателей. Поддерживается так же экспорт в html/jsonl. Если хотите собирать контакты с нескольких акков, то укажите им одинаковый `client_telemetry_id` в конфигах. |
|
|
264
|
-
| **delete-telemetry** | Удадяет
|
|
280
|
+
| **delete-telemetry** | Удадяет телеметрию (контакты работодателей, которые вас пригласили), если та была включена. |
|
|
265
281
|
|
|
266
282
|
### Формат текста сообщений
|
|
267
283
|
|
|
@@ -2,6 +2,7 @@ hh_applicant_tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
|
|
|
2
2
|
hh_applicant_tool/__main__.py,sha256=cwKJAAML0RRKT9Qbzcwf07HHcuSd8oh7kx4P1apndWQ,84
|
|
3
3
|
hh_applicant_tool/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
hh_applicant_tool/ai/blackbox.py,sha256=vqkEpsX7q7bgX49dmmifYJmrbuz_WBg5u9M9J9XdQlI,1670
|
|
5
|
+
hh_applicant_tool/ai/openai.py,sha256=jEh8y4Y51rcuvZ41tkcq65EHKt8bdT6NahzbHJN5KhI,1682
|
|
5
6
|
hh_applicant_tool/api/__init__.py,sha256=kgFSHibRaAugN2BA3U1djEa20qgKJUUVouwJzjEB0DU,84
|
|
6
7
|
hh_applicant_tool/api/client.py,sha256=-nQMX_KY2zcV98CtBqq7BHyseDs5LglhHoMF1C7Oi4M,9895
|
|
7
8
|
hh_applicant_tool/api/errors.py,sha256=Rd1XE2OTtZDa3GDqght2LtOnTHWtOx7Zsow87nn4x4A,1807
|
|
@@ -11,22 +12,22 @@ hh_applicant_tool/jsonc.py,sha256=QNS4gRHfi7SAeOFnffAIuhH7auC4Y4HAkmH12eX5PkI,40
|
|
|
11
12
|
hh_applicant_tool/main.py,sha256=A4YPkNXAdZY0GoGm0iigiQtzXTrpR3SaIGo54q9-Dd0,5652
|
|
12
13
|
hh_applicant_tool/mixins.py,sha256=8VoyrNgdlljy6pLTSFGJOYd9kagWT3yFOZYIGR6MEbI,425
|
|
13
14
|
hh_applicant_tool/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
hh_applicant_tool/operations/apply_similar.py,sha256=
|
|
15
|
+
hh_applicant_tool/operations/apply_similar.py,sha256=dS775_LJKpb7sHGLTno60KLJcIj4DnzY3Bu7yfzGadw,24916
|
|
15
16
|
hh_applicant_tool/operations/authorize.py,sha256=NYrxe6oemUBcDHioT1t1lJmi9l45V4ZXzQPD_-nf6hk,3328
|
|
16
17
|
hh_applicant_tool/operations/call_api.py,sha256=o3GZgtqk6w4zpCm-JTHVjFrKVOwW-vsu1HdRi-hqAjo,1423
|
|
17
|
-
hh_applicant_tool/operations/clear_negotiations.py,sha256=
|
|
18
|
+
hh_applicant_tool/operations/clear_negotiations.py,sha256=FG_43P5GWmfKUggkKZqDznQ2_iBJ3zrZtv8yEI2XOXQ,4527
|
|
18
19
|
hh_applicant_tool/operations/config.py,sha256=BzGWbHwNlXIpYHxnZUidDZTk1-7GZb8UL-asy8w4uN4,1390
|
|
19
20
|
hh_applicant_tool/operations/delete_telemetry.py,sha256=JHdh_l7IJL_qy5AIIy8FQpUupmH60D3a6zjfEVKkT2U,986
|
|
20
21
|
hh_applicant_tool/operations/get_employer_contacts.py,sha256=Sd-x3O08bmKm1OGVLtJ6rcPZ_j1jwjlqKV4z1n_G-38,9918
|
|
21
22
|
hh_applicant_tool/operations/list_resumes.py,sha256=dILHyBCSEVqdNAvD8SML5f2Lau1R2AzTaKE9B4FG8Wg,1109
|
|
22
23
|
hh_applicant_tool/operations/refresh_token.py,sha256=v_Fcw9mCfOdE6MLTCQjZQudhJPX0fup3k0BaIM394Qw,834
|
|
23
|
-
hh_applicant_tool/operations/reply_employers.py,sha256=
|
|
24
|
+
hh_applicant_tool/operations/reply_employers.py,sha256=40rsTgpoEUaRQUq5ZQYgojKb42smYRkbDDD2S_7Vwtg,13512
|
|
24
25
|
hh_applicant_tool/operations/update_resumes.py,sha256=_r7HA_vpYMs5DFY-mVP1ZRG9bggsv7ebKYrwteBmJ30,1053
|
|
25
26
|
hh_applicant_tool/operations/whoami.py,sha256=pNWJMmEQLBk3U6eiGz4CHcX7eXzDXcfezFjX7zLjqyA,711
|
|
26
27
|
hh_applicant_tool/telemetry_client.py,sha256=1EKZWc5kMx2yERW9SrR9vaf-OB6M_KKcMXeicH5YyY0,3834
|
|
27
|
-
hh_applicant_tool/types.py,sha256=
|
|
28
|
+
hh_applicant_tool/types.py,sha256=sQbt_vGXtWPRJ3UzcUkE87BTHOaGTsFxqdZa_qFghZE,864
|
|
28
29
|
hh_applicant_tool/utils.py,sha256=3T4A2AykGqTwtGAttmYplIjHwFl3pNAcbWIVuA-OheQ,3080
|
|
29
|
-
hh_applicant_tool-0.6.
|
|
30
|
-
hh_applicant_tool-0.6.
|
|
31
|
-
hh_applicant_tool-0.6.
|
|
32
|
-
hh_applicant_tool-0.6.
|
|
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,,
|
|
File without changes
|