python-doc-2 1.0.0__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.
- python_doc_2-1.0.0.dist-info/METADATA +11 -0
- python_doc_2-1.0.0.dist-info/RECORD +10 -0
- python_doc_2-1.0.0.dist-info/WHEEL +5 -0
- python_doc_2-1.0.0.dist-info/entry_points.txt +2 -0
- python_doc_2-1.0.0.dist-info/top_level.txt +1 -0
- tool/__init__.py +0 -0
- tool/api_key.txt +0 -0
- tool/cli.py +386 -0
- tool/crypto_key.py +58 -0
- tool/k.txt +1 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-doc-2
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Учебный проект: CLI инструмент для работы с ИИ
|
|
5
|
+
Requires-Python: >=3.8
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: openai>=1.0.0
|
|
8
|
+
Requires-Dist: python-docx>=1.1.0
|
|
9
|
+
Requires-Dist: pandas>=2.0.0
|
|
10
|
+
Requires-Dist: openpyxl>=3.1.0
|
|
11
|
+
Requires-Dist: PyPDF2>=3.0.0
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
tool/api_key.txt,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
tool/cli.py,sha256=GHYnSX20T6B3yR5jR0yVFjKrT4vCysMnN-I-AKBnQxA,17582
|
|
4
|
+
tool/crypto_key.py,sha256=7-ZMkLbgs0n1Hcjw9lUIQo7m-Vg0DABJVu6nvzEundc,2570
|
|
5
|
+
tool/k.txt,sha256=j-2AnneWlNTtTvBznbmaOzR9jOn6DhfiJQD1K93PEQQ,100
|
|
6
|
+
python_doc_2-1.0.0.dist-info/METADATA,sha256=b57E2OlHl75gO8UG7Wh3TTBy880bzqbfMNt6KIsTfLU,373
|
|
7
|
+
python_doc_2-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
8
|
+
python_doc_2-1.0.0.dist-info/entry_points.txt,sha256=rB-WZyo3vEofJXd2cKv8fFXJQRiStf1IIVNaleguDBM,42
|
|
9
|
+
python_doc_2-1.0.0.dist-info/top_level.txt,sha256=Z5SN2a_Wr-UEOwAp1ap88PiygkuvFvTwl9QNgw7baG0,5
|
|
10
|
+
python_doc_2-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tool
|
tool/__init__.py
ADDED
|
File without changes
|
tool/api_key.txt
ADDED
|
File without changes
|
tool/cli.py
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import re
|
|
4
|
+
import base64
|
|
5
|
+
import argparse
|
|
6
|
+
from openai import OpenAI
|
|
7
|
+
|
|
8
|
+
SUPPORTED_EXTENSIONS = [
|
|
9
|
+
'.txt', '.py', '.php', '.js', '.html', '.css', '.json', '.xml',
|
|
10
|
+
'.c', '.cpp', '.java', '.go', '.rs', '.md', '.docx', '.xlsx', '.xls', '.pdf', '.sql'
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp']
|
|
14
|
+
|
|
15
|
+
IMAGE_MIME_TYPES = {
|
|
16
|
+
'.png': 'image/png',
|
|
17
|
+
'.jpg': 'image/jpeg',
|
|
18
|
+
'.jpeg': 'image/jpeg',
|
|
19
|
+
'.gif': 'image/gif',
|
|
20
|
+
'.webp': 'image/webp',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# Системные промпты
|
|
24
|
+
SYSTEM_CODE = """Ты - помощник для генерации кода. Строго следуй этим правилам:
|
|
25
|
+
- Никогда не пиши комментарии к коду
|
|
26
|
+
- Никогда не используй эмодзи, никуда их не вставляй
|
|
27
|
+
- В ответе выдавай только код, никаких комментариев от тебя
|
|
28
|
+
- Пиши минимально CSS стилей, практически не пиши их"""
|
|
29
|
+
|
|
30
|
+
SYSTEM_GENERAL = """Ты - умный универсальный помощник. Отвечай чётко и по делу.
|
|
31
|
+
- Не используй эмодзи
|
|
32
|
+
- Если задача связана с анализом данных или документов — дай подробный структурированный ответ
|
|
33
|
+
- Если нужно сгенерировать файл определённого формата — выдай только содержимое файла без лишних пояснений"""
|
|
34
|
+
|
|
35
|
+
SYSTEM_STRICT = """Ты - строгий технический эксперт и аналитик. Твоя задача — найти проблемы, а не подтвердить что всё хорошо.
|
|
36
|
+
|
|
37
|
+
При анализе баз данных, кода, схем и документов:
|
|
38
|
+
- Активно ищи нарушения, избыточность, спорные решения — не жди очевидных ошибок
|
|
39
|
+
- При проверке нормальных форм (1НФ/2НФ/3НФ/BCNF) проверяй каждую зависимость строго и без исключений
|
|
40
|
+
- Не оправдывай сомнительные решения через "формально допустимо" — укажи проблему и предложи как исправить
|
|
41
|
+
- Если видишь спорный момент — назови его спорным, не замалчивай
|
|
42
|
+
- Не говори "соответствует" пока не проверил каждый атрибут каждой таблицы
|
|
43
|
+
- Структура ответа: 1) Найденные проблемы и нарушения, 2) Спорные моменты, 3) Что сделано правильно, 4) Конкретные рекомендации по исправлению
|
|
44
|
+
- Не используй эмодзи"""
|
|
45
|
+
|
|
46
|
+
SYSTEM_PROMPTS = {
|
|
47
|
+
"code": SYSTEM_CODE,
|
|
48
|
+
"general": SYSTEM_GENERAL,
|
|
49
|
+
"strict": SYSTEM_STRICT,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def read_file_content(file_path):
|
|
54
|
+
ext = os.path.splitext(file_path)[1].lower()
|
|
55
|
+
|
|
56
|
+
if ext in ['.txt', '.py', '.php', '.js', '.html', '.css', '.json', '.xml',
|
|
57
|
+
'.c', '.cpp', '.java', '.go', '.rs', '.md', '.sql']:
|
|
58
|
+
try:
|
|
59
|
+
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
60
|
+
return f.read()
|
|
61
|
+
except Exception as e:
|
|
62
|
+
return f"[Ошибка чтения файла: {e}]"
|
|
63
|
+
|
|
64
|
+
elif ext == '.docx':
|
|
65
|
+
try:
|
|
66
|
+
from docx import Document
|
|
67
|
+
doc = Document(file_path)
|
|
68
|
+
text = '\n'.join([p.text for p in doc.paragraphs])
|
|
69
|
+
return text if text else "[Файл не содержит текста]"
|
|
70
|
+
except ImportError:
|
|
71
|
+
return "[Ошибка: pip install python-docx]"
|
|
72
|
+
except Exception as e:
|
|
73
|
+
return f"[Ошибка чтения DOCX: {e}]"
|
|
74
|
+
|
|
75
|
+
elif ext in ['.xlsx', '.xls']:
|
|
76
|
+
try:
|
|
77
|
+
import pandas as pd
|
|
78
|
+
excel_file = pd.ExcelFile(file_path)
|
|
79
|
+
all_sheets_text = []
|
|
80
|
+
for sheet_name in excel_file.sheet_names:
|
|
81
|
+
df = pd.read_excel(file_path, sheet_name=sheet_name)
|
|
82
|
+
sheet_text = f"\n=== Лист: {sheet_name} ===\n"
|
|
83
|
+
sheet_text += df.to_string(index=False)
|
|
84
|
+
all_sheets_text.append(sheet_text)
|
|
85
|
+
return '\n'.join(all_sheets_text)
|
|
86
|
+
except ImportError:
|
|
87
|
+
return "[Ошибка: pip install pandas openpyxl]"
|
|
88
|
+
except Exception as e:
|
|
89
|
+
return f"[Ошибка чтения Excel: {e}]"
|
|
90
|
+
|
|
91
|
+
elif ext == '.pdf':
|
|
92
|
+
try:
|
|
93
|
+
import PyPDF2
|
|
94
|
+
with open(file_path, 'rb') as f:
|
|
95
|
+
reader = PyPDF2.PdfReader(f)
|
|
96
|
+
text = ''.join(page.extract_text() for page in reader.pages)
|
|
97
|
+
return text if text else "[PDF не содержит текста]"
|
|
98
|
+
except ImportError:
|
|
99
|
+
return "[Ошибка: pip install PyPDF2]"
|
|
100
|
+
except Exception as e:
|
|
101
|
+
return f"[Ошибка чтения PDF: {e}]"
|
|
102
|
+
|
|
103
|
+
else:
|
|
104
|
+
try:
|
|
105
|
+
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
106
|
+
return f.read()
|
|
107
|
+
except:
|
|
108
|
+
return f"[Неподдерживаемый формат: {ext}]"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def read_image_as_base64(file_path):
|
|
112
|
+
"""Читает изображение и возвращает base64 строку и mime-тип."""
|
|
113
|
+
ext = os.path.splitext(file_path)[1].lower()
|
|
114
|
+
mime = IMAGE_MIME_TYPES.get(ext, 'image/png')
|
|
115
|
+
try:
|
|
116
|
+
with open(file_path, 'rb') as f:
|
|
117
|
+
data = base64.standard_b64encode(f.read()).decode('utf-8')
|
|
118
|
+
return data, mime
|
|
119
|
+
except Exception as e:
|
|
120
|
+
return None, f"[Ошибка чтения изображения: {e}]"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def extract_files_from_prompt(prompt: str, base_dir: str) -> tuple:
|
|
124
|
+
"""Возвращает (text_files, image_files) найденные в промпте."""
|
|
125
|
+
text_found = []
|
|
126
|
+
image_found = []
|
|
127
|
+
seen = set()
|
|
128
|
+
|
|
129
|
+
all_extensions = SUPPORTED_EXTENSIONS + IMAGE_EXTENSIONS
|
|
130
|
+
ext_pattern = '|'.join(re.escape(e) for e in all_extensions)
|
|
131
|
+
|
|
132
|
+
# Способ 1: регулярка
|
|
133
|
+
pattern = rf'[\w\-\.А-Яа-яЁё]+(?:{ext_pattern})'
|
|
134
|
+
matches = re.findall(pattern, prompt, re.IGNORECASE)
|
|
135
|
+
for filename in matches:
|
|
136
|
+
full_path = os.path.join(base_dir, filename)
|
|
137
|
+
if os.path.exists(full_path) and full_path not in seen:
|
|
138
|
+
seen.add(full_path)
|
|
139
|
+
ext = os.path.splitext(filename)[1].lower()
|
|
140
|
+
if ext in IMAGE_EXTENSIONS:
|
|
141
|
+
image_found.append(full_path)
|
|
142
|
+
else:
|
|
143
|
+
text_found.append(full_path)
|
|
144
|
+
|
|
145
|
+
# Способ 2: сканируем папку
|
|
146
|
+
try:
|
|
147
|
+
for filename in os.listdir(base_dir):
|
|
148
|
+
ext = os.path.splitext(filename)[1].lower()
|
|
149
|
+
if ext not in all_extensions:
|
|
150
|
+
continue
|
|
151
|
+
full_path = os.path.join(base_dir, filename)
|
|
152
|
+
if full_path in seen:
|
|
153
|
+
continue
|
|
154
|
+
name_no_ext = os.path.splitext(filename)[0]
|
|
155
|
+
if filename.lower() in prompt.lower() or name_no_ext.lower() in prompt.lower():
|
|
156
|
+
seen.add(full_path)
|
|
157
|
+
if ext in IMAGE_EXTENSIONS:
|
|
158
|
+
image_found.append(full_path)
|
|
159
|
+
else:
|
|
160
|
+
text_found.append(full_path)
|
|
161
|
+
except Exception:
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
return text_found, image_found
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def get_prompt_interactive():
|
|
168
|
+
"""Читает многострочный промпт через input() — не попадает в bash history."""
|
|
169
|
+
print("Введите промпт (пустая строка для отправки):")
|
|
170
|
+
lines = []
|
|
171
|
+
try:
|
|
172
|
+
while True:
|
|
173
|
+
line = input("> ")
|
|
174
|
+
if line == "":
|
|
175
|
+
break
|
|
176
|
+
lines.append(line)
|
|
177
|
+
except (KeyboardInterrupt, EOFError):
|
|
178
|
+
if not lines:
|
|
179
|
+
print("\nОтменено.")
|
|
180
|
+
sys.exit(0)
|
|
181
|
+
return "\n".join(lines)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def detect_system_prompt(output_path: str, user_prompt: str) -> str:
|
|
185
|
+
ext = os.path.splitext(output_path)[1].lower()
|
|
186
|
+
code_extensions = ['.py', '.js', '.php', '.html', '.css', '.sql', '.json',
|
|
187
|
+
'.xml', '.c', '.cpp', '.java', '.go', '.rs', '.ts']
|
|
188
|
+
code_keywords = ['напиши код', 'сгенерируй код', 'напиши функцию', 'напиши скрипт',
|
|
189
|
+
'обнови код', 'исправь код', 'рефактор']
|
|
190
|
+
if ext in code_extensions:
|
|
191
|
+
return SYSTEM_CODE
|
|
192
|
+
if any(kw in user_prompt.lower() for kw in code_keywords):
|
|
193
|
+
return SYSTEM_CODE
|
|
194
|
+
return SYSTEM_GENERAL
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def build_messages(system_rules, user_prompt, text_contents, image_files):
|
|
198
|
+
"""Собирает messages для API с поддержкой изображений."""
|
|
199
|
+
|
|
200
|
+
# Если нет изображений — простой текстовый запрос
|
|
201
|
+
if not image_files:
|
|
202
|
+
content = user_prompt
|
|
203
|
+
if text_contents:
|
|
204
|
+
files_text = "\n\n".join(text_contents)
|
|
205
|
+
content = f"{user_prompt}\n\n{files_text}"
|
|
206
|
+
return [
|
|
207
|
+
{"role": "system", "content": system_rules},
|
|
208
|
+
{"role": "user", "content": content}
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
# Есть изображения — собираем multipart content
|
|
212
|
+
content_parts = []
|
|
213
|
+
|
|
214
|
+
# Сначала текст промпта + текстовые файлы
|
|
215
|
+
text_block = user_prompt
|
|
216
|
+
if text_contents:
|
|
217
|
+
files_text = "\n\n".join(text_contents)
|
|
218
|
+
text_block = f"{user_prompt}\n\n{files_text}"
|
|
219
|
+
|
|
220
|
+
content_parts.append({"type": "text", "text": text_block})
|
|
221
|
+
|
|
222
|
+
# Потом изображения
|
|
223
|
+
for img_path in image_files:
|
|
224
|
+
b64data, mime = read_image_as_base64(img_path)
|
|
225
|
+
if b64data:
|
|
226
|
+
content_parts.append({
|
|
227
|
+
"type": "image_url",
|
|
228
|
+
"image_url": {
|
|
229
|
+
"url": f"data:{mime};base64,{b64data}"
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
print(f"[Изображение загружено] {os.path.basename(img_path)}")
|
|
233
|
+
else:
|
|
234
|
+
print(f"[Ошибка изображения] {img_path}: {mime}")
|
|
235
|
+
|
|
236
|
+
return [
|
|
237
|
+
{"role": "system", "content": system_rules},
|
|
238
|
+
{"role": "user", "content": content_parts}
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def main():
|
|
243
|
+
parser = argparse.ArgumentParser(description="Быстрый CLI доступ к ИИ через OpenRouter")
|
|
244
|
+
parser.add_argument("-o", "--output", type=str, required=True, help="Путь к файлу для сохранения ответа")
|
|
245
|
+
parser.add_argument("-m", "--model", type=str, default="google/gemini-2.5-pro-preview", help="Модель на OpenRouter")
|
|
246
|
+
parser.add_argument("-f", "--file", action="append", help="Файл для чтения (можно несколько раз, включая .png/.jpg)")
|
|
247
|
+
parser.add_argument("-u", "--update", action="store_true", help="Режим обновления: перезаписывает указанный файл")
|
|
248
|
+
parser.add_argument("-s", "--system", type=str, choices=["code", "general", "strict"], default=None, help="Системный промпт: code / general / strict")
|
|
249
|
+
|
|
250
|
+
args = parser.parse_args()
|
|
251
|
+
|
|
252
|
+
# Чтение API-ключа
|
|
253
|
+
api_key_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "api_key.txt")
|
|
254
|
+
if not os.path.exists(api_key_file):
|
|
255
|
+
print("Ошибка: Файл 'api_key.txt' не найден!")
|
|
256
|
+
sys.exit(1)
|
|
257
|
+
with open(api_key_file, "r", encoding="utf-8") as f:
|
|
258
|
+
api_key = f.read().strip()
|
|
259
|
+
if not api_key:
|
|
260
|
+
print("Ошибка: Файл 'api_key.txt' пустой!")
|
|
261
|
+
sys.exit(1)
|
|
262
|
+
|
|
263
|
+
base_dir = os.getcwd()
|
|
264
|
+
file_list = args.file or []
|
|
265
|
+
|
|
266
|
+
# Разделяем явно указанные файлы на текстовые и изображения
|
|
267
|
+
explicit_text_files = []
|
|
268
|
+
explicit_image_files = []
|
|
269
|
+
for f in file_list:
|
|
270
|
+
ext = os.path.splitext(f)[1].lower()
|
|
271
|
+
if ext in IMAGE_EXTENSIONS:
|
|
272
|
+
explicit_image_files.append(os.path.abspath(f))
|
|
273
|
+
else:
|
|
274
|
+
explicit_text_files.append(f)
|
|
275
|
+
|
|
276
|
+
# ── Режим обновления файла ────────────────────────────────────────────────
|
|
277
|
+
if args.update:
|
|
278
|
+
if not explicit_text_files:
|
|
279
|
+
print("Ошибка: укажите файл через -f")
|
|
280
|
+
sys.exit(1)
|
|
281
|
+
|
|
282
|
+
target_file = explicit_text_files[0]
|
|
283
|
+
if not os.path.exists(target_file):
|
|
284
|
+
print(f"Ошибка: Файл '{target_file}' не найден")
|
|
285
|
+
sys.exit(1)
|
|
286
|
+
|
|
287
|
+
file_content = read_file_content(target_file)
|
|
288
|
+
if file_content.startswith("[Ошибка"):
|
|
289
|
+
print(file_content)
|
|
290
|
+
sys.exit(1)
|
|
291
|
+
|
|
292
|
+
user_prompt = get_prompt_interactive()
|
|
293
|
+
if user_prompt:
|
|
294
|
+
prompt_text = f"Обнови следующий код согласно запросу: {user_prompt}\n\nТекущий код:\n{file_content}\n\nВыдай полностью обновленный код без комментариев"
|
|
295
|
+
else:
|
|
296
|
+
prompt_text = f"Улучши и оптимизируй следующий код:\n\n{file_content}\n\nВыдай полностью обновленный код без комментариев"
|
|
297
|
+
|
|
298
|
+
args.output = target_file
|
|
299
|
+
system_rules = SYSTEM_PROMPTS[args.system] if args.system else SYSTEM_CODE
|
|
300
|
+
messages = build_messages(system_rules, prompt_text, [], [])
|
|
301
|
+
|
|
302
|
+
else:
|
|
303
|
+
# ── Читаем промпт ─────────────────────────────────────────────────────
|
|
304
|
+
user_prompt = get_prompt_interactive()
|
|
305
|
+
if not user_prompt:
|
|
306
|
+
print("Промпт не может быть пустым.")
|
|
307
|
+
sys.exit(1)
|
|
308
|
+
|
|
309
|
+
# Авто-режим: ищем файлы упомянутые в промпте
|
|
310
|
+
auto_text, auto_images = extract_files_from_prompt(user_prompt, base_dir)
|
|
311
|
+
|
|
312
|
+
# Объединяем текстовые файлы
|
|
313
|
+
all_text_files = []
|
|
314
|
+
seen = set()
|
|
315
|
+
for f in explicit_text_files + auto_text:
|
|
316
|
+
full = os.path.abspath(f)
|
|
317
|
+
if full not in seen:
|
|
318
|
+
seen.add(full)
|
|
319
|
+
all_text_files.append(full)
|
|
320
|
+
|
|
321
|
+
# Объединяем изображения
|
|
322
|
+
all_image_files = []
|
|
323
|
+
seen_img = set()
|
|
324
|
+
for f in explicit_image_files + auto_images:
|
|
325
|
+
full = os.path.abspath(f)
|
|
326
|
+
if full not in seen_img:
|
|
327
|
+
seen_img.add(full)
|
|
328
|
+
all_image_files.append(full)
|
|
329
|
+
|
|
330
|
+
# Читаем текстовые файлы
|
|
331
|
+
text_contents = []
|
|
332
|
+
for f_path in all_text_files:
|
|
333
|
+
if not os.path.exists(f_path):
|
|
334
|
+
print(f"Ошибка: файл не найден: {f_path}")
|
|
335
|
+
continue
|
|
336
|
+
content = read_file_content(f_path)
|
|
337
|
+
if content.startswith("[Ошибка"):
|
|
338
|
+
print(f"Проблема с файлом {f_path}: {content}")
|
|
339
|
+
else:
|
|
340
|
+
print(f"[Файл загружен] {os.path.basename(f_path)} ({len(content)} символов)")
|
|
341
|
+
text_contents.append(
|
|
342
|
+
f"=== СОДЕРЖИМОЕ ФАЙЛА: {os.path.basename(f_path)} ===\n{content}\n=== КОНЕЦ ФАЙЛА ==="
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
# Выбор системного промпта
|
|
346
|
+
if args.system:
|
|
347
|
+
system_rules = SYSTEM_PROMPTS[args.system]
|
|
348
|
+
else:
|
|
349
|
+
system_rules = detect_system_prompt(args.output, user_prompt)
|
|
350
|
+
|
|
351
|
+
messages = build_messages(system_rules, user_prompt, text_contents, all_image_files)
|
|
352
|
+
|
|
353
|
+
# ── Отправка запроса ──────────────────────────────────────────────────────
|
|
354
|
+
client = OpenAI(
|
|
355
|
+
base_url="https://openrouter.ai/api/v1",
|
|
356
|
+
api_key=api_key,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
print("\nДумаю...")
|
|
360
|
+
|
|
361
|
+
try:
|
|
362
|
+
response = client.chat.completions.create(
|
|
363
|
+
model=args.model,
|
|
364
|
+
messages=messages
|
|
365
|
+
)
|
|
366
|
+
answer = response.choices[0].message.content
|
|
367
|
+
|
|
368
|
+
output_dir = os.path.dirname(args.output)
|
|
369
|
+
if output_dir and not os.path.exists(output_dir):
|
|
370
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
371
|
+
|
|
372
|
+
with open(args.output, "w", encoding="utf-8") as f:
|
|
373
|
+
f.write(answer)
|
|
374
|
+
|
|
375
|
+
BLUE = '\033[94m'
|
|
376
|
+
RED = '\033[91m'
|
|
377
|
+
GREEN = '\033[92m'
|
|
378
|
+
RESET = '\033[0m'
|
|
379
|
+
print(f"{BLUE}[notice]{RESET} A new release of pip is available: {RED}24.0{RESET} -> {GREEN}25.0.1{RESET}")
|
|
380
|
+
print(f"{BLUE}[notice]{RESET} To update, run: {GREEN}python3.8 -m pip install --upgrade pip{RESET}")
|
|
381
|
+
|
|
382
|
+
except Exception as e:
|
|
383
|
+
print(f"Произошла ошибка при обращении к API: {e}")
|
|
384
|
+
|
|
385
|
+
if __name__ == "__main__":
|
|
386
|
+
main()
|
tool/crypto_key.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import base64
|
|
3
|
+
import hashlib
|
|
4
|
+
|
|
5
|
+
def derive_key(password: str) -> bytes:
|
|
6
|
+
# Генерируем 256-битный ключ из пароля
|
|
7
|
+
return hashlib.sha256(password.encode('utf-8')).digest()
|
|
8
|
+
|
|
9
|
+
def xor_crypt(text_bytes: bytes, key_bytes: bytes) -> bytes:
|
|
10
|
+
# Применяем операцию XOR между байтами текста и ключа
|
|
11
|
+
extended_key = key_bytes * (len(text_bytes) // len(key_bytes) + 1)
|
|
12
|
+
return bytes(a ^ b for a, b in zip(text_bytes, extended_key))
|
|
13
|
+
|
|
14
|
+
def encrypt(api_key: str, password: str) -> str:
|
|
15
|
+
key = derive_key(password)
|
|
16
|
+
encrypted_bytes = xor_crypt(api_key.encode('utf-8'), key)
|
|
17
|
+
# Кодируем в Base64 (urlsafe), чтобы ключ можно было безопасно передавать
|
|
18
|
+
return base64.urlsafe_b64encode(encrypted_bytes).decode('utf-8')
|
|
19
|
+
|
|
20
|
+
def decrypt(cipher_text: str, password: str) -> str:
|
|
21
|
+
key = derive_key(password)
|
|
22
|
+
encrypted_bytes = base64.urlsafe_b64decode(cipher_text.encode('utf-8'))
|
|
23
|
+
decrypted_bytes = xor_crypt(encrypted_bytes, key)
|
|
24
|
+
return decrypted_bytes.decode('utf-8')
|
|
25
|
+
|
|
26
|
+
def main():
|
|
27
|
+
parser = argparse.ArgumentParser(description="Кастомный CLI шифратор/дешифратор для API ключей")
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"action",
|
|
30
|
+
choices=["encrypt", "decrypt"],
|
|
31
|
+
help="Действие: encrypt (зашифровать) или decrypt (расшифровать)"
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"text",
|
|
35
|
+
help="Текст для обработки (сам API ключ или зашифрованная строка)"
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"-p", "--password",
|
|
39
|
+
required=True,
|
|
40
|
+
help="Мастер-пароль для шифрования/дешифрования"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
args = parser.parse_args()
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
if args.action == "encrypt":
|
|
47
|
+
result = encrypt(args.text, args.password)
|
|
48
|
+
print("\n=== Успешно зашифровано ===")
|
|
49
|
+
print(f"Результат: {result}\n")
|
|
50
|
+
elif args.action == "decrypt":
|
|
51
|
+
result = decrypt(args.text, args.password)
|
|
52
|
+
print("\n=== Успешно расшифровано ===")
|
|
53
|
+
print(f"Результат: {result}\n")
|
|
54
|
+
except Exception as e:
|
|
55
|
+
print(f"\n[Ошибка] Проверьте правильность зашифрованной строки или пароля. Детали: {e}\n")
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
main()
|
tool/k.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ZL3h9GqkWS0yVcFcwJp6pTLzZ5yMULAM9IVS-rfypEIg4f2jergXLC4CxluQyXH0NvUxmNYF41mviA-u7PCkGXbj_KIrvRgkKA==
|