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.

@@ -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 USER_AGENT_TEMPLATE % (
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
- raise
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}")
@@ -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.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
+ ![image](https://github.com/user-attachments/assets/4dde1926-b2ba-439f-9d00-6aabc92744a0)
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=um9NX22hNOtSuPCobCKf1anIFp-jiZlIXm4BuqN-L7k,7997
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=pKmyG55HGmOvOXtHK0x9JBdEfm-hd1b8XroG9tEPSPQ,3146
27
- hh_applicant_tool-0.5.5.dist-info/METADATA,sha256=Pm-g4v1ZQtgwRSEzWxQJnjRo9gE77vioW4IvxlRcEtI,20166
28
- hh_applicant_tool-0.5.5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
29
- hh_applicant_tool-0.5.5.dist-info/entry_points.txt,sha256=Vb7M2YaYLMtKYJZh8chIrXZApMzSRFT1-rQw-U9r10g,65
30
- hh_applicant_tool-0.5.5.dist-info/RECORD,,
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,,