hh-applicant-tool 1.4.7__py3-none-any.whl → 1.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.
- hh_applicant_tool/__main__.py +1 -1
- hh_applicant_tool/ai/__init__.py +1 -0
- hh_applicant_tool/ai/openai.py +30 -14
- hh_applicant_tool/api/__init__.py +4 -2
- hh_applicant_tool/api/client.py +32 -17
- hh_applicant_tool/{constants.py → api/client_keys.py} +3 -3
- hh_applicant_tool/{datatypes.py → api/datatypes.py} +2 -0
- hh_applicant_tool/api/errors.py +8 -2
- hh_applicant_tool/{utils → api}/user_agent.py +1 -1
- hh_applicant_tool/main.py +63 -38
- hh_applicant_tool/operations/apply_similar.py +136 -52
- hh_applicant_tool/operations/authorize.py +97 -28
- hh_applicant_tool/operations/call_api.py +3 -3
- hh_applicant_tool/operations/{check_negotiations.py → clear_negotiations.py} +40 -26
- hh_applicant_tool/operations/list_resumes.py +5 -7
- hh_applicant_tool/operations/query.py +5 -3
- hh_applicant_tool/operations/refresh_token.py +9 -2
- hh_applicant_tool/operations/reply_employers.py +80 -40
- hh_applicant_tool/operations/settings.py +2 -2
- hh_applicant_tool/operations/update_resumes.py +5 -4
- hh_applicant_tool/operations/whoami.py +3 -3
- hh_applicant_tool/storage/__init__.py +5 -1
- hh_applicant_tool/storage/facade.py +2 -2
- hh_applicant_tool/storage/models/base.py +9 -4
- hh_applicant_tool/storage/models/contacts.py +42 -0
- hh_applicant_tool/storage/queries/schema.sql +23 -10
- hh_applicant_tool/storage/repositories/base.py +69 -15
- hh_applicant_tool/storage/repositories/contacts.py +5 -10
- hh_applicant_tool/storage/repositories/employers.py +1 -0
- hh_applicant_tool/storage/repositories/errors.py +19 -0
- hh_applicant_tool/storage/repositories/negotiations.py +1 -0
- hh_applicant_tool/storage/repositories/resumes.py +2 -7
- hh_applicant_tool/storage/repositories/settings.py +1 -0
- hh_applicant_tool/storage/repositories/vacancies.py +1 -0
- hh_applicant_tool/storage/utils.py +12 -15
- hh_applicant_tool/utils/__init__.py +3 -3
- hh_applicant_tool/utils/config.py +1 -1
- hh_applicant_tool/utils/log.py +6 -3
- hh_applicant_tool/utils/mixins.py +28 -46
- hh_applicant_tool/utils/string.py +15 -0
- hh_applicant_tool/utils/terminal.py +115 -0
- {hh_applicant_tool-1.4.7.dist-info → hh_applicant_tool-1.5.7.dist-info}/METADATA +384 -162
- hh_applicant_tool-1.5.7.dist-info/RECORD +68 -0
- {hh_applicant_tool-1.4.7.dist-info → hh_applicant_tool-1.5.7.dist-info}/WHEEL +1 -1
- hh_applicant_tool/storage/models/contact.py +0 -16
- hh_applicant_tool-1.4.7.dist-info/RECORD +0 -67
- /hh_applicant_tool/utils/{dateutil.py → date.py} +0 -0
- /hh_applicant_tool/utils/{jsonutil.py → json.py} +0 -0
- {hh_applicant_tool-1.4.7.dist-info → hh_applicant_tool-1.5.7.dist-info}/entry_points.txt +0 -0
|
@@ -1,5 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
1
4
|
import ctypes
|
|
5
|
+
import io
|
|
6
|
+
import os
|
|
2
7
|
import platform
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from PIL import Image
|
|
12
|
+
except ImportError:
|
|
13
|
+
|
|
14
|
+
class Image:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
ESC = "\x1b"
|
|
3
19
|
|
|
4
20
|
|
|
5
21
|
def setup_terminal() -> None:
|
|
@@ -17,3 +33,102 @@ def setup_terminal() -> None:
|
|
|
17
33
|
# Если что-то пошло не так (старая Windows или нет прав),
|
|
18
34
|
# просто продолжаем работу без цветов
|
|
19
35
|
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def print_kitty_image(data: bytes) -> None:
|
|
39
|
+
# Кодируем весь файл целиком (он уже сжат в PNG)
|
|
40
|
+
b64data = base64.b64encode(data).decode("ascii")
|
|
41
|
+
|
|
42
|
+
# f=100 говорит терминалу: "это PNG, разберись сам с размерами"
|
|
43
|
+
# Нам больше не нужно указывать s=... и v=...
|
|
44
|
+
sys.stdout.write(f"\033_Ga=T,f=100;{b64data}\033\\")
|
|
45
|
+
sys.stdout.flush()
|
|
46
|
+
print()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def print_sixel_mage(image_bytes: bytes) -> None:
|
|
50
|
+
img = Image.open(io.BytesIO(image_bytes))
|
|
51
|
+
|
|
52
|
+
# Рекомендуется оставить ограничение размера,
|
|
53
|
+
# иначе Zellij может "лагать" на огромных картинках
|
|
54
|
+
# max_size = (800, 600)
|
|
55
|
+
# img.thumbnail(max_size, Image.Resampling.LANCZOS)
|
|
56
|
+
img = img.convert("RGB")
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
img = img.quantize(colors=256, method=Image.Quantize.MAXCOVERAGE)
|
|
60
|
+
except Exception:
|
|
61
|
+
img = img.quantize(colors=256)
|
|
62
|
+
|
|
63
|
+
palette = img.getpalette()[: 256 * 3]
|
|
64
|
+
width, height = img.size
|
|
65
|
+
pixels = img.load()
|
|
66
|
+
|
|
67
|
+
is_multiplexer = "ZELLIJ" in os.environ or "TMUX" in os.environ
|
|
68
|
+
|
|
69
|
+
# Собираем всё в список строк, чтобы минимизировать количество вызовов write
|
|
70
|
+
out = []
|
|
71
|
+
|
|
72
|
+
# 1. Начало (Обертка для Zellij)
|
|
73
|
+
if is_multiplexer:
|
|
74
|
+
out.append(f"{ESC}P+p")
|
|
75
|
+
|
|
76
|
+
# 2. Sixel заголовок + Растр
|
|
77
|
+
out.append(f'{ESC}Pq"1;1;{width};{height}')
|
|
78
|
+
|
|
79
|
+
# 3. Палитра
|
|
80
|
+
for i in range(256):
|
|
81
|
+
r, g, b = palette[i * 3 : i * 3 + 3]
|
|
82
|
+
out.append(f"#{i};2;{r * 100 // 255};{g * 100 // 255};{b * 100 // 255}")
|
|
83
|
+
|
|
84
|
+
# 4. Отрисовка
|
|
85
|
+
for y in range(0, height, 6):
|
|
86
|
+
h_chunk = min(6, height - y)
|
|
87
|
+
|
|
88
|
+
# Считаем уникальные цвета в полосе (быстрее, чем перебирать всю палитру)
|
|
89
|
+
colors_in_band = set()
|
|
90
|
+
for dy in range(h_chunk):
|
|
91
|
+
for x in range(width):
|
|
92
|
+
colors_in_band.add(pixels[x, y + dy])
|
|
93
|
+
|
|
94
|
+
for color in colors_in_band:
|
|
95
|
+
out.append(f"#{color}")
|
|
96
|
+
last_char = ""
|
|
97
|
+
count = 0
|
|
98
|
+
|
|
99
|
+
for x in range(width):
|
|
100
|
+
bits = 0
|
|
101
|
+
for dy in range(h_chunk):
|
|
102
|
+
if pixels[x, y + dy] == color:
|
|
103
|
+
bits |= 1 << dy
|
|
104
|
+
|
|
105
|
+
char = chr(63 + bits)
|
|
106
|
+
if char == last_char:
|
|
107
|
+
count += 1
|
|
108
|
+
else:
|
|
109
|
+
if count > 0:
|
|
110
|
+
out.append(
|
|
111
|
+
f"!{count}{last_char}"
|
|
112
|
+
if count > 3
|
|
113
|
+
else last_char * count
|
|
114
|
+
)
|
|
115
|
+
last_char, count = char, 1
|
|
116
|
+
|
|
117
|
+
if count > 0:
|
|
118
|
+
out.append(
|
|
119
|
+
f"!{count}{last_char}" if count > 3 else last_char * count
|
|
120
|
+
)
|
|
121
|
+
out.append("$")
|
|
122
|
+
out.append("-")
|
|
123
|
+
|
|
124
|
+
# 5. Конец Sixel
|
|
125
|
+
out.append(f"{ESC}\\")
|
|
126
|
+
|
|
127
|
+
# 6. Конец обертки мультиплексора
|
|
128
|
+
if is_multiplexer:
|
|
129
|
+
out.append(f"{ESC}\\")
|
|
130
|
+
|
|
131
|
+
# ВЫВОД: Одной строкой БЕЗ лишних переносов в конце
|
|
132
|
+
sys.stdout.write("".join(out))
|
|
133
|
+
sys.stdout.flush()
|
|
134
|
+
print()
|