hh-applicant-tool 1.4.12__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.
@@ -11,7 +11,6 @@ from typing import TYPE_CHECKING, Literal
11
11
  import requests
12
12
  from requests.exceptions import RequestException
13
13
 
14
- from ..ai.openai import ChatOpenAI
15
14
  from . import binpack
16
15
  from .log import collect_traceback_logs
17
16
 
@@ -38,9 +37,7 @@ class ErrorReporter:
38
37
  error_logs = ""
39
38
  if self.log_file.exists():
40
39
  with self.log_file.open(encoding="utf-8", errors="ignore") as fp:
41
- error_logs = collect_traceback_logs(
42
- fp, last_report, maxlen=10000
43
- )
40
+ error_logs = collect_traceback_logs(fp, last_report)
44
41
 
45
42
  # Эти данные нужны для воспроизведения ошибок. Среди них ваших
46
43
  # персональных данных нет
@@ -49,7 +46,7 @@ class ErrorReporter:
49
46
  for c in self.storage.vacancy_contacts.find(
50
47
  updated_at__ge=last_report
51
48
  )
52
- ][-10000:]
49
+ ]
53
50
 
54
51
  for c in vacancy_contacts:
55
52
  c.pop("id", 0)
@@ -71,7 +68,7 @@ class ErrorReporter:
71
68
  ]
72
69
  }
73
70
  for emp in self.storage.employers.find(updated_at__ge=last_report)
74
- ][-10000:]
71
+ ]
75
72
 
76
73
  vacancies = [
77
74
  {
@@ -94,7 +91,7 @@ class ErrorReporter:
94
91
  ]
95
92
  }
96
93
  for vac in self.storage.vacancies.find(updated_at__ge=last_report)
97
- ][-10000:]
94
+ ]
98
95
 
99
96
  # log.info("num vacncies: %d", len(vacancies))
100
97
 
@@ -106,10 +103,10 @@ class ErrorReporter:
106
103
  }
107
104
 
108
105
  return dict(
109
- error_logs=error_logs,
110
- vacancy_contacts=vacancy_contacts,
111
- employers=employers,
112
- vacancies=vacancies,
106
+ error_logs=error_logs[-100000:],
107
+ vacancy_contacts=vacancy_contacts[-10000:],
108
+ employers=employers[-10000:],
109
+ vacancies=vacancies[-10000:],
113
110
  package_version=get_package_version(),
114
111
  system_info=system_info,
115
112
  report_created=datetime.now(timezone.utc),
@@ -194,23 +191,7 @@ class VersionChecker:
194
191
  )
195
192
 
196
193
 
197
- class ChatOpenAISupport:
198
- def get_openai_chat(
199
- self: HHApplicantTool,
200
- system_prompt: str,
201
- ) -> ChatOpenAI:
202
- c = self.config.get("openai", {})
203
- if not (token := c.get("token")):
204
- raise ValueError("Токен для OpenAI не задан")
205
- return ChatOpenAI(
206
- token=token,
207
- model=c.get("model", "gpt-5.1"),
208
- system_prompt=system_prompt,
209
- session=self.session,
210
- )
211
-
212
-
213
- class MegaTool(ErrorReporter, VersionChecker, ChatOpenAISupport):
194
+ class MegaTool(ErrorReporter, VersionChecker):
214
195
  def _check_system(self: HHApplicantTool):
215
196
  if not self.storage.settings.get_value("disable_version_check", False):
216
197
  self._check_version()
@@ -4,9 +4,11 @@ import random
4
4
  import re
5
5
  from typing import Any
6
6
 
7
+
7
8
  def shorten(s: str, limit: int = 75, ellipsis: str = "…") -> str:
8
9
  return s[:limit] + bool(s[limit:]) * ellipsis
9
10
 
11
+
10
12
  def rand_text(s: str) -> str:
11
13
  while (
12
14
  temp := re.sub(
@@ -20,8 +22,21 @@ def rand_text(s: str) -> str:
20
22
  s = temp
21
23
  return s
22
24
 
25
+
23
26
  def bool2str(v: bool) -> str:
24
27
  return str(v).lower()
25
28
 
29
+
26
30
  def list2str(items: list[Any] | None) -> str:
27
31
  return ",".join(f"{v}" for v in items) if items else ""
32
+
33
+
34
+ def unescape_string(text: str) -> str:
35
+ if not text:
36
+ return ""
37
+ return (
38
+ text.replace(r"\n", "\n")
39
+ .replace(r"\r", "\r")
40
+ .replace(r"\t", "\t")
41
+ .replace(r"\\", "\\")
42
+ )
@@ -1,8 +1,22 @@
1
+ from __future__ import annotations
2
+
1
3
  import base64
2
4
  import ctypes
5
+ import io
6
+ import os
3
7
  import platform
4
8
  import sys
5
9
 
10
+ try:
11
+ from PIL import Image
12
+ except ImportError:
13
+
14
+ class Image:
15
+ pass
16
+
17
+
18
+ ESC = "\x1b"
19
+
6
20
 
7
21
  def setup_terminal() -> None:
8
22
  if platform.system() != "Windows":
@@ -30,3 +44,91 @@ def print_kitty_image(data: bytes) -> None:
30
44
  sys.stdout.write(f"\033_Ga=T,f=100;{b64data}\033\\")
31
45
  sys.stdout.flush()
32
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()