hh-applicant-tool 0.5.5__py3-none-any.whl → 0.5.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.
Potentially problematic release.
This version of hh-applicant-tool might be problematic. Click here for more details.
- hh_applicant_tool/api/client.py +4 -22
- hh_applicant_tool/jsonc.py +138 -0
- hh_applicant_tool/utils.py +2 -4
- {hh_applicant_tool-0.5.5.dist-info → hh_applicant_tool-0.5.7.dist-info}/METADATA +16 -1
- {hh_applicant_tool-0.5.5.dist-info → hh_applicant_tool-0.5.7.dist-info}/RECORD +7 -6
- {hh_applicant_tool-0.5.5.dist-info → hh_applicant_tool-0.5.7.dist-info}/WHEEL +0 -0
- {hh_applicant_tool-0.5.5.dist-info → hh_applicant_tool-0.5.7.dist-info}/entry_points.txt +0 -0
hh_applicant_tool/api/client.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import dataclasses
|
|
4
4
|
import json
|
|
5
5
|
import logging
|
|
6
|
+
import uuid
|
|
6
7
|
import time
|
|
7
8
|
from dataclasses import dataclass
|
|
8
9
|
from functools import partialmethod
|
|
@@ -12,12 +13,10 @@ from urllib.parse import urlencode
|
|
|
12
13
|
|
|
13
14
|
import requests
|
|
14
15
|
from requests import Response, Session
|
|
15
|
-
import random
|
|
16
16
|
|
|
17
17
|
from ..constants import (
|
|
18
18
|
ANDROID_CLIENT_ID,
|
|
19
19
|
ANDROID_CLIENT_SECRET,
|
|
20
|
-
USER_AGENT_TEMPLATE,
|
|
21
20
|
)
|
|
22
21
|
from ..types import AccessToken
|
|
23
22
|
from . import errors
|
|
@@ -55,24 +54,7 @@ class BaseClient:
|
|
|
55
54
|
logger.debug("Default Headers: %r", session.headers)
|
|
56
55
|
|
|
57
56
|
def default_user_agent(self) -> str:
|
|
58
|
-
return
|
|
59
|
-
random.choice(["8.0", "8.1", "9", "10", "11", "12"]),
|
|
60
|
-
random.choice(
|
|
61
|
-
[
|
|
62
|
-
"SM-G998B", # Samsung Galaxy S21 Ultra
|
|
63
|
-
"Pixel 6", # Google Pixel 6
|
|
64
|
-
"Mi 11", # Xiaomi Mi 11
|
|
65
|
-
"OnePlus 9", # OnePlus 9
|
|
66
|
-
"P40", # Huawei P40
|
|
67
|
-
"LG G8", # LG G8
|
|
68
|
-
"Xperia 1 II", # Sony Xperia 1 II
|
|
69
|
-
"Moto G Power", # Motorola Moto G Power
|
|
70
|
-
"HTC U12+", # HTC U12+
|
|
71
|
-
"ROG Phone 5", # Asus ROG Phone 5
|
|
72
|
-
]
|
|
73
|
-
),
|
|
74
|
-
random.randint(88, 130),
|
|
75
|
-
)
|
|
57
|
+
return f"ru.hh.android/7.122.11395, Device: 23053RN02Y, Android OS: 13 (UUID: {uuid.uuid4()})"
|
|
76
58
|
|
|
77
59
|
def additional_headers(
|
|
78
60
|
self,
|
|
@@ -117,8 +99,8 @@ class BaseClient:
|
|
|
117
99
|
try:
|
|
118
100
|
rv = response.json()
|
|
119
101
|
except json.decoder.JSONDecodeError:
|
|
120
|
-
if response.status_code not in [201, 204]:
|
|
121
|
-
|
|
102
|
+
# if response.status_code not in [201, 204]:
|
|
103
|
+
# raise
|
|
122
104
|
rv = {}
|
|
123
105
|
finally:
|
|
124
106
|
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/utils.py
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
3
|
+
Version: 0.5.7
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Senior YAML Developer
|
|
6
6
|
Author-email: yamldeveloper@proton.me
|
|
@@ -30,6 +30,21 @@ 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**.
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
Что может приложение?
|
|
40
|
+
|
|
41
|
+
- Рассылать отклики с сопроводительным.
|
|
42
|
+
- Автоматически поднимать резюме.
|
|
43
|
+
- Все это работает в фоне, те запустили, свернули и забыли до перезагрузки...
|
|
44
|
+
|
|
45
|
+
[Скачать его можно тут](https://t.me/hh_resume_automate).
|
|
46
|
+
|
|
47
|
+
|
|
33
48
|
### Описание
|
|
34
49
|
|
|
35
50
|
> Утилита для генерации сопроводительного письма может использовать AI
|
|
@@ -3,10 +3,11 @@ 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=
|
|
6
|
+
hh_applicant_tool/api/client.py,sha256=S6T2_QenuM3VsjzPpFuUduQP8ReMMSGynWZUmewlQ5s,7353
|
|
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/jsonc.py,sha256=Vni5ksSgoJBBNApS9EyMzD2jVUxYMFa2q1f8bUPH2OY,3791
|
|
10
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
|
|
@@ -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=
|
|
27
|
-
hh_applicant_tool-0.5.
|
|
28
|
-
hh_applicant_tool-0.5.
|
|
29
|
-
hh_applicant_tool-0.5.
|
|
30
|
-
hh_applicant_tool-0.5.
|
|
27
|
+
hh_applicant_tool/utils.py,sha256=3T4A2AykGqTwtGAttmYplIjHwFl3pNAcbWIVuA-OheQ,3080
|
|
28
|
+
hh_applicant_tool-0.5.7.dist-info/METADATA,sha256=4KSWg3IUcn4Pz64j35b91EtuNxnoGi_57KVqZTbLHQw,20931
|
|
29
|
+
hh_applicant_tool-0.5.7.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
30
|
+
hh_applicant_tool-0.5.7.dist-info/entry_points.txt,sha256=Vb7M2YaYLMtKYJZh8chIrXZApMzSRFT1-rQw-U9r10g,65
|
|
31
|
+
hh_applicant_tool-0.5.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|