hh-applicant-tool 0.5.9__tar.gz → 0.6.0__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.5.9 → hh_applicant_tool-0.6.0}/PKG-INFO +8 -8
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/README.md +7 -7
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/main.py +1 -2
- hh_applicant_tool-0.6.0/hh_applicant_tool/operations/get_employer_contacts.py +294 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/pyproject.toml +1 -1
- hh_applicant_tool-0.5.9/hh_applicant_tool/operations/get_employer_contacts.py +0 -87
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/__init__.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/__main__.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/ai/__init__.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/ai/blackbox.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/api/__init__.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/api/client.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/api/errors.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/color_log.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/constants.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/jsonc.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/mixins.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/__init__.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/apply_similar.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/authorize.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/call_api.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/clear_negotiations.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/config.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/delete_telemetry.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/list_resumes.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/refresh_token.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/reply_employers.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/update_resumes.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/whoami.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/telemetry_client.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/types.py +0 -0
- {hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: hh-applicant-tool
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Senior YAML Developer
|
|
6
6
|
Author-email: yamldeveloper@proton.me
|
|
@@ -30,11 +30,6 @@ Description-Content-Type: text/markdown
|
|
|
30
30
|
<img src="https://github.com/user-attachments/assets/29d91490-2c83-4e3f-a573-c7a6182a4044" width="500">
|
|
31
31
|
</div>
|
|
32
32
|
|
|
33
|
-
### Внимание!!!
|
|
34
|
-
|
|
35
|
-
Если для Вас проблема установить данную утилиту, лень разбираться с ее настройкой, то Вы можете установить мое приложение под **Android** [HH Resume Automate](https://github.com/s3rgeym/hh-resume-automate/).
|
|
36
|
-
|
|
37
|
-
|
|
38
33
|
### Описание
|
|
39
34
|
|
|
40
35
|
> Утилита для генерации сопроводительного письма может использовать AI
|
|
@@ -53,6 +48,10 @@ asdf/pyenv/conda и что-то еще. В школотронской Manjaro и
|
|
|
53
48
|
|
|
54
49
|
> Если в веб-интерфейсе выставить фильтры, то они будут применяться в скрипте при отклике на подходящие
|
|
55
50
|
|
|
51
|
+
### Внимание!!!
|
|
52
|
+
|
|
53
|
+
Если для Вас проблема установить данную утилиту, лень разбираться с ее настройкой, то Вы можете установить мое приложение под **Android** [HH Resume Automate](https://github.com/s3rgeym/hh-resume-automate/). Оно обладает минимальным функционалом: обновление резюме (одного) и рассылка откликов (чистить их и тп нельзя).
|
|
54
|
+
|
|
56
55
|
### Предыстория
|
|
57
56
|
|
|
58
57
|
Долгое время я делал массовые заявки с помощью консоли браузера:
|
|
@@ -184,7 +183,7 @@ hh-applicant-tool config -p
|
|
|
184
183
|
|
|
185
184
|
| Имя атрибута | Описание |
|
|
186
185
|
| --- | --- |
|
|
187
|
-
| `user_agent` | Кастомный юзерагент, передаваемый при кажом
|
|
186
|
+
| `user_agent` | Кастомный юзерагент, передаваемый при кажом запросе. |
|
|
188
187
|
| `proxy_url` | Прокси, используемый для всех запросов, например, `socks5h://127.0.0.1:9050` |
|
|
189
188
|
| `reply_message` | Сообщение для ответа работодателю при отклике на вакансии, см. формат сообщений |
|
|
190
189
|
|
|
@@ -257,7 +256,8 @@ https://hh.ru/employer/1918903
|
|
|
257
256
|
| **call-api** | Вызов произвольного метода API с выводом результата. |
|
|
258
257
|
| **refresh-token** | Обновляет access_token. |
|
|
259
258
|
| **config** | Редактировать конфигурационный файл. |
|
|
260
|
-
| **get-employer-contacts** | Получить список контактов
|
|
259
|
+
| **get-employer-contacts** | Получить список полученных вами контактов работодателей. Поддерживается так же экспорт в html/jsonl. Если хотите собирать контакты с нескольких акков, то укажите им одинаковый `client_telemetry_id` в конфигах. |
|
|
260
|
+
| **delete-telemetry** | Удадяет телеметрию, если та была включена. |
|
|
261
261
|
|
|
262
262
|
### Формат текста сообщений
|
|
263
263
|
|
|
@@ -11,11 +11,6 @@
|
|
|
11
11
|
<img src="https://github.com/user-attachments/assets/29d91490-2c83-4e3f-a573-c7a6182a4044" width="500">
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
|
-
### Внимание!!!
|
|
15
|
-
|
|
16
|
-
Если для Вас проблема установить данную утилиту, лень разбираться с ее настройкой, то Вы можете установить мое приложение под **Android** [HH Resume Automate](https://github.com/s3rgeym/hh-resume-automate/).
|
|
17
|
-
|
|
18
|
-
|
|
19
14
|
### Описание
|
|
20
15
|
|
|
21
16
|
> Утилита для генерации сопроводительного письма может использовать AI
|
|
@@ -34,6 +29,10 @@ asdf/pyenv/conda и что-то еще. В школотронской Manjaro и
|
|
|
34
29
|
|
|
35
30
|
> Если в веб-интерфейсе выставить фильтры, то они будут применяться в скрипте при отклике на подходящие
|
|
36
31
|
|
|
32
|
+
### Внимание!!!
|
|
33
|
+
|
|
34
|
+
Если для Вас проблема установить данную утилиту, лень разбираться с ее настройкой, то Вы можете установить мое приложение под **Android** [HH Resume Automate](https://github.com/s3rgeym/hh-resume-automate/). Оно обладает минимальным функционалом: обновление резюме (одного) и рассылка откликов (чистить их и тп нельзя).
|
|
35
|
+
|
|
37
36
|
### Предыстория
|
|
38
37
|
|
|
39
38
|
Долгое время я делал массовые заявки с помощью консоли браузера:
|
|
@@ -165,7 +164,7 @@ hh-applicant-tool config -p
|
|
|
165
164
|
|
|
166
165
|
| Имя атрибута | Описание |
|
|
167
166
|
| --- | --- |
|
|
168
|
-
| `user_agent` | Кастомный юзерагент, передаваемый при кажом
|
|
167
|
+
| `user_agent` | Кастомный юзерагент, передаваемый при кажом запросе. |
|
|
169
168
|
| `proxy_url` | Прокси, используемый для всех запросов, например, `socks5h://127.0.0.1:9050` |
|
|
170
169
|
| `reply_message` | Сообщение для ответа работодателю при отклике на вакансии, см. формат сообщений |
|
|
171
170
|
|
|
@@ -238,7 +237,8 @@ https://hh.ru/employer/1918903
|
|
|
238
237
|
| **call-api** | Вызов произвольного метода API с выводом результата. |
|
|
239
238
|
| **refresh-token** | Обновляет access_token. |
|
|
240
239
|
| **config** | Редактировать конфигурационный файл. |
|
|
241
|
-
| **get-employer-contacts** | Получить список контактов
|
|
240
|
+
| **get-employer-contacts** | Получить список полученных вами контактов работодателей. Поддерживается так же экспорт в html/jsonl. Если хотите собирать контакты с нескольких акков, то укажите им одинаковый `client_telemetry_id` в конфигах. |
|
|
241
|
+
| **delete-telemetry** | Удадяет телеметрию, если та была включена. |
|
|
242
242
|
|
|
243
243
|
### Формат текста сообщений
|
|
244
244
|
|
|
@@ -11,9 +11,8 @@ from typing import Literal, Sequence
|
|
|
11
11
|
|
|
12
12
|
from .api import ApiClient
|
|
13
13
|
from .color_log import ColorHandler
|
|
14
|
-
from .utils import Config, get_config_path
|
|
15
14
|
from .telemetry_client import TelemetryClient
|
|
16
|
-
|
|
15
|
+
from .utils import Config, get_config_path
|
|
17
16
|
|
|
18
17
|
DEFAULT_CONFIG_PATH = (
|
|
19
18
|
get_config_path() / (__package__ or "").replace("_", "-") / "config.json"
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
from os import getenv
|
|
4
|
+
import pathlib
|
|
5
|
+
from ..main import BaseOperation
|
|
6
|
+
from ..main import Namespace as BaseNamespace
|
|
7
|
+
from ..telemetry_client import TelemetryClient
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__package__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Namespace(BaseNamespace):
|
|
13
|
+
username: str | None
|
|
14
|
+
password: str | None
|
|
15
|
+
search: str | None
|
|
16
|
+
export: bool
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Operation(BaseOperation):
|
|
20
|
+
"""Выведет контакты работодателей, которые высылали вам приглашения"""
|
|
21
|
+
|
|
22
|
+
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
23
|
+
# parser.add_argument(
|
|
24
|
+
# "-u",
|
|
25
|
+
# "--username",
|
|
26
|
+
# type=str,
|
|
27
|
+
# help="Имя пользователя для аутентификации",
|
|
28
|
+
# default=getenv("AUTH_USERNAME"),
|
|
29
|
+
# )
|
|
30
|
+
# parser.add_argument(
|
|
31
|
+
# "-P",
|
|
32
|
+
# "--password",
|
|
33
|
+
# type=str,
|
|
34
|
+
# help="Пароль для аутентификации",
|
|
35
|
+
# default=getenv("AUTH_PASSWORD"),
|
|
36
|
+
# )
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"-s",
|
|
39
|
+
"--search",
|
|
40
|
+
type=str,
|
|
41
|
+
default="",
|
|
42
|
+
help="Строка поиска контактов работодателя (email, имя, название компании)",
|
|
43
|
+
)
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"-p",
|
|
46
|
+
"--page",
|
|
47
|
+
default=1,
|
|
48
|
+
help="Номер страницы в выдаче. Игнорируется при экспорте.",
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--export",
|
|
52
|
+
action=argparse.BooleanOptionalAction,
|
|
53
|
+
default=False,
|
|
54
|
+
help="Экспортировать контакты работодателей.",
|
|
55
|
+
)
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"-f",
|
|
58
|
+
"--format",
|
|
59
|
+
default="html",
|
|
60
|
+
choices=["html", "jsonl"],
|
|
61
|
+
help="Формат вывода",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def run(self, args: Namespace, _, telemetry_client: TelemetryClient) -> None:
|
|
65
|
+
if args.export:
|
|
66
|
+
contact_persons = []
|
|
67
|
+
page = 1
|
|
68
|
+
per_page = 100
|
|
69
|
+
while True:
|
|
70
|
+
res = telemetry_client.get_telemetry(
|
|
71
|
+
"/contact/persons",
|
|
72
|
+
{"search": args.search, "per_page": per_page, "page": page},
|
|
73
|
+
)
|
|
74
|
+
assert "contact_persons" in res
|
|
75
|
+
contact_persons += res["contact_persons"]
|
|
76
|
+
if per_page * page >= res["total"]:
|
|
77
|
+
break
|
|
78
|
+
page += 1
|
|
79
|
+
if args.format == "jsonl":
|
|
80
|
+
import json, sys
|
|
81
|
+
|
|
82
|
+
for contact in contact_persons:
|
|
83
|
+
json.dump(contact, sys.stdout, ensure_ascii=False)
|
|
84
|
+
sys.stdout.write("\n")
|
|
85
|
+
sys.stdout.flush()
|
|
86
|
+
else:
|
|
87
|
+
print(generate_html_report(contact_persons))
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
res = telemetry_client.get_telemetry(
|
|
91
|
+
"/contact/persons",
|
|
92
|
+
{"search": args.search, "per_page": 10, "page": args.page},
|
|
93
|
+
)
|
|
94
|
+
if "contact_persons" not in res:
|
|
95
|
+
print("❌", res)
|
|
96
|
+
return 1
|
|
97
|
+
|
|
98
|
+
print(
|
|
99
|
+
"Тут отображаются только данные, собранные с вашего telemetry_client_id. Вы так же можете их удалить с помощью команды delete-telemetry."
|
|
100
|
+
)
|
|
101
|
+
print()
|
|
102
|
+
|
|
103
|
+
print_contacts(res)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def generate_html_report(data: list[dict]) -> str:
|
|
107
|
+
"""
|
|
108
|
+
Генерирует HTML-отчет на основе предоставленных данных.
|
|
109
|
+
"""
|
|
110
|
+
html_content = """
|
|
111
|
+
<!DOCTYPE html>
|
|
112
|
+
<html lang="ru">
|
|
113
|
+
<head>
|
|
114
|
+
<meta charset="UTF-8">
|
|
115
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
116
|
+
<title>Отчет о сотрудниках и компаниях</title>
|
|
117
|
+
<style>
|
|
118
|
+
body {
|
|
119
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
120
|
+
margin: 20px;
|
|
121
|
+
background-color: #f4f7f6;
|
|
122
|
+
color: #333;
|
|
123
|
+
}
|
|
124
|
+
.container {
|
|
125
|
+
max-width: 900px;
|
|
126
|
+
margin: 20px auto;
|
|
127
|
+
background-color: #ffffff;
|
|
128
|
+
padding: 30px;
|
|
129
|
+
border-radius: 10px;
|
|
130
|
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
|
131
|
+
}
|
|
132
|
+
h1 {
|
|
133
|
+
color: #0056b3;
|
|
134
|
+
text-align: center;
|
|
135
|
+
margin-bottom: 30px;
|
|
136
|
+
}
|
|
137
|
+
.person-card {
|
|
138
|
+
background-color: #e9f0f8;
|
|
139
|
+
border: 1px solid #cce5ff;
|
|
140
|
+
border-radius: 8px;
|
|
141
|
+
padding: 20px;
|
|
142
|
+
margin-bottom: 25px;
|
|
143
|
+
transition: transform 0.2s ease-in-out;
|
|
144
|
+
}
|
|
145
|
+
.person-card:hover {
|
|
146
|
+
transform: translateY(-5px);
|
|
147
|
+
}
|
|
148
|
+
.person-card h2 {
|
|
149
|
+
color: #004085;
|
|
150
|
+
margin-top: 0;
|
|
151
|
+
margin-bottom: 10px;
|
|
152
|
+
border-bottom: 2px solid #0056b3;
|
|
153
|
+
padding-bottom: 5px;
|
|
154
|
+
}
|
|
155
|
+
.person-card p {
|
|
156
|
+
margin: 5px 0;
|
|
157
|
+
}
|
|
158
|
+
.person-card strong {
|
|
159
|
+
color: #004085;
|
|
160
|
+
}
|
|
161
|
+
.employer-info {
|
|
162
|
+
background-color: #d1ecf1;
|
|
163
|
+
border-left: 5px solid #007bff;
|
|
164
|
+
padding: 15px;
|
|
165
|
+
margin-top: 15px;
|
|
166
|
+
border-radius: 5px;
|
|
167
|
+
}
|
|
168
|
+
.employer-info h3 {
|
|
169
|
+
color: #0056b3;
|
|
170
|
+
margin-top: 0;
|
|
171
|
+
margin-bottom: 10px;
|
|
172
|
+
}
|
|
173
|
+
ul {
|
|
174
|
+
list-style-type: none;
|
|
175
|
+
padding: 0;
|
|
176
|
+
}
|
|
177
|
+
ul li {
|
|
178
|
+
background-color: #f8fafd;
|
|
179
|
+
padding: 8px 12px;
|
|
180
|
+
margin-bottom: 5px;
|
|
181
|
+
border-radius: 4px;
|
|
182
|
+
border: 1px solid #e0e9f1;
|
|
183
|
+
}
|
|
184
|
+
a {
|
|
185
|
+
color: #007bff;
|
|
186
|
+
text-decoration: none;
|
|
187
|
+
}
|
|
188
|
+
a:hover {
|
|
189
|
+
text-decoration: underline;
|
|
190
|
+
}
|
|
191
|
+
.no-data {
|
|
192
|
+
color: #6c757d;
|
|
193
|
+
font-style: italic;
|
|
194
|
+
}
|
|
195
|
+
</style>
|
|
196
|
+
</head>
|
|
197
|
+
<body>
|
|
198
|
+
<div class="container">
|
|
199
|
+
<h1>Отчет о сотрудниках</h1>
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
for item in data:
|
|
203
|
+
name = item.get("name", "N/A")
|
|
204
|
+
email = item.get("email", "N/A")
|
|
205
|
+
employer = item.get("employer") or {}
|
|
206
|
+
|
|
207
|
+
employer_name = employer.get("name", "N/A")
|
|
208
|
+
employer_area = employer.get("area", "N/A")
|
|
209
|
+
employer_site_url = employer.get("site_url", "")
|
|
210
|
+
|
|
211
|
+
phone_numbers = [
|
|
212
|
+
pn["phone_number"]
|
|
213
|
+
for pn in item.get("phone_numbers", [])
|
|
214
|
+
if "phone_number" in pn
|
|
215
|
+
]
|
|
216
|
+
telegram_usernames = [
|
|
217
|
+
tu["username"]
|
|
218
|
+
for tu in item.get("telegram_usernames", [])
|
|
219
|
+
if "username" in tu
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
html_content += f"""
|
|
223
|
+
<div class="person-card">
|
|
224
|
+
<h2>{name}</h2>
|
|
225
|
+
<p><strong>Email:</strong> <a href="mailto:{email}">{email}</a></p>
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
if employer_name != "N/A":
|
|
229
|
+
html_content += f"""
|
|
230
|
+
<div class="employer-info">
|
|
231
|
+
<h3>Работодатель: {employer_name}</h3>
|
|
232
|
+
<p><strong>Город:</strong> {employer_area}</p>
|
|
233
|
+
"""
|
|
234
|
+
if employer_site_url:
|
|
235
|
+
html_content += f"""
|
|
236
|
+
<p><strong>Сайт:</strong> <a href="{employer_site_url}" target="_blank">{employer_site_url}</a></p>
|
|
237
|
+
"""
|
|
238
|
+
html_content += "</div>" # Закрываем employer-info
|
|
239
|
+
else:
|
|
240
|
+
html_content += (
|
|
241
|
+
'<p class="no-data">Информация о работодателе отсутствует.</p>'
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
if phone_numbers:
|
|
245
|
+
html_content += "<p><strong>Номера телефонов:</strong></p><ul>"
|
|
246
|
+
for phone in phone_numbers:
|
|
247
|
+
html_content += f"<li><a href='tel:{phone}'>{phone}</a></li>"
|
|
248
|
+
html_content += "</ul>"
|
|
249
|
+
else:
|
|
250
|
+
html_content += '<p class="no-data">Номера телефонов отсутствуют.</p>'
|
|
251
|
+
|
|
252
|
+
if telegram_usernames:
|
|
253
|
+
html_content += "<p><strong>Имена пользователей Telegram:</strong></p><ul>"
|
|
254
|
+
for username in telegram_usernames:
|
|
255
|
+
html_content += f"<li><a href='https://t.me/{username}' target='_blank'>@{username}</a></li>"
|
|
256
|
+
html_content += "</ul>"
|
|
257
|
+
else:
|
|
258
|
+
html_content += (
|
|
259
|
+
'<p class="no-data">Имена пользователей Telegram отсутствуют.</p>'
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
html_content += "</div>" # Закрываем person-card
|
|
263
|
+
|
|
264
|
+
html_content += """
|
|
265
|
+
</div>
|
|
266
|
+
</body>
|
|
267
|
+
</html>
|
|
268
|
+
"""
|
|
269
|
+
return html_content
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def print_contacts(data: dict) -> None:
|
|
273
|
+
"""Вывод всех контактов в древовидной структуре."""
|
|
274
|
+
page = data["page"]
|
|
275
|
+
pages = (data["total"] // data["per_page"]) + 1
|
|
276
|
+
print(f"Страница {page}/{pages}:")
|
|
277
|
+
contacts = data.get("contact_persons", [])
|
|
278
|
+
for idx, contact in enumerate(contacts):
|
|
279
|
+
is_last_contact = idx == len(contacts) - 1
|
|
280
|
+
print_contact(contact, is_last_contact)
|
|
281
|
+
print()
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def print_contact(contact: dict, is_last_contact: bool) -> None:
|
|
285
|
+
"""Вывод информации о конкретном контакте."""
|
|
286
|
+
prefix = "└──" if is_last_contact else "├──"
|
|
287
|
+
print(f" {prefix} 🧑 {contact.get('name', 'Имя скрыто')}")
|
|
288
|
+
prefix2 = " " if is_last_contact else " │ "
|
|
289
|
+
print(f"{prefix2}├── 📧 Email: {contact.get('email', 'н/д')}")
|
|
290
|
+
employer = contact.get("employer") or {}
|
|
291
|
+
print(f"{prefix2}├── 🏢 Работодатель: {employer.get('name', 'н/д')}")
|
|
292
|
+
print(f"{prefix2}├── 🏠 Город: {employer.get('area', 'н/д')}")
|
|
293
|
+
print(f"{prefix2}└── 🌐 Сайт: {employer.get('site_url', 'н/д')}")
|
|
294
|
+
print(prefix2)
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import argparse
|
|
2
|
-
import logging
|
|
3
|
-
from os import getenv
|
|
4
|
-
|
|
5
|
-
from ..main import BaseOperation
|
|
6
|
-
from ..main import Namespace as BaseNamespace
|
|
7
|
-
from ..telemetry_client import TelemetryClient
|
|
8
|
-
|
|
9
|
-
logger = logging.getLogger(__package__)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Namespace(BaseNamespace):
|
|
13
|
-
username: str | None = None
|
|
14
|
-
password: str | None = None
|
|
15
|
-
search: str | None = None
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Operation(BaseOperation):
|
|
19
|
-
"""Выведет контакты работодателя по заданной строке поиска"""
|
|
20
|
-
|
|
21
|
-
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
22
|
-
parser.add_argument(
|
|
23
|
-
"-u",
|
|
24
|
-
"--username",
|
|
25
|
-
type=str,
|
|
26
|
-
help="Имя пользователя для аутентификации",
|
|
27
|
-
default=getenv("AUTH_USERNAME"),
|
|
28
|
-
)
|
|
29
|
-
parser.add_argument(
|
|
30
|
-
"-P",
|
|
31
|
-
"--password",
|
|
32
|
-
type=str,
|
|
33
|
-
help="Пароль для аутентификации",
|
|
34
|
-
default=getenv("AUTH_PASSWORD"),
|
|
35
|
-
)
|
|
36
|
-
parser.add_argument(
|
|
37
|
-
"-s",
|
|
38
|
-
"--search",
|
|
39
|
-
type=str,
|
|
40
|
-
default="",
|
|
41
|
-
help="Строка поиска для контактов работодателя",
|
|
42
|
-
)
|
|
43
|
-
parser.add_argument(
|
|
44
|
-
"-p",
|
|
45
|
-
"--page",
|
|
46
|
-
default=1,
|
|
47
|
-
help="Номер страницы в выдаче",
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
def run(self, args: Namespace, _, telemetry_client: TelemetryClient) -> None:
|
|
51
|
-
results = telemetry_client.get_telemetry(
|
|
52
|
-
"/contact/persons",
|
|
53
|
-
{"search": args.search, "per_page": 10, "page": args.page},
|
|
54
|
-
)
|
|
55
|
-
if "contact_persons" not in results:
|
|
56
|
-
print("❌", results)
|
|
57
|
-
return 1
|
|
58
|
-
|
|
59
|
-
print(
|
|
60
|
-
"Тут отображаются только данные, собранные с вашего telemetry_client_id. Вы так же можете их удалить с помощью команды delete-telemetry."
|
|
61
|
-
)
|
|
62
|
-
print()
|
|
63
|
-
|
|
64
|
-
self._print_contacts(results)
|
|
65
|
-
|
|
66
|
-
def _print_contacts(self, data: dict) -> None:
|
|
67
|
-
"""Вывод всех контактов в древовидной структуре."""
|
|
68
|
-
page = data["page"]
|
|
69
|
-
pages = (data["total"] // data["per_page"]) + 1
|
|
70
|
-
print(f"Страница {page}/{pages}:")
|
|
71
|
-
contacts = data.get("contact_persons", [])
|
|
72
|
-
for idx, contact in enumerate(contacts):
|
|
73
|
-
is_last_contact = idx == len(contacts) - 1
|
|
74
|
-
self._print_contact(contact, is_last_contact)
|
|
75
|
-
print()
|
|
76
|
-
|
|
77
|
-
def _print_contact(self, contact: dict, is_last_contact: bool) -> None:
|
|
78
|
-
"""Вывод информации о конкретном контакте."""
|
|
79
|
-
prefix = "└──" if is_last_contact else "├──"
|
|
80
|
-
print(f" {prefix} 🧑 {contact.get('name', 'Имя скрыто')}")
|
|
81
|
-
prefix2 = " " if is_last_contact else " │ "
|
|
82
|
-
print(f"{prefix2}├── 📧 Email: {contact.get('email', 'н/д')}")
|
|
83
|
-
employer = contact.get("employer") or {}
|
|
84
|
-
print(f"{prefix2}├── 🏢 Работодатель: {employer.get('name', 'н/д')}")
|
|
85
|
-
print(f"{prefix2}├── 🏠 Город: {employer.get('area', 'н/д')}")
|
|
86
|
-
print(f"{prefix2}└── 🌐 Сайт: {employer.get('site_url', 'н/д')}")
|
|
87
|
-
print(prefix2)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/__init__.py
RENAMED
|
File without changes
|
{hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/apply_similar.py
RENAMED
|
File without changes
|
{hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/authorize.py
RENAMED
|
File without changes
|
{hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/call_api.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/delete_telemetry.py
RENAMED
|
File without changes
|
{hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/list_resumes.py
RENAMED
|
File without changes
|
{hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/refresh_token.py
RENAMED
|
File without changes
|
{hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/reply_employers.py
RENAMED
|
File without changes
|
{hh_applicant_tool-0.5.9 → hh_applicant_tool-0.6.0}/hh_applicant_tool/operations/update_resumes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|