hh-applicant-tool 0.5.7__py3-none-any.whl → 0.5.9__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.
Potentially problematic release.
This version of hh-applicant-tool might be problematic. Click here for more details.
- hh_applicant_tool/api/client.py +101 -42
- hh_applicant_tool/api/errors.py +14 -8
- hh_applicant_tool/constants.py +6 -4
- hh_applicant_tool/jsonc.py +10 -5
- hh_applicant_tool/main.py +18 -2
- hh_applicant_tool/mixins.py +1 -1
- hh_applicant_tool/operations/apply_similar.py +37 -44
- hh_applicant_tool/operations/authorize.py +9 -19
- hh_applicant_tool/operations/call_api.py +8 -9
- hh_applicant_tool/operations/clear_negotiations.py +10 -14
- hh_applicant_tool/operations/config.py +21 -6
- hh_applicant_tool/operations/delete_telemetry.py +30 -0
- hh_applicant_tool/operations/get_employer_contacts.py +15 -31
- hh_applicant_tool/operations/list_resumes.py +4 -7
- hh_applicant_tool/operations/refresh_token.py +4 -17
- hh_applicant_tool/operations/reply_employers.py +12 -11
- hh_applicant_tool/operations/update_resumes.py +4 -5
- hh_applicant_tool/operations/whoami.py +4 -5
- hh_applicant_tool/telemetry_client.py +15 -2
- {hh_applicant_tool-0.5.7.dist-info → hh_applicant_tool-0.5.9.dist-info}/METADATA +13 -22
- hh_applicant_tool-0.5.9.dist-info/RECORD +32 -0
- hh_applicant_tool-0.5.7.dist-info/RECORD +0 -31
- {hh_applicant_tool-0.5.7.dist-info → hh_applicant_tool-0.5.9.dist-info}/WHEEL +0 -0
- {hh_applicant_tool-0.5.7.dist-info → hh_applicant_tool-0.5.9.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import logging
|
|
3
|
-
import time
|
|
4
3
|
from urllib.parse import parse_qs, urlsplit
|
|
5
4
|
import sys
|
|
6
5
|
from typing import Any
|
|
@@ -35,9 +34,8 @@ except ImportError:
|
|
|
35
34
|
pass
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
from ..api import
|
|
39
|
-
from ..main import BaseOperation, Namespace
|
|
40
|
-
from ..utils import Config
|
|
37
|
+
from ..api import ApiClient # noqa: E402
|
|
38
|
+
from ..main import BaseOperation, Namespace # noqa: E402
|
|
41
39
|
|
|
42
40
|
logger = logging.getLogger(__package__)
|
|
43
41
|
|
|
@@ -54,10 +52,9 @@ class HHAndroidUrlSchemeHandler(QWebEngineUrlSchemeHandler):
|
|
|
54
52
|
|
|
55
53
|
|
|
56
54
|
class WebViewWindow(QMainWindow):
|
|
57
|
-
def __init__(self,
|
|
55
|
+
def __init__(self, api_client: ApiClient) -> None:
|
|
58
56
|
super().__init__()
|
|
59
|
-
self.
|
|
60
|
-
self.config = config
|
|
57
|
+
self.api_client = api_client
|
|
61
58
|
# Настройка WebEngineView
|
|
62
59
|
self.web_view = QWebEngineView()
|
|
63
60
|
self.setCentralWidget(self.web_view)
|
|
@@ -68,16 +65,15 @@ class WebViewWindow(QMainWindow):
|
|
|
68
65
|
profile.installUrlSchemeHandler(b"hhandroid", self.hhandroid_handler)
|
|
69
66
|
# Настройки окна для мобильного вида
|
|
70
67
|
self.resize(480, 800)
|
|
71
|
-
self.web_view.setUrl(QUrl(
|
|
68
|
+
self.web_view.setUrl(QUrl(api_client.oauth_client.authorize_url))
|
|
72
69
|
|
|
73
70
|
def handle_redirect_uri(self, redirect_uri: str) -> None:
|
|
74
71
|
logger.debug(f"handle redirect uri: {redirect_uri}")
|
|
75
72
|
sp = urlsplit(redirect_uri)
|
|
76
73
|
code = parse_qs(sp.query).get("code", [None])[0]
|
|
77
74
|
if code:
|
|
78
|
-
token = self.oauth_client.authenticate(code)
|
|
79
|
-
|
|
80
|
-
self.config.save(token=dict(token, created_at=int(time.time())))
|
|
75
|
+
token = self.api_client.oauth_client.authenticate(code)
|
|
76
|
+
self.api_client.handle_access_token(token)
|
|
81
77
|
print("🔓 Авторизация прошла успешно!")
|
|
82
78
|
self.close()
|
|
83
79
|
|
|
@@ -88,21 +84,15 @@ class Operation(BaseOperation):
|
|
|
88
84
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
89
85
|
pass
|
|
90
86
|
|
|
91
|
-
def run(self, args: Namespace) -> None:
|
|
87
|
+
def run(self, args: Namespace, api_client: ApiClient, *_) -> None:
|
|
92
88
|
if not QT_IMPORTED:
|
|
93
89
|
print_err(
|
|
94
90
|
"❗Критиническая Ошибка: PyQt6 не был импортирован, возможно, вы долбоеб и забыли его установить, либо же криворукие разрабы этой либы опять все сломали..."
|
|
95
91
|
)
|
|
96
92
|
sys.exit(1)
|
|
97
93
|
|
|
98
|
-
oauth = OAuthClient(
|
|
99
|
-
user_agent=(args.config["oauth_user_agent"] or args.config["user_agent"]),
|
|
100
|
-
)
|
|
101
|
-
|
|
102
94
|
app = QApplication(sys.argv)
|
|
103
|
-
window = WebViewWindow(
|
|
104
|
-
oauth.authorize_url, oauth_client=oauth, config=args.config
|
|
105
|
-
)
|
|
95
|
+
window = WebViewWindow(api_client=api_client)
|
|
106
96
|
window.show()
|
|
107
97
|
|
|
108
98
|
app.exec()
|
|
@@ -4,8 +4,8 @@ import json
|
|
|
4
4
|
import logging
|
|
5
5
|
import sys
|
|
6
6
|
|
|
7
|
-
from ..api import ApiError
|
|
8
|
-
from ..main import BaseOperation
|
|
7
|
+
from ..api import ApiError, ApiClient
|
|
8
|
+
from ..main import BaseOperation
|
|
9
9
|
from ..main import Namespace as BaseNamespace
|
|
10
10
|
|
|
11
11
|
logger = logging.getLogger(__package__)
|
|
@@ -22,23 +22,22 @@ class Operation(BaseOperation):
|
|
|
22
22
|
"""Вызвать произвольный метод API <https://github.com/hhru/api>."""
|
|
23
23
|
|
|
24
24
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
25
|
-
parser.add_argument("endpoint")
|
|
25
|
+
parser.add_argument("endpoint", help="Путь до эндпоинта API")
|
|
26
26
|
parser.add_argument(
|
|
27
27
|
"param",
|
|
28
28
|
nargs="*",
|
|
29
|
-
help="PARAM=VALUE",
|
|
29
|
+
help="Параметры указываются в виде PARAM=VALUE",
|
|
30
30
|
default=[],
|
|
31
31
|
)
|
|
32
32
|
parser.add_argument(
|
|
33
33
|
"-m", "--method", "--meth", default="GET", help="HTTP Метод"
|
|
34
34
|
)
|
|
35
35
|
|
|
36
|
-
def run(self, args: Namespace) -> None:
|
|
37
|
-
api = get_api(args)
|
|
36
|
+
def run(self, args: Namespace, api_client: ApiClient, *_) -> None:
|
|
38
37
|
params = dict(x.split("=", 1) for x in args.param)
|
|
39
38
|
try:
|
|
40
|
-
result =
|
|
41
|
-
print(json.dumps(result, ensure_ascii=
|
|
39
|
+
result = api_client.request(args.method, args.endpoint, params=params)
|
|
40
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
42
41
|
except ApiError as ex:
|
|
43
|
-
json.dump(ex.data, sys.stderr, ensure_ascii=
|
|
42
|
+
json.dump(ex.data, sys.stderr, ensure_ascii=False)
|
|
44
43
|
return 1
|
|
@@ -7,7 +7,6 @@ from ..api import ApiClient, ClientError
|
|
|
7
7
|
from ..constants import INVALID_ISO8601_FORMAT
|
|
8
8
|
from ..main import BaseOperation
|
|
9
9
|
from ..main import Namespace as BaseNamespace
|
|
10
|
-
from ..main import get_api
|
|
11
10
|
from ..types import ApiListResponse
|
|
12
11
|
from ..utils import print_err, truncate_string
|
|
13
12
|
|
|
@@ -44,12 +43,12 @@ class Operation(BaseOperation):
|
|
|
44
43
|
action=argparse.BooleanOptionalAction,
|
|
45
44
|
)
|
|
46
45
|
|
|
47
|
-
def _get_active_negotiations(self,
|
|
46
|
+
def _get_active_negotiations(self, api_client: ApiClient) -> list[dict]:
|
|
48
47
|
rv = []
|
|
49
48
|
page = 0
|
|
50
49
|
per_page = 100
|
|
51
50
|
while True:
|
|
52
|
-
r: ApiListResponse =
|
|
51
|
+
r: ApiListResponse = api_client.get(
|
|
53
52
|
"/negotiations", page=page, per_page=per_page, status="active"
|
|
54
53
|
)
|
|
55
54
|
rv.extend(r["items"])
|
|
@@ -58,9 +57,8 @@ class Operation(BaseOperation):
|
|
|
58
57
|
break
|
|
59
58
|
return rv
|
|
60
59
|
|
|
61
|
-
def run(self, args: Namespace) -> None:
|
|
62
|
-
|
|
63
|
-
negotiations = self._get_active_negotiations(api)
|
|
60
|
+
def run(self, args: Namespace, api_client: ApiClient, *_) -> None:
|
|
61
|
+
negotiations = self._get_active_negotiations(api_client)
|
|
64
62
|
print("Всего активных:", len(negotiations))
|
|
65
63
|
for item in negotiations:
|
|
66
64
|
state = item["state"]
|
|
@@ -69,20 +67,18 @@ class Operation(BaseOperation):
|
|
|
69
67
|
# hidden True
|
|
70
68
|
is_discard = state["id"] == "discard"
|
|
71
69
|
if not item["hidden"] and (
|
|
72
|
-
args.all
|
|
70
|
+
args.all
|
|
73
71
|
or is_discard
|
|
74
72
|
or (
|
|
75
73
|
state["id"] == "response"
|
|
76
|
-
and (
|
|
77
|
-
|
|
78
|
-
).replace(tzinfo=timezone.utc)
|
|
79
|
-
> datetime.strptime(
|
|
80
|
-
item["updated_at"], INVALID_ISO8601_FORMAT
|
|
74
|
+
and (datetime.utcnow() - timedelta(days=args.older_than)).replace(
|
|
75
|
+
tzinfo=timezone.utc
|
|
81
76
|
)
|
|
77
|
+
> datetime.strptime(item["updated_at"], INVALID_ISO8601_FORMAT)
|
|
82
78
|
)
|
|
83
79
|
):
|
|
84
80
|
decline_allowed = item.get("decline_allowed") or False
|
|
85
|
-
r =
|
|
81
|
+
r = api_client.delete(
|
|
86
82
|
f"/negotiations/active/{item['id']}",
|
|
87
83
|
with_decline_message=decline_allowed,
|
|
88
84
|
)
|
|
@@ -99,7 +95,7 @@ class Operation(BaseOperation):
|
|
|
99
95
|
if is_discard and args.blacklist_discard:
|
|
100
96
|
employer = vacancy["employer"]
|
|
101
97
|
try:
|
|
102
|
-
r =
|
|
98
|
+
r = api_client.put(f"/employers/blacklisted/{employer['id']}")
|
|
103
99
|
assert not r
|
|
104
100
|
print(
|
|
105
101
|
"🚫 Заблокировали",
|
|
@@ -2,6 +2,7 @@ import argparse
|
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
4
|
import subprocess
|
|
5
|
+
from typing import Any
|
|
5
6
|
|
|
6
7
|
from ..main import BaseOperation
|
|
7
8
|
from ..main import Namespace as BaseNamespace
|
|
@@ -12,25 +13,39 @@ EDITOR = os.getenv("EDITOR", "nano")
|
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class Namespace(BaseNamespace):
|
|
15
|
-
|
|
16
|
+
show_path: bool
|
|
17
|
+
key: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_value(data: dict[str, Any], path: str) -> Any:
|
|
21
|
+
for key in path.split("."):
|
|
22
|
+
if key not in data:
|
|
23
|
+
return None
|
|
24
|
+
data = data[key]
|
|
25
|
+
return data
|
|
16
26
|
|
|
17
27
|
|
|
18
28
|
class Operation(BaseOperation):
|
|
19
|
-
"""
|
|
29
|
+
"""Операции с конфигурационным файлом"""
|
|
20
30
|
|
|
21
31
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
22
32
|
parser.add_argument(
|
|
23
33
|
"-p",
|
|
24
|
-
"--
|
|
34
|
+
"--show-path",
|
|
35
|
+
"--path",
|
|
25
36
|
type=bool,
|
|
26
37
|
default=False,
|
|
27
38
|
action=argparse.BooleanOptionalAction,
|
|
28
|
-
help="
|
|
39
|
+
help="Вывести полный путь к конфигу",
|
|
29
40
|
)
|
|
41
|
+
parser.add_argument("-k", "--key", help="Вывести отдельное значение из конфига")
|
|
30
42
|
|
|
31
|
-
def run(self, args: Namespace) -> None:
|
|
43
|
+
def run(self, args: Namespace, *_) -> None:
|
|
44
|
+
if args.key:
|
|
45
|
+
print(get_value(args.config, args.key))
|
|
46
|
+
return
|
|
32
47
|
config_path = str(args.config._config_path)
|
|
33
|
-
if args.
|
|
48
|
+
if args.show_path:
|
|
34
49
|
print(config_path)
|
|
35
50
|
else:
|
|
36
51
|
subprocess.call([EDITOR, config_path])
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Этот модуль можно использовать как образец для других
|
|
2
|
+
import argparse
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from ..telemetry_client import TelemetryClient, TelemetryError
|
|
6
|
+
|
|
7
|
+
from ..main import BaseOperation
|
|
8
|
+
from ..main import Namespace as BaseNamespace
|
|
9
|
+
from ..utils import print_err
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__package__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Namespace(BaseNamespace):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Operation(BaseOperation):
|
|
19
|
+
"""Удалить всю телеметрию, сохраненную на сервере."""
|
|
20
|
+
|
|
21
|
+
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
def run(self, a, b, telemetry_client: TelemetryClient) -> None:
|
|
25
|
+
try:
|
|
26
|
+
telemetry_client.send_telemetry("/delete")
|
|
27
|
+
print("✅ Вся телеметрия, сохраненная на сервере, была успешно удалена!")
|
|
28
|
+
except TelemetryError as ex:
|
|
29
|
+
print_err("❗ Ошибка:", ex)
|
|
30
|
+
return 1
|
|
@@ -4,7 +4,6 @@ from os import getenv
|
|
|
4
4
|
|
|
5
5
|
from ..main import BaseOperation
|
|
6
6
|
from ..main import Namespace as BaseNamespace
|
|
7
|
-
from ..main import get_proxies
|
|
8
7
|
from ..telemetry_client import TelemetryClient
|
|
9
8
|
|
|
10
9
|
logger = logging.getLogger(__package__)
|
|
@@ -48,27 +47,27 @@ class Operation(BaseOperation):
|
|
|
48
47
|
help="Номер страницы в выдаче",
|
|
49
48
|
)
|
|
50
49
|
|
|
51
|
-
def run(self, args: Namespace) -> None:
|
|
52
|
-
|
|
53
|
-
client = TelemetryClient(proxies=proxies)
|
|
54
|
-
auth = (
|
|
55
|
-
(args.username, args.password)
|
|
56
|
-
if args.username and args.password
|
|
57
|
-
else None
|
|
58
|
-
)
|
|
59
|
-
# Аутентификация пользователя
|
|
60
|
-
results = client.get_telemetry(
|
|
50
|
+
def run(self, args: Namespace, _, telemetry_client: TelemetryClient) -> None:
|
|
51
|
+
results = telemetry_client.get_telemetry(
|
|
61
52
|
"/contact/persons",
|
|
62
53
|
{"search": args.search, "per_page": 10, "page": args.page},
|
|
63
|
-
auth=auth,
|
|
64
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
|
+
|
|
65
64
|
self._print_contacts(results)
|
|
66
65
|
|
|
67
66
|
def _print_contacts(self, data: dict) -> None:
|
|
68
67
|
"""Вывод всех контактов в древовидной структуре."""
|
|
69
68
|
page = data["page"]
|
|
70
69
|
pages = (data["total"] // data["per_page"]) + 1
|
|
71
|
-
print(f"
|
|
70
|
+
print(f"Страница {page}/{pages}:")
|
|
72
71
|
contacts = data.get("contact_persons", [])
|
|
73
72
|
for idx, contact in enumerate(contacts):
|
|
74
73
|
is_last_contact = idx == len(contacts) - 1
|
|
@@ -78,26 +77,11 @@ class Operation(BaseOperation):
|
|
|
78
77
|
def _print_contact(self, contact: dict, is_last_contact: bool) -> None:
|
|
79
78
|
"""Вывод информации о конкретном контакте."""
|
|
80
79
|
prefix = "└──" if is_last_contact else "├──"
|
|
81
|
-
print(f" {prefix} 🧑 {contact.get('name', '
|
|
80
|
+
print(f" {prefix} 🧑 {contact.get('name', 'Имя скрыто')}")
|
|
82
81
|
prefix2 = " " if is_last_contact else " │ "
|
|
83
82
|
print(f"{prefix2}├── 📧 Email: {contact.get('email', 'н/д')}")
|
|
84
83
|
employer = contact.get("employer") or {}
|
|
85
84
|
print(f"{prefix2}├── 🏢 Работодатель: {employer.get('name', 'н/д')}")
|
|
86
85
|
print(f"{prefix2}├── 🏠 Город: {employer.get('area', 'н/д')}")
|
|
87
|
-
print(f"{prefix2}
|
|
88
|
-
|
|
89
|
-
phones = contact["phone_numbers"] or [{"phone_number": "(нет номеров)"}]
|
|
90
|
-
print(f"{prefix2}├── 📞 Телефоны:")
|
|
91
|
-
last_phone = len(phones) - 1
|
|
92
|
-
for i, phone in enumerate(phones):
|
|
93
|
-
sub_prefix = "└──" if i == last_phone else "├──"
|
|
94
|
-
print(f"{prefix2}│ {sub_prefix} {phone['phone_number']}")
|
|
95
|
-
|
|
96
|
-
telegrams = contact["telegram_usernames"] or [
|
|
97
|
-
{"username": "(нет аккаунтов)"}
|
|
98
|
-
]
|
|
99
|
-
print(f"{prefix2}└── 📱 Telegram:")
|
|
100
|
-
last_telegram = len(telegrams) - 1
|
|
101
|
-
for i, telegram in enumerate(telegrams):
|
|
102
|
-
sub_prefix = "└──" if i == last_telegram else "├──"
|
|
103
|
-
print(f"{prefix2} {sub_prefix} {telegram['username']}")
|
|
86
|
+
print(f"{prefix2}└── 🌐 Сайт: {employer.get('site_url', 'н/д')}")
|
|
87
|
+
print(prefix2)
|
|
@@ -5,7 +5,7 @@ import logging
|
|
|
5
5
|
from prettytable import PrettyTable
|
|
6
6
|
|
|
7
7
|
from ..api import ApiClient
|
|
8
|
-
from ..main import BaseOperation
|
|
8
|
+
from ..main import BaseOperation
|
|
9
9
|
from ..main import Namespace as BaseNamespace
|
|
10
10
|
from ..types import ApiListResponse
|
|
11
11
|
from ..utils import truncate_string
|
|
@@ -23,12 +23,9 @@ class Operation(BaseOperation):
|
|
|
23
23
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
24
24
|
pass
|
|
25
25
|
|
|
26
|
-
def run(self, args: Namespace) -> None:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
t = PrettyTable(
|
|
30
|
-
field_names=["ID", "Название", "Статус"], align="l", valign="t"
|
|
31
|
-
)
|
|
26
|
+
def run(self, args: Namespace, api_client: ApiClient, *_) -> None:
|
|
27
|
+
resumes: ApiListResponse = api_client.get("/resumes/mine")
|
|
28
|
+
t = PrettyTable(field_names=["ID", "Название", "Статус"], align="l", valign="t")
|
|
32
29
|
t.add_rows(
|
|
33
30
|
[
|
|
34
31
|
(
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Этот модуль можно использовать как образец для других
|
|
2
2
|
import argparse
|
|
3
3
|
import logging
|
|
4
|
-
|
|
5
|
-
from ..api import ApiError,
|
|
4
|
+
from typing import Any
|
|
5
|
+
from ..api import ApiError, ApiClient
|
|
6
6
|
from ..main import BaseOperation
|
|
7
7
|
from ..main import Namespace as BaseNamespace
|
|
8
8
|
from ..utils import print_err
|
|
@@ -20,22 +20,9 @@ class Operation(BaseOperation):
|
|
|
20
20
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
21
21
|
pass
|
|
22
22
|
|
|
23
|
-
def run(self, args:
|
|
24
|
-
if (
|
|
25
|
-
not args.config["token"]
|
|
26
|
-
or not args.config["token"]["refresh_token"]
|
|
27
|
-
):
|
|
28
|
-
print_err("❗ Необходим refresh_token!")
|
|
29
|
-
return 1
|
|
23
|
+
def run(self, _, api_client: ApiClient, *args: Any) -> None:
|
|
30
24
|
try:
|
|
31
|
-
|
|
32
|
-
user_agent=(
|
|
33
|
-
args.config["oauth_user_agent"]
|
|
34
|
-
or args.config["user_agent"]
|
|
35
|
-
),
|
|
36
|
-
)
|
|
37
|
-
token = oauth.refresh_access(args.config["token"]["refresh_token"])
|
|
38
|
-
args.config.save(token=token)
|
|
25
|
+
api_client.refresh_access_token()
|
|
39
26
|
print("✅ Токен обновлен!")
|
|
40
27
|
except ApiError as ex:
|
|
41
28
|
print_err("❗ Ошибка:", ex)
|
|
@@ -4,10 +4,9 @@ import random
|
|
|
4
4
|
import time
|
|
5
5
|
from typing import Tuple
|
|
6
6
|
|
|
7
|
-
from ..api import ApiError
|
|
7
|
+
from ..api import ApiError, ApiClient
|
|
8
8
|
from ..main import BaseOperation
|
|
9
9
|
from ..main import Namespace as BaseNamespace
|
|
10
|
-
from ..main import get_api
|
|
11
10
|
from ..mixins import GetResumeIdMixin
|
|
12
11
|
from ..utils import parse_interval, random_text
|
|
13
12
|
|
|
@@ -67,8 +66,8 @@ class Operation(BaseOperation, GetResumeIdMixin):
|
|
|
67
66
|
action=argparse.BooleanOptionalAction,
|
|
68
67
|
)
|
|
69
68
|
|
|
70
|
-
def run(self, args: Namespace) -> None:
|
|
71
|
-
self.
|
|
69
|
+
def run(self, args: Namespace, api_client: ApiClient, *_) -> None:
|
|
70
|
+
self.api_client = api_client
|
|
72
71
|
self.resume_id = self._get_resume_id()
|
|
73
72
|
self.reply_min_interval, self.reply_max_interval = args.reply_interval
|
|
74
73
|
self.reply_message = args.reply_message or args.config["reply_message"]
|
|
@@ -80,7 +79,7 @@ class Operation(BaseOperation, GetResumeIdMixin):
|
|
|
80
79
|
self._reply_chats()
|
|
81
80
|
|
|
82
81
|
def _reply_chats(self) -> None:
|
|
83
|
-
me = self.me = self.
|
|
82
|
+
me = self.me = self.api_client.get("/me")
|
|
84
83
|
|
|
85
84
|
basic_message_placeholders = {
|
|
86
85
|
"first_name": me.get("first_name", ""),
|
|
@@ -124,7 +123,7 @@ class Operation(BaseOperation, GetResumeIdMixin):
|
|
|
124
123
|
last_message: dict | None = None
|
|
125
124
|
message_history: list[str] = []
|
|
126
125
|
while True:
|
|
127
|
-
messages_res = self.
|
|
126
|
+
messages_res = self.api_client.get(
|
|
128
127
|
f"/negotiations/{nid}/messages", page=page
|
|
129
128
|
)
|
|
130
129
|
|
|
@@ -160,10 +159,12 @@ class Operation(BaseOperation, GetResumeIdMixin):
|
|
|
160
159
|
print("💼", message_placeholders["vacancy_name"])
|
|
161
160
|
print("📅", vacancy["created_at"])
|
|
162
161
|
if salary:
|
|
163
|
-
salary_from = salary.get("from")or "-"
|
|
164
|
-
salary_to = salary.get("to")or "-"
|
|
162
|
+
salary_from = salary.get("from") or "-"
|
|
163
|
+
salary_to = salary.get("to") or "-"
|
|
165
164
|
salary_currency = salary.get("currency")
|
|
166
|
-
print(
|
|
165
|
+
print(
|
|
166
|
+
"💵 от", salary_from, "до", salary_to, salary_currency
|
|
167
|
+
)
|
|
167
168
|
print("")
|
|
168
169
|
print("Последние сообщения:")
|
|
169
170
|
for msg in (
|
|
@@ -192,7 +193,7 @@ class Operation(BaseOperation, GetResumeIdMixin):
|
|
|
192
193
|
self.reply_max_interval,
|
|
193
194
|
)
|
|
194
195
|
)
|
|
195
|
-
self.
|
|
196
|
+
self.api_client.post(
|
|
196
197
|
f"/negotiations/{nid}/messages",
|
|
197
198
|
message=message,
|
|
198
199
|
)
|
|
@@ -208,7 +209,7 @@ class Operation(BaseOperation, GetResumeIdMixin):
|
|
|
208
209
|
def _get_negotiations(self) -> list[dict]:
|
|
209
210
|
rv = []
|
|
210
211
|
for page in range(self.max_pages):
|
|
211
|
-
res = self.
|
|
212
|
+
res = self.api_client.get("/negotiations", page=page, status="active")
|
|
212
213
|
rv.extend(res["items"])
|
|
213
214
|
if page >= res["pages"] - 1:
|
|
214
215
|
break
|
|
@@ -3,7 +3,7 @@ import argparse
|
|
|
3
3
|
import logging
|
|
4
4
|
|
|
5
5
|
from ..api import ApiClient, ApiError
|
|
6
|
-
from ..main import BaseOperation
|
|
6
|
+
from ..main import BaseOperation
|
|
7
7
|
from ..main import Namespace as BaseNamespace
|
|
8
8
|
from ..types import ApiListResponse
|
|
9
9
|
from ..utils import print_err, truncate_string
|
|
@@ -21,12 +21,11 @@ class Operation(BaseOperation):
|
|
|
21
21
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
22
22
|
pass
|
|
23
23
|
|
|
24
|
-
def run(self, args: Namespace) -> None:
|
|
25
|
-
|
|
26
|
-
resumes: ApiListResponse = api.get("/resumes/mine")
|
|
24
|
+
def run(self, args: Namespace, api_client: ApiClient, *_) -> None:
|
|
25
|
+
resumes: ApiListResponse = api_client.get("/resumes/mine")
|
|
27
26
|
for resume in resumes["items"]:
|
|
28
27
|
try:
|
|
29
|
-
res =
|
|
28
|
+
res = api_client.post(f"/resumes/{resume['id']}/publish")
|
|
30
29
|
assert res == {}
|
|
31
30
|
print("✅ Обновлено", truncate_string(resume["title"]))
|
|
32
31
|
except ApiError as ex:
|
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
import logging
|
|
5
5
|
|
|
6
6
|
from ..api import ApiClient
|
|
7
|
-
from ..main import BaseOperation
|
|
7
|
+
from ..main import BaseOperation
|
|
8
8
|
from ..main import Namespace as BaseNamespace
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger(__package__)
|
|
@@ -20,7 +20,6 @@ class Operation(BaseOperation):
|
|
|
20
20
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
21
21
|
pass
|
|
22
22
|
|
|
23
|
-
def run(self, args: Namespace) -> None:
|
|
24
|
-
|
|
25
|
-
result =
|
|
26
|
-
print(json.dumps(result, ensure_ascii=True, indent=2, sort_keys=True))
|
|
23
|
+
def run(self, args: Namespace, api_client: ApiClient, _) -> None:
|
|
24
|
+
result = api_client.get("/me")
|
|
25
|
+
print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import json
|
|
2
4
|
import logging
|
|
3
5
|
import os
|
|
@@ -6,9 +8,10 @@ import warnings
|
|
|
6
8
|
from functools import partialmethod
|
|
7
9
|
from typing import Any, Dict, Optional
|
|
8
10
|
from urllib.parse import urljoin
|
|
9
|
-
|
|
10
11
|
import requests
|
|
12
|
+
from .utils import Config
|
|
11
13
|
|
|
14
|
+
# Сертификат на сервере давно истек, но его обновлять мне лень...
|
|
12
15
|
warnings.filterwarnings("ignore", message="Unverified HTTPS request")
|
|
13
16
|
|
|
14
17
|
logger = logging.getLogger(__package__)
|
|
@@ -28,6 +31,7 @@ class TelemetryClient:
|
|
|
28
31
|
|
|
29
32
|
def __init__(
|
|
30
33
|
self,
|
|
34
|
+
telemetry_client_id: str,
|
|
31
35
|
server_address: Optional[str] = None,
|
|
32
36
|
*,
|
|
33
37
|
session: Optional[requests.Session] = None,
|
|
@@ -35,6 +39,7 @@ class TelemetryClient:
|
|
|
35
39
|
proxies: dict | None = None,
|
|
36
40
|
delay: Optional[float] = None,
|
|
37
41
|
) -> None:
|
|
42
|
+
self.send_telemetry_id = telemetry_client_id
|
|
38
43
|
self.server_address = os.getenv(
|
|
39
44
|
"TELEMETRY_SERVER", server_address or self.server_address
|
|
40
45
|
)
|
|
@@ -67,7 +72,10 @@ class TelemetryClient:
|
|
|
67
72
|
response = self.session.request(
|
|
68
73
|
method,
|
|
69
74
|
url,
|
|
70
|
-
headers={
|
|
75
|
+
headers={
|
|
76
|
+
"User-Agent": self.user_agent,
|
|
77
|
+
"X-Telemetry-Client-ID": self.send_telemetry_id,
|
|
78
|
+
},
|
|
71
79
|
proxies=self.proxies,
|
|
72
80
|
params=data if not has_body else None,
|
|
73
81
|
json=data if has_body else None,
|
|
@@ -91,3 +99,8 @@ class TelemetryClient:
|
|
|
91
99
|
|
|
92
100
|
get_telemetry = partialmethod(request, "GET")
|
|
93
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"])
|