hh-applicant-tool 0.6.12__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.
- hh_applicant_tool/__init__.py +1 -0
- hh_applicant_tool/__main__.py +1 -1
- hh_applicant_tool/ai/base.py +2 -0
- hh_applicant_tool/ai/openai.py +24 -30
- hh_applicant_tool/api/client.py +82 -98
- hh_applicant_tool/api/errors.py +57 -8
- hh_applicant_tool/constants.py +0 -3
- hh_applicant_tool/datatypes.py +291 -0
- hh_applicant_tool/main.py +236 -82
- hh_applicant_tool/operations/apply_similar.py +268 -348
- hh_applicant_tool/operations/authorize.py +245 -70
- hh_applicant_tool/operations/call_api.py +18 -8
- hh_applicant_tool/operations/check_negotiations.py +102 -0
- hh_applicant_tool/operations/check_proxy.py +30 -0
- hh_applicant_tool/operations/config.py +119 -18
- hh_applicant_tool/operations/install.py +34 -0
- hh_applicant_tool/operations/list_resumes.py +24 -10
- hh_applicant_tool/operations/log.py +77 -0
- hh_applicant_tool/operations/migrate_db.py +65 -0
- hh_applicant_tool/operations/query.py +120 -0
- hh_applicant_tool/operations/refresh_token.py +14 -13
- hh_applicant_tool/operations/reply_employers.py +148 -167
- hh_applicant_tool/operations/settings.py +95 -0
- hh_applicant_tool/operations/uninstall.py +26 -0
- hh_applicant_tool/operations/update_resumes.py +21 -10
- hh_applicant_tool/operations/whoami.py +40 -7
- hh_applicant_tool/storage/__init__.py +4 -0
- hh_applicant_tool/storage/facade.py +24 -0
- hh_applicant_tool/storage/models/__init__.py +0 -0
- hh_applicant_tool/storage/models/base.py +169 -0
- hh_applicant_tool/storage/models/contact.py +16 -0
- hh_applicant_tool/storage/models/employer.py +12 -0
- hh_applicant_tool/storage/models/negotiation.py +16 -0
- hh_applicant_tool/storage/models/resume.py +19 -0
- hh_applicant_tool/storage/models/setting.py +6 -0
- hh_applicant_tool/storage/models/vacancy.py +36 -0
- hh_applicant_tool/storage/queries/migrations/.gitkeep +0 -0
- hh_applicant_tool/storage/queries/schema.sql +119 -0
- hh_applicant_tool/storage/repositories/__init__.py +0 -0
- hh_applicant_tool/storage/repositories/base.py +176 -0
- hh_applicant_tool/storage/repositories/contacts.py +19 -0
- hh_applicant_tool/storage/repositories/employers.py +13 -0
- hh_applicant_tool/storage/repositories/negotiations.py +12 -0
- hh_applicant_tool/storage/repositories/resumes.py +14 -0
- hh_applicant_tool/storage/repositories/settings.py +34 -0
- hh_applicant_tool/storage/repositories/vacancies.py +8 -0
- hh_applicant_tool/storage/utils.py +49 -0
- hh_applicant_tool/utils/__init__.py +31 -0
- hh_applicant_tool/utils/attrdict.py +6 -0
- hh_applicant_tool/utils/binpack.py +167 -0
- hh_applicant_tool/utils/config.py +55 -0
- hh_applicant_tool/utils/dateutil.py +19 -0
- hh_applicant_tool/{jsonc.py → utils/jsonc.py} +12 -6
- hh_applicant_tool/utils/jsonutil.py +61 -0
- hh_applicant_tool/utils/log.py +144 -0
- hh_applicant_tool/utils/misc.py +12 -0
- hh_applicant_tool/utils/mixins.py +220 -0
- hh_applicant_tool/utils/string.py +27 -0
- hh_applicant_tool/utils/terminal.py +19 -0
- hh_applicant_tool/utils/user_agent.py +17 -0
- hh_applicant_tool-1.4.7.dist-info/METADATA +628 -0
- hh_applicant_tool-1.4.7.dist-info/RECORD +67 -0
- hh_applicant_tool/ai/blackbox.py +0 -55
- hh_applicant_tool/color_log.py +0 -35
- hh_applicant_tool/mixins.py +0 -13
- hh_applicant_tool/operations/clear_negotiations.py +0 -113
- hh_applicant_tool/operations/delete_telemetry.py +0 -30
- hh_applicant_tool/operations/get_employer_contacts.py +0 -293
- hh_applicant_tool/telemetry_client.py +0 -106
- hh_applicant_tool/types.py +0 -45
- hh_applicant_tool/utils.py +0 -104
- hh_applicant_tool-0.6.12.dist-info/METADATA +0 -349
- hh_applicant_tool-0.6.12.dist-info/RECORD +0 -33
- {hh_applicant_tool-0.6.12.dist-info → hh_applicant_tool-1.4.7.dist-info}/WHEEL +0 -0
- {hh_applicant_tool-0.6.12.dist-info → hh_applicant_tool-1.4.7.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
hh_applicant_tool/__init__.py,sha256=ic_GIg9QtlrtgbXEDdhFg3J52VJRRu_OoEsFoRmZ_wk,34
|
|
2
|
+
hh_applicant_tool/__main__.py,sha256=o6kDuEJML3MnZW3HUKRBjaMeTjYjBQAwqRkavfwQSQ8,92
|
|
3
|
+
hh_applicant_tool/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
hh_applicant_tool/ai/base.py,sha256=wrccJEO5FNfW6LQTj-pLZAu0VCcs8QXdzf74erBuTew,35
|
|
5
|
+
hh_applicant_tool/ai/openai.py,sha256=R82CiGxklm5WxLVUNpXzdOVrKVRI5uT7Lzd9puT-FqY,1655
|
|
6
|
+
hh_applicant_tool/api/__init__.py,sha256=kgFSHibRaAugN2BA3U1djEa20qgKJUUVouwJzjEB0DU,84
|
|
7
|
+
hh_applicant_tool/api/client.py,sha256=iy4W2ubaDMxkcWqjRU6cH7V2PcSxAy5IYKU-dStEra0,9392
|
|
8
|
+
hh_applicant_tool/api/errors.py,sha256=u6ogOo0QijQ-UCi6aApeR2v4bqh1neSQ-184owiPWiI,3399
|
|
9
|
+
hh_applicant_tool/constants.py,sha256=OkVtQXRIyrZvgJ0N7Oxe-L5BSPyJL5DhyUU7exAHIuI,622
|
|
10
|
+
hh_applicant_tool/datatypes.py,sha256=YAeqY8khKolBFUt-bNuUXQlmpxkkEGLef5f07QtTSuM,5939
|
|
11
|
+
hh_applicant_tool/main.py,sha256=b3CKwewS5RduUGf7PLG2sJDXjTzVUIoj0x52Gm78Mac,9999
|
|
12
|
+
hh_applicant_tool/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
hh_applicant_tool/operations/apply_similar.py,sha256=nE5GKg4g5uOoV8FE7KT9ctGf_MUTg5yr1M_pBvmPxgM,20030
|
|
14
|
+
hh_applicant_tool/operations/authorize.py,sha256=rg3ks71_LuQrfFoIFEb03bHAp29q00sT5NcGwzuuAYs,10728
|
|
15
|
+
hh_applicant_tool/operations/call_api.py,sha256=9bnyakrQ3f5DiAL7Qr6bhkVqQeFFSloslSFNPkj0CGU,1519
|
|
16
|
+
hh_applicant_tool/operations/check_negotiations.py,sha256=LHrgVCfJF4My-l1YUBDTV_c-0kDEem5HylvkZBq-41c,3629
|
|
17
|
+
hh_applicant_tool/operations/check_proxy.py,sha256=HUbDN_CacFEQLGq8B2SBZ_F7-appdMeIrblFrxvt5MI,777
|
|
18
|
+
hh_applicant_tool/operations/config.py,sha256=eNQik_sBobrNMBB3tZ2tuCveCqH-XCub26WInd6GYT0,4725
|
|
19
|
+
hh_applicant_tool/operations/install.py,sha256=e626LnUB5OVsZ1M6cMXfW94hSlN9BDwPfKYcXMtPEgc,794
|
|
20
|
+
hh_applicant_tool/operations/list_resumes.py,sha256=OU5s197LGAGvcvNTaVGxU-TAhiu0hkC3RCGoN5sdJ5Y,1330
|
|
21
|
+
hh_applicant_tool/operations/log.py,sha256=VwnKYDYOOMJKZPGe3X2auo89rAIdbR_7KzXzbt4ve7Y,2542
|
|
22
|
+
hh_applicant_tool/operations/migrate_db.py,sha256=CMWpkhnvJvyGb4QSrtfxrWfEehmHyZ_flevMMgobkfI,2073
|
|
23
|
+
hh_applicant_tool/operations/query.py,sha256=sJz6yenn3eCs2U3clhe9ipC03SniGeer_lIf5ZN8qZg,3660
|
|
24
|
+
hh_applicant_tool/operations/refresh_token.py,sha256=K-L8GMC9azu3tngdVsb8zub9SmzKpuCjbCYAwNgp6Uo,640
|
|
25
|
+
hh_applicant_tool/operations/reply_employers.py,sha256=h0-8d62fRG71DiDkpNDFAiBfTeU4t39B7Zwq4zxl3SE,11979
|
|
26
|
+
hh_applicant_tool/operations/settings.py,sha256=HYxx81vIVbyXOf--tMpA3BrrHjqWNx8F8YDByaWTnwc,2924
|
|
27
|
+
hh_applicant_tool/operations/uninstall.py,sha256=IfFTCzvMT1FfoVj-yvIXwa-UaxV5ai8mmRs9hVDiWa4,626
|
|
28
|
+
hh_applicant_tool/operations/update_resumes.py,sha256=GqNZ5yDIAVW4C8QfzxTliF9CIY-PPDccMcZ2pajDCag,1177
|
|
29
|
+
hh_applicant_tool/operations/whoami.py,sha256=_kkz8pFRF9ChsoxqVyJ4dokABu-ELeld9RSLwoxPdlA,1687
|
|
30
|
+
hh_applicant_tool/storage/__init__.py,sha256=ggelL-_xA2foBurkYP6OfgXBl-9uhfGX1PXPnZxlPSc,153
|
|
31
|
+
hh_applicant_tool/storage/facade.py,sha256=j-GurkPFA5megmGXosWShR6GwmQsiyo5QQdEu1_VRKs,910
|
|
32
|
+
hh_applicant_tool/storage/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
|
+
hh_applicant_tool/storage/models/base.py,sha256=iO9ZpMYpRHNOFsDtsgqDPJFZ56wt7WXVMlQ_Qr4iBOA,5511
|
|
34
|
+
hh_applicant_tool/storage/models/contact.py,sha256=4zfriRYyWLBL0Suiams7Qn5RxuJPPjywEc6ICFdTrIQ,502
|
|
35
|
+
hh_applicant_tool/storage/models/employer.py,sha256=QH9F-SIUWweeHZzWoptgIdlFQy4wxqakLmpVs7Fg9nI,344
|
|
36
|
+
hh_applicant_tool/storage/models/negotiation.py,sha256=HSSXD2YMvaVkNqHBLmFxVcpbpOpQzaCPraL-7j7m7Fk,454
|
|
37
|
+
hh_applicant_tool/storage/models/resume.py,sha256=R3FcVhlUvG-5K23OvLtOWbf0zGQLx7Nods25L6xq-1c,548
|
|
38
|
+
hh_applicant_tool/storage/models/setting.py,sha256=DIRvW1IX-1E3Af-JJY8RZwxn-E39OSHHwNDk-N4Yh-4,123
|
|
39
|
+
hh_applicant_tool/storage/models/vacancy.py,sha256=97OkoCh6kvjRuQoKYRGAHJE9L1ncELL427L-TXcnqbA,1097
|
|
40
|
+
hh_applicant_tool/storage/queries/migrations/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
+
hh_applicant_tool/storage/queries/schema.sql,sha256=zOikp5dpqUk8QGqcNolA51yPhaL-3uRUM3pmy6SfrSs,3893
|
|
42
|
+
hh_applicant_tool/storage/repositories/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
|
+
hh_applicant_tool/storage/repositories/base.py,sha256=aSHJZKCi-MB1LZKZKZXmPvikPi31z5FTBoSozFPEkfk,5734
|
|
44
|
+
hh_applicant_tool/storage/repositories/contacts.py,sha256=0ijj_wpyaHUQtJTlts5ar8SX-fe7ZGywa0y788zYZgs,461
|
|
45
|
+
hh_applicant_tool/storage/repositories/employers.py,sha256=WWN6K-Qk7_PJfSUl8BOvNmbyO4Xjy2jaagCmvdnbAzY,309
|
|
46
|
+
hh_applicant_tool/storage/repositories/negotiations.py,sha256=IcTGXx0FK1FeGC49WBfvKOFwARSD6OpBTn_l0iZTKU4,259
|
|
47
|
+
hh_applicant_tool/storage/repositories/resumes.py,sha256=jYJ5O5FVyR8F-T6SXvgmfFEw5k0d9K-UjaPSHCRChHc,354
|
|
48
|
+
hh_applicant_tool/storage/repositories/settings.py,sha256=-amBuLNVQDjdqcCGbyWrbQmON_XdettkEDKJW0OwQYk,808
|
|
49
|
+
hh_applicant_tool/storage/repositories/vacancies.py,sha256=ykrn204H5hkbXVo_Ga-9Ry9BJ-b6EKHm-nht713B3p0,181
|
|
50
|
+
hh_applicant_tool/storage/utils.py,sha256=tjQDlsB1p7_mvXAipuxddWKpjV-V5G1YGCTu-52OXB4,1610
|
|
51
|
+
hh_applicant_tool/utils/__init__.py,sha256=OD_dtkJUt81bGKobilsxs1BBwxqfmAu-Ga3PNjaWWRQ,744
|
|
52
|
+
hh_applicant_tool/utils/attrdict.py,sha256=wlbow24rovk9kZ0lc0xA4ULlgZ-UWj4GUAmZhAbW9Wg,155
|
|
53
|
+
hh_applicant_tool/utils/binpack.py,sha256=qW55IPleip_wi1GyNBhPwCYP3GGQzi8BASGKD3sOIw8,4658
|
|
54
|
+
hh_applicant_tool/utils/config.py,sha256=cZUVt5F15BbY47X4fAvOLtpBUp9hxwzEMOG30jS30Ro,1600
|
|
55
|
+
hh_applicant_tool/utils/dateutil.py,sha256=foPghTLlgThL-oiMu_LedaG8KS5IMfjGo1wsrAanhPM,456
|
|
56
|
+
hh_applicant_tool/utils/jsonc.py,sha256=WATp8MeKglEI6MRdXWBmqvW3q8IRNpt7UPjAKIf5VBs,4072
|
|
57
|
+
hh_applicant_tool/utils/jsonutil.py,sha256=uW5Ajdv_IYCfVRHJiUVjQnb5nI7XRMva-s3QcVWruvY,1701
|
|
58
|
+
hh_applicant_tool/utils/log.py,sha256=-RtmzoSY9TD4JC2tG2k01x-nJRuYg2eYFXSIH5GGGCA,4084
|
|
59
|
+
hh_applicant_tool/utils/misc.py,sha256=BW0oUEozsMS8WBBIH6y-lUC_MBkuwhO2zbL0nM_Fi50,238
|
|
60
|
+
hh_applicant_tool/utils/mixins.py,sha256=kCMO4oehmWXkrmz8ogJUpUQ1Mk5W-eBNz75pmWoZaas,7282
|
|
61
|
+
hh_applicant_tool/utils/string.py,sha256=dQn1uhwYXesjRmqkDn5HAHOdeaGF2v1HXpT1Quff6Eg,618
|
|
62
|
+
hh_applicant_tool/utils/terminal.py,sha256=pn5aKU3H5t4RXepdNynM-QX8imgxKsriM0EL7AnZ7oM,676
|
|
63
|
+
hh_applicant_tool/utils/user_agent.py,sha256=4Qmql_E9wxhWfZ_ohmr1cqi2bzgYvbKvcsOXrB1KBL8,498
|
|
64
|
+
hh_applicant_tool-1.4.7.dist-info/METADATA,sha256=5ARmoKG8tGX_YpfJiZjTCsIjnVWFbq0MMr9r7JV3Vdk,43268
|
|
65
|
+
hh_applicant_tool-1.4.7.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
66
|
+
hh_applicant_tool-1.4.7.dist-info/entry_points.txt,sha256=Vb7M2YaYLMtKYJZh8chIrXZApMzSRFT1-rQw-U9r10g,65
|
|
67
|
+
hh_applicant_tool-1.4.7.dist-info/RECORD,,
|
hh_applicant_tool/ai/blackbox.py
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from copy import deepcopy
|
|
3
|
-
|
|
4
|
-
import requests
|
|
5
|
-
|
|
6
|
-
logger = logging.getLogger(__package__)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class BlackboxError(Exception):
|
|
10
|
-
pass
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class BlackboxChat:
|
|
14
|
-
chat_endpoint: str = "https://www.blackbox.ai/api/chat"
|
|
15
|
-
|
|
16
|
-
def __init__(
|
|
17
|
-
self,
|
|
18
|
-
session_id: str,
|
|
19
|
-
chat_payload: dict,
|
|
20
|
-
proxies: dict[str, str] = {},
|
|
21
|
-
session: requests.Session | None = None,
|
|
22
|
-
):
|
|
23
|
-
self.session_id = session_id
|
|
24
|
-
self.chat_payload = chat_payload
|
|
25
|
-
self.proxies = proxies
|
|
26
|
-
self.session = session or requests.session()
|
|
27
|
-
|
|
28
|
-
def default_headers(self) -> dict[str, str]:
|
|
29
|
-
return {
|
|
30
|
-
"Accept": "*/*",
|
|
31
|
-
"Accept-Language": "en-US,en;q=0.9,ru;q=0.8",
|
|
32
|
-
"Content-Type": "application/json",
|
|
33
|
-
"Origin": "https://www.blackbox.ai",
|
|
34
|
-
"Priority": "u=0",
|
|
35
|
-
"Referer": "https://www.blackbox.ai/",
|
|
36
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
def send_message(self, message: str) -> str:
|
|
40
|
-
payload = deepcopy(self.chat_payload)
|
|
41
|
-
payload["messages"].append(
|
|
42
|
-
{**payload["messages"][0], "content": message}
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
try:
|
|
46
|
-
response = self.session.post(
|
|
47
|
-
self.chat_endpoint,
|
|
48
|
-
json=payload,
|
|
49
|
-
cookies={"sessionId": self.session_id},
|
|
50
|
-
headers=self.default_headers(),
|
|
51
|
-
proxies=self.proxies,
|
|
52
|
-
)
|
|
53
|
-
return response.text
|
|
54
|
-
except requests.exceptions.RequestException as ex:
|
|
55
|
-
raise BlackboxError(str(ex)) from ex
|
hh_applicant_tool/color_log.py
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import enum
|
|
2
|
-
import logging
|
|
3
|
-
from enum import auto
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Color(enum.Enum):
|
|
7
|
-
BLACK = 30
|
|
8
|
-
RED = auto()
|
|
9
|
-
GREEN = auto()
|
|
10
|
-
YELLOW = auto()
|
|
11
|
-
BLUE = auto()
|
|
12
|
-
PURPLE = auto()
|
|
13
|
-
CYAN = auto()
|
|
14
|
-
WHITE = auto()
|
|
15
|
-
|
|
16
|
-
def __str__(self) -> str:
|
|
17
|
-
return str(self.value)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class ColorHandler(logging.StreamHandler):
|
|
21
|
-
_color_map = {
|
|
22
|
-
"CRITICAL": Color.RED,
|
|
23
|
-
"ERROR": Color.RED,
|
|
24
|
-
"WARNING": Color.RED,
|
|
25
|
-
"INFO": Color.GREEN,
|
|
26
|
-
"DEBUG": Color.BLUE,
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
def format(self, record: logging.LogRecord) -> str:
|
|
30
|
-
message = super().format(record)
|
|
31
|
-
isatty = getattr(self.stream, "isatty", None)
|
|
32
|
-
if isatty and isatty():
|
|
33
|
-
color_code = self._color_map[record.levelname]
|
|
34
|
-
return f"\033[{color_code}m{message}\033[0m"
|
|
35
|
-
return message
|
hh_applicant_tool/mixins.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from .api import ApiError
|
|
2
|
-
from .types import ApiListResponse
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class GetResumeIdMixin:
|
|
6
|
-
def _get_resume_id(self) -> str:
|
|
7
|
-
try:
|
|
8
|
-
resumes: ApiListResponse = self.api_client.get("/resumes/mine")
|
|
9
|
-
return resumes["items"][0]["id"]
|
|
10
|
-
except (ApiError, KeyError, IndexError) as ex:
|
|
11
|
-
raise Exception("Не могу получить идентификатор резюме") from ex
|
|
12
|
-
|
|
13
|
-
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
# Этот модуль можно использовать как образец для других
|
|
2
|
-
import argparse
|
|
3
|
-
import logging
|
|
4
|
-
from datetime import datetime, timedelta, timezone
|
|
5
|
-
|
|
6
|
-
from ..api import ApiClient, ClientError
|
|
7
|
-
from ..constants import INVALID_ISO8601_FORMAT
|
|
8
|
-
from ..main import BaseOperation
|
|
9
|
-
from ..main import Namespace as BaseNamespace
|
|
10
|
-
from ..types import ApiListResponse
|
|
11
|
-
from ..utils import print_err, truncate_string
|
|
12
|
-
|
|
13
|
-
logger = logging.getLogger(__package__)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class Namespace(BaseNamespace):
|
|
17
|
-
older_than: int
|
|
18
|
-
blacklist_discard: bool
|
|
19
|
-
all: bool
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class Operation(BaseOperation):
|
|
23
|
-
"""Отменяет старые отклики, скрывает отказы с опциональной блокировкой работодателя."""
|
|
24
|
-
|
|
25
|
-
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
26
|
-
parser.add_argument(
|
|
27
|
-
"--older-than",
|
|
28
|
-
type=int,
|
|
29
|
-
default=30,
|
|
30
|
-
help="Удалить отклики старше опр. кол-ва дней. По умолчанию: %(default)d",
|
|
31
|
-
)
|
|
32
|
-
parser.add_argument(
|
|
33
|
-
"--all",
|
|
34
|
-
type=bool,
|
|
35
|
-
default=False,
|
|
36
|
-
action=argparse.BooleanOptionalAction,
|
|
37
|
-
help="Удалить все отклики в тч с приглашениями",
|
|
38
|
-
)
|
|
39
|
-
parser.add_argument(
|
|
40
|
-
"--blacklist-discard",
|
|
41
|
-
help="Если установлен, то заблокирует работодателя в случае отказа, чтобы его вакансии не отображались в возможных",
|
|
42
|
-
type=bool,
|
|
43
|
-
default=False,
|
|
44
|
-
action=argparse.BooleanOptionalAction,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
def _get_active_negotiations(self, api_client: ApiClient) -> list[dict]:
|
|
48
|
-
rv = []
|
|
49
|
-
page = 0
|
|
50
|
-
per_page = 100
|
|
51
|
-
while True:
|
|
52
|
-
r: ApiListResponse = api_client.get(
|
|
53
|
-
"/negotiations", page=page, per_page=per_page, status="active"
|
|
54
|
-
)
|
|
55
|
-
rv.extend(r["items"])
|
|
56
|
-
page += 1
|
|
57
|
-
if page >= r["pages"]:
|
|
58
|
-
break
|
|
59
|
-
return rv
|
|
60
|
-
|
|
61
|
-
def run(self, args: Namespace, api_client: ApiClient, *_) -> None:
|
|
62
|
-
negotiations = self._get_active_negotiations(api_client)
|
|
63
|
-
print("Всего активных:", len(negotiations))
|
|
64
|
-
for item in negotiations:
|
|
65
|
-
state = item["state"]
|
|
66
|
-
# messaging_status archived
|
|
67
|
-
# decline_allowed False
|
|
68
|
-
# hidden True
|
|
69
|
-
is_discard = state["id"] == "discard"
|
|
70
|
-
if not item["hidden"] and (
|
|
71
|
-
args.all
|
|
72
|
-
or is_discard
|
|
73
|
-
or (
|
|
74
|
-
state["id"] == "response"
|
|
75
|
-
and (datetime.utcnow() - timedelta(days=args.older_than)).replace(
|
|
76
|
-
tzinfo=timezone.utc
|
|
77
|
-
)
|
|
78
|
-
> datetime.strptime(item["updated_at"], INVALID_ISO8601_FORMAT)
|
|
79
|
-
)
|
|
80
|
-
):
|
|
81
|
-
decline_allowed = item.get("decline_allowed") or False
|
|
82
|
-
r = api_client.delete(
|
|
83
|
-
f"/negotiations/active/{item['id']}",
|
|
84
|
-
with_decline_message=decline_allowed,
|
|
85
|
-
)
|
|
86
|
-
assert {} == r
|
|
87
|
-
vacancy = item["vacancy"]
|
|
88
|
-
print(
|
|
89
|
-
"❌ Удалили",
|
|
90
|
-
state["name"].lower(),
|
|
91
|
-
vacancy["alternate_url"],
|
|
92
|
-
"(",
|
|
93
|
-
truncate_string(vacancy["name"]),
|
|
94
|
-
")",
|
|
95
|
-
)
|
|
96
|
-
if is_discard and args.blacklist_discard:
|
|
97
|
-
employer = vacancy.get("employer", {})
|
|
98
|
-
if not employer or 'id' not in employer:
|
|
99
|
-
# Работодатель удален или скрыт
|
|
100
|
-
continue
|
|
101
|
-
try:
|
|
102
|
-
r = api_client.put(f"/employers/blacklisted/{employer['id']}")
|
|
103
|
-
assert not r
|
|
104
|
-
print(
|
|
105
|
-
"🚫 Заблокировали",
|
|
106
|
-
employer["alternate_url"],
|
|
107
|
-
"(",
|
|
108
|
-
truncate_string(employer["name"]),
|
|
109
|
-
")",
|
|
110
|
-
)
|
|
111
|
-
except ClientError as ex:
|
|
112
|
-
print_err("❗ Ошибка:", ex)
|
|
113
|
-
print("🧹 Чистка заявок завершена!")
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# Этот модуль можно использовать как образец для других
|
|
2
|
-
import argparse
|
|
3
|
-
import logging
|
|
4
|
-
|
|
5
|
-
from ..telemetry_client import TelemetryClient, TelemetryError
|
|
6
|
-
|
|
7
|
-
from ..main import BaseOperation
|
|
8
|
-
from ..main import Namespace as BaseNamespace
|
|
9
|
-
from ..utils import print_err
|
|
10
|
-
|
|
11
|
-
logger = logging.getLogger(__package__)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class Namespace(BaseNamespace):
|
|
15
|
-
pass
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Operation(BaseOperation):
|
|
19
|
-
"""Удалить всю телеметрию, сохраненную на сервере."""
|
|
20
|
-
|
|
21
|
-
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
22
|
-
pass
|
|
23
|
-
|
|
24
|
-
def run(self, a, b, telemetry_client: TelemetryClient) -> None:
|
|
25
|
-
try:
|
|
26
|
-
telemetry_client.send_telemetry("/delete")
|
|
27
|
-
print("✅ Вся телеметрия, сохраненная на сервере, была успешно удалена!")
|
|
28
|
-
except TelemetryError as ex:
|
|
29
|
-
print_err("❗ Ошибка:", ex)
|
|
30
|
-
return 1
|
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
import argparse
|
|
2
|
-
import logging
|
|
3
|
-
from os import getenv
|
|
4
|
-
import pathlib
|
|
5
|
-
from ..main import BaseOperation
|
|
6
|
-
from ..main import Namespace as BaseNamespace
|
|
7
|
-
from ..telemetry_client import TelemetryClient
|
|
8
|
-
|
|
9
|
-
logger = logging.getLogger(__package__)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Namespace(BaseNamespace):
|
|
13
|
-
username: str | None
|
|
14
|
-
password: str | None
|
|
15
|
-
search: str | None
|
|
16
|
-
export: bool
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class Operation(BaseOperation):
|
|
20
|
-
"""Выведет контакты работодателей, которые высылали вам приглашения"""
|
|
21
|
-
|
|
22
|
-
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
23
|
-
# parser.add_argument(
|
|
24
|
-
# "-u",
|
|
25
|
-
# "--username",
|
|
26
|
-
# type=str,
|
|
27
|
-
# help="Имя пользователя для аутентификации",
|
|
28
|
-
# default=getenv("AUTH_USERNAME"),
|
|
29
|
-
# )
|
|
30
|
-
# parser.add_argument(
|
|
31
|
-
# "-P",
|
|
32
|
-
# "--password",
|
|
33
|
-
# type=str,
|
|
34
|
-
# help="Пароль для аутентификации",
|
|
35
|
-
# default=getenv("AUTH_PASSWORD"),
|
|
36
|
-
# )
|
|
37
|
-
parser.add_argument(
|
|
38
|
-
"-s",
|
|
39
|
-
"--search",
|
|
40
|
-
type=str,
|
|
41
|
-
default="",
|
|
42
|
-
help="Строка поиска контактов работодателя (email, имя, название компании)",
|
|
43
|
-
)
|
|
44
|
-
parser.add_argument(
|
|
45
|
-
"-p",
|
|
46
|
-
"--page",
|
|
47
|
-
default=1,
|
|
48
|
-
help="Номер страницы в выдаче. Игнорируется при экспорте.",
|
|
49
|
-
)
|
|
50
|
-
parser.add_argument(
|
|
51
|
-
"--export",
|
|
52
|
-
action=argparse.BooleanOptionalAction,
|
|
53
|
-
default=False,
|
|
54
|
-
help="Экспортировать контакты работодателей.",
|
|
55
|
-
)
|
|
56
|
-
parser.add_argument(
|
|
57
|
-
"-f",
|
|
58
|
-
"--format",
|
|
59
|
-
default="html",
|
|
60
|
-
choices=["html", "jsonl"],
|
|
61
|
-
help="Формат вывода",
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
def run(self, args: Namespace, _, telemetry_client: TelemetryClient) -> None:
|
|
65
|
-
if args.export:
|
|
66
|
-
contact_persons = []
|
|
67
|
-
page = 1
|
|
68
|
-
per_page = 100
|
|
69
|
-
while True:
|
|
70
|
-
res = telemetry_client.get_telemetry(
|
|
71
|
-
"/contact/persons",
|
|
72
|
-
{"search": args.search, "per_page": per_page, "page": page},
|
|
73
|
-
)
|
|
74
|
-
assert "contact_persons" in res
|
|
75
|
-
contact_persons += res["contact_persons"]
|
|
76
|
-
if per_page * page >= res["total"]:
|
|
77
|
-
break
|
|
78
|
-
page += 1
|
|
79
|
-
if args.format == "jsonl":
|
|
80
|
-
import json, sys
|
|
81
|
-
|
|
82
|
-
for contact in contact_persons:
|
|
83
|
-
json.dump(contact, sys.stdout, ensure_ascii=False)
|
|
84
|
-
sys.stdout.write("\n")
|
|
85
|
-
sys.stdout.flush()
|
|
86
|
-
else:
|
|
87
|
-
print(generate_html_report(contact_persons))
|
|
88
|
-
return
|
|
89
|
-
|
|
90
|
-
res = telemetry_client.get_telemetry(
|
|
91
|
-
"/contact/persons",
|
|
92
|
-
{"search": args.search, "per_page": 10, "page": args.page},
|
|
93
|
-
)
|
|
94
|
-
if "contact_persons" not in res:
|
|
95
|
-
print("❌", res)
|
|
96
|
-
return 1
|
|
97
|
-
|
|
98
|
-
print(
|
|
99
|
-
"Тут отображаются только данные, собранные с вашего telemetry_client_id. Вы так же можете их удалить с помощью команды delete-telemetry."
|
|
100
|
-
)
|
|
101
|
-
print()
|
|
102
|
-
|
|
103
|
-
print_contacts(res)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def generate_html_report(data: list[dict]) -> str:
|
|
107
|
-
"""
|
|
108
|
-
Генерирует HTML-отчет на основе предоставленных данных.
|
|
109
|
-
"""
|
|
110
|
-
html_content = """\
|
|
111
|
-
<!DOCTYPE html>
|
|
112
|
-
<html lang="ru">
|
|
113
|
-
<head>
|
|
114
|
-
<meta charset="UTF-8">
|
|
115
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
116
|
-
<title>Контакты работодателей</title>
|
|
117
|
-
<style>
|
|
118
|
-
body {
|
|
119
|
-
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
120
|
-
margin: 20px;
|
|
121
|
-
background-color: #f4f7f6;
|
|
122
|
-
color: #333;
|
|
123
|
-
}
|
|
124
|
-
.container {
|
|
125
|
-
max-width: 900px;
|
|
126
|
-
margin: 20px auto;
|
|
127
|
-
background-color: #ffffff;
|
|
128
|
-
padding: 30px;
|
|
129
|
-
border-radius: 10px;
|
|
130
|
-
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
|
131
|
-
}
|
|
132
|
-
h1 {
|
|
133
|
-
color: #0056b3;
|
|
134
|
-
text-align: center;
|
|
135
|
-
margin-bottom: 30px;
|
|
136
|
-
}
|
|
137
|
-
.person-card {
|
|
138
|
-
background-color: #e9f0f8;
|
|
139
|
-
border: 1px solid #cce5ff;
|
|
140
|
-
border-radius: 8px;
|
|
141
|
-
padding: 20px;
|
|
142
|
-
margin-bottom: 25px;
|
|
143
|
-
transition: transform 0.2s ease-in-out;
|
|
144
|
-
}
|
|
145
|
-
.person-card:hover {
|
|
146
|
-
transform: translateY(-5px);
|
|
147
|
-
}
|
|
148
|
-
.person-card h2 {
|
|
149
|
-
color: #004085;
|
|
150
|
-
margin-top: 0;
|
|
151
|
-
margin-bottom: 10px;
|
|
152
|
-
border-bottom: 2px solid #0056b3;
|
|
153
|
-
padding-bottom: 5px;
|
|
154
|
-
}
|
|
155
|
-
.person-card p {
|
|
156
|
-
margin: 5px 0;
|
|
157
|
-
}
|
|
158
|
-
.person-card strong {
|
|
159
|
-
color: #004085;
|
|
160
|
-
}
|
|
161
|
-
.employer-info {
|
|
162
|
-
background-color: #d1ecf1;
|
|
163
|
-
border-left: 5px solid #007bff;
|
|
164
|
-
padding: 15px;
|
|
165
|
-
margin-top: 15px;
|
|
166
|
-
border-radius: 5px;
|
|
167
|
-
}
|
|
168
|
-
.employer-info h3 {
|
|
169
|
-
color: #0056b3;
|
|
170
|
-
margin-top: 0;
|
|
171
|
-
margin-bottom: 10px;
|
|
172
|
-
}
|
|
173
|
-
ul {
|
|
174
|
-
list-style-type: none;
|
|
175
|
-
padding: 0;
|
|
176
|
-
}
|
|
177
|
-
ul li {
|
|
178
|
-
background-color: #f8fafd;
|
|
179
|
-
padding: 8px 12px;
|
|
180
|
-
margin-bottom: 5px;
|
|
181
|
-
border-radius: 4px;
|
|
182
|
-
border: 1px solid #e0e9f1;
|
|
183
|
-
}
|
|
184
|
-
a {
|
|
185
|
-
color: #007bff;
|
|
186
|
-
text-decoration: none;
|
|
187
|
-
}
|
|
188
|
-
a:hover {
|
|
189
|
-
text-decoration: underline;
|
|
190
|
-
}
|
|
191
|
-
.no-data {
|
|
192
|
-
color: #6c757d;
|
|
193
|
-
font-style: italic;
|
|
194
|
-
}
|
|
195
|
-
</style>
|
|
196
|
-
</head>
|
|
197
|
-
<body>
|
|
198
|
-
<div class="container">
|
|
199
|
-
<h1>Полученные контакты</h1>
|
|
200
|
-
"""
|
|
201
|
-
|
|
202
|
-
for item in data:
|
|
203
|
-
name = item.get("name", "N/A")
|
|
204
|
-
email = item.get("email", "N/A")
|
|
205
|
-
employer = item.get("employer") or {}
|
|
206
|
-
|
|
207
|
-
employer_name = employer.get("name", "N/A")
|
|
208
|
-
employer_area = employer.get("area", "N/A")
|
|
209
|
-
employer_site_url = employer.get("site_url", "")
|
|
210
|
-
|
|
211
|
-
phone_numbers = [
|
|
212
|
-
pn["phone_number"]
|
|
213
|
-
for pn in item.get("phone_numbers", [])
|
|
214
|
-
if "phone_number" in pn
|
|
215
|
-
]
|
|
216
|
-
telegram_usernames = [
|
|
217
|
-
tu["username"]
|
|
218
|
-
for tu in item.get("telegram_usernames", [])
|
|
219
|
-
if "username" in tu
|
|
220
|
-
]
|
|
221
|
-
|
|
222
|
-
html_content += f"""\
|
|
223
|
-
<div class="person-card">
|
|
224
|
-
<h2>{name}</h2>
|
|
225
|
-
<p><strong>Email:</strong> <a href="mailto:{email}">{email}</a></p>
|
|
226
|
-
"""
|
|
227
|
-
|
|
228
|
-
if employer_name != "N/A":
|
|
229
|
-
html_content += f"""\
|
|
230
|
-
<div class="employer-info">
|
|
231
|
-
<h3>Работодатель: {employer_name}</h3>
|
|
232
|
-
<p><strong>Город:</strong> {employer_area}</p>
|
|
233
|
-
"""
|
|
234
|
-
if employer_site_url:
|
|
235
|
-
html_content += f"""\
|
|
236
|
-
<p><strong>Сайт:</strong> <a href="{employer_site_url}" target="_blank">{employer_site_url}</a></p>
|
|
237
|
-
"""
|
|
238
|
-
html_content += "</div>" # Закрываем employer-info
|
|
239
|
-
else:
|
|
240
|
-
html_content += (
|
|
241
|
-
'<p class="no-data">Информация о работодателе отсутствует.</p>'
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
if phone_numbers:
|
|
245
|
-
html_content += "<p><strong>Номера телефонов:</strong></p><ul>"
|
|
246
|
-
for phone in phone_numbers:
|
|
247
|
-
html_content += f"<li><a href='tel:{phone}'>{phone}</a></li>"
|
|
248
|
-
html_content += "</ul>"
|
|
249
|
-
else:
|
|
250
|
-
html_content += '<p class="no-data">Номера телефонов отсутствуют.</p>'
|
|
251
|
-
|
|
252
|
-
if telegram_usernames:
|
|
253
|
-
html_content += "<p><strong>Имена пользователей Telegram:</strong></p><ul>"
|
|
254
|
-
for username in telegram_usernames:
|
|
255
|
-
html_content += f"<li><a href='https://t.me/{username}' target='_blank'>@{username}</a></li>"
|
|
256
|
-
html_content += "</ul>"
|
|
257
|
-
else:
|
|
258
|
-
html_content += (
|
|
259
|
-
'<p class="no-data">Имена пользователей Telegram отсутствуют.</p>'
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
html_content += "</div>" # Закрываем person-card
|
|
263
|
-
|
|
264
|
-
html_content += """\
|
|
265
|
-
</div>
|
|
266
|
-
</body>
|
|
267
|
-
</html>"""
|
|
268
|
-
return html_content
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def print_contacts(data: dict) -> None:
|
|
272
|
-
"""Вывод всех контактов в древовидной структуре."""
|
|
273
|
-
page = data["page"]
|
|
274
|
-
pages = (data["total"] // data["per_page"]) + 1
|
|
275
|
-
print(f"Страница {page}/{pages}:")
|
|
276
|
-
contacts = data.get("contact_persons", [])
|
|
277
|
-
for idx, contact in enumerate(contacts):
|
|
278
|
-
is_last_contact = idx == len(contacts) - 1
|
|
279
|
-
print_contact(contact, is_last_contact)
|
|
280
|
-
print()
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
def print_contact(contact: dict, is_last_contact: bool) -> None:
|
|
284
|
-
"""Вывод информации о конкретном контакте."""
|
|
285
|
-
prefix = "└──" if is_last_contact else "├──"
|
|
286
|
-
print(f" {prefix} 🧑 {contact.get('name', 'Имя скрыто')}")
|
|
287
|
-
prefix2 = " " if is_last_contact else " │ "
|
|
288
|
-
print(f"{prefix2}├── 📧 Email: {contact.get('email', 'н/д')}")
|
|
289
|
-
employer = contact.get("employer") or {}
|
|
290
|
-
print(f"{prefix2}├── 🏢 Работодатель: {employer.get('name', 'н/д')}")
|
|
291
|
-
print(f"{prefix2}├── 🏠 Город: {employer.get('area', 'н/д')}")
|
|
292
|
-
print(f"{prefix2}└── 🌐 Сайт: {employer.get('site_url', 'н/д')}")
|
|
293
|
-
print(prefix2)
|