hh-applicant-tool 0.5.4__py3-none-any.whl → 0.5.6__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.

@@ -117,8 +117,8 @@ class BaseClient:
117
117
  try:
118
118
  rv = response.json()
119
119
  except json.decoder.JSONDecodeError:
120
- if response.status_code not in [201, 204]:
121
- raise
120
+ # if response.status_code not in [201, 204]:
121
+ # raise
122
122
  rv = {}
123
123
  finally:
124
124
  logger.debug(
@@ -0,0 +1,138 @@
1
+ import re
2
+ import enum
3
+ from dataclasses import dataclass
4
+ import ast
5
+ from typing import Any, Iterator
6
+ from collections import OrderedDict
7
+
8
+
9
+ class TokenType(enum.Enum):
10
+ WHITESPACE = r"\s+"
11
+ COMMENT = r"//.*|/\*[\s\S]*?\*/"
12
+ NUMBER = r"-?\d+(?:\.\d+)?"
13
+ STRING = r'"(?:\\"|[^"]+)*"'
14
+ KEYWORD = r"null|true|false"
15
+ OPEN_CURLY = r"\{"
16
+ CLOSE_CURLY = r"\}"
17
+ OPEN_SQUARE = r"\["
18
+ CLOSE_SQUARE = r"\]"
19
+ COLON = r":"
20
+ COMMA = r","
21
+ UNKNOWN = r"."
22
+ EOF = r"$"
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class Token:
27
+ token_type: TokenType
28
+ value: str
29
+
30
+
31
+ def tokenize(s: str) -> Iterator[Token]:
32
+ token_patterns = "|".join(f"(?P<{t.name}>{t.value})" for t in TokenType)
33
+ regex = re.compile(token_patterns, re.MULTILINE)
34
+ for m in regex.finditer(s):
35
+ assert type(m.lastgroup) is str
36
+ yield Token(TokenType[m.lastgroup], m.group())
37
+
38
+
39
+ class JSONCParser:
40
+ def parse(self, s: str) -> Any:
41
+ self.token_it = filter(
42
+ lambda t: t.token_type not in [TokenType.COMMENT, TokenType.WHITESPACE],
43
+ tokenize(s),
44
+ )
45
+ self.next_token = None
46
+ self.advance()
47
+ result = self.parse_value()
48
+ self.expect(TokenType.EOF)
49
+ return result
50
+
51
+ def parse_object(self) -> dict:
52
+ # obj = OrderedDict()
53
+ obj = {}
54
+
55
+ while True:
56
+ self.expect(TokenType.STRING)
57
+ key = ast.literal_eval(self.token.value)
58
+ self.expect(TokenType.COLON)
59
+ value = self.parse_value()
60
+ obj[key] = value
61
+ if not self.match(TokenType.COMMA):
62
+ break
63
+
64
+ self.expect(TokenType.CLOSE_CURLY)
65
+ return obj
66
+
67
+ def parse_array(self) -> list:
68
+ arr = []
69
+
70
+ while True:
71
+ arr.append(self.parse_value())
72
+ if not self.match(TokenType.COMMA):
73
+ break
74
+
75
+ self.expect(TokenType.CLOSE_SQUARE)
76
+ return arr
77
+
78
+ def parse_value(self) -> Any:
79
+ if self.match(TokenType.OPEN_CURLY):
80
+ return self.parse_object()
81
+ elif self.match(TokenType.OPEN_SQUARE):
82
+ return self.parse_array()
83
+ elif self.match(TokenType.STRING):
84
+ return ast.literal_eval(self.token.value)
85
+ elif self.match(TokenType.NUMBER):
86
+ num = self.token.value
87
+ return float(num) if "." in num else int(num)
88
+ elif self.match(TokenType.KEYWORD):
89
+ return {"null": None, "true": True, "false": False}[self.token.value]
90
+ else:
91
+ raise SyntaxError(f"Unexpected token: {self.token.token_type.name}")
92
+
93
+ def advance(self):
94
+ self.token, self.next_token = (
95
+ self.next_token,
96
+ next(self.token_it, Token(TokenType.EOF, "")),
97
+ )
98
+ # print(f"{self.token =}, {self.next_token =}")
99
+
100
+ def match(self, token_type: TokenType) -> bool:
101
+ if self.next_token.token_type == token_type:
102
+ self.advance()
103
+ return True
104
+ return False
105
+
106
+ def expect(self, token_type: TokenType):
107
+ if not self.match(token_type):
108
+ raise SyntaxError(
109
+ f"Expected {token_type.name}, got {self.next_token.token_type.name}"
110
+ )
111
+
112
+
113
+ def parse_jsonc(s: str) -> Any:
114
+ return JSONCParser().parse(s)
115
+
116
+
117
+ if __name__ == "__main__":
118
+ json_str = """\
119
+ {
120
+ // Это комментарий
121
+ "name": "John",
122
+ "age": 30,
123
+ "scores": [95.5, 88, null],
124
+ "metadata": {
125
+ "active": true,
126
+ /* Многострочный
127
+ комментарий */
128
+ "tags": [ "foo", "bar" ]
129
+ }
130
+ }
131
+ """
132
+ try:
133
+ result = parse_jsonc(json_str)
134
+ import pprint
135
+
136
+ pprint.pprint(result)
137
+ except SyntaxError as e:
138
+ print(f"Syntax error: {e}")
hh_applicant_tool/main.py CHANGED
@@ -21,8 +21,7 @@ logger = logging.getLogger(__package__)
21
21
 
22
22
 
23
23
  class BaseOperation:
24
- def setup_parser(self, parser: argparse.ArgumentParser) -> None:
25
- ...
24
+ def setup_parser(self, parser: argparse.ArgumentParser) -> None: ...
26
25
 
27
26
  def run(self, args: argparse.Namespace) -> None | int:
28
27
  raise NotImplementedError()
@@ -99,9 +98,7 @@ class HHApplicantTool:
99
98
  default=0.334,
100
99
  help="Задержка между запросами к API HH",
101
100
  )
102
- parser.add_argument(
103
- "--user-agent", help="User-Agent для каждого запроса"
104
- )
101
+ parser.add_argument("--user-agent", help="User-Agent для каждого запроса")
105
102
  parser.add_argument(
106
103
  "--proxy-url", help="Прокси, используемый для запросов к API"
107
104
  )
@@ -4,12 +4,18 @@ import time
4
4
  from urllib.parse import parse_qs, urlsplit
5
5
  import sys
6
6
  from typing import Any
7
+ from ..utils import print_err
8
+
9
+
10
+ QT_IMPORTED = False
7
11
 
8
12
  try:
9
13
  from PyQt6.QtCore import QUrl
10
14
  from PyQt6.QtWidgets import QApplication, QMainWindow
11
15
  from PyQt6.QtWebEngineCore import QWebEngineUrlSchemeHandler
12
16
  from PyQt6.QtWebEngineWidgets import QWebEngineView
17
+
18
+ QT_IMPORTED = True
13
19
  except ImportError:
14
20
  # Заглушки чтобы на сервере не нужно было ставить сотни мегабайт qt-говна
15
21
 
@@ -48,9 +54,7 @@ class HHAndroidUrlSchemeHandler(QWebEngineUrlSchemeHandler):
48
54
 
49
55
 
50
56
  class WebViewWindow(QMainWindow):
51
- def __init__(
52
- self, url: str, oauth_client: OAuthClient, config: Config
53
- ) -> None:
57
+ def __init__(self, url: str, oauth_client: OAuthClient, config: Config) -> None:
54
58
  super().__init__()
55
59
  self.oauth_client = oauth_client
56
60
  self.config = config
@@ -85,10 +89,14 @@ class Operation(BaseOperation):
85
89
  pass
86
90
 
87
91
  def run(self, args: Namespace) -> None:
92
+ if not QT_IMPORTED:
93
+ print_err(
94
+ "❗Критиническая Ошибка: PyQt6 не был импортирован, возможно, вы долбоеб и забыли его установить, либо же криворукие разрабы этой либы опять все сломали..."
95
+ )
96
+ sys.exit(1)
97
+
88
98
  oauth = OAuthClient(
89
- user_agent=(
90
- args.config["oauth_user_agent"] or args.config["user_agent"]
91
- ),
99
+ user_agent=(args.config["oauth_user_agent"] or args.config["user_agent"]),
92
100
  )
93
101
 
94
102
  app = QApplication(sys.argv)
@@ -14,6 +14,7 @@ from threading import Lock
14
14
  from typing import Any
15
15
 
16
16
  from .constants import INVALID_ISO8601_FORMAT
17
+ from .jsonc import parse_jsonc
17
18
 
18
19
  print_err = partial(print, file=sys.stderr, flush=True)
19
20
 
@@ -45,10 +46,7 @@ class Config(dict):
45
46
  if self._config_path.exists():
46
47
  with self._lock:
47
48
  with self._config_path.open("r", encoding="utf-8", errors="replace") as f:
48
- try:
49
- self.update(json.load(f))
50
- except ValueError:
51
- pass
49
+ self.update(json.load(f))
52
50
 
53
51
  def save(self, *args: Any, **kwargs: Any) -> None:
54
52
  self.update(*args, **kwargs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: hh-applicant-tool
3
- Version: 0.5.4
3
+ Version: 0.5.6
4
4
  Summary:
5
5
  Author: Senior YAML Developer
6
6
  Author-email: yamldeveloper@proton.me
@@ -12,8 +12,8 @@ Classifier: Programming Language :: Python :: 3.12
12
12
  Classifier: Programming Language :: Python :: 3.13
13
13
  Provides-Extra: qt
14
14
  Requires-Dist: prettytable (>=3.6.0,<4.0.0)
15
- Requires-Dist: pyqt6 (>=6.7.1,<7.0.0) ; extra == "qt"
16
- Requires-Dist: pyqt6-webengine (>=6.7.0,<7.0.0) ; extra == "qt"
15
+ Requires-Dist: pyqt6 (==6.7.0) ; extra == "qt"
16
+ Requires-Dist: pyqt6-webengine (==6.7.0) ; extra == "qt"
17
17
  Requires-Dist: requests[socks] (>=2.32.3,<3.0.0)
18
18
  Description-Content-Type: text/markdown
19
19
 
@@ -34,7 +34,7 @@ Description-Content-Type: text/markdown
34
34
 
35
35
  > Утилита для генерации сопроводительного письма может использовать AI
36
36
 
37
- Утилита для успешных волчат и старых волков с опытом, служащая для автоматизации действий на HH.RU таких как рассылка откликов на подходящие вакансии и обновление всех резюме (бесплатный аналог услуги на HH). Но данная утилита больше чем просто спамилка откликами, вы так же выступаете в роли тайного агента, и если в списке подходящих вакансий встречается отказ, она возвращает ссылку на обсуждение работодателя в группе [Отзывы о работодателях с HH.RU](https://t.me/otzyvy_headhunter). Там вы можете написать отзыв о работодателе и почитать чужие. Для этого собираются данные о работодателях и их вакансиях (персональные данные пользователя не передаются ни в каком виде). Отправку данных на сервер разработчика можно отключить, но тогда вы не получите ссылку на обсуждение, а так же не сможете пожаловаться на неадекватного мудака, выкатившего отказ после "небольшого" тестового задания на недельку. Через сайты на таких жаловаться бесполезно: владелец сайта за деньги или после угроз судом удаляют отзывы. Единственное место где можно написать отзыв это **Telegram**.
37
+ Утилита для успешных волчат и старых волков с опытом, служащая для автоматизации действий на HH.RU таких как рассылка откликов на подходящие вакансии и обновление всех резюме (бесплатный аналог услуги на HH). Утилита позволяет получить контактные данные работодателя, даже если тот не присылал приглашения. Для этого собираются данные о работодателях и их вакансиях (персональные данные пользователя не передаются ни в каком виде, а данные работодателя сами по себе персональными данными не являются, так как часто указаны на сайтах и так же доступны неограниченному кругу лиц на hh). Отправку этих данных на сервер разработчика можно отключить. У утилиты есть группа, с которой я еще не придумал, что делать: [Отзывы о работодателях с HH.RU](https://t.me/otzyvy_headhunter). Там сейчас постятся ссылки для отзывов на отказников.
38
38
 
39
39
  Работает с Python >= 3.10. Нужную версию Python можно поставить через
40
40
  asdf/pyenv/conda и что-то еще.
@@ -81,7 +81,7 @@ $ pipx upgrade hh-applicant-tool
81
81
  Данная политика разрешает текущему пользователю (от которого зашли) запускать скрипты. Без нее не будут работать виртуальные окружения.
82
82
  * Создайте и активируйте виртуальное окружение:
83
83
  ```ps
84
- PS> python -m pip venv hh-applicant-venv
84
+ PS> python -m venv hh-applicant-venv
85
85
  PS> .\hh-applicant-venv\Scripts\activate
86
86
  ```
87
87
  * Поставьте все пакеты в виртуальное окружение `hh-applicant-venv`:
@@ -251,7 +251,7 @@ https://hh.ru/employer/1918903
251
251
  | **call-api** | Вызов произвольного метода API с выводом результата. |
252
252
  | **refresh-token** | Обновляет access_token. |
253
253
  | **config** | Редактировать конфигурационный файл. |
254
- | **get-employer-contacts** | Получить список контактов работодателя, даже если тот не высылал приглашения. Это функционал для избранных, но в группе есть бесплатный бот с тем же функционалом. |
254
+ | **get-employer-contacts** | Получить список контактов работодателя, даже если тот не высылал приглашения. В группе есть бесплатный бот с тем же функционалом. |
255
255
 
256
256
  ### Формат текста сообщений
257
257
 
@@ -3,15 +3,16 @@ hh_applicant_tool/__main__.py,sha256=cwKJAAML0RRKT9Qbzcwf07HHcuSd8oh7kx4P1apndWQ
3
3
  hh_applicant_tool/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  hh_applicant_tool/ai/blackbox.py,sha256=vqkEpsX7q7bgX49dmmifYJmrbuz_WBg5u9M9J9XdQlI,1670
5
5
  hh_applicant_tool/api/__init__.py,sha256=kgFSHibRaAugN2BA3U1djEa20qgKJUUVouwJzjEB0DU,84
6
- hh_applicant_tool/api/client.py,sha256=um9NX22hNOtSuPCobCKf1anIFp-jiZlIXm4BuqN-L7k,7997
6
+ hh_applicant_tool/api/client.py,sha256=KtSPJoAYWrTrdgWBIF9aipLqWd1khufLmyL6G4rnBGs,8001
7
7
  hh_applicant_tool/api/errors.py,sha256=0SoWKnsSUA0K2YgQA8GwGhe-pRMwtfK94MR6_MgbORQ,1722
8
8
  hh_applicant_tool/color_log.py,sha256=gN6j1Ayy1G7qOMI_e3WvfYw_ublzeQbKgsVLhqGg_3s,823
9
9
  hh_applicant_tool/constants.py,sha256=lpgKkP2chWgTnBXvzxbSPWpKcfzp8VxMTlkssFcQhH4,469
10
- hh_applicant_tool/main.py,sha256=z_SAW7cV83P5mVEkuddSzETUGLqocsNyEKlA6HBHjQ0,4806
10
+ hh_applicant_tool/jsonc.py,sha256=Vni5ksSgoJBBNApS9EyMzD2jVUxYMFa2q1f8bUPH2OY,3791
11
+ hh_applicant_tool/main.py,sha256=j4kG7H0qxJebGwVs3fjc4udOk7rVvffsB2114UgI9e8,4776
11
12
  hh_applicant_tool/mixins.py,sha256=66LmyYSsDfhrpUwoAONjzrd5aoXqaZVoQ-zXhyYbYMk,418
12
13
  hh_applicant_tool/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
14
  hh_applicant_tool/operations/apply_similar.py,sha256=IYpf8PabOJF6vmbsKYir1NgW0Y2XviBanIGjsbz9AD4,16649
14
- hh_applicant_tool/operations/authorize.py,sha256=TyUTCSOGwSYVJMEd5vSI981LRRI-RZf8hnlVYhtRVwA,3184
15
+ hh_applicant_tool/operations/authorize.py,sha256=e0V3uo9daCa4zZDb4vxoiQqtmSIRV04wzGSqCN280q0,3599
15
16
  hh_applicant_tool/operations/call_api.py,sha256=qwPDrWP9tiqHkaYYWYBZtymj9AaxObB86Eny-Bf5q_c,1314
16
17
  hh_applicant_tool/operations/clear_negotiations.py,sha256=GEsL1snxz8TJ3i7Nz6LkP4FZOzRBdAofX3xf3Ywj64k,4369
17
18
  hh_applicant_tool/operations/config.py,sha256=PJpPPDJaQwqyZ_T55Mlf0oAoeNeMUG_yJoz605XZGxI,959
@@ -23,8 +24,8 @@ hh_applicant_tool/operations/update_resumes.py,sha256=gGxMYMoT9GqJjwn4AgrOAEJCZu
23
24
  hh_applicant_tool/operations/whoami.py,sha256=sg0r7m6oKkpMEmGt4QdtYdS-1gf-1KKdnk32yblbRJs,714
24
25
  hh_applicant_tool/telemetry_client.py,sha256=wYLbKnx3sOmESFHqjLt-0Gww1O3lJiXFYdWnsorIhK8,3261
25
26
  hh_applicant_tool/types.py,sha256=q3yaIcq-UOkPzjxws0OFa4w9fTty-yx79_dic70_dUM,843
26
- hh_applicant_tool/utils.py,sha256=pKmyG55HGmOvOXtHK0x9JBdEfm-hd1b8XroG9tEPSPQ,3146
27
- hh_applicant_tool-0.5.4.dist-info/METADATA,sha256=d8j1CSX0o5ezl2xq1J-jd5T-ckWLgsgMAd9W2aD8oGM,20687
28
- hh_applicant_tool-0.5.4.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
29
- hh_applicant_tool-0.5.4.dist-info/entry_points.txt,sha256=Vb7M2YaYLMtKYJZh8chIrXZApMzSRFT1-rQw-U9r10g,65
30
- hh_applicant_tool-0.5.4.dist-info/RECORD,,
27
+ hh_applicant_tool/utils.py,sha256=3T4A2AykGqTwtGAttmYplIjHwFl3pNAcbWIVuA-OheQ,3080
28
+ hh_applicant_tool-0.5.6.dist-info/METADATA,sha256=IMtOrcHN6AVVH177zmoM41a36R4PPmdR-3vKgzi_OJ4,20166
29
+ hh_applicant_tool-0.5.6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
30
+ hh_applicant_tool-0.5.6.dist-info/entry_points.txt,sha256=Vb7M2YaYLMtKYJZh8chIrXZApMzSRFT1-rQw-U9r10g,65
31
+ hh_applicant_tool-0.5.6.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.1
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any