hh-applicant-tool 0.3.2__tar.gz → 0.3.3__tar.gz
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.
Potentially problematic release.
This version of hh-applicant-tool might be problematic. Click here for more details.
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/PKG-INFO +11 -7
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/README.md +10 -6
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/operations/apply_similar.py +116 -112
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/telemetry_client.py +9 -3
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/pyproject.toml +1 -1
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/__init__.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/__main__.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/api/__init__.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/api/client.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/api/errors.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/color_log.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/constants.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/main.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/operations/__init__.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/operations/authorize.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/operations/call_api.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/operations/clear_negotiations.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/operations/list_resumes.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/operations/refresh_token.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/operations/update_resumes.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/operations/whoami.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/types.py +0 -0
- {hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: hh-applicant-tool
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Senior YAML Developer
|
|
6
6
|
Author-email: yamldeveloper@proton.me
|
|
@@ -37,13 +37,13 @@ Description-Content-Type: text/markdown
|
|
|
37
37
|
Работает с Python >= 3.10. Нужную версию Python можно поставить через
|
|
38
38
|
asdf/pyenv/conda и что-то еще...
|
|
39
39
|
|
|
40
|
+
Данная утилита написана для Linux, но будет работать и на Ga..Mac OS, и в Windows, но с WSL не будет, так как для авторизации требуются оконный сервер X11 либо Wayland — только прямая установка пакета через pip в Windows. После авторизации вы можете перенести `~/.config/hh-applicant-tool/config.json` (я не знаю, где винда что хранит) на сервер и запускать утилиту через systemd или cron. Столь странный процесс связан с тем, что на странице авторизации запускается море скриптов, которые шифруют данные на клиенте перед отправкой на сервер, а так же выполняется куча запросов чтобы проверить не бот ли ты. Хорошо, что после авторизации никаких проверок по факту нет, даже айпи не проверяется на соответсвие тому с какого была авторизация. В этой лапше мне лень разбираться. Так же при наличии рутованного телефона можно вытащить `access` и `refresh` токены из официального приложения и добавить их в конфиг.
|
|
41
|
+
|
|
40
42
|
Пример работы:
|
|
41
43
|
|
|
42
44
|

|
|
43
45
|
|
|
44
46
|
|
|
45
|
-
Данная утилита написана для Linux, но будет работать и на Ga..Mac OS, в Windows, но в WSL она не работает, так как для авторизации требуются оконный сервер X11 либо Wayland — только прямая установка пакета через pip в Windows. После авторизации вы можете перенести `~/.config/hh-applicant-tool/config.json` (я не знаю, где винда что хранит) на сервер и запускать утилиту через systemd или cron. Столь странный процесс связан с тем, что на странице авторизации запускается море скриптов, которые шифруют данные на клиенте перед отправкой на сервер, а так же выполняется куча запросов чтобы проверить не бот ли ты. Хорошо, что после авторизации никаких проверок по факту нет, даже айпи не проверяется на соответсвие тому с какого была авторизация. В этой лапше мне лень разбираться. Так же при наличии рутованного телефона можно вытащить `access` и `refresh` токены из официального приложения и добавить их в конфиг.
|
|
46
|
-
|
|
47
47
|
Предыстория.
|
|
48
48
|
|
|
49
49
|
Был один знакомый знакомого, который работал хрюшей. Этот чувак не заморачивался с чтением резюме, а тупо скриптами рассылал предложения о работе... Бывают, конечно, филологини, которые не могут отличить Java от JavaScript, но я думаю, что <s>в значительном числе случаев, тут имеют место такие вот рассылки</s> они просто идиотки... И я тупо стал спамить как они. Мне уже было просто лень читать весь этот бред, что пишут долбоебы в описании вакансий. Там стандартное ООП, алгоритмы и прочая хуета... Вроде все подходят, а вроде хз — все не мое.
|
|
@@ -60,10 +60,14 @@ $$('[data-qa="vacancy-serp__vacancy_response"]').forEach((el) => el.click());
|
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
62
|
# Версия с поддержкой авторизации через запуск окна с браузером (эта версия очень много весит)
|
|
63
|
+
# Можно использовать обычный pip
|
|
63
64
|
$ pipx install 'hh-applicant-tool[qt]'
|
|
64
65
|
|
|
65
66
|
# Если хочется использовать самую последнюю версию, то можно установить ее через git
|
|
66
67
|
$ pipx install git+https://github.com/s3rgeym/hh-applicant-tool
|
|
68
|
+
|
|
69
|
+
# Для обновления до новой версии
|
|
70
|
+
$ pipx upgrade 'hh-applicant-tool'
|
|
67
71
|
```
|
|
68
72
|
|
|
69
73
|
Использование:
|
|
@@ -142,7 +146,7 @@ $ hh-applicant-tool -vv authorize
|
|
|
142
146
|
|
|
143
147
|

|
|
144
148
|
|
|
145
|
-
В случае успешной авторизации токены будут сохранены `~/.config/hh-applicant-tool/config.json`:
|
|
149
|
+
В случае успешной авторизации токены будут сохранены в `~/.config/hh-applicant-tool/config.json`:
|
|
146
150
|
|
|
147
151
|
```json
|
|
148
152
|
{
|
|
@@ -231,8 +235,6 @@ rm -f ~/.local/share/applications/hhandroid.desktop
|
|
|
231
235
|
|
|
232
236
|
Утилита собирает и передает на сервер разработчика следующую ифнормацию:
|
|
233
237
|
|
|
234
|
-
Утилита собирает следующую информацию:
|
|
235
|
-
|
|
236
238
|
1. Название вакансии.
|
|
237
239
|
1. Тип вакансии (открытая/закрытая).
|
|
238
240
|
1. Город, в котором размещена вакансия.
|
|
@@ -247,6 +249,8 @@ rm -f ~/.local/share/applications/hhandroid.desktop
|
|
|
247
249
|
1. Ссылка на сайт компании.
|
|
248
250
|
1. Город, в котором находится компания.
|
|
249
251
|
|
|
250
|
-
|
|
252
|
+
[Исходники сервера](https://gist.github.com/s3rgeym/b9fb04ef529a511326413c1090597ac5)
|
|
253
|
+
|
|
254
|
+
!!! УТИЛИТА НЕ СОБИРАЕТ НИКАКИХ ПЕРСОНАЛЬНЫХ ДАННЫХ ПОЛЬЗОВАТЕЛЕЙ (IP ТОЖЕ НЕ СОХРАНЯЕТ) — ТОЛЬКО ДАННЫЕ ВСЯКИХ РАБОТАДАТЕЛЕЙ И ИХ ОВЧАРОК. ТАК ЖЕ Я ОБЕЩАЮ, ЧТО УТИЛИТА ВСЕГДА БУДЕТ БЕСПЛАТНОЙ, ВСЕ КТО ЕЮ ПЫТАЮТСЯ ТОРГОВАТЬ — УЕБКИ И У НИХ ПОЧЕРНЕЕТ И ОТВАЛИТСЯ ХУЙ. ЕДИНСТВЕННАЯ ПЛАТА ЗА ЕЕ ИСПОЛЬЗОВАНИЕ — ЭТО ПОМОЩЬ В ПАРСИНГЕ САЙТА HEADHUNTER (МЕНЯ ИНТЕРЕСУЕТ ЕГО БАЗА КОМПАНИЙ)
|
|
251
255
|
|
|
252
256
|
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
Работает с Python >= 3.10. Нужную версию Python можно поставить через
|
|
19
19
|
asdf/pyenv/conda и что-то еще...
|
|
20
20
|
|
|
21
|
+
Данная утилита написана для Linux, но будет работать и на Ga..Mac OS, и в Windows, но с WSL не будет, так как для авторизации требуются оконный сервер X11 либо Wayland — только прямая установка пакета через pip в Windows. После авторизации вы можете перенести `~/.config/hh-applicant-tool/config.json` (я не знаю, где винда что хранит) на сервер и запускать утилиту через systemd или cron. Столь странный процесс связан с тем, что на странице авторизации запускается море скриптов, которые шифруют данные на клиенте перед отправкой на сервер, а так же выполняется куча запросов чтобы проверить не бот ли ты. Хорошо, что после авторизации никаких проверок по факту нет, даже айпи не проверяется на соответсвие тому с какого была авторизация. В этой лапше мне лень разбираться. Так же при наличии рутованного телефона можно вытащить `access` и `refresh` токены из официального приложения и добавить их в конфиг.
|
|
22
|
+
|
|
21
23
|
Пример работы:
|
|
22
24
|
|
|
23
25
|

|
|
24
26
|
|
|
25
27
|
|
|
26
|
-
Данная утилита написана для Linux, но будет работать и на Ga..Mac OS, в Windows, но в WSL она не работает, так как для авторизации требуются оконный сервер X11 либо Wayland — только прямая установка пакета через pip в Windows. После авторизации вы можете перенести `~/.config/hh-applicant-tool/config.json` (я не знаю, где винда что хранит) на сервер и запускать утилиту через systemd или cron. Столь странный процесс связан с тем, что на странице авторизации запускается море скриптов, которые шифруют данные на клиенте перед отправкой на сервер, а так же выполняется куча запросов чтобы проверить не бот ли ты. Хорошо, что после авторизации никаких проверок по факту нет, даже айпи не проверяется на соответсвие тому с какого была авторизация. В этой лапше мне лень разбираться. Так же при наличии рутованного телефона можно вытащить `access` и `refresh` токены из официального приложения и добавить их в конфиг.
|
|
27
|
-
|
|
28
28
|
Предыстория.
|
|
29
29
|
|
|
30
30
|
Был один знакомый знакомого, который работал хрюшей. Этот чувак не заморачивался с чтением резюме, а тупо скриптами рассылал предложения о работе... Бывают, конечно, филологини, которые не могут отличить Java от JavaScript, но я думаю, что <s>в значительном числе случаев, тут имеют место такие вот рассылки</s> они просто идиотки... И я тупо стал спамить как они. Мне уже было просто лень читать весь этот бред, что пишут долбоебы в описании вакансий. Там стандартное ООП, алгоритмы и прочая хуета... Вроде все подходят, а вроде хз — все не мое.
|
|
@@ -41,10 +41,14 @@ $$('[data-qa="vacancy-serp__vacancy_response"]').forEach((el) => el.click());
|
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
43
|
# Версия с поддержкой авторизации через запуск окна с браузером (эта версия очень много весит)
|
|
44
|
+
# Можно использовать обычный pip
|
|
44
45
|
$ pipx install 'hh-applicant-tool[qt]'
|
|
45
46
|
|
|
46
47
|
# Если хочется использовать самую последнюю версию, то можно установить ее через git
|
|
47
48
|
$ pipx install git+https://github.com/s3rgeym/hh-applicant-tool
|
|
49
|
+
|
|
50
|
+
# Для обновления до новой версии
|
|
51
|
+
$ pipx upgrade 'hh-applicant-tool'
|
|
48
52
|
```
|
|
49
53
|
|
|
50
54
|
Использование:
|
|
@@ -123,7 +127,7 @@ $ hh-applicant-tool -vv authorize
|
|
|
123
127
|
|
|
124
128
|

|
|
125
129
|
|
|
126
|
-
В случае успешной авторизации токены будут сохранены `~/.config/hh-applicant-tool/config.json`:
|
|
130
|
+
В случае успешной авторизации токены будут сохранены в `~/.config/hh-applicant-tool/config.json`:
|
|
127
131
|
|
|
128
132
|
```json
|
|
129
133
|
{
|
|
@@ -212,8 +216,6 @@ rm -f ~/.local/share/applications/hhandroid.desktop
|
|
|
212
216
|
|
|
213
217
|
Утилита собирает и передает на сервер разработчика следующую ифнормацию:
|
|
214
218
|
|
|
215
|
-
Утилита собирает следующую информацию:
|
|
216
|
-
|
|
217
219
|
1. Название вакансии.
|
|
218
220
|
1. Тип вакансии (открытая/закрытая).
|
|
219
221
|
1. Город, в котором размещена вакансия.
|
|
@@ -228,5 +230,7 @@ rm -f ~/.local/share/applications/hhandroid.desktop
|
|
|
228
230
|
1. Ссылка на сайт компании.
|
|
229
231
|
1. Город, в котором находится компания.
|
|
230
232
|
|
|
231
|
-
|
|
233
|
+
[Исходники сервера](https://gist.github.com/s3rgeym/b9fb04ef529a511326413c1090597ac5)
|
|
234
|
+
|
|
235
|
+
!!! УТИЛИТА НЕ СОБИРАЕТ НИКАКИХ ПЕРСОНАЛЬНЫХ ДАННЫХ ПОЛЬЗОВАТЕЛЕЙ (IP ТОЖЕ НЕ СОХРАНЯЕТ) — ТОЛЬКО ДАННЫЕ ВСЯКИХ РАБОТАДАТЕЛЕЙ И ИХ ОВЧАРОК. ТАК ЖЕ Я ОБЕЩАЮ, ЧТО УТИЛИТА ВСЕГДА БУДЕТ БЕСПЛАТНОЙ, ВСЕ КТО ЕЮ ПЫТАЮТСЯ ТОРГОВАТЬ — УЕБКИ И У НИХ ПОЧЕРНЕЕТ И ОТВАЛИТСЯ ХУЙ. ЕДИНСТВЕННАЯ ПЛАТА ЗА ЕЕ ИСПОЛЬЗОВАНИЕ — ЭТО ПОМОЩЬ В ПАРСИНГЕ САЙТА HEADHUNTER (МЕНЯ ИНТЕРЕСУЕТ ЕГО БАЗА КОМПАНИЙ)
|
|
232
236
|
|
{hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/operations/apply_similar.py
RENAMED
|
@@ -9,12 +9,10 @@ from typing import TextIO, Tuple
|
|
|
9
9
|
from ..api import ApiClient, ApiError, BadRequest
|
|
10
10
|
from ..main import BaseOperation
|
|
11
11
|
from ..main import Namespace as BaseNamespace
|
|
12
|
+
from ..telemetry_client import TelemetryError
|
|
13
|
+
from ..telemetry_client import get_client as get_telemetry_client
|
|
12
14
|
from ..types import ApiListResponse, VacancyItem
|
|
13
|
-
from ..utils import print_err, truncate_string
|
|
14
|
-
from ..telemetry_client import (
|
|
15
|
-
get_client as get_telemetry_client,
|
|
16
|
-
TelemetryError,
|
|
17
|
-
)
|
|
15
|
+
from ..utils import fix_datetime, print_err, truncate_string
|
|
18
16
|
|
|
19
17
|
logger = logging.getLogger(__package__)
|
|
20
18
|
|
|
@@ -71,11 +69,32 @@ class Operation(BaseOperation):
|
|
|
71
69
|
access_token=args.config["token"]["access_token"],
|
|
72
70
|
user_agent=args.config["user_agent"],
|
|
73
71
|
)
|
|
72
|
+
resume_id = self._get_resume_id(args, api)
|
|
73
|
+
application_messages = self._get_application_messages(args)
|
|
74
|
+
|
|
75
|
+
apply_min_interval, apply_max_interval = args.apply_interval
|
|
76
|
+
page_min_interval, page_max_interval = args.page_interval
|
|
77
|
+
|
|
78
|
+
self._apply_similar(
|
|
79
|
+
api,
|
|
80
|
+
resume_id,
|
|
81
|
+
args.force_message,
|
|
82
|
+
application_messages,
|
|
83
|
+
apply_min_interval,
|
|
84
|
+
apply_max_interval,
|
|
85
|
+
page_min_interval,
|
|
86
|
+
page_max_interval,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def _get_resume_id(self, args: Namespace, api: ApiClient) -> str:
|
|
74
90
|
if not (
|
|
75
91
|
resume_id := args.resume_id or args.config["default_resume_id"]
|
|
76
92
|
):
|
|
77
93
|
resumes: ApiListResponse = api.get("/resumes/mine")
|
|
78
94
|
resume_id = resumes["items"][0]["id"]
|
|
95
|
+
return resume_id
|
|
96
|
+
|
|
97
|
+
def _get_application_messages(self, args: Namespace) -> list[str]:
|
|
79
98
|
if args.message_list:
|
|
80
99
|
application_messages = list(
|
|
81
100
|
filter(None, map(str.strip, args.message_list))
|
|
@@ -88,51 +107,7 @@ class Operation(BaseOperation):
|
|
|
88
107
|
"Хочу присоединиться к вашей успешной команде лидеров рынка в качестве %(name)s",
|
|
89
108
|
"Мое резюме содержит все баззворды, указанные в вашей вакансии %(name)s",
|
|
90
109
|
]
|
|
91
|
-
|
|
92
|
-
apply_min_interval, apply_max_interval = args.apply_interval
|
|
93
|
-
page_min_interval, page_max_interval = args.page_interval
|
|
94
|
-
|
|
95
|
-
self._apply_similar(
|
|
96
|
-
api,
|
|
97
|
-
resume_id,
|
|
98
|
-
args.force_message,
|
|
99
|
-
application_messages,
|
|
100
|
-
apply_min_interval,
|
|
101
|
-
apply_max_interval,
|
|
102
|
-
page_min_interval,
|
|
103
|
-
page_max_interval,
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
def _get_vacancies(
|
|
107
|
-
self,
|
|
108
|
-
api: ApiClient,
|
|
109
|
-
resume_id: str,
|
|
110
|
-
page_min_interval: float,
|
|
111
|
-
page_max_interval: float,
|
|
112
|
-
) -> list[VacancyItem]:
|
|
113
|
-
rv = []
|
|
114
|
-
per_page = 100
|
|
115
|
-
for page in range(20):
|
|
116
|
-
res: ApiListResponse = api.get(
|
|
117
|
-
f"/resumes/{resume_id}/similar_vacancies",
|
|
118
|
-
page=page,
|
|
119
|
-
per_page=per_page,
|
|
120
|
-
order_by="relevance",
|
|
121
|
-
)
|
|
122
|
-
rv.extend(res["items"])
|
|
123
|
-
|
|
124
|
-
if getenv("TEST_TELEMETRY"):
|
|
125
|
-
break
|
|
126
|
-
|
|
127
|
-
if page >= res["pages"] - 1:
|
|
128
|
-
break
|
|
129
|
-
|
|
130
|
-
# Задержка перед получением следующей страницы
|
|
131
|
-
if page > 0:
|
|
132
|
-
interval = random.uniform(page_min_interval, page_max_interval)
|
|
133
|
-
time.sleep(interval)
|
|
134
|
-
|
|
135
|
-
return rv
|
|
110
|
+
return application_messages
|
|
136
111
|
|
|
137
112
|
def _apply_similar(
|
|
138
113
|
self,
|
|
@@ -145,42 +120,34 @@ class Operation(BaseOperation):
|
|
|
145
120
|
page_min_interval: float,
|
|
146
121
|
page_max_interval: float,
|
|
147
122
|
) -> None:
|
|
148
|
-
item: VacancyItem
|
|
149
|
-
|
|
150
|
-
# Телеметрия не включает ваши персональные данные, она нужна для сбора информации о работодателях и их вакансиях
|
|
151
123
|
telemetry_client = get_telemetry_client()
|
|
152
124
|
telemetry_data = defaultdict(dict)
|
|
153
125
|
|
|
154
|
-
|
|
155
|
-
api, resume_id, page_min_interval, page_max_interval
|
|
156
|
-
)
|
|
126
|
+
vacancies = self._get_vacancies(
|
|
127
|
+
api, resume_id, page_min_interval, page_max_interval, per_page=100
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
self._collect_vacancy_telemetry(telemetry_data, vacancies)
|
|
131
|
+
|
|
132
|
+
for vacancy in vacancies:
|
|
157
133
|
try:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
"
|
|
163
|
-
|
|
164
|
-
"area": item.get("area", {}).get("name"), # город
|
|
165
|
-
"salary": item.get("salary"), # from, to, currency, gross
|
|
166
|
-
"direct_url": item.get(
|
|
167
|
-
"alternate_url"
|
|
168
|
-
), # ссылка на вакансию
|
|
169
|
-
"created_at": fix_datetime(
|
|
170
|
-
item.get("created_at")
|
|
171
|
-
), # будем вычислять говно-вакансии, которые по полгода висят
|
|
172
|
-
"published_at": fix_datetime(item.get("published_at")),
|
|
173
|
-
"contacts": item.get(
|
|
174
|
-
"contacts"
|
|
175
|
-
), # пиздорванки там телеграм для связи указывают
|
|
176
|
-
# Остальное неинтересно
|
|
177
|
-
}
|
|
134
|
+
if getenv("TEST_TELEMETRY"):
|
|
135
|
+
break
|
|
136
|
+
|
|
137
|
+
if vacancy["has_test"]:
|
|
138
|
+
print("🚫 Пропускаем тест", vacancy["alternate_url"])
|
|
139
|
+
continue
|
|
178
140
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
141
|
+
relations = vacancy.get("relations", [])
|
|
142
|
+
|
|
143
|
+
if relations:
|
|
144
|
+
print(
|
|
145
|
+
"🚫 Пропускаем ответ на заявку",
|
|
146
|
+
vacancy["alternate_url"],
|
|
147
|
+
)
|
|
148
|
+
continue
|
|
182
149
|
|
|
183
|
-
|
|
150
|
+
employer_id = vacancy["employer"]["id"]
|
|
184
151
|
employer = api.get(f"/employers/{employer_id}")
|
|
185
152
|
|
|
186
153
|
telemetry_data["employers"][employer_id] = {
|
|
@@ -190,29 +157,6 @@ class Operation(BaseOperation):
|
|
|
190
157
|
"site_url": employer.get("site_url"),
|
|
191
158
|
"area": employer.get("area", {}).get("name"), # город
|
|
192
159
|
}
|
|
193
|
-
|
|
194
|
-
if getenv("TEST_TELEMETRY"):
|
|
195
|
-
break
|
|
196
|
-
|
|
197
|
-
if item["has_test"]:
|
|
198
|
-
print("Пропускаем тест", item["alternate_url"])
|
|
199
|
-
continue
|
|
200
|
-
|
|
201
|
-
relations = item.get("relations", [])
|
|
202
|
-
|
|
203
|
-
# Там черезжопно нужно хеш отклика получать чтобы его отменить
|
|
204
|
-
# if "got_response" in relations:
|
|
205
|
-
# # Тупая пизда ее даже не рассматривала
|
|
206
|
-
# print(
|
|
207
|
-
# "Отменяем заявку чтобы отправить ее снова",
|
|
208
|
-
# item["alternate_url"],
|
|
209
|
-
# )
|
|
210
|
-
# api.delete(f"/negotiations/active/{item['id']}")
|
|
211
|
-
# elif relations:
|
|
212
|
-
if relations:
|
|
213
|
-
print("Пропускаем ответ на заявку", item["alternate_url"])
|
|
214
|
-
continue
|
|
215
|
-
|
|
216
160
|
# Задержка перед отправкой отклика
|
|
217
161
|
interval = random.uniform(
|
|
218
162
|
apply_min_interval, apply_max_interval
|
|
@@ -221,10 +165,10 @@ class Operation(BaseOperation):
|
|
|
221
165
|
|
|
222
166
|
params = {
|
|
223
167
|
"resume_id": resume_id,
|
|
224
|
-
"vacancy_id":
|
|
168
|
+
"vacancy_id": vacancy["id"],
|
|
225
169
|
"message": (
|
|
226
|
-
random.choice(application_messages) %
|
|
227
|
-
if force_message or
|
|
170
|
+
random.choice(application_messages) % vacancy
|
|
171
|
+
if force_message or vacancy["response_letter_required"]
|
|
228
172
|
else ""
|
|
229
173
|
),
|
|
230
174
|
}
|
|
@@ -233,9 +177,9 @@ class Operation(BaseOperation):
|
|
|
233
177
|
assert res == {}
|
|
234
178
|
print(
|
|
235
179
|
"📨 Отправили отклик",
|
|
236
|
-
|
|
180
|
+
vacancy["alternate_url"],
|
|
237
181
|
"(",
|
|
238
|
-
truncate_string(
|
|
182
|
+
truncate_string(vacancy["name"]),
|
|
239
183
|
")",
|
|
240
184
|
)
|
|
241
185
|
except ApiError as ex:
|
|
@@ -245,8 +189,68 @@ class Operation(BaseOperation):
|
|
|
245
189
|
|
|
246
190
|
print("📝 Отклики на вакансии разосланы!")
|
|
247
191
|
|
|
248
|
-
|
|
192
|
+
self._send_telemetry(telemetry_client, telemetry_data)
|
|
193
|
+
|
|
194
|
+
def _get_vacancies(
|
|
195
|
+
self,
|
|
196
|
+
api: ApiClient,
|
|
197
|
+
resume_id: str,
|
|
198
|
+
page_min_interval: float,
|
|
199
|
+
page_max_interval: float,
|
|
200
|
+
per_page: int,
|
|
201
|
+
) -> list[VacancyItem]:
|
|
202
|
+
rv = []
|
|
203
|
+
for page in range(20):
|
|
204
|
+
res: ApiListResponse = api.get(
|
|
205
|
+
f"/resumes/{resume_id}/similar_vacancies",
|
|
206
|
+
page=page,
|
|
207
|
+
per_page=per_page,
|
|
208
|
+
order_by="relevance",
|
|
209
|
+
)
|
|
210
|
+
rv.extend(res["items"])
|
|
211
|
+
|
|
212
|
+
if getenv("TEST_TELEMETRY"):
|
|
213
|
+
break
|
|
214
|
+
|
|
215
|
+
if page >= res["pages"] - 1:
|
|
216
|
+
break
|
|
217
|
+
|
|
218
|
+
# Задержка перед получением следующей страницы
|
|
219
|
+
if page > 0:
|
|
220
|
+
interval = random.uniform(page_min_interval, page_max_interval)
|
|
221
|
+
time.sleep(interval)
|
|
222
|
+
|
|
223
|
+
return rv
|
|
224
|
+
|
|
225
|
+
def _collect_vacancy_telemetry(
|
|
226
|
+
self, telemetry_data: defaultdict, vacancies: list[VacancyItem]
|
|
227
|
+
) -> None:
|
|
228
|
+
for vacancy in vacancies:
|
|
229
|
+
vacancy_id = vacancy["id"]
|
|
230
|
+
telemetry_data["vacancies"][vacancy_id] = {
|
|
231
|
+
"name": vacancy.get("name"),
|
|
232
|
+
"type": vacancy.get("type", {}).get("id"), # open/closed
|
|
233
|
+
"area": vacancy.get("area", {}).get("name"), # город
|
|
234
|
+
"salary": vacancy.get("salary"), # from, to, currency, gross
|
|
235
|
+
"direct_url": vacancy.get(
|
|
236
|
+
"alternate_url"
|
|
237
|
+
), # ссылка на вакансию
|
|
238
|
+
"created_at": fix_datetime(
|
|
239
|
+
vacancy.get("created_at")
|
|
240
|
+
), # будем вычислять говно-вакансии, которые по полгода висят
|
|
241
|
+
"published_at": fix_datetime(vacancy.get("published_at")),
|
|
242
|
+
"contacts": vacancy.get(
|
|
243
|
+
"contacts"
|
|
244
|
+
), # пиздорванки там телеграм для связи указывают
|
|
245
|
+
"employer_id": int(vacancy["employer"]["id"]),
|
|
246
|
+
# Остальное неинтересно
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
def _send_telemetry(
|
|
250
|
+
self, telemetry_client, telemetry_data: defaultdict
|
|
251
|
+
) -> None:
|
|
249
252
|
try:
|
|
250
|
-
telemetry_client.send_telemetry("/collect", dict(telemetry_data))
|
|
251
|
-
|
|
252
|
-
|
|
253
|
+
res = telemetry_client.send_telemetry("/collect", dict(telemetry_data))
|
|
254
|
+
logger.debug(res)
|
|
255
|
+
except TelemetryError as ex:
|
|
256
|
+
logger.error(ex)
|
|
@@ -19,7 +19,9 @@ class TelemetryError(Exception):
|
|
|
19
19
|
class TelemetryClient:
|
|
20
20
|
"""Клиент для отправки телеметрии на сервер."""
|
|
21
21
|
|
|
22
|
-
server_address = base64.b64decode(
|
|
22
|
+
server_address = base64.b64decode(
|
|
23
|
+
"aHR0cDovLzMxLjEzMS4yNTEuMTA3OjU0MTU2"
|
|
24
|
+
).decode()
|
|
23
25
|
|
|
24
26
|
def __init__(
|
|
25
27
|
self,
|
|
@@ -53,8 +55,12 @@ class TelemetryClient:
|
|
|
53
55
|
|
|
54
56
|
try:
|
|
55
57
|
response = self.session.post(url, json=data)
|
|
56
|
-
response.raise_for_status()
|
|
57
|
-
|
|
58
|
+
# response.raise_for_status()
|
|
59
|
+
result = response.json()
|
|
60
|
+
if "error" in result:
|
|
61
|
+
raise TelemetryError(result)
|
|
62
|
+
return result
|
|
63
|
+
|
|
58
64
|
except (
|
|
59
65
|
requests.exceptions.RequestException,
|
|
60
66
|
json.JSONDecodeError,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/operations/__init__.py
RENAMED
|
File without changes
|
{hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/operations/authorize.py
RENAMED
|
File without changes
|
{hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/operations/call_api.py
RENAMED
|
File without changes
|
|
File without changes
|
{hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/operations/list_resumes.py
RENAMED
|
File without changes
|
{hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/operations/refresh_token.py
RENAMED
|
File without changes
|
{hh_applicant_tool-0.3.2 → hh_applicant_tool-0.3.3}/hh_applicant_tool/operations/update_resumes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|