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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. hh_applicant_tool/__init__.py +1 -0
  2. hh_applicant_tool/__main__.py +1 -1
  3. hh_applicant_tool/ai/base.py +2 -0
  4. hh_applicant_tool/ai/openai.py +23 -33
  5. hh_applicant_tool/api/client.py +50 -64
  6. hh_applicant_tool/api/errors.py +51 -7
  7. hh_applicant_tool/constants.py +0 -3
  8. hh_applicant_tool/datatypes.py +291 -0
  9. hh_applicant_tool/main.py +233 -111
  10. hh_applicant_tool/operations/apply_similar.py +266 -362
  11. hh_applicant_tool/operations/authorize.py +256 -120
  12. hh_applicant_tool/operations/call_api.py +18 -8
  13. hh_applicant_tool/operations/check_negotiations.py +102 -0
  14. hh_applicant_tool/operations/check_proxy.py +30 -0
  15. hh_applicant_tool/operations/config.py +119 -16
  16. hh_applicant_tool/operations/install.py +34 -0
  17. hh_applicant_tool/operations/list_resumes.py +24 -10
  18. hh_applicant_tool/operations/log.py +77 -0
  19. hh_applicant_tool/operations/migrate_db.py +65 -0
  20. hh_applicant_tool/operations/query.py +120 -0
  21. hh_applicant_tool/operations/refresh_token.py +14 -13
  22. hh_applicant_tool/operations/reply_employers.py +148 -167
  23. hh_applicant_tool/operations/settings.py +95 -0
  24. hh_applicant_tool/operations/uninstall.py +26 -0
  25. hh_applicant_tool/operations/update_resumes.py +21 -10
  26. hh_applicant_tool/operations/whoami.py +40 -7
  27. hh_applicant_tool/storage/__init__.py +4 -0
  28. hh_applicant_tool/storage/facade.py +24 -0
  29. hh_applicant_tool/storage/models/__init__.py +0 -0
  30. hh_applicant_tool/storage/models/base.py +169 -0
  31. hh_applicant_tool/storage/models/contact.py +16 -0
  32. hh_applicant_tool/storage/models/employer.py +12 -0
  33. hh_applicant_tool/storage/models/negotiation.py +16 -0
  34. hh_applicant_tool/storage/models/resume.py +19 -0
  35. hh_applicant_tool/storage/models/setting.py +6 -0
  36. hh_applicant_tool/storage/models/vacancy.py +36 -0
  37. hh_applicant_tool/storage/queries/migrations/.gitkeep +0 -0
  38. hh_applicant_tool/storage/queries/schema.sql +119 -0
  39. hh_applicant_tool/storage/repositories/__init__.py +0 -0
  40. hh_applicant_tool/storage/repositories/base.py +176 -0
  41. hh_applicant_tool/storage/repositories/contacts.py +19 -0
  42. hh_applicant_tool/storage/repositories/employers.py +13 -0
  43. hh_applicant_tool/storage/repositories/negotiations.py +12 -0
  44. hh_applicant_tool/storage/repositories/resumes.py +14 -0
  45. hh_applicant_tool/storage/repositories/settings.py +34 -0
  46. hh_applicant_tool/storage/repositories/vacancies.py +8 -0
  47. hh_applicant_tool/storage/utils.py +49 -0
  48. hh_applicant_tool/utils/__init__.py +31 -0
  49. hh_applicant_tool/utils/attrdict.py +6 -0
  50. hh_applicant_tool/utils/binpack.py +167 -0
  51. hh_applicant_tool/utils/config.py +55 -0
  52. hh_applicant_tool/utils/dateutil.py +19 -0
  53. hh_applicant_tool/{jsonc.py → utils/jsonc.py} +12 -6
  54. hh_applicant_tool/utils/jsonutil.py +61 -0
  55. hh_applicant_tool/utils/log.py +144 -0
  56. hh_applicant_tool/utils/misc.py +12 -0
  57. hh_applicant_tool/utils/mixins.py +220 -0
  58. hh_applicant_tool/utils/string.py +27 -0
  59. hh_applicant_tool/utils/terminal.py +19 -0
  60. hh_applicant_tool/utils/user_agent.py +17 -0
  61. hh_applicant_tool-1.4.7.dist-info/METADATA +628 -0
  62. hh_applicant_tool-1.4.7.dist-info/RECORD +67 -0
  63. hh_applicant_tool/ai/blackbox.py +0 -55
  64. hh_applicant_tool/color_log.py +0 -47
  65. hh_applicant_tool/mixins.py +0 -13
  66. hh_applicant_tool/operations/clear_negotiations.py +0 -109
  67. hh_applicant_tool/operations/delete_telemetry.py +0 -30
  68. hh_applicant_tool/operations/get_employer_contacts.py +0 -348
  69. hh_applicant_tool/telemetry_client.py +0 -106
  70. hh_applicant_tool/types.py +0 -45
  71. hh_applicant_tool/utils.py +0 -119
  72. hh_applicant_tool-0.7.10.dist-info/METADATA +0 -452
  73. hh_applicant_tool-0.7.10.dist-info/RECORD +0 -33
  74. {hh_applicant_tool-0.7.10.dist-info → hh_applicant_tool-1.4.7.dist-info}/WHEEL +0 -0
  75. {hh_applicant_tool-0.7.10.dist-info → hh_applicant_tool-1.4.7.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,220 @@
1
+ from __future__ import annotations
2
+
3
+ import platform
4
+ import socket
5
+ from datetime import datetime, timedelta
6
+ from functools import cache
7
+ from importlib.metadata import version
8
+ from logging import getLogger
9
+ from typing import TYPE_CHECKING, Literal
10
+
11
+ import requests
12
+ from requests.exceptions import RequestException
13
+
14
+ from ..ai.openai import ChatOpenAI
15
+ from . import binpack
16
+ from .log import collect_traceback_logs
17
+
18
+ if TYPE_CHECKING:
19
+ from ..main import HHApplicantTool
20
+
21
+ log = getLogger(__package__)
22
+
23
+
24
+ def parse_version(v: str) -> tuple[int, int, int]:
25
+ return tuple(map(int, v.split(".")))
26
+
27
+
28
+ @cache
29
+ def get_package_version() -> str | None:
30
+ return version("hh-applicant-tool")
31
+
32
+
33
+ class ErrorReporter:
34
+ def build_report(
35
+ self: HHApplicantTool,
36
+ last_report: datetime,
37
+ ) -> dict:
38
+ error_logs = ""
39
+ if self.log_file.exists():
40
+ with self.log_file.open(encoding="utf-8", errors="ignore") as fp:
41
+ error_logs = collect_traceback_logs(
42
+ fp, last_report, maxlen=10000
43
+ )
44
+
45
+ # Эти данные нужны для воспроизведения ошибок. Среди них ваших нет
46
+ contacts = [
47
+ c.to_dict()
48
+ for c in self.storage.employer_contacts.find(
49
+ updated_at__ge=last_report
50
+ )
51
+ ][-10000:]
52
+
53
+ for c in contacts:
54
+ c.pop("id", 0)
55
+
56
+ employers = [
57
+ {
58
+ k: v
59
+ for k, v in emp.to_dict().items()
60
+ if k
61
+ in [
62
+ "id",
63
+ "type",
64
+ "alternate_url",
65
+ "area_id",
66
+ "area_name",
67
+ "name",
68
+ "site_url",
69
+ "created_at",
70
+ ]
71
+ }
72
+ for emp in self.storage.employers.find(updated_at__ge=last_report)
73
+ ][-10000:]
74
+
75
+ vacancies = [
76
+ {
77
+ k: v
78
+ for k, v in vac.to_dict().items()
79
+ if k
80
+ in [
81
+ "id",
82
+ "alternate_url",
83
+ "area_id",
84
+ "area_name",
85
+ "salary_from",
86
+ "salary_to",
87
+ "currency",
88
+ "name",
89
+ "professional_roles",
90
+ "experience",
91
+ "remote",
92
+ "created_at",
93
+ ]
94
+ }
95
+ for vac in self.storage.vacancies.find(updated_at__ge=last_report)
96
+ ][-10000:]
97
+
98
+ # log.info("num vacncies: %d", len(vacancies))
99
+
100
+ system_info = {
101
+ "os": platform.system(),
102
+ "os_release": platform.release(),
103
+ "hostname": socket.gethostname(),
104
+ "python_version": platform.python_version(),
105
+ }
106
+
107
+ return dict(
108
+ error_logs=error_logs,
109
+ contacts=contacts,
110
+ employers=employers,
111
+ vacancies=vacancies,
112
+ package_version=get_package_version(),
113
+ system_info=system_info,
114
+ report_created=datetime.now(),
115
+ )
116
+
117
+ def send_report(self: HHApplicantTool, data: bytes) -> int:
118
+ try:
119
+ r = self.session.post(
120
+ # "http://localhost:8000/report",
121
+ "https://hh-applicant-tool.mooo.com:54157/report",
122
+ data=data,
123
+ timeout=15.0,
124
+ )
125
+ r.raise_for_status()
126
+ return r.status_code == 200
127
+ except RequestException:
128
+ # log.error("Network error: %s", e)
129
+ return False
130
+
131
+ def process_reporting(self):
132
+ # Получаем timestamp последнего репорта
133
+ last_report = datetime.fromtimestamp(
134
+ self.storage.settings.get_value("_last_report", 0)
135
+ )
136
+
137
+ if datetime.now() >= last_report + timedelta(hours=72):
138
+ try:
139
+ report_dict = self.build_report(last_report)
140
+ has_data = any(
141
+ [
142
+ report_dict.get("error_logs"),
143
+ report_dict.get("employers"),
144
+ report_dict.get("contacts"),
145
+ report_dict.get("vacancies"),
146
+ ]
147
+ )
148
+ if has_data:
149
+ data = binpack.serialize(report_dict)
150
+ log.debug("Report body size: %d", len(data))
151
+ # print(binpack.deserialize(data))
152
+ if self.send_report(data):
153
+ log.debug("Report was sent")
154
+ else:
155
+ log.debug("Report failed")
156
+ else:
157
+ log.debug("Nothing to report")
158
+ finally:
159
+ # Сохраняем время последней попытки/удачного репорта
160
+ self.storage.settings.set_value("_last_report", datetime.now())
161
+
162
+
163
+ class VersionChecker:
164
+ def get_latest_version(self: HHApplicantTool) -> Literal[False] | str:
165
+ try:
166
+ response = self.session.get(
167
+ "https://pypi.org/pypi/hh-applicant-tool/json", timeout=15
168
+ )
169
+ ver = response.json().get("info", {}).get("version")
170
+ # log.debug(ver)
171
+ return ver
172
+ except requests.RequestException:
173
+ return False
174
+
175
+ def check_version(self: HHApplicantTool) -> bool:
176
+ if datetime.now().timestamp() >= self.storage.settings.get_value(
177
+ "_next_version_check", 0
178
+ ):
179
+ if v := self.get_latest_version():
180
+ self.storage.settings.set_value("_latest_version", v)
181
+ self.storage.settings.set_value(
182
+ "_next_version_check", datetime.now() + timedelta(hours=1)
183
+ )
184
+
185
+ if (
186
+ latest_ver := self.storage.settings.get_value("_latest_version")
187
+ ) and (cur_ver := get_package_version()):
188
+ if parse_version(latest_ver) > parse_version(cur_ver):
189
+ log.warning(
190
+ "ТЕКУЩАЯ ВЕРСИЯ %s УСТАРЕЛА. РЕКОМЕНДУЕТСЯ ОБНОВИТЬ ЕЁ ДО ВЕРСИИ %s.",
191
+ cur_ver,
192
+ latest_ver,
193
+ )
194
+
195
+
196
+ class ChatOpenAISupport:
197
+ def get_openai_chat(
198
+ self: HHApplicantTool,
199
+ system_prompt: str,
200
+ ) -> ChatOpenAI:
201
+ c = self.config.get("openai", {})
202
+ if not (token := c.get("token")):
203
+ raise ValueError("Токен для OpenAI не задан")
204
+ return ChatOpenAI(
205
+ token=token,
206
+ model=c.get("model", "gpt-5.1"),
207
+ system_prompt=system_prompt,
208
+ session=self.session,
209
+ )
210
+
211
+
212
+ class MegaTool(ErrorReporter, VersionChecker, ChatOpenAISupport):
213
+ def check_system(self: HHApplicantTool):
214
+ if not self.storage.settings.get_value("disable_version_check", False):
215
+ self.check_version()
216
+
217
+ if self.storage.settings.get_value("send_error_reports", True):
218
+ self.process_reporting()
219
+ else:
220
+ log.warning("ОТКЛЮЧЕНА ОТПРАВКА СООБЩЕНИЙ ОБ ОШИБКАХ!")
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ import re
5
+ from typing import Any
6
+
7
+ def shorten(s: str, limit: int = 75, ellipsis: str = "…") -> str:
8
+ return s[:limit] + bool(s[limit:]) * ellipsis
9
+
10
+ def rand_text(s: str) -> str:
11
+ while (
12
+ temp := re.sub(
13
+ r"{([^{}]+)}",
14
+ lambda m: random.choice(
15
+ m.group(1).split("|"),
16
+ ),
17
+ s,
18
+ )
19
+ ) != s:
20
+ s = temp
21
+ return s
22
+
23
+ def bool2str(v: bool) -> str:
24
+ return str(v).lower()
25
+
26
+ def list2str(items: list[Any] | None) -> str:
27
+ return ",".join(f"{v}" for v in items) if items else ""
@@ -0,0 +1,19 @@
1
+ import ctypes
2
+ import platform
3
+
4
+
5
+ def setup_terminal() -> None:
6
+ if platform.system() != "Windows":
7
+ return
8
+ try:
9
+ kernel32 = ctypes.windll.kernel32
10
+ # -11 = STD_OUTPUT_HANDLE
11
+ handle = kernel32.GetStdHandle(-11)
12
+ mode = ctypes.c_uint()
13
+ if kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
14
+ # 0x0004 = ENABLE_VIRTUAL_TERMINAL_PROCESSING
15
+ kernel32.SetConsoleMode(handle, mode.value | 0x0004)
16
+ except Exception:
17
+ # Если что-то пошло не так (старая Windows или нет прав),
18
+ # просто продолжаем работу без цветов
19
+ pass
@@ -0,0 +1,17 @@
1
+ import random
2
+ import uuid
3
+
4
+
5
+ def hh_android_useragent() -> str:
6
+ """Generates Android App User-Agent"""
7
+ devices = (
8
+ "23053RN02A, 23053RN02Y, 23053RN02I, 23053RN02L, 23077RABDC".split(", ")
9
+ )
10
+ device = random.choice(devices)
11
+ minor = random.randint(100, 150)
12
+ patch = random.randint(10000, 15000)
13
+ android = random.randint(11, 15)
14
+ return (
15
+ f"ru.hh.android/7.{minor}.{patch}, Device: {device}, "
16
+ f"Android OS: {android} (UUID: {uuid.uuid4()})"
17
+ )