pycraft-tools 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.
help_manager.py
ADDED
|
@@ -0,0 +1,4335 @@
|
|
|
1
|
+
"""
|
|
2
|
+
================================================================================
|
|
3
|
+
📦 ПОМОЩНИК ДЛЯ PYTHON (help_manager.py)
|
|
4
|
+
================================================================================
|
|
5
|
+
Всё в одном файле: голос, базы данных, музыка, парсинг, автоматизация и ИИ.
|
|
6
|
+
Автор: Юсуф
|
|
7
|
+
================================================================================
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# =============================================================================
|
|
11
|
+
# ВСТРОЕННЫЕ МОДУЛИ (лёгкие, импортируем сразу)
|
|
12
|
+
# =============================================================================
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import time
|
|
16
|
+
import random
|
|
17
|
+
import functools
|
|
18
|
+
import threading
|
|
19
|
+
import json
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
import socket
|
|
22
|
+
import subprocess
|
|
23
|
+
import webbrowser
|
|
24
|
+
import tempfile
|
|
25
|
+
import sqlite3
|
|
26
|
+
import math
|
|
27
|
+
import statistics
|
|
28
|
+
import string
|
|
29
|
+
from typing import Any, Callable, Optional, Tuple, List, Union
|
|
30
|
+
from urllib.parse import quote
|
|
31
|
+
import shutil
|
|
32
|
+
|
|
33
|
+
# =============================================================================
|
|
34
|
+
# КЛАСС Manager – 30+ УНИВЕРСАЛЬНЫХ ФУНКЦИЙ
|
|
35
|
+
# =============================================================================
|
|
36
|
+
class Manager:
|
|
37
|
+
"""
|
|
38
|
+
⚡ СТАТИЧЕСКИЕ МЕТОДЫ — вызывай напрямую без создания объекта:
|
|
39
|
+
Manager.название_функции()
|
|
40
|
+
|
|
41
|
+
🎯 Возможности класса:
|
|
42
|
+
- Парсинг веб-страниц и получение погоды
|
|
43
|
+
- Голосовой ввод и озвучка текста
|
|
44
|
+
- Воспроизведение музыки и звуковые сигналы
|
|
45
|
+
- Работа с файлами и JSON
|
|
46
|
+
- Время и дата в любом формате
|
|
47
|
+
- Генерация случайных чисел и выбор случайных элементов
|
|
48
|
+
- Проверка интернета и скачивание файлов
|
|
49
|
+
- Очистка консоли и задержки
|
|
50
|
+
- Поиск и открытие видео на YouTube
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
# ------------------------------ 🌐 ПАРСИНГ И ПОГОДА ------------------------------
|
|
54
|
+
@staticmethod
|
|
55
|
+
def get_text_with_url(url, class_name):
|
|
56
|
+
"""
|
|
57
|
+
🔍 ПАРСИНГ: Извлекает текст элемента по CSS-классу со страницы.
|
|
58
|
+
|
|
59
|
+
Параметры:
|
|
60
|
+
url (str): полный URL страницы (например, "https://example.com")
|
|
61
|
+
class_name (str): имя CSS-класса, содержимое которого нужно извлечь
|
|
62
|
+
|
|
63
|
+
Возвращает:
|
|
64
|
+
str: текст элемента или сообщение об ошибке
|
|
65
|
+
|
|
66
|
+
📝 Пример:
|
|
67
|
+
text = Manager.get_text_with_url("https://example.com", "article__title")
|
|
68
|
+
print(text) # Заголовок статьи
|
|
69
|
+
|
|
70
|
+
🎯 Возможности:
|
|
71
|
+
- Извлечение цен, названий, описаний с сайтов
|
|
72
|
+
- Сбор информации для ботов и парсеров
|
|
73
|
+
"""
|
|
74
|
+
import requests
|
|
75
|
+
from bs4 import BeautifulSoup
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
encoded_url = quote(url, safe=':/?&=')
|
|
79
|
+
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}
|
|
80
|
+
response = requests.get(encoded_url, headers=headers, timeout=10)
|
|
81
|
+
response.raise_for_status()
|
|
82
|
+
soup = BeautifulSoup(response.content, 'html.parser')
|
|
83
|
+
element = soup.find(class_=class_name)
|
|
84
|
+
return element.text.strip() if element else f"❌ Класс '{class_name}' не найден"
|
|
85
|
+
except Exception as e:
|
|
86
|
+
return f"❌ Ошибка: {e}"
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def get_weather(city="Москва", api="28c9d95c5e0b423d23e81c1d43c10cf0"):
|
|
90
|
+
"""
|
|
91
|
+
☁️ ПОГОДА: Возвращает словарь с температурой, ветром, влажностью.
|
|
92
|
+
|
|
93
|
+
Параметры:
|
|
94
|
+
city (str): название города (по умолчанию "Москва")
|
|
95
|
+
api (str): API-ключ OpenWeatherMap (встроен запасной)
|
|
96
|
+
|
|
97
|
+
Возвращает:
|
|
98
|
+
dict: словарь с ключами "Температура", "Ощущается", "Описание",
|
|
99
|
+
"Влажность", "Ветер" или строку с ошибкой
|
|
100
|
+
|
|
101
|
+
📝 Пример:
|
|
102
|
+
weather = Manager.get_weather("Москва")
|
|
103
|
+
print(weather["Температура"]) # +15 °C
|
|
104
|
+
print(weather["Влажность"]) # 65 %
|
|
105
|
+
|
|
106
|
+
🎯 Возможности:
|
|
107
|
+
- Прогноз для любого города мира
|
|
108
|
+
- Можно встроить в голосового ассистента
|
|
109
|
+
- Автоматические уведомления о погоде
|
|
110
|
+
"""
|
|
111
|
+
import requests
|
|
112
|
+
|
|
113
|
+
url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={api}&units=metric&lang=ru"
|
|
114
|
+
try:
|
|
115
|
+
response = requests.get(url, timeout=10)
|
|
116
|
+
response.raise_for_status()
|
|
117
|
+
data = response.json()
|
|
118
|
+
return {
|
|
119
|
+
"Температура": f"{round(data['main']['temp'])} °C",
|
|
120
|
+
"Ощущается": f"{round(data['main']['feels_like'])} °C",
|
|
121
|
+
"Описание": data['weather'][0]['description'],
|
|
122
|
+
"Влажность": f"{data['main']['humidity']} %",
|
|
123
|
+
"Ветер": f"{data['wind']['speed']} м/с"
|
|
124
|
+
}
|
|
125
|
+
except Exception as e:
|
|
126
|
+
return f"❌ Ошибка погоды: {e}"
|
|
127
|
+
|
|
128
|
+
# ------------------------------ 🎙️ ГОЛОС И ЗВУК ------------------------------
|
|
129
|
+
@staticmethod
|
|
130
|
+
def listen_text(seconds=5):
|
|
131
|
+
"""
|
|
132
|
+
🎤 ГОЛОСОВОЙ ВВОД: Слушает микрофон и возвращает распознанный текст.
|
|
133
|
+
|
|
134
|
+
Параметры:
|
|
135
|
+
seconds (int): сколько секунд слушать (по умолчанию 5)
|
|
136
|
+
|
|
137
|
+
Возвращает:
|
|
138
|
+
str: распознанный текст или пустую строку, если ничего не услышано
|
|
139
|
+
|
|
140
|
+
📝 Пример:
|
|
141
|
+
command = Manager.listen_text(5)
|
|
142
|
+
if command:
|
|
143
|
+
print(f"Вы сказали: {command}")
|
|
144
|
+
else:
|
|
145
|
+
print("Ничего не услышано")
|
|
146
|
+
|
|
147
|
+
🎯 Возможности:
|
|
148
|
+
- Голосовое управление ботом
|
|
149
|
+
- Команды для ассистента
|
|
150
|
+
- Распознавание русского языка
|
|
151
|
+
- Автоматическая настройка шумоподавления
|
|
152
|
+
"""
|
|
153
|
+
import speech_recognition as sr
|
|
154
|
+
|
|
155
|
+
r = sr.Recognizer()
|
|
156
|
+
try:
|
|
157
|
+
with sr.Microphone() as source:
|
|
158
|
+
r.adjust_for_ambient_noise(source)
|
|
159
|
+
audio = r.listen(source, timeout=seconds, phrase_time_limit=seconds)
|
|
160
|
+
return r.recognize_google(audio, language="ru-RU")
|
|
161
|
+
except:
|
|
162
|
+
return ""
|
|
163
|
+
|
|
164
|
+
@staticmethod
|
|
165
|
+
def say(text, lang='ru'):
|
|
166
|
+
"""
|
|
167
|
+
🔊 ОЗВУЧКА: Превращает текст в речь с помощью Google TTS.
|
|
168
|
+
|
|
169
|
+
Параметры:
|
|
170
|
+
text (str): текст для озвучки
|
|
171
|
+
lang (str): язык ('ru' — русский, 'en' — английский)
|
|
172
|
+
|
|
173
|
+
📝 Пример:
|
|
174
|
+
Manager.say("Привет, я твой помощник!")
|
|
175
|
+
Manager.say("Hello, I am your assistant!", lang='en')
|
|
176
|
+
|
|
177
|
+
🎯 Возможности:
|
|
178
|
+
- Голосовые подсказки и приветствия
|
|
179
|
+
- Озвучка уведомлений
|
|
180
|
+
- Поддержка множества языков
|
|
181
|
+
- Автоматическое удаление временных файлов
|
|
182
|
+
"""
|
|
183
|
+
from gtts import gTTS
|
|
184
|
+
from pydub import AudioSegment
|
|
185
|
+
from pydub.playback import play
|
|
186
|
+
|
|
187
|
+
temp_file = None
|
|
188
|
+
try:
|
|
189
|
+
with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as fp:
|
|
190
|
+
temp_file = fp.name
|
|
191
|
+
tts = gTTS(text=text, lang=lang)
|
|
192
|
+
tts.save(temp_file)
|
|
193
|
+
sound = AudioSegment.from_mp3(temp_file)
|
|
194
|
+
play(sound)
|
|
195
|
+
finally:
|
|
196
|
+
if temp_file and os.path.exists(temp_file):
|
|
197
|
+
os.unlink(temp_file)
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
def play_music(file_path):
|
|
201
|
+
"""
|
|
202
|
+
🎵 МУЗЫКА: Воспроизводит аудиофайл (MP3, WAV, OGG) и ждёт окончания.
|
|
203
|
+
|
|
204
|
+
Параметры:
|
|
205
|
+
file_path (str): полный путь к аудиофайлу
|
|
206
|
+
|
|
207
|
+
📝 Пример:
|
|
208
|
+
Manager.play_music("C:/Music/song.mp3")
|
|
209
|
+
Manager.play_music("background.wav")
|
|
210
|
+
|
|
211
|
+
🎯 Возможности:
|
|
212
|
+
- Фоновое воспроизведение музыки
|
|
213
|
+
- Поддержка основных аудиоформатов (MP3, WAV, OGG)
|
|
214
|
+
- Блокирует выполнение до окончания трека
|
|
215
|
+
"""
|
|
216
|
+
import pygame
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
pygame.mixer.init()
|
|
220
|
+
pygame.mixer.music.load(file_path)
|
|
221
|
+
pygame.mixer.music.play()
|
|
222
|
+
while pygame.mixer.music.get_busy():
|
|
223
|
+
time.sleep(0.1)
|
|
224
|
+
except Exception as e:
|
|
225
|
+
print(f"❌ Ошибка воспроизведения: {e}")
|
|
226
|
+
|
|
227
|
+
# ------------------------------ 📂 ФАЙЛЫ И ПУТИ ------------------------------
|
|
228
|
+
@staticmethod
|
|
229
|
+
def search_file_path(relative_path):
|
|
230
|
+
"""
|
|
231
|
+
📁 ПОИСК ФАЙЛА: Возвращает полный путь к файлу относительно папки скрипта.
|
|
232
|
+
|
|
233
|
+
Параметры:
|
|
234
|
+
relative_path (str): относительный путь к файлу (например, "config.json")
|
|
235
|
+
|
|
236
|
+
Возвращает:
|
|
237
|
+
str: полный абсолютный путь к файлу
|
|
238
|
+
|
|
239
|
+
📝 Пример:
|
|
240
|
+
full_path = Manager.search_file_path("config.json")
|
|
241
|
+
# Вернёт что-то вроде: C:\\Users\\Yusuf\\Programming\\Пайтон\\projects\\config.json
|
|
242
|
+
|
|
243
|
+
🎯 Возможности:
|
|
244
|
+
- Удобная работа с файлами в проекте
|
|
245
|
+
- Не нужно прописывать длинные абсолютные пути
|
|
246
|
+
- Кроссплатформенность (Windows/Linux/Mac)
|
|
247
|
+
"""
|
|
248
|
+
base = os.path.dirname(os.path.abspath(__file__))
|
|
249
|
+
return os.path.join(base, relative_path)
|
|
250
|
+
|
|
251
|
+
@staticmethod
|
|
252
|
+
def open_file(relative_path):
|
|
253
|
+
"""
|
|
254
|
+
🚀 ЗАПУСК ФАЙЛА: Открывает Python-скрипт в новом окне консоли.
|
|
255
|
+
|
|
256
|
+
Параметры:
|
|
257
|
+
relative_path (str): путь к Python-файлу относительно папки скрипта
|
|
258
|
+
|
|
259
|
+
📝 Пример:
|
|
260
|
+
Manager.open_file("bot.py")
|
|
261
|
+
Manager.open_file("utils/helper.py")
|
|
262
|
+
|
|
263
|
+
🎯 Возможности:
|
|
264
|
+
- Запуск модулей из главного скрипта
|
|
265
|
+
- Параллельная работа нескольких программ
|
|
266
|
+
- Новое окно консоли для каждого запуска
|
|
267
|
+
"""
|
|
268
|
+
full = Manager.search_file_path(relative_path)
|
|
269
|
+
os.system(f'start cmd /k python "{full}"')
|
|
270
|
+
|
|
271
|
+
@staticmethod
|
|
272
|
+
def search_and_open_youtube(query, max_results=1):
|
|
273
|
+
"""
|
|
274
|
+
🔍 ОТКРЫТЬ ВИДЕО НА YOUTUBE: Ищет видео по запросу и открывает в браузере.
|
|
275
|
+
|
|
276
|
+
Параметры:
|
|
277
|
+
query (str): строка для поиска (название песни, видео и т.д.)
|
|
278
|
+
max_results (int): сколько результатов искать (по умолчанию 1)
|
|
279
|
+
|
|
280
|
+
Возвращает:
|
|
281
|
+
str или False: URL открытого видео или False, если ничего не найдено
|
|
282
|
+
|
|
283
|
+
📝 Пример:
|
|
284
|
+
url = Manager.search_and_open_youtube("Morgenshtern Cadillac")
|
|
285
|
+
if url:
|
|
286
|
+
print(f"Открыто: {url}")
|
|
287
|
+
|
|
288
|
+
🎯 Возможности:
|
|
289
|
+
- Быстрый доступ к музыке и видео
|
|
290
|
+
- Можно использовать в голосовом ассистенте
|
|
291
|
+
"""
|
|
292
|
+
from youtube_search import YoutubeSearch
|
|
293
|
+
|
|
294
|
+
results = YoutubeSearch(query, max_results=max_results).to_dict()
|
|
295
|
+
if results:
|
|
296
|
+
video_url = f"https://youtube.com{results[0]['url_suffix']}"
|
|
297
|
+
webbrowser.open(video_url)
|
|
298
|
+
return video_url
|
|
299
|
+
return False
|
|
300
|
+
|
|
301
|
+
@staticmethod
|
|
302
|
+
def load_json(relative_path):
|
|
303
|
+
"""
|
|
304
|
+
📂 ЗАГРУЗКА JSON: Читает JSON-файл и возвращает словарь.
|
|
305
|
+
|
|
306
|
+
Параметры:
|
|
307
|
+
relative_path (str): путь к JSON-файлу относительно папки скрипта
|
|
308
|
+
|
|
309
|
+
Возвращает:
|
|
310
|
+
dict или None: содержимое JSON-файла в виде словаря, или None при ошибке
|
|
311
|
+
|
|
312
|
+
📝 Пример:
|
|
313
|
+
data = Manager.load_json("settings.json")
|
|
314
|
+
if data:
|
|
315
|
+
print(data["theme"]) # "dark"
|
|
316
|
+
print(data["volume"]) # 80
|
|
317
|
+
|
|
318
|
+
🎯 Возможности:
|
|
319
|
+
- Хранение настроек приложения
|
|
320
|
+
- Сохранение данных бота
|
|
321
|
+
- Чтение конфигурационных файлов
|
|
322
|
+
"""
|
|
323
|
+
full = Manager.search_file_path(relative_path)
|
|
324
|
+
try:
|
|
325
|
+
with open(full, 'r', encoding='utf-8') as f:
|
|
326
|
+
return json.load(f)
|
|
327
|
+
except Exception as e:
|
|
328
|
+
print(f"❌ Ошибка загрузки JSON: {e}")
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
@staticmethod
|
|
332
|
+
def save_json(relative_path, data, mode="w"):
|
|
333
|
+
"""
|
|
334
|
+
💾 СОХРАНЕНИЕ JSON: Записывает данные в JSON-файл.
|
|
335
|
+
|
|
336
|
+
Параметры:
|
|
337
|
+
relative_path (str): путь к JSON-файлу относительно папки скрипта
|
|
338
|
+
data (dict): данные для сохранения
|
|
339
|
+
mode (str): режим записи ("w" — перезапись, "a" — добавление)
|
|
340
|
+
|
|
341
|
+
Возвращает:
|
|
342
|
+
bool: True при успехе, False при ошибке
|
|
343
|
+
|
|
344
|
+
📝 Пример:
|
|
345
|
+
settings = {"theme": "dark", "volume": 80}
|
|
346
|
+
Manager.save_json("settings.json", settings)
|
|
347
|
+
print("Настройки сохранены!")
|
|
348
|
+
|
|
349
|
+
🎯 Возможности:
|
|
350
|
+
- Сохранение прогресса игрока
|
|
351
|
+
- Экспорт данных
|
|
352
|
+
- Создание резервных копий
|
|
353
|
+
"""
|
|
354
|
+
full = Manager.search_file_path(relative_path)
|
|
355
|
+
try:
|
|
356
|
+
with open(full, mode, encoding='utf-8') as f:
|
|
357
|
+
json.dump(data, f, ensure_ascii=False, indent=4)
|
|
358
|
+
return True
|
|
359
|
+
except Exception as e:
|
|
360
|
+
print(f"❌ Ошибка сохранения JSON: {e}")
|
|
361
|
+
return False
|
|
362
|
+
|
|
363
|
+
# ------------------------------ ⏰ ВРЕМЯ И ДАТА ------------------------------
|
|
364
|
+
@staticmethod
|
|
365
|
+
def now(format="%Y-%m-%d %H:%M:%S"):
|
|
366
|
+
"""
|
|
367
|
+
🕐 ТЕКУЩЕЕ ВРЕМЯ: Возвращает дату и время в нужном формате.
|
|
368
|
+
|
|
369
|
+
Параметры:
|
|
370
|
+
format (str): формат даты/времени (по умолчанию "ГГГГ-ММ-ДД ЧЧ:ММ:СС")
|
|
371
|
+
|
|
372
|
+
Возвращает:
|
|
373
|
+
str: отформатированная строка с текущим временем
|
|
374
|
+
|
|
375
|
+
📝 Пример:
|
|
376
|
+
print(Manager.now()) # 2026-04-03 15:30:00
|
|
377
|
+
print(Manager.now("%H:%M")) # 15:30
|
|
378
|
+
print(Manager.now("%d.%m.%Y")) # 03.04.2026
|
|
379
|
+
|
|
380
|
+
🎯 Возможности:
|
|
381
|
+
- Логирование событий с временными метками
|
|
382
|
+
- Отображение времени в интерфейсе
|
|
383
|
+
- Создание уникальных имён файлов
|
|
384
|
+
"""
|
|
385
|
+
return datetime.now().strftime(format)
|
|
386
|
+
|
|
387
|
+
@staticmethod
|
|
388
|
+
def today():
|
|
389
|
+
"""
|
|
390
|
+
📅 СЕГОДНЯШНЯЯ ДАТА: Возвращает строку в формате ГГГГ-ММ-ДД.
|
|
391
|
+
|
|
392
|
+
Возвращает:
|
|
393
|
+
str: сегодняшняя дата (например, "2026-04-03")
|
|
394
|
+
|
|
395
|
+
📝 Пример:
|
|
396
|
+
date = Manager.today()
|
|
397
|
+
print(f"Сегодня: {date}") # Сегодня: 2026-04-03
|
|
398
|
+
|
|
399
|
+
# Использование в имени файла
|
|
400
|
+
backup_name = f"backup_{Manager.today()}.json"
|
|
401
|
+
Manager.save_json(backup_name, data)
|
|
402
|
+
|
|
403
|
+
🎯 Возможности:
|
|
404
|
+
- Формирование ежедневных отчётов
|
|
405
|
+
- Именование бэкапов по дате
|
|
406
|
+
- Проверка актуальности данных
|
|
407
|
+
"""
|
|
408
|
+
return datetime.now().strftime("%Y-%m-%d")
|
|
409
|
+
|
|
410
|
+
# ------------------------------ 🎲 СЛУЧАЙНОСТИ ------------------------------
|
|
411
|
+
@staticmethod
|
|
412
|
+
def randint(a, b):
|
|
413
|
+
"""
|
|
414
|
+
🎲 СЛУЧАЙНОЕ ЧИСЛО: Возвращает случайное целое число в диапазоне от a до b (включительно).
|
|
415
|
+
|
|
416
|
+
Параметры:
|
|
417
|
+
a (int): нижняя граница диапазона
|
|
418
|
+
b (int): верхняя граница диапазона
|
|
419
|
+
|
|
420
|
+
Возвращает:
|
|
421
|
+
int: случайное целое число
|
|
422
|
+
|
|
423
|
+
📝 Пример:
|
|
424
|
+
dice = Manager.randint(1, 6)
|
|
425
|
+
print(f"На кубике выпало: {dice}")
|
|
426
|
+
|
|
427
|
+
bonus = Manager.randint(10, 50)
|
|
428
|
+
print(f"Случайный бонус: +{bonus} золота")
|
|
429
|
+
|
|
430
|
+
🎯 Возможности:
|
|
431
|
+
- Генерация бонусов в игре
|
|
432
|
+
- Создание случайных паролей
|
|
433
|
+
- Случайные задержки в автоматизации
|
|
434
|
+
"""
|
|
435
|
+
return random.randint(a, b)
|
|
436
|
+
|
|
437
|
+
@staticmethod
|
|
438
|
+
def choice(seq):
|
|
439
|
+
"""
|
|
440
|
+
🎲 СЛУЧАЙНЫЙ ВЫБОР: Возвращает случайный элемент из последовательности.
|
|
441
|
+
|
|
442
|
+
Параметры:
|
|
443
|
+
seq (list/tuple/str): последовательность элементов
|
|
444
|
+
|
|
445
|
+
Возвращает:
|
|
446
|
+
любой тип: случайный элемент из последовательности
|
|
447
|
+
|
|
448
|
+
📝 Пример:
|
|
449
|
+
fruit = Manager.choice(["яблоко", "банан", "груша"])
|
|
450
|
+
print(f"Сегодня едим: {fruit}")
|
|
451
|
+
|
|
452
|
+
# Случайный ответ бота
|
|
453
|
+
answers = ["Привет!", "Здравствуй!", "Рад тебя видеть!"]
|
|
454
|
+
greeting = Manager.choice(answers)
|
|
455
|
+
|
|
456
|
+
🎯 Возможности:
|
|
457
|
+
- Случайные ответы бота
|
|
458
|
+
- Выбор случайного задания или уровня
|
|
459
|
+
- Разнообразие в автоматических сообщениях
|
|
460
|
+
"""
|
|
461
|
+
return random.choice(seq)
|
|
462
|
+
|
|
463
|
+
# ------------------------------ 🌐 СЕТЬ ------------------------------
|
|
464
|
+
@staticmethod
|
|
465
|
+
def is_connected():
|
|
466
|
+
"""
|
|
467
|
+
🌐 ПРОВЕРКА ИНТЕРНЕТА: Проверяет наличие интернет-соединения.
|
|
468
|
+
|
|
469
|
+
Возвращает:
|
|
470
|
+
bool: True если интернет есть, False если нет
|
|
471
|
+
|
|
472
|
+
📝 Пример:
|
|
473
|
+
if Manager.is_connected():
|
|
474
|
+
weather = Manager.get_weather("Москва")
|
|
475
|
+
print(weather)
|
|
476
|
+
else:
|
|
477
|
+
print("Нет интернета, работаем офлайн")
|
|
478
|
+
|
|
479
|
+
🎯 Возможности:
|
|
480
|
+
- Проверка перед API-запросами
|
|
481
|
+
- Переключение в офлайн-режим
|
|
482
|
+
- Диагностика сетевых проблем
|
|
483
|
+
"""
|
|
484
|
+
try:
|
|
485
|
+
socket.create_connection(("8.8.8.8", 53), timeout=3)
|
|
486
|
+
return True
|
|
487
|
+
except:
|
|
488
|
+
return False
|
|
489
|
+
|
|
490
|
+
@staticmethod
|
|
491
|
+
def download_file(url, save_path):
|
|
492
|
+
"""
|
|
493
|
+
📥 СКАЧИВАНИЕ ФАЙЛА: Загружает файл из интернета и сохраняет локально.
|
|
494
|
+
|
|
495
|
+
Параметры:
|
|
496
|
+
url (str): прямая ссылка на файл
|
|
497
|
+
save_path (str): путь для сохранения относительно папки скрипта
|
|
498
|
+
|
|
499
|
+
Возвращает:
|
|
500
|
+
bool: True при успешном скачивании, False при ошибке
|
|
501
|
+
|
|
502
|
+
📝 Пример:
|
|
503
|
+
success = Manager.download_file(
|
|
504
|
+
"https://site.com/photo.jpg",
|
|
505
|
+
"images/photo.jpg"
|
|
506
|
+
)
|
|
507
|
+
if success:
|
|
508
|
+
print("Файл скачан!")
|
|
509
|
+
|
|
510
|
+
🎯 Возможности:
|
|
511
|
+
- Обновление данных бота
|
|
512
|
+
- Скачивание обновлений программы
|
|
513
|
+
- Сохранение изображений и документов
|
|
514
|
+
"""
|
|
515
|
+
import requests
|
|
516
|
+
|
|
517
|
+
try:
|
|
518
|
+
response = requests.get(url, stream=True, timeout=15)
|
|
519
|
+
response.raise_for_status()
|
|
520
|
+
full = Manager.search_file_path(save_path)
|
|
521
|
+
with open(full, 'wb') as f:
|
|
522
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
523
|
+
f.write(chunk)
|
|
524
|
+
return True
|
|
525
|
+
except Exception as e:
|
|
526
|
+
print(f"❌ Ошибка скачивания: {e}")
|
|
527
|
+
return False
|
|
528
|
+
|
|
529
|
+
# ------------------------------ 🛠️ ПРОЧЕЕ ------------------------------
|
|
530
|
+
@staticmethod
|
|
531
|
+
def clear_console():
|
|
532
|
+
"""
|
|
533
|
+
🧹 ОЧИСТКА КОНСОЛИ: Очищает экран терминала.
|
|
534
|
+
|
|
535
|
+
📝 Пример:
|
|
536
|
+
Manager.clear_console()
|
|
537
|
+
print("Новый чистый вывод!")
|
|
538
|
+
|
|
539
|
+
🎯 Возможности:
|
|
540
|
+
- Красивый интерфейс командной строки
|
|
541
|
+
- Скрытие старых сообщений
|
|
542
|
+
- Подготовка к новому выводу
|
|
543
|
+
"""
|
|
544
|
+
os.system('cls' if os.name == 'nt' else 'clear')
|
|
545
|
+
|
|
546
|
+
@staticmethod
|
|
547
|
+
def wait(seconds, message="Ждём..."):
|
|
548
|
+
"""
|
|
549
|
+
⏳ ЗАДЕРЖКА: Пауза на указанное время с отображением сообщения.
|
|
550
|
+
|
|
551
|
+
Параметры:
|
|
552
|
+
seconds (int/float): время задержки в секундах
|
|
553
|
+
message (str): сообщение, которое будет показано на время ожидания
|
|
554
|
+
|
|
555
|
+
📝 Пример:
|
|
556
|
+
Manager.wait(3, "Загрузка данных...")
|
|
557
|
+
print("Готово!")
|
|
558
|
+
|
|
559
|
+
Manager.wait(1.5) # просто подождать полторы секунды
|
|
560
|
+
|
|
561
|
+
🎯 Возможности:
|
|
562
|
+
- Паузы между действиями в автоматизации
|
|
563
|
+
- Ожидание загрузки данных
|
|
564
|
+
- Таймеры обратного отсчёта
|
|
565
|
+
"""
|
|
566
|
+
print(message)
|
|
567
|
+
time.sleep(seconds)
|
|
568
|
+
|
|
569
|
+
@staticmethod
|
|
570
|
+
def beep(Gh=1000, time=500):
|
|
571
|
+
"""
|
|
572
|
+
🔊 СИГНАЛ: Издаёт системный звуковой сигнал.
|
|
573
|
+
|
|
574
|
+
Параметры:
|
|
575
|
+
Gh (int): частота звука в Герцах (по умолчанию 1000)
|
|
576
|
+
time (int): длительность в миллисекундах (по умолчанию 500)
|
|
577
|
+
|
|
578
|
+
📝 Пример:
|
|
579
|
+
Manager.beep(1000, 500) # звук 1000 Гц длительностью 500 мс
|
|
580
|
+
Manager.beep(500, 200) # низкий короткий сигнал
|
|
581
|
+
Manager.beep(2000, 1000) # высокий длинный сигнал
|
|
582
|
+
|
|
583
|
+
🎯 Возможности:
|
|
584
|
+
- Звуковые уведомления об ошибках
|
|
585
|
+
- Сигналы о завершении длительных операций
|
|
586
|
+
- Звуковая обратная связь в программах
|
|
587
|
+
"""
|
|
588
|
+
from winsound import Beep
|
|
589
|
+
|
|
590
|
+
try:
|
|
591
|
+
Beep(Gh, time)
|
|
592
|
+
except:
|
|
593
|
+
print("❌ Не удалось воспроизвести звуковой сигнал")
|
|
594
|
+
|
|
595
|
+
# =============================================================================
|
|
596
|
+
# КЛАСС FolderUtils – РАБОТА С ПАПКАМИ И ФАЙЛОВОЙ СИСТЕМОЙ
|
|
597
|
+
# =============================================================================
|
|
598
|
+
class FolderUtils:
|
|
599
|
+
"""
|
|
600
|
+
📁 РАБОТА С ПАПКАМИ: создание, перемещение, поиск, информация.
|
|
601
|
+
|
|
602
|
+
📝 Примеры:
|
|
603
|
+
# Создать папку
|
|
604
|
+
FolderUtils.create("my_project/scripts")
|
|
605
|
+
|
|
606
|
+
# Переместить файл
|
|
607
|
+
FolderUtils.move("old.txt", "archive/old.txt")
|
|
608
|
+
|
|
609
|
+
# Найти все Python-файлы
|
|
610
|
+
py_files = FolderUtils.find_files(".", ".py")
|
|
611
|
+
|
|
612
|
+
# Получить имя файла из пути
|
|
613
|
+
name = FolderUtils.get_filename("C:/Users/file.txt") # "file.txt"
|
|
614
|
+
|
|
615
|
+
# Текущая директория
|
|
616
|
+
print(FolderUtils.current_dir())
|
|
617
|
+
|
|
618
|
+
# Размер папки
|
|
619
|
+
size = FolderUtils.get_size("my_project") # в байтах
|
|
620
|
+
|
|
621
|
+
🎯 Возможности:
|
|
622
|
+
- Создание и удаление папок
|
|
623
|
+
- Перемещение и копирование файлов/папок
|
|
624
|
+
- Поиск файлов по расширению
|
|
625
|
+
- Получение информации о файлах и папках
|
|
626
|
+
- Работа с путями
|
|
627
|
+
"""
|
|
628
|
+
|
|
629
|
+
@staticmethod
|
|
630
|
+
def create(path):
|
|
631
|
+
"""
|
|
632
|
+
📂 СОЗДАТЬ ПАПКУ: Создаёт папку (и все промежуточные, если нужно).
|
|
633
|
+
|
|
634
|
+
Параметры:
|
|
635
|
+
path (str): путь к создаваемой папке
|
|
636
|
+
|
|
637
|
+
Возвращает:
|
|
638
|
+
bool: True если создана, False если уже существует
|
|
639
|
+
|
|
640
|
+
📝 Пример:
|
|
641
|
+
FolderUtils.create("my_project/data")
|
|
642
|
+
FolderUtils.create("backups/2024/январь")
|
|
643
|
+
"""
|
|
644
|
+
import os
|
|
645
|
+
if not os.path.exists(path):
|
|
646
|
+
os.makedirs(path)
|
|
647
|
+
print(f"📂 Папка создана: {path}")
|
|
648
|
+
return True
|
|
649
|
+
else:
|
|
650
|
+
print(f"📂 Папка уже существует: {path}")
|
|
651
|
+
return False
|
|
652
|
+
|
|
653
|
+
@staticmethod
|
|
654
|
+
def delete(path):
|
|
655
|
+
"""
|
|
656
|
+
🗑️ УДАЛИТЬ ПАПКУ: Удаляет папку со всем содержимым (осторожно!).
|
|
657
|
+
|
|
658
|
+
Параметры:
|
|
659
|
+
path (str): путь к папке
|
|
660
|
+
|
|
661
|
+
Возвращает:
|
|
662
|
+
bool: True если удалена, False если ошибка
|
|
663
|
+
|
|
664
|
+
📝 Пример:
|
|
665
|
+
FolderUtils.delete("temp_files")
|
|
666
|
+
"""
|
|
667
|
+
import shutil
|
|
668
|
+
try:
|
|
669
|
+
if os.path.exists(path):
|
|
670
|
+
shutil.rmtree(path)
|
|
671
|
+
print(f"🗑️ Папка удалена: {path}")
|
|
672
|
+
return True
|
|
673
|
+
else:
|
|
674
|
+
print(f"❌ Папка не найдена: {path}")
|
|
675
|
+
return False
|
|
676
|
+
except Exception as e:
|
|
677
|
+
print(f"❌ Ошибка удаления: {e}")
|
|
678
|
+
return False
|
|
679
|
+
|
|
680
|
+
@staticmethod
|
|
681
|
+
def move(src, dst):
|
|
682
|
+
"""
|
|
683
|
+
📦 ПЕРЕМЕСТИТЬ: Перемещает файл или папку в новое место.
|
|
684
|
+
|
|
685
|
+
Параметры:
|
|
686
|
+
src (str): исходный путь
|
|
687
|
+
dst (str): путь назначения
|
|
688
|
+
|
|
689
|
+
Возвращает:
|
|
690
|
+
bool: True при успехе
|
|
691
|
+
|
|
692
|
+
📝 Пример:
|
|
693
|
+
FolderUtils.move("old_file.txt", "archive/old_file.txt")
|
|
694
|
+
FolderUtils.move("my_folder", "backup/my_folder")
|
|
695
|
+
"""
|
|
696
|
+
import shutil
|
|
697
|
+
try:
|
|
698
|
+
shutil.move(src, dst)
|
|
699
|
+
print(f"📦 Перемещено: {src} → {dst}")
|
|
700
|
+
return True
|
|
701
|
+
except Exception as e:
|
|
702
|
+
print(f"❌ Ошибка перемещения: {e}")
|
|
703
|
+
return False
|
|
704
|
+
|
|
705
|
+
@staticmethod
|
|
706
|
+
def copy(src, dst):
|
|
707
|
+
"""
|
|
708
|
+
📋 КОПИРОВАТЬ: Копирует файл или папку.
|
|
709
|
+
|
|
710
|
+
Параметры:
|
|
711
|
+
src (str): исходный путь
|
|
712
|
+
dst (str): путь назначения
|
|
713
|
+
|
|
714
|
+
Возвращает:
|
|
715
|
+
bool: True при успехе
|
|
716
|
+
|
|
717
|
+
📝 Пример:
|
|
718
|
+
FolderUtils.copy("config.json", "backup/config.json")
|
|
719
|
+
"""
|
|
720
|
+
import shutil
|
|
721
|
+
try:
|
|
722
|
+
if os.path.isdir(src):
|
|
723
|
+
shutil.copytree(src, dst)
|
|
724
|
+
else:
|
|
725
|
+
shutil.copy2(src, dst)
|
|
726
|
+
print(f"📋 Скопировано: {src} → {dst}")
|
|
727
|
+
return True
|
|
728
|
+
except Exception as e:
|
|
729
|
+
print(f"❌ Ошибка копирования: {e}")
|
|
730
|
+
return False
|
|
731
|
+
|
|
732
|
+
@staticmethod
|
|
733
|
+
def find_files(directory, extension=None):
|
|
734
|
+
"""
|
|
735
|
+
🔍 НАЙТИ ФАЙЛЫ: Ищет все файлы в папке (и подпапках).
|
|
736
|
+
|
|
737
|
+
Параметры:
|
|
738
|
+
directory (str): папка для поиска
|
|
739
|
+
extension (str, optional): фильтр по расширению (например, ".py", ".txt")
|
|
740
|
+
|
|
741
|
+
Возвращает:
|
|
742
|
+
list[str]: список полных путей к найденным файлам
|
|
743
|
+
|
|
744
|
+
📝 Пример:
|
|
745
|
+
# Все файлы
|
|
746
|
+
all_files = FolderUtils.find_files("my_project")
|
|
747
|
+
|
|
748
|
+
# Только Python-файлы
|
|
749
|
+
py_files = FolderUtils.find_files("my_project", ".py")
|
|
750
|
+
|
|
751
|
+
# Только текстовые
|
|
752
|
+
txt_files = FolderUtils.find_files("my_project", ".txt")
|
|
753
|
+
"""
|
|
754
|
+
import os
|
|
755
|
+
result = []
|
|
756
|
+
for root, dirs, files in os.walk(directory):
|
|
757
|
+
for file in files:
|
|
758
|
+
if extension is None or file.endswith(extension):
|
|
759
|
+
result.append(os.path.join(root, file))
|
|
760
|
+
print(f"🔍 Найдено {len(result)} файлов" + (f" с расширением {extension}" if extension else ""))
|
|
761
|
+
return result
|
|
762
|
+
|
|
763
|
+
@staticmethod
|
|
764
|
+
def find_folders(directory):
|
|
765
|
+
"""
|
|
766
|
+
📁 НАЙТИ ПАПКИ: Возвращает список всех подпапок.
|
|
767
|
+
|
|
768
|
+
Параметры:
|
|
769
|
+
directory (str): папка для поиска
|
|
770
|
+
|
|
771
|
+
Возвращает:
|
|
772
|
+
list[str]: список путей к подпапкам
|
|
773
|
+
|
|
774
|
+
📝 Пример:
|
|
775
|
+
folders = FolderUtils.find_folders("my_project")
|
|
776
|
+
for folder in folders:
|
|
777
|
+
print(folder)
|
|
778
|
+
"""
|
|
779
|
+
import os
|
|
780
|
+
result = []
|
|
781
|
+
for root, dirs, files in os.walk(directory):
|
|
782
|
+
for d in dirs:
|
|
783
|
+
result.append(os.path.join(root, d))
|
|
784
|
+
print(f"📁 Найдено {len(result)} подпапок")
|
|
785
|
+
return result
|
|
786
|
+
|
|
787
|
+
@staticmethod
|
|
788
|
+
def current_dir():
|
|
789
|
+
"""
|
|
790
|
+
📍 ТЕКУЩАЯ ПАПКА: Возвращает путь к текущей рабочей директории.
|
|
791
|
+
|
|
792
|
+
Возвращает:
|
|
793
|
+
str: полный путь
|
|
794
|
+
|
|
795
|
+
📝 Пример:
|
|
796
|
+
where_am_i = FolderUtils.current_dir()
|
|
797
|
+
print(f"Скрипт запущен из: {where_am_i}")
|
|
798
|
+
"""
|
|
799
|
+
import os
|
|
800
|
+
return os.getcwd()
|
|
801
|
+
|
|
802
|
+
@staticmethod
|
|
803
|
+
def get_filename(path):
|
|
804
|
+
"""
|
|
805
|
+
📄 ИМЯ ФАЙЛА: Извлекает имя файла из полного пути.
|
|
806
|
+
|
|
807
|
+
Параметры:
|
|
808
|
+
path (str): полный путь к файлу
|
|
809
|
+
|
|
810
|
+
Возвращает:
|
|
811
|
+
str: имя файла с расширением
|
|
812
|
+
|
|
813
|
+
📝 Пример:
|
|
814
|
+
name = FolderUtils.get_filename("C:/Users/Yusuf/doc.txt") # "doc.txt"
|
|
815
|
+
name = FolderUtils.get_filename("/home/user/file.py") # "file.py"
|
|
816
|
+
"""
|
|
817
|
+
import os
|
|
818
|
+
return os.path.basename(path)
|
|
819
|
+
|
|
820
|
+
@staticmethod
|
|
821
|
+
def get_extension(path):
|
|
822
|
+
"""
|
|
823
|
+
🔤 РАСШИРЕНИЕ ФАЙЛА: Извлекает расширение файла.
|
|
824
|
+
|
|
825
|
+
Параметры:
|
|
826
|
+
path (str): путь к файлу
|
|
827
|
+
|
|
828
|
+
Возвращает:
|
|
829
|
+
str: расширение с точкой (например, ".txt", ".py")
|
|
830
|
+
|
|
831
|
+
📝 Пример:
|
|
832
|
+
ext = FolderUtils.get_extension("photo.jpg") # ".jpg"
|
|
833
|
+
ext = FolderUtils.get_extension("script.py") # ".py"
|
|
834
|
+
"""
|
|
835
|
+
import os
|
|
836
|
+
return os.path.splitext(path)[1]
|
|
837
|
+
|
|
838
|
+
@staticmethod
|
|
839
|
+
def get_filename_without_ext(path):
|
|
840
|
+
"""
|
|
841
|
+
📄 ИМЯ БЕЗ РАСШИРЕНИЯ: Извлекает имя файла без расширения.
|
|
842
|
+
|
|
843
|
+
Параметры:
|
|
844
|
+
path (str): путь к файлу
|
|
845
|
+
|
|
846
|
+
Возвращает:
|
|
847
|
+
str: имя файла без расширения
|
|
848
|
+
|
|
849
|
+
📝 Пример:
|
|
850
|
+
name = FolderUtils.get_filename_without_ext("photo.jpg") # "photo"
|
|
851
|
+
name = FolderUtils.get_filename_without_ext("script.py") # "script"
|
|
852
|
+
"""
|
|
853
|
+
import os
|
|
854
|
+
return os.path.splitext(os.path.basename(path))[0]
|
|
855
|
+
|
|
856
|
+
@staticmethod
|
|
857
|
+
def get_parent(path):
|
|
858
|
+
"""
|
|
859
|
+
📂 РОДИТЕЛЬСКАЯ ПАПКА: Возвращает путь к родительской папке.
|
|
860
|
+
|
|
861
|
+
Параметры:
|
|
862
|
+
path (str): путь к файлу или папке
|
|
863
|
+
|
|
864
|
+
Возвращает:
|
|
865
|
+
str: путь к родительской папке
|
|
866
|
+
|
|
867
|
+
📝 Пример:
|
|
868
|
+
parent = FolderUtils.get_parent("C:/Users/Yusuf/doc.txt") # "C:/Users/Yusuf"
|
|
869
|
+
parent = FolderUtils.get_parent("/home/user/projects") # "/home/user"
|
|
870
|
+
"""
|
|
871
|
+
import os
|
|
872
|
+
return os.path.dirname(path)
|
|
873
|
+
|
|
874
|
+
@staticmethod
|
|
875
|
+
def get_size(path):
|
|
876
|
+
"""
|
|
877
|
+
📏 РАЗМЕР: Вычисляет размер файла или папки (всего содержимого).
|
|
878
|
+
|
|
879
|
+
Параметры:
|
|
880
|
+
path (str): путь к файлу или папке
|
|
881
|
+
|
|
882
|
+
Возвращает:
|
|
883
|
+
int: размер в байтах
|
|
884
|
+
|
|
885
|
+
📝 Пример:
|
|
886
|
+
size_bytes = FolderUtils.get_size("my_project")
|
|
887
|
+
size_mb = size_bytes / (1024 * 1024)
|
|
888
|
+
print(f"Размер проекта: {size_mb:.2f} MB")
|
|
889
|
+
"""
|
|
890
|
+
import os
|
|
891
|
+
total = 0
|
|
892
|
+
if os.path.isfile(path):
|
|
893
|
+
return os.path.getsize(path)
|
|
894
|
+
elif os.path.isdir(path):
|
|
895
|
+
for root, dirs, files in os.walk(path):
|
|
896
|
+
for f in files:
|
|
897
|
+
fp = os.path.join(root, f)
|
|
898
|
+
if os.path.exists(fp):
|
|
899
|
+
total += os.path.getsize(fp)
|
|
900
|
+
return total
|
|
901
|
+
|
|
902
|
+
@staticmethod
|
|
903
|
+
def format_size(size_bytes):
|
|
904
|
+
"""
|
|
905
|
+
📊 ФОРМАТИРОВАТЬ РАЗМЕР: Превращает байты в читаемую строку.
|
|
906
|
+
|
|
907
|
+
Параметры:
|
|
908
|
+
size_bytes (int): размер в байтах
|
|
909
|
+
|
|
910
|
+
Возвращает:
|
|
911
|
+
str: читаемая строка (например, "2.5 MB")
|
|
912
|
+
|
|
913
|
+
📝 Пример:
|
|
914
|
+
size = FolderUtils.get_size("my_project")
|
|
915
|
+
print(FolderUtils.format_size(size)) # "145.3 MB"
|
|
916
|
+
"""
|
|
917
|
+
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
|
|
918
|
+
if size_bytes < 1024:
|
|
919
|
+
return f"{size_bytes:.1f} {unit}"
|
|
920
|
+
size_bytes /= 1024
|
|
921
|
+
return f"{size_bytes:.1f} PB"
|
|
922
|
+
|
|
923
|
+
@staticmethod
|
|
924
|
+
def exists(path):
|
|
925
|
+
"""
|
|
926
|
+
❓ СУЩЕСТВУЕТ?: Проверяет, существует ли файл или папка.
|
|
927
|
+
|
|
928
|
+
Параметры:
|
|
929
|
+
path (str): путь для проверки
|
|
930
|
+
|
|
931
|
+
Возвращает:
|
|
932
|
+
bool: True если существует
|
|
933
|
+
|
|
934
|
+
📝 Пример:
|
|
935
|
+
if FolderUtils.exists("config.json"):
|
|
936
|
+
print("Конфиг найден!")
|
|
937
|
+
else:
|
|
938
|
+
print("Конфиг отсутствует")
|
|
939
|
+
"""
|
|
940
|
+
import os
|
|
941
|
+
return os.path.exists(path)
|
|
942
|
+
|
|
943
|
+
@staticmethod
|
|
944
|
+
def is_file(path):
|
|
945
|
+
"""
|
|
946
|
+
📄 ЭТО ФАЙЛ?: Проверяет, является ли путь файлом.
|
|
947
|
+
|
|
948
|
+
Параметры:
|
|
949
|
+
path (str): путь для проверки
|
|
950
|
+
|
|
951
|
+
Возвращает:
|
|
952
|
+
bool: True если это файл
|
|
953
|
+
|
|
954
|
+
📝 Пример:
|
|
955
|
+
if FolderUtils.is_file("script.py"):
|
|
956
|
+
print("Это файл, можно читать")
|
|
957
|
+
"""
|
|
958
|
+
import os
|
|
959
|
+
return os.path.isfile(path)
|
|
960
|
+
|
|
961
|
+
@staticmethod
|
|
962
|
+
def is_folder(path):
|
|
963
|
+
"""
|
|
964
|
+
📁 ЭТО ПАПКА?: Проверяет, является ли путь папкой.
|
|
965
|
+
|
|
966
|
+
Параметры:
|
|
967
|
+
path (str): путь для проверки
|
|
968
|
+
|
|
969
|
+
Возвращает:
|
|
970
|
+
bool: True если это папка
|
|
971
|
+
|
|
972
|
+
📝 Пример:
|
|
973
|
+
if FolderUtils.is_folder("my_project"):
|
|
974
|
+
contents = FolderUtils.find_files("my_project")
|
|
975
|
+
"""
|
|
976
|
+
import os
|
|
977
|
+
return os.path.isdir(path)
|
|
978
|
+
|
|
979
|
+
@staticmethod
|
|
980
|
+
def list_contents(directory):
|
|
981
|
+
"""
|
|
982
|
+
📋 СОДЕРЖИМОЕ ПАПКИ: Возвращает список файлов и папок в директории.
|
|
983
|
+
|
|
984
|
+
Параметры:
|
|
985
|
+
directory (str): путь к папке
|
|
986
|
+
|
|
987
|
+
Возвращает:
|
|
988
|
+
dict: {"files": [...], "folders": [...]}
|
|
989
|
+
|
|
990
|
+
📝 Пример:
|
|
991
|
+
contents = FolderUtils.list_contents("my_project")
|
|
992
|
+
print(f"Файлов: {len(contents['files'])}")
|
|
993
|
+
print(f"Папок: {len(contents['folders'])}")
|
|
994
|
+
"""
|
|
995
|
+
import os
|
|
996
|
+
files = []
|
|
997
|
+
folders = []
|
|
998
|
+
try:
|
|
999
|
+
for item in os.listdir(directory):
|
|
1000
|
+
full_path = os.path.join(directory, item)
|
|
1001
|
+
if os.path.isfile(full_path):
|
|
1002
|
+
files.append(item)
|
|
1003
|
+
elif os.path.isdir(full_path):
|
|
1004
|
+
folders.append(item)
|
|
1005
|
+
except Exception as e:
|
|
1006
|
+
print(f"❌ Ошибка чтения папки: {e}")
|
|
1007
|
+
return {"files": files, "folders": folders}
|
|
1008
|
+
|
|
1009
|
+
# =============================================================================
|
|
1010
|
+
# КЛАСС Console – РАБОТА С КОНСОЛЬЮ (ЦВЕТА, КУРСОР, ПРОГРЕСС)
|
|
1011
|
+
# =============================================================================
|
|
1012
|
+
class Console:
|
|
1013
|
+
"""
|
|
1014
|
+
🖥️ РАБОТА С КОНСОЛЬЮ: цвета, управление курсором, прогресс-бары и скрытие окна.
|
|
1015
|
+
|
|
1016
|
+
Все методы статические — вызывай напрямую:
|
|
1017
|
+
Console.hide()
|
|
1018
|
+
Console.success("Готово!")
|
|
1019
|
+
Console.progress_bar(100, "Загрузка")
|
|
1020
|
+
|
|
1021
|
+
🎯 Возможности класса:
|
|
1022
|
+
- Скрытие и показ консольного окна
|
|
1023
|
+
- Цветной вывод текста (16 цветов + стили)
|
|
1024
|
+
- Управление положением курсора
|
|
1025
|
+
- Прогресс-бары для длительных операций
|
|
1026
|
+
- Быстрые сообщения: success, error, warning, info
|
|
1027
|
+
- Красивые заголовки на всю ширину консоли
|
|
1028
|
+
"""
|
|
1029
|
+
|
|
1030
|
+
# ──────────────────────────────────────────────
|
|
1031
|
+
# СКРЫТИЕ / ПОКАЗ КОНСОЛИ
|
|
1032
|
+
# ──────────────────────────────────────────────
|
|
1033
|
+
@staticmethod
|
|
1034
|
+
def hide():
|
|
1035
|
+
"""
|
|
1036
|
+
👻 СКРЫТЬ КОНСОЛЬ: Мгновенно скрывает окно консоли, перезапуская скрипт в фоне.
|
|
1037
|
+
|
|
1038
|
+
📝 Пример:
|
|
1039
|
+
Console.hide()
|
|
1040
|
+
# Скрипт продолжит работу, но окна не будет видно
|
|
1041
|
+
|
|
1042
|
+
🎯 Возможности:
|
|
1043
|
+
- Создание фоновых сервисов
|
|
1044
|
+
- Скрытие технического вывода от пользователя
|
|
1045
|
+
- Работа программы в трее
|
|
1046
|
+
"""
|
|
1047
|
+
if '--hidden' not in sys.argv:
|
|
1048
|
+
pythonw_path = sys.executable.replace('python.exe', 'pythonw.exe')
|
|
1049
|
+
if os.path.exists(pythonw_path):
|
|
1050
|
+
subprocess.Popen(
|
|
1051
|
+
[pythonw_path] + sys.argv + ['--hidden'],
|
|
1052
|
+
creationflags=0x00000008 | 0x08000000,
|
|
1053
|
+
stdout=subprocess.DEVNULL,
|
|
1054
|
+
stderr=subprocess.DEVNULL,
|
|
1055
|
+
stdin=subprocess.DEVNULL
|
|
1056
|
+
)
|
|
1057
|
+
sys.exit(0)
|
|
1058
|
+
|
|
1059
|
+
@staticmethod
|
|
1060
|
+
def show():
|
|
1061
|
+
"""
|
|
1062
|
+
📺 ПОКАЗАТЬ КОНСОЛЬ: Создаёт новую консоль и привязывает к ней процесс.
|
|
1063
|
+
|
|
1064
|
+
📝 Пример:
|
|
1065
|
+
Console.show()
|
|
1066
|
+
print("Консоль снова видна!")
|
|
1067
|
+
|
|
1068
|
+
🎯 Возможности:
|
|
1069
|
+
- Восстановление скрытой консоли
|
|
1070
|
+
- Отладка фоновых процессов
|
|
1071
|
+
"""
|
|
1072
|
+
import ctypes
|
|
1073
|
+
|
|
1074
|
+
kernel32 = ctypes.WinDLL('kernel32')
|
|
1075
|
+
kernel32.AllocConsole()
|
|
1076
|
+
|
|
1077
|
+
# ──────────────────────────────────────────────
|
|
1078
|
+
# ЦВЕТА И СТИЛИ
|
|
1079
|
+
# ──────────────────────────────────────────────
|
|
1080
|
+
@staticmethod
|
|
1081
|
+
def color(text, fg=None, bg=None, style=None):
|
|
1082
|
+
"""
|
|
1083
|
+
🎨 ЦВЕТНОЙ ТЕКСТ: Окрашивает текст для вывода в консоль.
|
|
1084
|
+
|
|
1085
|
+
Параметры:
|
|
1086
|
+
text (str): текст для окрашивания
|
|
1087
|
+
fg (str): цвет текста ('red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white')
|
|
1088
|
+
bg (str): цвет фона ('bg_red', 'bg_green', 'bg_blue', ...)
|
|
1089
|
+
style (str): стиль текста ('bold', 'underline', 'italic', 'blink', ...)
|
|
1090
|
+
|
|
1091
|
+
Возвращает:
|
|
1092
|
+
str: строка с ANSI-кодами для цветного вывода
|
|
1093
|
+
|
|
1094
|
+
📝 Пример:
|
|
1095
|
+
print(Console.color("Ошибка!", "red", style="bold"))
|
|
1096
|
+
print(Console.color("Успех!", "green"))
|
|
1097
|
+
print(Console.color("Важно", "yellow", "bg_blue", "underline"))
|
|
1098
|
+
|
|
1099
|
+
🎯 Возможности:
|
|
1100
|
+
- Выделение важных сообщений
|
|
1101
|
+
- Цветные логи и отчёты
|
|
1102
|
+
- Красивый интерфейс командной строки
|
|
1103
|
+
"""
|
|
1104
|
+
colors = {
|
|
1105
|
+
'black': '30', 'red': '31', 'green': '32', 'yellow': '33',
|
|
1106
|
+
'blue': '34', 'magenta': '35', 'cyan': '36', 'white': '37',
|
|
1107
|
+
'bright_black': '90', 'bright_red': '91', 'bright_green': '92',
|
|
1108
|
+
'bright_yellow': '93', 'bright_blue': '94', 'bright_magenta': '95',
|
|
1109
|
+
'bright_cyan': '96', 'bright_white': '97',
|
|
1110
|
+
'bg_black': '40', 'bg_red': '41', 'bg_green': '42', 'bg_yellow': '43',
|
|
1111
|
+
'bg_blue': '44', 'bg_magenta': '45', 'bg_cyan': '46', 'bg_white': '47',
|
|
1112
|
+
'bg_bright_black': '100', 'bg_bright_red': '101', 'bg_bright_green': '102',
|
|
1113
|
+
'bg_bright_yellow': '103', 'bg_bright_blue': '104', 'bg_bright_magenta': '105',
|
|
1114
|
+
'bg_bright_cyan': '106', 'bg_bright_white': '107',
|
|
1115
|
+
'bold': '1', 'dim': '2', 'italic': '3', 'underline': '4',
|
|
1116
|
+
'blink': '5', 'reverse': '7', 'hidden': '8', 'strikethrough': '9'
|
|
1117
|
+
}
|
|
1118
|
+
codes = []
|
|
1119
|
+
if fg and fg in colors:
|
|
1120
|
+
codes.append(colors[fg])
|
|
1121
|
+
if bg and bg in colors:
|
|
1122
|
+
codes.append(colors[bg])
|
|
1123
|
+
if style and style in colors:
|
|
1124
|
+
codes.append(colors[style])
|
|
1125
|
+
if codes:
|
|
1126
|
+
return f'\033[{";".join(codes)}m{text}\033[0m'
|
|
1127
|
+
return text
|
|
1128
|
+
|
|
1129
|
+
# ──────────────────────────────────────────────
|
|
1130
|
+
# УПРАВЛЕНИЕ КОНСОЛЬЮ
|
|
1131
|
+
# ──────────────────────────────────────────────
|
|
1132
|
+
@staticmethod
|
|
1133
|
+
def clear():
|
|
1134
|
+
"""
|
|
1135
|
+
🧹 ОЧИСТКА: Полностью очищает экран консоли.
|
|
1136
|
+
|
|
1137
|
+
📝 Пример:
|
|
1138
|
+
Console.clear()
|
|
1139
|
+
print("Чистый экран!")
|
|
1140
|
+
|
|
1141
|
+
🎯 Возможности:
|
|
1142
|
+
- Очистка перед новым выводом
|
|
1143
|
+
- Удаление старых сообщений
|
|
1144
|
+
"""
|
|
1145
|
+
os.system('cls' if os.name == 'nt' else 'clear')
|
|
1146
|
+
|
|
1147
|
+
@staticmethod
|
|
1148
|
+
def move_to(x, y):
|
|
1149
|
+
"""
|
|
1150
|
+
➡️ ПЕРЕМЕСТИТЬ КУРСОР: Перемещает курсор в указанную позицию (x, y).
|
|
1151
|
+
|
|
1152
|
+
Параметры:
|
|
1153
|
+
x (int): столбец (начиная с 1)
|
|
1154
|
+
y (int): строка (начиная с 1)
|
|
1155
|
+
|
|
1156
|
+
📝 Пример:
|
|
1157
|
+
Console.move_to(10, 5)
|
|
1158
|
+
print("Текст в позиции 10,5")
|
|
1159
|
+
|
|
1160
|
+
🎯 Возможности:
|
|
1161
|
+
- Создание интерфейсов в консоли
|
|
1162
|
+
- Обновление данных на экране без очистки
|
|
1163
|
+
"""
|
|
1164
|
+
sys.stdout.write(f'\033[{y};{x}H')
|
|
1165
|
+
sys.stdout.flush()
|
|
1166
|
+
|
|
1167
|
+
@staticmethod
|
|
1168
|
+
def move_up(n=1):
|
|
1169
|
+
"""
|
|
1170
|
+
⬆️ ВВЕРХ: Перемещает курсор вверх на n строк.
|
|
1171
|
+
|
|
1172
|
+
Параметры:
|
|
1173
|
+
n (int): количество строк для перемещения
|
|
1174
|
+
|
|
1175
|
+
📝 Пример:
|
|
1176
|
+
Console.move_up(3)
|
|
1177
|
+
print("Этот текст будет на 3 строки выше")
|
|
1178
|
+
"""
|
|
1179
|
+
sys.stdout.write(f'\033[{n}A')
|
|
1180
|
+
sys.stdout.flush()
|
|
1181
|
+
|
|
1182
|
+
@staticmethod
|
|
1183
|
+
def move_down(n=1):
|
|
1184
|
+
"""
|
|
1185
|
+
⬇️ ВНИЗ: Перемещает курсор вниз на n строк.
|
|
1186
|
+
|
|
1187
|
+
Параметры:
|
|
1188
|
+
n (int): количество строк для перемещения
|
|
1189
|
+
|
|
1190
|
+
📝 Пример:
|
|
1191
|
+
Console.move_down(2)
|
|
1192
|
+
print("Этот текст будет на 2 строки ниже")
|
|
1193
|
+
"""
|
|
1194
|
+
sys.stdout.write(f'\033[{n}B')
|
|
1195
|
+
sys.stdout.flush()
|
|
1196
|
+
|
|
1197
|
+
@staticmethod
|
|
1198
|
+
def hide_cursor():
|
|
1199
|
+
"""
|
|
1200
|
+
🙈 СКРЫТЬ КУРСОР: Делает мигающий курсор невидимым.
|
|
1201
|
+
|
|
1202
|
+
📝 Пример:
|
|
1203
|
+
Console.hide_cursor()
|
|
1204
|
+
# Курсор исчезнет
|
|
1205
|
+
|
|
1206
|
+
🎯 Возможности:
|
|
1207
|
+
- Улучшение визуала для анимаций
|
|
1208
|
+
- Скрытие курсора во время загрузки
|
|
1209
|
+
"""
|
|
1210
|
+
sys.stdout.write('\033[?25l')
|
|
1211
|
+
sys.stdout.flush()
|
|
1212
|
+
|
|
1213
|
+
@staticmethod
|
|
1214
|
+
def show_cursor():
|
|
1215
|
+
"""
|
|
1216
|
+
👁️ ПОКАЗАТЬ КУРСОР: Возвращает видимость мигающего курсора.
|
|
1217
|
+
|
|
1218
|
+
📝 Пример:
|
|
1219
|
+
Console.show_cursor()
|
|
1220
|
+
# Курсор снова виден
|
|
1221
|
+
"""
|
|
1222
|
+
sys.stdout.write('\033[?25h')
|
|
1223
|
+
sys.stdout.flush()
|
|
1224
|
+
|
|
1225
|
+
@staticmethod
|
|
1226
|
+
def save_pos():
|
|
1227
|
+
"""
|
|
1228
|
+
💾 СОХРАНИТЬ ПОЗИЦИЮ: Сохраняет текущую позицию курсора.
|
|
1229
|
+
|
|
1230
|
+
📝 Пример:
|
|
1231
|
+
Console.save_pos()
|
|
1232
|
+
Console.move_to(1, 10)
|
|
1233
|
+
print("Временный вывод")
|
|
1234
|
+
Console.restore_pos()
|
|
1235
|
+
print("Вернулись обратно")
|
|
1236
|
+
|
|
1237
|
+
🎯 Возможности:
|
|
1238
|
+
- Временный вывод без потери позиции
|
|
1239
|
+
- Создание сложных консольных интерфейсов
|
|
1240
|
+
"""
|
|
1241
|
+
sys.stdout.write('\033[s')
|
|
1242
|
+
sys.stdout.flush()
|
|
1243
|
+
|
|
1244
|
+
@staticmethod
|
|
1245
|
+
def restore_pos():
|
|
1246
|
+
"""
|
|
1247
|
+
🔄 ВОССТАНОВИТЬ ПОЗИЦИЮ: Возвращает курсор на сохранённую позицию.
|
|
1248
|
+
|
|
1249
|
+
📝 Пример:
|
|
1250
|
+
Console.restore_pos()
|
|
1251
|
+
"""
|
|
1252
|
+
sys.stdout.write('\033[u')
|
|
1253
|
+
sys.stdout.flush()
|
|
1254
|
+
|
|
1255
|
+
# ──────────────────────────────────────────────
|
|
1256
|
+
# ПОЛЕЗНЫЕ УТИЛИТЫ
|
|
1257
|
+
# ──────────────────────────────────────────────
|
|
1258
|
+
@staticmethod
|
|
1259
|
+
def size():
|
|
1260
|
+
"""
|
|
1261
|
+
📏 РАЗМЕР КОНСОЛИ: Возвращает размер консоли (ширина, высота).
|
|
1262
|
+
|
|
1263
|
+
Возвращает:
|
|
1264
|
+
tuple: (ширина, высота) в символах
|
|
1265
|
+
|
|
1266
|
+
📝 Пример:
|
|
1267
|
+
w, h = Console.size()
|
|
1268
|
+
print(f"Консоль: {w}x{h} символов")
|
|
1269
|
+
|
|
1270
|
+
🎯 Возможности:
|
|
1271
|
+
- Адаптивный вывод под размер окна
|
|
1272
|
+
- Центрирование текста
|
|
1273
|
+
"""
|
|
1274
|
+
try:
|
|
1275
|
+
return os.get_terminal_size().columns, os.get_terminal_size().lines
|
|
1276
|
+
except:
|
|
1277
|
+
return 80, 24
|
|
1278
|
+
|
|
1279
|
+
@staticmethod
|
|
1280
|
+
def center(text):
|
|
1281
|
+
"""
|
|
1282
|
+
🎯 ЦЕНТРИРОВАНИЕ: Центрирует текст по ширине консоли.
|
|
1283
|
+
|
|
1284
|
+
Параметры:
|
|
1285
|
+
text (str): текст для центрирования
|
|
1286
|
+
|
|
1287
|
+
Возвращает:
|
|
1288
|
+
str: текст, дополненный пробелами до ширины консоли
|
|
1289
|
+
|
|
1290
|
+
📝 Пример:
|
|
1291
|
+
centered = Console.center("Добро пожаловать!")
|
|
1292
|
+
print(centered)
|
|
1293
|
+
"""
|
|
1294
|
+
w, h = Console.size()
|
|
1295
|
+
return text.center(w)
|
|
1296
|
+
|
|
1297
|
+
@staticmethod
|
|
1298
|
+
def progress_bar(total, prefix="", suffix="", length=50, fill="█"):
|
|
1299
|
+
"""
|
|
1300
|
+
📊 ПРОГРЕСС-БАР: Показывает анимированный индикатор выполнения.
|
|
1301
|
+
|
|
1302
|
+
Параметры:
|
|
1303
|
+
total (int): общее количество итераций
|
|
1304
|
+
prefix (str): текст перед прогресс-баром
|
|
1305
|
+
suffix (str): текст после прогресс-бара
|
|
1306
|
+
length (int): длина прогресс-бара в символах
|
|
1307
|
+
fill (str): символ заполнения
|
|
1308
|
+
|
|
1309
|
+
📝 Пример:
|
|
1310
|
+
Console.progress_bar(100, "Загрузка:", "Пожалуйста, подождите")
|
|
1311
|
+
# Выведет: Загрузка: [████████████░░░░░░░░░░░░░░░░░░░░░░░░] 40.0% Пожалуйста, подождите
|
|
1312
|
+
|
|
1313
|
+
🎯 Возможности:
|
|
1314
|
+
- Отображение прогресса загрузки
|
|
1315
|
+
- Визуализация выполнения длительных операций
|
|
1316
|
+
- Информирование пользователя о статусе
|
|
1317
|
+
"""
|
|
1318
|
+
for i in range(total + 1):
|
|
1319
|
+
percent = i / total
|
|
1320
|
+
filled = int(length * percent)
|
|
1321
|
+
bar = fill * filled + "░" * (length - filled)
|
|
1322
|
+
sys.stdout.write(f"\r{prefix} [{bar}] {percent:.1%} {suffix}")
|
|
1323
|
+
sys.stdout.flush()
|
|
1324
|
+
if i < total:
|
|
1325
|
+
time.sleep(0.05)
|
|
1326
|
+
print()
|
|
1327
|
+
|
|
1328
|
+
# ──────────────────────────────────────────────
|
|
1329
|
+
# ОФОРМЛЕНИЕ ТЕКСТА (быстрые сообщения)
|
|
1330
|
+
# ──────────────────────────────────────────────
|
|
1331
|
+
@staticmethod
|
|
1332
|
+
def success(text):
|
|
1333
|
+
"""
|
|
1334
|
+
✅ УСПЕХ: Выводит зелёное сообщение об успехе.
|
|
1335
|
+
|
|
1336
|
+
Параметры:
|
|
1337
|
+
text (str): текст сообщения
|
|
1338
|
+
|
|
1339
|
+
📝 Пример:
|
|
1340
|
+
Console.success("Файл сохранён!")
|
|
1341
|
+
# Выведет: ✓ Файл сохранён! (зелёным цветом)
|
|
1342
|
+
|
|
1343
|
+
🎯 Возможности:
|
|
1344
|
+
- Уведомления об успешных операциях
|
|
1345
|
+
- Позитивная обратная связь пользователю
|
|
1346
|
+
"""
|
|
1347
|
+
print(Console.color(f"✓ {text}", "green"))
|
|
1348
|
+
|
|
1349
|
+
@staticmethod
|
|
1350
|
+
def error(text):
|
|
1351
|
+
"""
|
|
1352
|
+
❌ ОШИБКА: Выводит красное сообщение об ошибке.
|
|
1353
|
+
|
|
1354
|
+
Параметры:
|
|
1355
|
+
text (str): текст сообщения
|
|
1356
|
+
|
|
1357
|
+
📝 Пример:
|
|
1358
|
+
Console.error("Не удалось подключиться к серверу!")
|
|
1359
|
+
# Выведет: ✗ Не удалось подключиться к серверу! (красным цветом)
|
|
1360
|
+
|
|
1361
|
+
🎯 Возможности:
|
|
1362
|
+
- Отображение критических ошибок
|
|
1363
|
+
- Привлечение внимания к проблемам
|
|
1364
|
+
"""
|
|
1365
|
+
print(Console.color(f"✗ {text}", "red"))
|
|
1366
|
+
|
|
1367
|
+
@staticmethod
|
|
1368
|
+
def warning(text):
|
|
1369
|
+
"""
|
|
1370
|
+
⚠️ ПРЕДУПРЕЖДЕНИЕ: Выводит жёлтое предупреждение.
|
|
1371
|
+
|
|
1372
|
+
Параметры:
|
|
1373
|
+
text (str): текст предупреждения
|
|
1374
|
+
|
|
1375
|
+
📝 Пример:
|
|
1376
|
+
Console.warning("Батарея ниже 10%!")
|
|
1377
|
+
# Выведет: ⚠ Батарея ниже 10%! (жёлтым цветом)
|
|
1378
|
+
|
|
1379
|
+
🎯 Возможности:
|
|
1380
|
+
- Некритичные предупреждения
|
|
1381
|
+
- Напоминания и советы
|
|
1382
|
+
"""
|
|
1383
|
+
print(Console.color(f"⚠ {text}", "yellow"))
|
|
1384
|
+
|
|
1385
|
+
@staticmethod
|
|
1386
|
+
def info(text):
|
|
1387
|
+
"""
|
|
1388
|
+
ℹ️ ИНФОРМАЦИЯ: Выводит голубое информационное сообщение.
|
|
1389
|
+
|
|
1390
|
+
Параметры:
|
|
1391
|
+
text (str): текст сообщения
|
|
1392
|
+
|
|
1393
|
+
📝 Пример:
|
|
1394
|
+
Console.info("Сервер запущен на порту 8080")
|
|
1395
|
+
# Выведет: ℹ Сервер запущен на порту 8080 (голубым цветом)
|
|
1396
|
+
|
|
1397
|
+
🎯 Возможности:
|
|
1398
|
+
- Информационные сообщения
|
|
1399
|
+
- Статус выполнения операций
|
|
1400
|
+
"""
|
|
1401
|
+
print(Console.color(f"ℹ {text}", "cyan"))
|
|
1402
|
+
|
|
1403
|
+
@staticmethod
|
|
1404
|
+
def header(text, symbol="="):
|
|
1405
|
+
"""
|
|
1406
|
+
📌 ЗАГОЛОВОК: Выводит жирный заголовок на всю ширину консоли.
|
|
1407
|
+
|
|
1408
|
+
Параметры:
|
|
1409
|
+
text (str): текст заголовка
|
|
1410
|
+
symbol (str): символ для линии (по умолчанию "=")
|
|
1411
|
+
|
|
1412
|
+
📝 Пример:
|
|
1413
|
+
Console.header("МОЯ ПРОГРАММА")
|
|
1414
|
+
# Выведет:
|
|
1415
|
+
# ============================================
|
|
1416
|
+
# МОЯ ПРОГРАММА
|
|
1417
|
+
# ============================================
|
|
1418
|
+
|
|
1419
|
+
🎯 Возможности:
|
|
1420
|
+
- Оформление разделов программы
|
|
1421
|
+
- Красивые заголовки в отчётах
|
|
1422
|
+
"""
|
|
1423
|
+
w, h = Console.size()
|
|
1424
|
+
line = symbol * w
|
|
1425
|
+
print(Console.color(line, "blue", style="bold"))
|
|
1426
|
+
print(Console.color(text.center(w), "blue", style="bold"))
|
|
1427
|
+
print(Console.color(line, "blue", style="bold"))
|
|
1428
|
+
|
|
1429
|
+
|
|
1430
|
+
# =============================================================================
|
|
1431
|
+
# КЛАСС BazaDB – УНИВЕРСАЛЬНАЯ РАБОТА С SQLite
|
|
1432
|
+
# =============================================================================
|
|
1433
|
+
class BazaDB:
|
|
1434
|
+
"""
|
|
1435
|
+
🗄️ БАЗА ДАННЫХ SQLite — лёгкая, быстрая, без сервера.
|
|
1436
|
+
|
|
1437
|
+
📝 Пример использования:
|
|
1438
|
+
db = BazaDB("mybase.db")
|
|
1439
|
+
db.create_table("users", ["name", "age"])
|
|
1440
|
+
db.insert("users", {"name": "Юсуф", "age": 12})
|
|
1441
|
+
users = db.get_all("users")
|
|
1442
|
+
db.close()
|
|
1443
|
+
|
|
1444
|
+
🎯 Возможности класса:
|
|
1445
|
+
- Создание и удаление таблиц
|
|
1446
|
+
- Добавление, поиск, обновление и удаление записей
|
|
1447
|
+
- Поиск по точному совпадению и по части текста
|
|
1448
|
+
- Статистика и список таблиц
|
|
1449
|
+
- Полная очистка и удаление таблиц
|
|
1450
|
+
"""
|
|
1451
|
+
|
|
1452
|
+
def __init__(self, db_name):
|
|
1453
|
+
"""
|
|
1454
|
+
🚀 ПОДКЛЮЧЕНИЕ: Открывает или создаёт файл базы данных SQLite.
|
|
1455
|
+
|
|
1456
|
+
Параметры:
|
|
1457
|
+
db_name (str): имя файла базы данных (например, "mybase.db")
|
|
1458
|
+
|
|
1459
|
+
📝 Пример:
|
|
1460
|
+
db = BazaDB("shop.db")
|
|
1461
|
+
db = BazaDB(":memory:") # база в оперативной памяти
|
|
1462
|
+
|
|
1463
|
+
🎯 Возможности:
|
|
1464
|
+
- Автоматическое создание файла, если его нет
|
|
1465
|
+
- Поддержка баз в памяти (для тестов)
|
|
1466
|
+
"""
|
|
1467
|
+
self.conn = sqlite3.connect(db_name)
|
|
1468
|
+
self.cursor = self.conn.cursor()
|
|
1469
|
+
print(f"✅ Подключено к базе: {db_name}")
|
|
1470
|
+
|
|
1471
|
+
def create_table(self, table_name, columns, drop_if_exists=False):
|
|
1472
|
+
"""
|
|
1473
|
+
📦 СОЗДАТЬ ТАБЛИЦУ: Создаёт новую таблицу с указанными колонками.
|
|
1474
|
+
|
|
1475
|
+
Параметры:
|
|
1476
|
+
table_name (str): имя таблицы
|
|
1477
|
+
columns (list): список имён колонок (id добавится автоматически)
|
|
1478
|
+
drop_if_exists (bool): удалить таблицу, если она уже существует
|
|
1479
|
+
|
|
1480
|
+
📝 Пример:
|
|
1481
|
+
db.create_table("products", ["name", "price", "quantity"])
|
|
1482
|
+
db.create_table("users", ["email", "password"], drop_if_exists=True)
|
|
1483
|
+
|
|
1484
|
+
🎯 Возможности:
|
|
1485
|
+
- Автоматическое добавление PRIMARY KEY колонки id
|
|
1486
|
+
- Все колонки создаются с типом TEXT (для универсальности)
|
|
1487
|
+
- Опциональное удаление существующей таблицы
|
|
1488
|
+
"""
|
|
1489
|
+
if drop_if_exists:
|
|
1490
|
+
self.cursor.execute(f'DROP TABLE IF EXISTS {table_name}')
|
|
1491
|
+
cols = "id INTEGER PRIMARY KEY AUTOINCREMENT"
|
|
1492
|
+
for col in columns:
|
|
1493
|
+
cols += f", {col} TEXT"
|
|
1494
|
+
self.cursor.execute(f'CREATE TABLE IF NOT EXISTS {table_name} ({cols})')
|
|
1495
|
+
self.conn.commit()
|
|
1496
|
+
print(f" 📦 Таблица '{table_name}' создана")
|
|
1497
|
+
|
|
1498
|
+
def insert(self, table_name, data):
|
|
1499
|
+
"""
|
|
1500
|
+
➕ ДОБАВИТЬ ЗАПИСЬ: Вставляет новую строку в таблицу.
|
|
1501
|
+
|
|
1502
|
+
Параметры:
|
|
1503
|
+
table_name (str): имя таблицы
|
|
1504
|
+
data (dict): словарь {колонка: значение}
|
|
1505
|
+
|
|
1506
|
+
Возвращает:
|
|
1507
|
+
int: ID добавленной записи
|
|
1508
|
+
|
|
1509
|
+
📝 Пример:
|
|
1510
|
+
new_id = db.insert("users", {"name": "Анна", "age": "25"})
|
|
1511
|
+
print(f"Добавлен пользователь с ID={new_id}")
|
|
1512
|
+
|
|
1513
|
+
🎯 Возможности:
|
|
1514
|
+
- Автоматическая генерация ID
|
|
1515
|
+
- Частичное заполнение (можно указать не все колонки)
|
|
1516
|
+
"""
|
|
1517
|
+
columns = ', '.join(data.keys())
|
|
1518
|
+
placeholders = ', '.join(['?' for _ in data])
|
|
1519
|
+
values = tuple(data.values())
|
|
1520
|
+
query = f'INSERT INTO {table_name} ({columns}) VALUES ({placeholders})'
|
|
1521
|
+
self.cursor.execute(query, values)
|
|
1522
|
+
self.conn.commit()
|
|
1523
|
+
last_id = self.cursor.lastrowid
|
|
1524
|
+
print(f" ➕ Добавлена запись ID={last_id} в '{table_name}'")
|
|
1525
|
+
return last_id
|
|
1526
|
+
|
|
1527
|
+
def get_all(self, table_name):
|
|
1528
|
+
"""
|
|
1529
|
+
📋 ПОЛУЧИТЬ ВСЁ: Возвращает все записи из таблицы.
|
|
1530
|
+
|
|
1531
|
+
Параметры:
|
|
1532
|
+
table_name (str): имя таблицы
|
|
1533
|
+
|
|
1534
|
+
Возвращает:
|
|
1535
|
+
list[dict]: список словарей с данными
|
|
1536
|
+
|
|
1537
|
+
📝 Пример:
|
|
1538
|
+
all_users = db.get_all("users")
|
|
1539
|
+
for user in all_users:
|
|
1540
|
+
print(f"{user['id']}: {user['name']}, {user['age']} лет")
|
|
1541
|
+
|
|
1542
|
+
🎯 Возможности:
|
|
1543
|
+
- Выгрузка всех данных для отчётов
|
|
1544
|
+
- Отображение содержимого таблицы
|
|
1545
|
+
"""
|
|
1546
|
+
self.cursor.execute(f'SELECT * FROM {table_name}')
|
|
1547
|
+
rows = self.cursor.fetchall()
|
|
1548
|
+
self.cursor.execute(f'PRAGMA table_info({table_name})')
|
|
1549
|
+
columns = [col[1] for col in self.cursor.fetchall()]
|
|
1550
|
+
result = [dict(zip(columns, row)) for row in rows]
|
|
1551
|
+
print(f" 📋 Получено {len(result)} записей из '{table_name}'")
|
|
1552
|
+
return result
|
|
1553
|
+
|
|
1554
|
+
def find(self, table_name, column, value):
|
|
1555
|
+
"""
|
|
1556
|
+
🔍 ПОИСК ПО ТОЧНОМУ ЗНАЧЕНИЮ: Ищет записи, где column = value.
|
|
1557
|
+
|
|
1558
|
+
Параметры:
|
|
1559
|
+
table_name (str): имя таблицы
|
|
1560
|
+
column (str): имя колонки для поиска
|
|
1561
|
+
value (str): точное значение для поиска
|
|
1562
|
+
|
|
1563
|
+
Возвращает:
|
|
1564
|
+
list[dict]: список найденных записей
|
|
1565
|
+
|
|
1566
|
+
📝 Пример:
|
|
1567
|
+
adults = db.find("users", "age", "18")
|
|
1568
|
+
for adult in adults:
|
|
1569
|
+
print(adult["name"])
|
|
1570
|
+
|
|
1571
|
+
🎯 Возможности:
|
|
1572
|
+
- Фильтрация по точному значению
|
|
1573
|
+
- Поиск пользователей по email, имени и т.д.
|
|
1574
|
+
"""
|
|
1575
|
+
query = f'SELECT * FROM {table_name} WHERE {column} = ?'
|
|
1576
|
+
self.cursor.execute(query, (value,))
|
|
1577
|
+
rows = self.cursor.fetchall()
|
|
1578
|
+
self.cursor.execute(f'PRAGMA table_info({table_name})')
|
|
1579
|
+
columns = [col[1] for col in self.cursor.fetchall()]
|
|
1580
|
+
result = [dict(zip(columns, row)) for row in rows]
|
|
1581
|
+
print(f" 🔍 Найдено {len(result)} записей")
|
|
1582
|
+
return result
|
|
1583
|
+
|
|
1584
|
+
def search(self, table_name, column, text):
|
|
1585
|
+
"""
|
|
1586
|
+
🔎 ПОИСК ПО ЧАСТИ ТЕКСТА: Ищет записи, где column содержит text.
|
|
1587
|
+
|
|
1588
|
+
Параметры:
|
|
1589
|
+
table_name (str): имя таблицы
|
|
1590
|
+
column (str): имя колонки для поиска
|
|
1591
|
+
text (str): текст для поиска (регистр важен!)
|
|
1592
|
+
|
|
1593
|
+
Возвращает:
|
|
1594
|
+
list[dict]: список найденных записей
|
|
1595
|
+
|
|
1596
|
+
📝 Пример:
|
|
1597
|
+
results = db.search("users", "name", "юс")
|
|
1598
|
+
# Найдёт: Юсуф, Люся и т.д.
|
|
1599
|
+
|
|
1600
|
+
🎯 Возможности:
|
|
1601
|
+
- Нечёткий поиск по подстроке
|
|
1602
|
+
- Автодополнение и подсказки
|
|
1603
|
+
"""
|
|
1604
|
+
query = f'SELECT * FROM {table_name} WHERE {column} LIKE ?'
|
|
1605
|
+
self.cursor.execute(query, (f'%{text}%',))
|
|
1606
|
+
rows = self.cursor.fetchall()
|
|
1607
|
+
self.cursor.execute(f'PRAGMA table_info({table_name})')
|
|
1608
|
+
columns = [col[1] for col in self.cursor.fetchall()]
|
|
1609
|
+
result = [dict(zip(columns, row)) for row in rows]
|
|
1610
|
+
print(f" 🔎 Найдено {len(result)} записей")
|
|
1611
|
+
return result
|
|
1612
|
+
|
|
1613
|
+
def update(self, table_name, id, data):
|
|
1614
|
+
"""
|
|
1615
|
+
✏️ ОБНОВИТЬ ЗАПИСЬ: Обновляет данные в строке по ID.
|
|
1616
|
+
|
|
1617
|
+
Параметры:
|
|
1618
|
+
table_name (str): имя таблицы
|
|
1619
|
+
id (int): ID записи для обновления
|
|
1620
|
+
data (dict): словарь {колонка: новое_значение}
|
|
1621
|
+
|
|
1622
|
+
Возвращает:
|
|
1623
|
+
bool: True если обновлено, False если запись не найдена
|
|
1624
|
+
|
|
1625
|
+
📝 Пример:
|
|
1626
|
+
db.update("users", 1, {"age": "13", "name": "Юсуф"})
|
|
1627
|
+
# Обновит возраст и имя у пользователя с ID=1
|
|
1628
|
+
|
|
1629
|
+
🎯 Возможности:
|
|
1630
|
+
- Частичное обновление (только указанные поля)
|
|
1631
|
+
- Массовое обновление при необходимости
|
|
1632
|
+
"""
|
|
1633
|
+
set_clause = ', '.join([f'{col} = ?' for col in data])
|
|
1634
|
+
values = tuple(data.values()) + (id,)
|
|
1635
|
+
query = f'UPDATE {table_name} SET {set_clause} WHERE id = ?'
|
|
1636
|
+
self.cursor.execute(query, values)
|
|
1637
|
+
self.conn.commit()
|
|
1638
|
+
if self.cursor.rowcount:
|
|
1639
|
+
print(f" ✏️ Обновлена запись ID={id}")
|
|
1640
|
+
return True
|
|
1641
|
+
print(f" ❌ Запись ID={id} не найдена")
|
|
1642
|
+
return False
|
|
1643
|
+
|
|
1644
|
+
def delete(self, table_name, id):
|
|
1645
|
+
"""
|
|
1646
|
+
🗑️ УДАЛИТЬ ПО ID: Удаляет запись с указанным ID.
|
|
1647
|
+
|
|
1648
|
+
Параметры:
|
|
1649
|
+
table_name (str): имя таблицы
|
|
1650
|
+
id (int): ID записи для удаления
|
|
1651
|
+
|
|
1652
|
+
Возвращает:
|
|
1653
|
+
bool: True если удалено, False если запись не найдена
|
|
1654
|
+
|
|
1655
|
+
📝 Пример:
|
|
1656
|
+
if db.delete("users", 5):
|
|
1657
|
+
print("Пользователь удалён")
|
|
1658
|
+
else:
|
|
1659
|
+
print("Пользователь не найден")
|
|
1660
|
+
|
|
1661
|
+
🎯 Возможности:
|
|
1662
|
+
- Точечное удаление записей
|
|
1663
|
+
- Очистка устаревших данных
|
|
1664
|
+
"""
|
|
1665
|
+
self.cursor.execute(f'DELETE FROM {table_name} WHERE id = ?', (id,))
|
|
1666
|
+
self.conn.commit()
|
|
1667
|
+
if self.cursor.rowcount:
|
|
1668
|
+
print(f" 🗑️ Удалена запись ID={id}")
|
|
1669
|
+
return True
|
|
1670
|
+
print(f" ❌ Запись ID={id} не найдена")
|
|
1671
|
+
return False
|
|
1672
|
+
|
|
1673
|
+
def clear_table(self, table_name):
|
|
1674
|
+
"""
|
|
1675
|
+
🧹 ОЧИСТИТЬ ТАБЛИЦУ: Удаляет все записи, но структура таблицы остаётся.
|
|
1676
|
+
|
|
1677
|
+
Параметры:
|
|
1678
|
+
table_name (str): имя таблицы
|
|
1679
|
+
|
|
1680
|
+
📝 Пример:
|
|
1681
|
+
db.clear_table("temp_logs")
|
|
1682
|
+
print("Временные логи очищены")
|
|
1683
|
+
|
|
1684
|
+
🎯 Возможности:
|
|
1685
|
+
- Быстрая очистка всех данных
|
|
1686
|
+
- Сохранение структуры таблицы для дальнейшего использования
|
|
1687
|
+
"""
|
|
1688
|
+
self.cursor.execute(f'DELETE FROM {table_name}')
|
|
1689
|
+
self.conn.commit()
|
|
1690
|
+
print(f" 🧹 Таблица '{table_name}' очищена")
|
|
1691
|
+
|
|
1692
|
+
def drop_table(self, table_name):
|
|
1693
|
+
"""
|
|
1694
|
+
💥 УДАЛИТЬ ТАБЛИЦУ: Полностью удаляет таблицу со всеми данными и структурой.
|
|
1695
|
+
|
|
1696
|
+
Параметры:
|
|
1697
|
+
table_name (str): имя таблицы для удаления
|
|
1698
|
+
|
|
1699
|
+
📝 Пример:
|
|
1700
|
+
db.drop_table("old_backups")
|
|
1701
|
+
# Таблица old_backups полностью удалена
|
|
1702
|
+
|
|
1703
|
+
🎯 Возможности:
|
|
1704
|
+
- Удаление устаревших таблиц
|
|
1705
|
+
- Очистка схемы базы данных
|
|
1706
|
+
"""
|
|
1707
|
+
self.cursor.execute(f'DROP TABLE IF EXISTS {table_name}')
|
|
1708
|
+
self.conn.commit()
|
|
1709
|
+
print(f" 💥 Таблица '{table_name}' удалена")
|
|
1710
|
+
|
|
1711
|
+
def stats(self, table_name):
|
|
1712
|
+
"""
|
|
1713
|
+
📊 СТАТИСТИКА: Показывает количество записей и список колонок таблицы.
|
|
1714
|
+
|
|
1715
|
+
Параметры:
|
|
1716
|
+
table_name (str): имя таблицы
|
|
1717
|
+
|
|
1718
|
+
Возвращает:
|
|
1719
|
+
dict: словарь с ключами "total" (количество записей) и "columns" (список колонок)
|
|
1720
|
+
|
|
1721
|
+
📝 Пример:
|
|
1722
|
+
stats = db.stats("users")
|
|
1723
|
+
print(f"Всего пользователей: {stats['total']}")
|
|
1724
|
+
print(f"Поля: {', '.join(stats['columns'])}")
|
|
1725
|
+
|
|
1726
|
+
🎯 Возможности:
|
|
1727
|
+
- Мониторинг размера таблиц
|
|
1728
|
+
- Проверка структуры данных
|
|
1729
|
+
"""
|
|
1730
|
+
self.cursor.execute(f'SELECT COUNT(*) FROM {table_name}')
|
|
1731
|
+
total = self.cursor.fetchone()[0]
|
|
1732
|
+
self.cursor.execute(f'PRAGMA table_info({table_name})')
|
|
1733
|
+
columns = [col[1] for col in self.cursor.fetchall()]
|
|
1734
|
+
print(f"\n📊 Статистика '{table_name}':")
|
|
1735
|
+
print(f" Записей: {total}")
|
|
1736
|
+
print(f" Колонки: {', '.join(columns)}")
|
|
1737
|
+
return {"total": total, "columns": columns}
|
|
1738
|
+
|
|
1739
|
+
def show_tables(self):
|
|
1740
|
+
"""
|
|
1741
|
+
📁 ПОКАЗАТЬ ТАБЛИЦЫ: Выводит список всех таблиц в базе данных.
|
|
1742
|
+
|
|
1743
|
+
📝 Пример:
|
|
1744
|
+
db.show_tables()
|
|
1745
|
+
# Выведет:
|
|
1746
|
+
# 📋 Таблицы в базе:
|
|
1747
|
+
# 📁 users
|
|
1748
|
+
# 📁 products
|
|
1749
|
+
# 📁 orders
|
|
1750
|
+
|
|
1751
|
+
🎯 Возможности:
|
|
1752
|
+
- Просмотр структуры базы данных
|
|
1753
|
+
- Проверка наличия нужных таблиц
|
|
1754
|
+
"""
|
|
1755
|
+
self.cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
|
1756
|
+
tables = self.cursor.fetchall()
|
|
1757
|
+
print("\n📋 Таблицы в базе:")
|
|
1758
|
+
for t in tables:
|
|
1759
|
+
print(f" 📁 {t[0]}")
|
|
1760
|
+
|
|
1761
|
+
def close(self):
|
|
1762
|
+
"""
|
|
1763
|
+
🔌 ЗАКРЫТЬ СОЕДИНЕНИЕ: Обязательно вызывать в конце работы с базой.
|
|
1764
|
+
|
|
1765
|
+
📝 Пример:
|
|
1766
|
+
db.close()
|
|
1767
|
+
print("Работа с базой завершена")
|
|
1768
|
+
|
|
1769
|
+
🎯 Возможности:
|
|
1770
|
+
- Корректное завершение работы
|
|
1771
|
+
- Сохранение всех изменений
|
|
1772
|
+
- Освобождение системных ресурсов
|
|
1773
|
+
"""
|
|
1774
|
+
self.conn.close()
|
|
1775
|
+
print("🔌 Соединение закрыто")
|
|
1776
|
+
|
|
1777
|
+
|
|
1778
|
+
# =============================================================================
|
|
1779
|
+
# КЛАСС Music – МУЗЫКАЛЬНЫЙ ПЛЕЕР С ЯНДЕКС МУЗЫКОЙ
|
|
1780
|
+
# =============================================================================
|
|
1781
|
+
class Music:
|
|
1782
|
+
"""
|
|
1783
|
+
🎵 МУЗЫКАЛЬНЫЙ ПЛЕЕР — управление Яндекс.Музыкой через VLC.
|
|
1784
|
+
|
|
1785
|
+
📝 Пример использования:
|
|
1786
|
+
from help_manager import Music
|
|
1787
|
+
|
|
1788
|
+
player = Music() # Создать плеер
|
|
1789
|
+
player.play("Imagine Dragons") # Включить трек
|
|
1790
|
+
player.pause() # Пауза / продолжить
|
|
1791
|
+
player.volume(80) # Установить громкость
|
|
1792
|
+
player.add_to_queue("Eminem - Mockingbird") # Добавить в очередь
|
|
1793
|
+
player.next() # Следующий трек
|
|
1794
|
+
player.play_my_wave() # Запустить Мою волну
|
|
1795
|
+
player.search_playlist("рок") # Найти и включить плейлист
|
|
1796
|
+
player.stop() # Остановить
|
|
1797
|
+
|
|
1798
|
+
🎯 Возможности класса:
|
|
1799
|
+
- Воспроизведение треков по названию
|
|
1800
|
+
- Управление очередью (добавление, переключение, очистка)
|
|
1801
|
+
- Пауза / продолжение / остановка
|
|
1802
|
+
- Регулировка громкости (0-100)
|
|
1803
|
+
- Режим «Моя волна» (любимые треки/рекомендации)
|
|
1804
|
+
- Поиск и воспроизведение плейлистов по названию
|
|
1805
|
+
- Кэширование ссылок на треки (ускорение повторного поиска)
|
|
1806
|
+
- Автоматическое переключение треков в очереди
|
|
1807
|
+
"""
|
|
1808
|
+
|
|
1809
|
+
def __init__(self, token=None):
|
|
1810
|
+
"""
|
|
1811
|
+
🎧 ИНИЦИАЛИЗАЦИЯ ПЛЕЕРА: Подключается к Яндекс.Музыке.
|
|
1812
|
+
|
|
1813
|
+
Параметры:
|
|
1814
|
+
token (str, optional): токен Яндекс.Музыки. Если не указан, используется встроенный.
|
|
1815
|
+
|
|
1816
|
+
📝 Пример:
|
|
1817
|
+
player = Music() # с встроенным токеном
|
|
1818
|
+
player = Music(token="y0_AgAAAA...") # со своим токеном
|
|
1819
|
+
|
|
1820
|
+
🎯 Возможности:
|
|
1821
|
+
- Автоматическая загрузка кэша треков
|
|
1822
|
+
- Готовность к воспроизведению сразу после создания
|
|
1823
|
+
"""
|
|
1824
|
+
from yandex_music import Client
|
|
1825
|
+
|
|
1826
|
+
self.token = token or "y0__xDMkd7CBhje-AYg2Zr6-xYw3peC3QeDw6D9crPyCF1q5ruJOitnYr6esA"
|
|
1827
|
+
if not self.token:
|
|
1828
|
+
raise ValueError("❌ Токен не найден! Укажи token или создай .env файл")
|
|
1829
|
+
|
|
1830
|
+
self.client = Client(self.token).init()
|
|
1831
|
+
self.player = None
|
|
1832
|
+
self.current_track = None
|
|
1833
|
+
self._is_playing = False
|
|
1834
|
+
self.paused = False
|
|
1835
|
+
self.volume_level = 50
|
|
1836
|
+
self.queue = [] # Очередь треков: [{'name': ..., 'url': ...}, ...]
|
|
1837
|
+
self.queue_index = 0 # Индекс текущего трека
|
|
1838
|
+
self._cache = {} # Кэш ссылок
|
|
1839
|
+
import vlc
|
|
1840
|
+
self.vlc_instance = vlc.Instance('--quiet')
|
|
1841
|
+
self._cache_file = "track_cache.json"
|
|
1842
|
+
self._load_cache()
|
|
1843
|
+
|
|
1844
|
+
print("✅ Музыкальный плеер готов")
|
|
1845
|
+
|
|
1846
|
+
# ========== Внутренние методы (не вызываются напрямую) ==========
|
|
1847
|
+
|
|
1848
|
+
def _load_cache(self):
|
|
1849
|
+
"""
|
|
1850
|
+
📦 ЗАГРУЗКА КЭША: Загружает кэшированные ссылки на треки из файла.
|
|
1851
|
+
Удаляет записи старше 3 часов.
|
|
1852
|
+
|
|
1853
|
+
(Вызывается автоматически при создании плеера)
|
|
1854
|
+
"""
|
|
1855
|
+
if os.path.exists(self._cache_file):
|
|
1856
|
+
try:
|
|
1857
|
+
with open(self._cache_file, 'r', encoding='utf-8') as f:
|
|
1858
|
+
raw_cache = json.load(f)
|
|
1859
|
+
now = time.time()
|
|
1860
|
+
self._cache = {}
|
|
1861
|
+
for query, data in raw_cache.items():
|
|
1862
|
+
if isinstance(data, dict) and now - data.get('timestamp', 0) < 10800: # 3 часа
|
|
1863
|
+
self._cache[query] = data['url']
|
|
1864
|
+
print(f"📦 Загружен кэш: {len(self._cache)} треков")
|
|
1865
|
+
except:
|
|
1866
|
+
self._cache = {}
|
|
1867
|
+
else:
|
|
1868
|
+
self._cache = {}
|
|
1869
|
+
|
|
1870
|
+
def _save_cache(self):
|
|
1871
|
+
"""
|
|
1872
|
+
💾 СОХРАНЕНИЕ КЭША: Сохраняет кэшированные ссылки в JSON-файл.
|
|
1873
|
+
|
|
1874
|
+
(Вызывается автоматически при добавлении новых треков в кэш)
|
|
1875
|
+
"""
|
|
1876
|
+
try:
|
|
1877
|
+
data_to_save = {}
|
|
1878
|
+
now = time.time()
|
|
1879
|
+
for query, url in self._cache.items():
|
|
1880
|
+
data_to_save[query] = {'url': url, 'timestamp': now}
|
|
1881
|
+
with open(self._cache_file, 'w', encoding='utf-8') as f:
|
|
1882
|
+
json.dump(data_to_save, f, ensure_ascii=False, indent=4)
|
|
1883
|
+
except Exception as e:
|
|
1884
|
+
print(f"⚠️ Не удалось сохранить кэш: {e}")
|
|
1885
|
+
|
|
1886
|
+
def _get_track_url(self, query):
|
|
1887
|
+
"""
|
|
1888
|
+
🔗 ПОЛУЧЕНИЕ ССЫЛКИ: Получает прямую ссылку на трек (из кэша или через API Яндекса).
|
|
1889
|
+
|
|
1890
|
+
Параметры:
|
|
1891
|
+
query (str): название трека (например, "Morgenshtern Cadillac")
|
|
1892
|
+
|
|
1893
|
+
Возвращает:
|
|
1894
|
+
str или None: URL трека или None, если трек не найден
|
|
1895
|
+
|
|
1896
|
+
(Внутренний метод)
|
|
1897
|
+
"""
|
|
1898
|
+
if query in self._cache:
|
|
1899
|
+
return self._cache[query]
|
|
1900
|
+
|
|
1901
|
+
search = self.client.search(query)
|
|
1902
|
+
if not search.tracks or not search.tracks.results:
|
|
1903
|
+
return None
|
|
1904
|
+
|
|
1905
|
+
track = search.tracks.results[0]
|
|
1906
|
+
full_track = self.client.tracks([track.id])[0]
|
|
1907
|
+
|
|
1908
|
+
url = None
|
|
1909
|
+
try:
|
|
1910
|
+
info = full_track.get_download_info()
|
|
1911
|
+
if info:
|
|
1912
|
+
url = info[0].get_direct_link()
|
|
1913
|
+
except:
|
|
1914
|
+
pass
|
|
1915
|
+
if not url:
|
|
1916
|
+
try:
|
|
1917
|
+
url = full_track.download_url
|
|
1918
|
+
except:
|
|
1919
|
+
pass
|
|
1920
|
+
|
|
1921
|
+
if url:
|
|
1922
|
+
self._cache[query] = url
|
|
1923
|
+
self._save_cache()
|
|
1924
|
+
return url
|
|
1925
|
+
|
|
1926
|
+
def _play_url(self, url, track_name):
|
|
1927
|
+
"""
|
|
1928
|
+
▶️ ЗАПУСК ТРЕКА: Запускает воспроизведение трека в VLC.
|
|
1929
|
+
|
|
1930
|
+
Параметры:
|
|
1931
|
+
url (str): прямая ссылка на трек
|
|
1932
|
+
track_name (str): название трека для отображения
|
|
1933
|
+
|
|
1934
|
+
(Внутренний метод)
|
|
1935
|
+
"""
|
|
1936
|
+
if self.player:
|
|
1937
|
+
self.player.stop()
|
|
1938
|
+
|
|
1939
|
+
self.player = self.vlc_instance.media_player_new()
|
|
1940
|
+
media = self.vlc_instance.media_new(url)
|
|
1941
|
+
self.player.set_media(media)
|
|
1942
|
+
self.player.play()
|
|
1943
|
+
self.player.audio_set_volume(self.volume_level)
|
|
1944
|
+
self._is_playing = True
|
|
1945
|
+
self.paused = False
|
|
1946
|
+
self.current_track = track_name
|
|
1947
|
+
print(f"▶️ Сейчас играет: {track_name}")
|
|
1948
|
+
|
|
1949
|
+
def _wait_for_end(self):
|
|
1950
|
+
"""
|
|
1951
|
+
⏳ ОЖИДАНИЕ ОКОНЧАНИЯ: Ждёт, пока текущий трек доиграет до конца.
|
|
1952
|
+
|
|
1953
|
+
(Внутренний метод, используется когда wait=True)
|
|
1954
|
+
"""
|
|
1955
|
+
import vlc
|
|
1956
|
+
if not self.player:
|
|
1957
|
+
return
|
|
1958
|
+
for _ in range(50):
|
|
1959
|
+
if self.player.get_state() == vlc.State.Playing:
|
|
1960
|
+
break
|
|
1961
|
+
time.sleep(0.1)
|
|
1962
|
+
while True:
|
|
1963
|
+
state = self.player.get_state()
|
|
1964
|
+
if state in (vlc.State.Ended, vlc.State.Stopped, vlc.State.Error):
|
|
1965
|
+
break
|
|
1966
|
+
time.sleep(0.3)
|
|
1967
|
+
time.sleep(0.2)
|
|
1968
|
+
|
|
1969
|
+
# ========== Базовое управление ==========
|
|
1970
|
+
|
|
1971
|
+
def play(self, query, wait=True):
|
|
1972
|
+
"""
|
|
1973
|
+
▶️ ВКЛЮЧИТЬ ТРЕК: Ищет трек по названию и запускает воспроизведение.
|
|
1974
|
+
Очищает предыдущую очередь.
|
|
1975
|
+
|
|
1976
|
+
Параметры:
|
|
1977
|
+
query (str): название трека (например, "Miyagi Captain")
|
|
1978
|
+
wait (bool): ждать ли окончания трека (по умолчанию True)
|
|
1979
|
+
|
|
1980
|
+
Возвращает:
|
|
1981
|
+
bool: True, если трек найден и запущен, иначе False
|
|
1982
|
+
|
|
1983
|
+
📝 Пример:
|
|
1984
|
+
player.play("Basta - Sansara") # запустить и ждать
|
|
1985
|
+
player.play("Imagine Dragons", wait=False) # запустить и не ждать
|
|
1986
|
+
|
|
1987
|
+
🎯 Возможности:
|
|
1988
|
+
- Автоматический поиск трека на Яндекс.Музыке
|
|
1989
|
+
- Кэширование ссылок для быстрого повтора
|
|
1990
|
+
"""
|
|
1991
|
+
url = self._get_track_url(query)
|
|
1992
|
+
if not url:
|
|
1993
|
+
print(f"❌ Не найден: {query}")
|
|
1994
|
+
return False
|
|
1995
|
+
|
|
1996
|
+
self.queue = [{'name': query, 'url': url}]
|
|
1997
|
+
self.queue_index = 0
|
|
1998
|
+
self._play_url(url, query)
|
|
1999
|
+
if wait:
|
|
2000
|
+
self._wait_for_end()
|
|
2001
|
+
return True
|
|
2002
|
+
|
|
2003
|
+
def pause(self):
|
|
2004
|
+
"""
|
|
2005
|
+
⏸️ ПАУЗА / ПРОДОЛЖИТЬ: Ставит трек на паузу или продолжает воспроизведение.
|
|
2006
|
+
|
|
2007
|
+
📝 Пример:
|
|
2008
|
+
player.pause() # если играл — пауза, если на паузе — продолжит
|
|
2009
|
+
|
|
2010
|
+
🎯 Возможности:
|
|
2011
|
+
- Удобное управление без запоминания состояния
|
|
2012
|
+
"""
|
|
2013
|
+
if self.player:
|
|
2014
|
+
if self._is_playing:
|
|
2015
|
+
self.player.pause()
|
|
2016
|
+
self._is_playing = False
|
|
2017
|
+
self.paused = True
|
|
2018
|
+
print("⏸️ Пауза")
|
|
2019
|
+
else:
|
|
2020
|
+
self.player.play()
|
|
2021
|
+
self._is_playing = True
|
|
2022
|
+
self.paused = False
|
|
2023
|
+
print("▶️ Продолжение")
|
|
2024
|
+
|
|
2025
|
+
def stop(self):
|
|
2026
|
+
"""
|
|
2027
|
+
⏹️ ОСТАНОВИТЬ: Полностью останавливает воспроизведение.
|
|
2028
|
+
|
|
2029
|
+
📝 Пример:
|
|
2030
|
+
player.stop()
|
|
2031
|
+
|
|
2032
|
+
🎯 Возможности:
|
|
2033
|
+
- Полная остановка плеера
|
|
2034
|
+
- Сброс текущего трека
|
|
2035
|
+
"""
|
|
2036
|
+
if self.player:
|
|
2037
|
+
self.player.stop()
|
|
2038
|
+
self._is_playing = False
|
|
2039
|
+
self.paused = False
|
|
2040
|
+
self.current_track = None
|
|
2041
|
+
print("⏹️ Остановлено")
|
|
2042
|
+
|
|
2043
|
+
def next(self, wait=True):
|
|
2044
|
+
"""
|
|
2045
|
+
⏭️ СЛЕДУЮЩИЙ ТРЕК: Переключает на следующий трек в очереди.
|
|
2046
|
+
|
|
2047
|
+
Параметры:
|
|
2048
|
+
wait (bool): ждать ли окончания трека (по умолчанию True)
|
|
2049
|
+
|
|
2050
|
+
Возвращает:
|
|
2051
|
+
bool: True, если переключение выполнено, иначе False
|
|
2052
|
+
|
|
2053
|
+
📝 Пример:
|
|
2054
|
+
player.next() # следующий и ждать
|
|
2055
|
+
player.next(wait=False) # следующий без ожидания
|
|
2056
|
+
|
|
2057
|
+
🎯 Возможности:
|
|
2058
|
+
- Автоматическое переключение плейлистов
|
|
2059
|
+
"""
|
|
2060
|
+
if not self.queue or self.queue_index + 1 >= len(self.queue):
|
|
2061
|
+
self.stop()
|
|
2062
|
+
print("⏹️ Это был последний трек в очереди")
|
|
2063
|
+
return False
|
|
2064
|
+
self.queue_index += 1
|
|
2065
|
+
track = self.queue[self.queue_index]
|
|
2066
|
+
self._play_url(track['url'], track['name'])
|
|
2067
|
+
if wait:
|
|
2068
|
+
self._wait_for_end()
|
|
2069
|
+
return True
|
|
2070
|
+
|
|
2071
|
+
def prev(self, wait=True):
|
|
2072
|
+
"""
|
|
2073
|
+
⏮️ ПРЕДЫДУЩИЙ ТРЕК: Переключает на предыдущий трек в очереди.
|
|
2074
|
+
|
|
2075
|
+
Параметры:
|
|
2076
|
+
wait (bool): ждать ли окончания трека (по умолчанию True)
|
|
2077
|
+
|
|
2078
|
+
Возвращает:
|
|
2079
|
+
bool: True, если переключение выполнено, иначе False
|
|
2080
|
+
|
|
2081
|
+
📝 Пример:
|
|
2082
|
+
player.prev()
|
|
2083
|
+
player.prev(wait=False)
|
|
2084
|
+
"""
|
|
2085
|
+
if not self.queue or self.queue_index <= 0:
|
|
2086
|
+
print("⏮️ Это первый трек в очереди")
|
|
2087
|
+
return False
|
|
2088
|
+
self.queue_index -= 1
|
|
2089
|
+
track = self.queue[self.queue_index]
|
|
2090
|
+
self._play_url(track['url'], track['name'])
|
|
2091
|
+
if wait:
|
|
2092
|
+
self._wait_for_end()
|
|
2093
|
+
return True
|
|
2094
|
+
|
|
2095
|
+
def volume(self, level):
|
|
2096
|
+
"""
|
|
2097
|
+
🔊 ГРОМКОСТЬ: Устанавливает громкость воспроизведения.
|
|
2098
|
+
|
|
2099
|
+
Параметры:
|
|
2100
|
+
level (int): число от 0 (без звука) до 100 (максимум)
|
|
2101
|
+
|
|
2102
|
+
📝 Пример:
|
|
2103
|
+
player.volume(80) # громкость 80%
|
|
2104
|
+
player.volume(0) # без звука
|
|
2105
|
+
|
|
2106
|
+
🎯 Возможности:
|
|
2107
|
+
- Плавная регулировка громкости
|
|
2108
|
+
- Автоматическое ограничение диапазона 0-100
|
|
2109
|
+
"""
|
|
2110
|
+
self.volume_level = max(0, min(100, level))
|
|
2111
|
+
if self.player:
|
|
2112
|
+
self.player.audio_set_volume(self.volume_level)
|
|
2113
|
+
print(f"🔊 Громкость: {self.volume_level}%")
|
|
2114
|
+
|
|
2115
|
+
def volume_up(self, step=10):
|
|
2116
|
+
"""
|
|
2117
|
+
🔊➕ ГРОМЧЕ: Увеличивает громкость на указанный шаг.
|
|
2118
|
+
|
|
2119
|
+
Параметры:
|
|
2120
|
+
step (int): на сколько увеличить (по умолчанию 10)
|
|
2121
|
+
|
|
2122
|
+
📝 Пример:
|
|
2123
|
+
player.volume_up() # +10%
|
|
2124
|
+
player.volume_up(20) # +20%
|
|
2125
|
+
"""
|
|
2126
|
+
self.volume(self.volume_level + step)
|
|
2127
|
+
|
|
2128
|
+
def volume_down(self, step=10):
|
|
2129
|
+
"""
|
|
2130
|
+
🔊➖ ТИШЕ: Уменьшает громкость на указанный шаг.
|
|
2131
|
+
|
|
2132
|
+
Параметры:
|
|
2133
|
+
step (int): на сколько уменьшить (по умолчанию 10)
|
|
2134
|
+
|
|
2135
|
+
📝 Пример:
|
|
2136
|
+
player.volume_down() # -10%
|
|
2137
|
+
player.volume_down(5) # -5%
|
|
2138
|
+
"""
|
|
2139
|
+
self.volume(self.volume_level - step)
|
|
2140
|
+
|
|
2141
|
+
# ========== Очередь ==========
|
|
2142
|
+
|
|
2143
|
+
def add_to_queue(self, query):
|
|
2144
|
+
"""
|
|
2145
|
+
➕ ДОБАВИТЬ В ОЧЕРЕДЬ: Добавляет трек в конец очереди (не прерывая текущий).
|
|
2146
|
+
|
|
2147
|
+
Параметры:
|
|
2148
|
+
query (str): название трека
|
|
2149
|
+
|
|
2150
|
+
Возвращает:
|
|
2151
|
+
bool: True, если трек найден и добавлен, иначе False
|
|
2152
|
+
|
|
2153
|
+
📝 Пример:
|
|
2154
|
+
player.add_to_queue("Eminem - Lose Yourself")
|
|
2155
|
+
player.add_to_queue("Miyagi - Captain")
|
|
2156
|
+
|
|
2157
|
+
🎯 Возможности:
|
|
2158
|
+
- Составление плейлиста на лету
|
|
2159
|
+
- Не прерывает текущее воспроизведение
|
|
2160
|
+
"""
|
|
2161
|
+
url = self._get_track_url(query)
|
|
2162
|
+
if not url:
|
|
2163
|
+
print(f"❌ Не найден: {query}")
|
|
2164
|
+
return False
|
|
2165
|
+
self.queue.append({'name': query, 'url': url})
|
|
2166
|
+
print(f"➕ В очередь добавлен: {query}")
|
|
2167
|
+
return True
|
|
2168
|
+
|
|
2169
|
+
def show_queue(self):
|
|
2170
|
+
"""
|
|
2171
|
+
📋 ПОКАЗАТЬ ОЧЕРЕДЬ: Выводит текущую очередь треков.
|
|
2172
|
+
|
|
2173
|
+
📝 Пример:
|
|
2174
|
+
player.show_queue()
|
|
2175
|
+
# Выведет:
|
|
2176
|
+
# 📋 ОЧЕРЕДЬ (3 треков):
|
|
2177
|
+
# ▶️ 1. Miyagi - Captain
|
|
2178
|
+
# 2. Basta - Sansara
|
|
2179
|
+
# 3. Eminem - Lose Yourself
|
|
2180
|
+
|
|
2181
|
+
🎯 Возможности:
|
|
2182
|
+
- Просмотр плейлиста
|
|
2183
|
+
- Контроль порядка треков
|
|
2184
|
+
"""
|
|
2185
|
+
if not self.queue:
|
|
2186
|
+
print("📭 Очередь пуста")
|
|
2187
|
+
return
|
|
2188
|
+
print(f"\n📋 ОЧЕРЕДЬ ({len(self.queue)} треков):")
|
|
2189
|
+
for i, track in enumerate(self.queue):
|
|
2190
|
+
mark = "▶️" if i == self.queue_index else " "
|
|
2191
|
+
print(f" {mark} {i+1}. {track['name']}")
|
|
2192
|
+
|
|
2193
|
+
def clear_queue(self):
|
|
2194
|
+
"""
|
|
2195
|
+
🧹 ОЧИСТИТЬ ОЧЕРЕДЬ: Удаляет все треки из очереди.
|
|
2196
|
+
|
|
2197
|
+
📝 Пример:
|
|
2198
|
+
player.clear_queue()
|
|
2199
|
+
player.show_queue() # 📭 Очередь пуста
|
|
2200
|
+
|
|
2201
|
+
🎯 Возможности:
|
|
2202
|
+
- Быстрая очистка плейлиста
|
|
2203
|
+
- Подготовка к новому набору треков
|
|
2204
|
+
"""
|
|
2205
|
+
self.queue = []
|
|
2206
|
+
self.queue_index = 0
|
|
2207
|
+
print("🧹 Очередь очищена")
|
|
2208
|
+
|
|
2209
|
+
# ========== Моя волна и плейлисты ==========
|
|
2210
|
+
|
|
2211
|
+
def play_my_wave(self, limit=10, wait=True):
|
|
2212
|
+
"""
|
|
2213
|
+
🌊 НАСТОЯЩАЯ МОЯ ВОЛНА: Запускает бесконечный поток рекомендаций Яндекс.Музыки.
|
|
2214
|
+
|
|
2215
|
+
Использует station (radio) — те же алгоритмы, что и в приложении Яндекс.Музыки.
|
|
2216
|
+
Это НЕ просто лайкнутые треки, а уникальные рекомендации на основе твоих вкусов.
|
|
2217
|
+
|
|
2218
|
+
Параметры:
|
|
2219
|
+
limit (int): сколько треков загрузить (по умолчанию 10)
|
|
2220
|
+
wait (bool): ждать ли окончания первого трека
|
|
2221
|
+
|
|
2222
|
+
Возвращает:
|
|
2223
|
+
bool: True при успехе, иначе False
|
|
2224
|
+
|
|
2225
|
+
📝 Пример:
|
|
2226
|
+
player.play_my_wave() # 10 треков
|
|
2227
|
+
player.play_my_wave(limit=5, wait=False) # 5 треков, не ждать
|
|
2228
|
+
|
|
2229
|
+
🎯 Возможности:
|
|
2230
|
+
- Уникальные рекомендации как в приложении Яндекс.Музыки
|
|
2231
|
+
- Каждый запуск — новые треки
|
|
2232
|
+
- Учитывает твои вкусы и историю прослушивания
|
|
2233
|
+
"""
|
|
2234
|
+
try:
|
|
2235
|
+
print("🎵 Загружаю «Мою волну» (рекомендации)...")
|
|
2236
|
+
|
|
2237
|
+
# Получаем станцию "Моя волна" — это настоящие рекомендации
|
|
2238
|
+
station = self.client.rotor_station_info_dashboard()
|
|
2239
|
+
|
|
2240
|
+
if not station or not station.stations:
|
|
2241
|
+
# Если не получилось — fallback на лайки
|
|
2242
|
+
print("⚠️ Станция недоступна, использую лайкнутые треки")
|
|
2243
|
+
return self._play_liked_tracks(limit, wait)
|
|
2244
|
+
|
|
2245
|
+
# Берём первую станцию (обычно это "Моя волна")
|
|
2246
|
+
my_wave = station.stations[0]
|
|
2247
|
+
|
|
2248
|
+
# Получаем треки из станции
|
|
2249
|
+
queue = self.client.rotor_station_tracks(my_wave.station)
|
|
2250
|
+
|
|
2251
|
+
if not queue or not hasattr(queue, 'sequence'):
|
|
2252
|
+
print("⚠️ Не удалось загрузить рекомендации, использую лайки")
|
|
2253
|
+
return self._play_liked_tracks(limit, wait)
|
|
2254
|
+
|
|
2255
|
+
self.queue = []
|
|
2256
|
+
for track_item in list(queue.sequence)[:limit]:
|
|
2257
|
+
try:
|
|
2258
|
+
track = track_item.track
|
|
2259
|
+
name = f"{track.title} — {track.artists[0].name}"
|
|
2260
|
+
url = self._get_track_url(name)
|
|
2261
|
+
if url:
|
|
2262
|
+
self.queue.append({'name': name, 'url': url})
|
|
2263
|
+
except:
|
|
2264
|
+
continue
|
|
2265
|
+
|
|
2266
|
+
if not self.queue:
|
|
2267
|
+
print("⚠️ Не удалось загрузить ссылки, использую лайки")
|
|
2268
|
+
return self._play_liked_tracks(limit, wait)
|
|
2269
|
+
|
|
2270
|
+
self.queue_index = 0
|
|
2271
|
+
self._play_url(self.queue[0]['url'], self.queue[0]['name'])
|
|
2272
|
+
print(f"✅ Загружено {len(self.queue)} треков из «Моей волны»")
|
|
2273
|
+
if wait:
|
|
2274
|
+
self._wait_for_end()
|
|
2275
|
+
return True
|
|
2276
|
+
|
|
2277
|
+
except Exception as e:
|
|
2278
|
+
print(f"⚠️ Ошибка загрузки рекомендаций: {e}")
|
|
2279
|
+
print("🔄 Переключаюсь на лайкнутые треки...")
|
|
2280
|
+
return self.play_liked_tracks(limit, wait)
|
|
2281
|
+
|
|
2282
|
+
def play_liked_tracks(self, limit=10, wait=True):
|
|
2283
|
+
"""
|
|
2284
|
+
❤️ ЗАПАСНОЙ ВАРИАНТ: Играет лайкнутые треки, если рекомендации недоступны.
|
|
2285
|
+
(Внутренний метод, вызывается автоматически)
|
|
2286
|
+
"""
|
|
2287
|
+
try:
|
|
2288
|
+
print("🎵 Загружаю лайкнутые треки...")
|
|
2289
|
+
likes = self.client.users_likes_tracks()
|
|
2290
|
+
if not likes:
|
|
2291
|
+
print("❌ Нет лайкнутых треков.")
|
|
2292
|
+
return False
|
|
2293
|
+
|
|
2294
|
+
self.queue = []
|
|
2295
|
+
for like in likes[:limit]:
|
|
2296
|
+
track = like.fetch_track()
|
|
2297
|
+
name = f"{track.title} — {track.artists[0].name}"
|
|
2298
|
+
url = self._get_track_url(name)
|
|
2299
|
+
if url:
|
|
2300
|
+
self.queue.append({'name': name, 'url': url})
|
|
2301
|
+
|
|
2302
|
+
if not self.queue:
|
|
2303
|
+
print("❌ Не удалось загрузить треки")
|
|
2304
|
+
return False
|
|
2305
|
+
|
|
2306
|
+
self.queue_index = 0
|
|
2307
|
+
self._play_url(self.queue[0]['url'], self.queue[0]['name'])
|
|
2308
|
+
print(f"✅ Загружено {len(self.queue)} треков из лайков")
|
|
2309
|
+
if wait:
|
|
2310
|
+
self._wait_for_end()
|
|
2311
|
+
return True
|
|
2312
|
+
except Exception as e:
|
|
2313
|
+
print(f"❌ Ошибка: {e}")
|
|
2314
|
+
return False
|
|
2315
|
+
|
|
2316
|
+
def search_playlist(self, query, limit=10, wait=True):
|
|
2317
|
+
"""
|
|
2318
|
+
🔍 НАЙТИ ПЛЕЙЛИСТ: Находит плейлист по названию и включает его.
|
|
2319
|
+
|
|
2320
|
+
Параметры:
|
|
2321
|
+
query (str): название плейлиста (например, "рок музыка", "поп 2024")
|
|
2322
|
+
limit (int): количество загружаемых треков
|
|
2323
|
+
wait (bool): ждать ли окончания первого трека
|
|
2324
|
+
|
|
2325
|
+
Возвращает:
|
|
2326
|
+
bool: True при успехе, иначе False
|
|
2327
|
+
|
|
2328
|
+
📝 Пример:
|
|
2329
|
+
player.search_playlist("поп музыка")
|
|
2330
|
+
player.search_playlist("рок", limit=20, wait=False)
|
|
2331
|
+
|
|
2332
|
+
🎯 Возможности:
|
|
2333
|
+
- Поиск любого публичного плейлиста
|
|
2334
|
+
- Быстрое переключение настроения
|
|
2335
|
+
"""
|
|
2336
|
+
try:
|
|
2337
|
+
print(f"🔍 Ищу плейлист: {query}")
|
|
2338
|
+
search = self.client.search(query)
|
|
2339
|
+
if not search.playlists or not search.playlists.results:
|
|
2340
|
+
print("❌ Плейлист не найден")
|
|
2341
|
+
return False
|
|
2342
|
+
|
|
2343
|
+
playlist = search.playlists.results[0]
|
|
2344
|
+
print(f"✅ Найден плейлист: {playlist.title}")
|
|
2345
|
+
|
|
2346
|
+
self.queue = []
|
|
2347
|
+
|
|
2348
|
+
# Пробуем получить треки через fetch_tracks (более надёжный способ)
|
|
2349
|
+
try:
|
|
2350
|
+
# У плейлиста может быть разная структура — пробуем оба варианта
|
|
2351
|
+
if hasattr(playlist, 'fetch_tracks'):
|
|
2352
|
+
tracks_data = playlist.fetch_tracks()
|
|
2353
|
+
elif hasattr(playlist, 'tracks'):
|
|
2354
|
+
tracks_data = playlist.tracks
|
|
2355
|
+
else:
|
|
2356
|
+
print("❌ Не удалось получить треки (неподдерживаемый формат плейлиста)")
|
|
2357
|
+
return False
|
|
2358
|
+
|
|
2359
|
+
# Ограничиваем количество
|
|
2360
|
+
track_list = list(tracks_data)[:limit]
|
|
2361
|
+
|
|
2362
|
+
for item in track_list:
|
|
2363
|
+
try:
|
|
2364
|
+
# Структура трека может быть разной
|
|
2365
|
+
if hasattr(item, 'track') and item.track:
|
|
2366
|
+
track_info = item.track
|
|
2367
|
+
elif hasattr(item, 'fetch_track'):
|
|
2368
|
+
track_info = item.fetch_track()
|
|
2369
|
+
else:
|
|
2370
|
+
continue
|
|
2371
|
+
|
|
2372
|
+
# Безопасно получаем название и исполнителя
|
|
2373
|
+
title = getattr(track_info, 'title', None)
|
|
2374
|
+
artists = getattr(track_info, 'artists', None)
|
|
2375
|
+
|
|
2376
|
+
if title and artists:
|
|
2377
|
+
artist_name = artists[0].name if artists else "Неизвестен"
|
|
2378
|
+
name = f"{title} — {artist_name}"
|
|
2379
|
+
elif title:
|
|
2380
|
+
name = title
|
|
2381
|
+
else:
|
|
2382
|
+
continue
|
|
2383
|
+
|
|
2384
|
+
url = self._get_track_url(name)
|
|
2385
|
+
if url:
|
|
2386
|
+
self.queue.append({'name': name, 'url': url})
|
|
2387
|
+
except:
|
|
2388
|
+
continue
|
|
2389
|
+
|
|
2390
|
+
except Exception as e:
|
|
2391
|
+
print(f"⚠️ Ошибка при загрузке треков: {e}")
|
|
2392
|
+
return False
|
|
2393
|
+
|
|
2394
|
+
if not self.queue:
|
|
2395
|
+
print("❌ Не удалось загрузить треки из плейлиста")
|
|
2396
|
+
return False
|
|
2397
|
+
|
|
2398
|
+
self.queue_index = 0
|
|
2399
|
+
self._play_url(self.queue[0]['url'], self.queue[0]['name'])
|
|
2400
|
+
print(f"✅ Загружено {len(self.queue)} треков из «{playlist.title}»")
|
|
2401
|
+
if wait:
|
|
2402
|
+
self._wait_for_end()
|
|
2403
|
+
return True
|
|
2404
|
+
|
|
2405
|
+
except Exception as e:
|
|
2406
|
+
print(f"❌ Ошибка: {e}")
|
|
2407
|
+
return False
|
|
2408
|
+
|
|
2409
|
+
def mood(self, mood_name, limit=5, wait=True):
|
|
2410
|
+
"""
|
|
2411
|
+
🎭 МУЗЫКА ПО НАСТРОЕНИЮ: Включает плейлист, соответствующий настроению.
|
|
2412
|
+
|
|
2413
|
+
Сначала пробует найти плейлист с настроением, если не получается —
|
|
2414
|
+
включает лайкнутые треки (Мою волну).
|
|
2415
|
+
|
|
2416
|
+
Параметры:
|
|
2417
|
+
mood_name (str): настроение ('happy', 'sad', 'energy', 'calm')
|
|
2418
|
+
limit (int): количество треков
|
|
2419
|
+
wait (bool): ждать ли окончания первого трека
|
|
2420
|
+
|
|
2421
|
+
Возвращает:
|
|
2422
|
+
bool: True при успехе, иначе False
|
|
2423
|
+
|
|
2424
|
+
📝 Пример:
|
|
2425
|
+
player.mood("happy") # весёлая музыка
|
|
2426
|
+
player.mood("calm", limit=3) # спокойная, 3 трека
|
|
2427
|
+
|
|
2428
|
+
🎯 Возможности:
|
|
2429
|
+
- Быстрый подбор музыки под настроение
|
|
2430
|
+
- Если плейлист не найден — включает Мою волну
|
|
2431
|
+
"""
|
|
2432
|
+
print(f"🎭 Включаю музыку под настроение: {mood_name}")
|
|
2433
|
+
|
|
2434
|
+
# Сначала пробуем найти плейлист
|
|
2435
|
+
result = self.search_playlist(mood_name, limit, wait)
|
|
2436
|
+
|
|
2437
|
+
# Если не получилось — включаем Мою волну как fallback
|
|
2438
|
+
if not result:
|
|
2439
|
+
print(f"⚠️ Плейлист '{mood_name}' не загрузился, включаю «Мою волну»")
|
|
2440
|
+
result = self.play_my_wave(limit, wait)
|
|
2441
|
+
|
|
2442
|
+
return result
|
|
2443
|
+
|
|
2444
|
+
# ========== Информационные методы ==========
|
|
2445
|
+
|
|
2446
|
+
def is_now_playing(self):
|
|
2447
|
+
"""
|
|
2448
|
+
▶️ ИГРАЕТ СЕЙЧАС?: Проверяет, воспроизводится ли трек в данный момент.
|
|
2449
|
+
|
|
2450
|
+
Возвращает:
|
|
2451
|
+
bool: True если трек играет, False если пауза или остановлен
|
|
2452
|
+
|
|
2453
|
+
📝 Пример:
|
|
2454
|
+
if player.is_now_playing():
|
|
2455
|
+
print("Сейчас играет:", player.get_current_track())
|
|
2456
|
+
|
|
2457
|
+
🎯 Возможности:
|
|
2458
|
+
- Проверка состояния плеера
|
|
2459
|
+
- Условное выполнение действий
|
|
2460
|
+
"""
|
|
2461
|
+
return self._is_playing
|
|
2462
|
+
|
|
2463
|
+
def get_current_track(self):
|
|
2464
|
+
"""
|
|
2465
|
+
🎵 ТЕКУЩИЙ ТРЕК: Возвращает название текущего трека.
|
|
2466
|
+
|
|
2467
|
+
Возвращает:
|
|
2468
|
+
str или None: название трека или None, если ничего не играет
|
|
2469
|
+
|
|
2470
|
+
📝 Пример:
|
|
2471
|
+
track = player.get_current_track()
|
|
2472
|
+
if track:
|
|
2473
|
+
print(f"Сейчас играет: {track}")
|
|
2474
|
+
|
|
2475
|
+
🎯 Возможности:
|
|
2476
|
+
- Отображение текущего трека в интерфейсе
|
|
2477
|
+
- Логирование прослушанной музыки
|
|
2478
|
+
"""
|
|
2479
|
+
return self.current_track
|
|
2480
|
+
|
|
2481
|
+
def get_volume(self):
|
|
2482
|
+
"""
|
|
2483
|
+
🔊 ТЕКУЩАЯ ГРОМКОСТЬ: Возвращает текущий уровень громкости.
|
|
2484
|
+
|
|
2485
|
+
Возвращает:
|
|
2486
|
+
int: число от 0 до 100
|
|
2487
|
+
|
|
2488
|
+
📝 Пример:
|
|
2489
|
+
vol = player.get_volume()
|
|
2490
|
+
print(f"Громкость: {vol}%")
|
|
2491
|
+
|
|
2492
|
+
🎯 Возможности:
|
|
2493
|
+
- Сохранение предпочтений громкости
|
|
2494
|
+
- Отображение в интерфейсе
|
|
2495
|
+
"""
|
|
2496
|
+
return self.volume_level
|
|
2497
|
+
|
|
2498
|
+
def get_queue_length(self):
|
|
2499
|
+
"""
|
|
2500
|
+
📏 ДЛИНА ОЧЕРЕДИ: Возвращает количество треков в очереди.
|
|
2501
|
+
|
|
2502
|
+
Возвращает:
|
|
2503
|
+
int: количество треков
|
|
2504
|
+
|
|
2505
|
+
📝 Пример:
|
|
2506
|
+
count = player.get_queue_length()
|
|
2507
|
+
print(f"В очереди {count} треков")
|
|
2508
|
+
|
|
2509
|
+
🎯 Возможности:
|
|
2510
|
+
- Информирование о размере плейлиста
|
|
2511
|
+
- Проверка, есть ли ещё треки
|
|
2512
|
+
"""
|
|
2513
|
+
return len(self.queue)
|
|
2514
|
+
|
|
2515
|
+
|
|
2516
|
+
# =============================================================================
|
|
2517
|
+
# КЛАСС Face – РАСПОЗНАВАНИЕ ЛИЦ И ЭМОЦИЙ
|
|
2518
|
+
# =============================================================================
|
|
2519
|
+
class Face:
|
|
2520
|
+
"""
|
|
2521
|
+
🧠 РАСПОЗНАВАНИЕ ЛИЦ И ЭМОЦИЙ — анализ через веб-камеру.
|
|
2522
|
+
|
|
2523
|
+
📝 Пример использования:
|
|
2524
|
+
emotion = Face.detect_emotion()
|
|
2525
|
+
if emotion == 'happy':
|
|
2526
|
+
print("Ты счастлив!")
|
|
2527
|
+
is_smiling = Face.detect_smile()
|
|
2528
|
+
Face.music_by_mood() # включит музыку под настроение
|
|
2529
|
+
|
|
2530
|
+
🎯 Возможности класса:
|
|
2531
|
+
- Определение эмоций (7 базовых эмоций)
|
|
2532
|
+
- Обнаружение улыбки
|
|
2533
|
+
- Автоматический запуск музыки по настроению
|
|
2534
|
+
"""
|
|
2535
|
+
|
|
2536
|
+
@staticmethod
|
|
2537
|
+
def detect_emotion():
|
|
2538
|
+
"""
|
|
2539
|
+
🎭 ОПРЕДЕЛИТЬ ЭМОЦИЮ: Анализирует лицо через веб-камеру с помощью DeepFace.
|
|
2540
|
+
|
|
2541
|
+
Возвращает:
|
|
2542
|
+
str или None: одна из эмоций ('happy', 'sad', 'angry', 'surprise', 'fear', 'disgust', 'neutral')
|
|
2543
|
+
или None, если лицо не найдено
|
|
2544
|
+
|
|
2545
|
+
📝 Пример:
|
|
2546
|
+
emotion = Face.detect_emotion()
|
|
2547
|
+
if emotion == 'happy':
|
|
2548
|
+
print("Ты сегодня в хорошем настроении!")
|
|
2549
|
+
elif emotion == 'sad':
|
|
2550
|
+
print("Может, включить музыку?")
|
|
2551
|
+
|
|
2552
|
+
🎯 Возможности:
|
|
2553
|
+
- Определение настроения пользователя
|
|
2554
|
+
- Адаптация интерфейса под эмоции
|
|
2555
|
+
- Использование в играх и интерактивных приложениях
|
|
2556
|
+
"""
|
|
2557
|
+
import cv2
|
|
2558
|
+
from deepface import DeepFace
|
|
2559
|
+
|
|
2560
|
+
cap = cv2.VideoCapture(0)
|
|
2561
|
+
ret, frame = cap.read()
|
|
2562
|
+
cap.release()
|
|
2563
|
+
|
|
2564
|
+
if not ret:
|
|
2565
|
+
print("❌ Не удалось получить кадр с камеры")
|
|
2566
|
+
return None
|
|
2567
|
+
|
|
2568
|
+
try:
|
|
2569
|
+
result = DeepFace.analyze(
|
|
2570
|
+
img_path=frame,
|
|
2571
|
+
actions=['emotion'],
|
|
2572
|
+
enforce_detection=False,
|
|
2573
|
+
silent=True
|
|
2574
|
+
)
|
|
2575
|
+
emotion = result[0]['dominant_emotion']
|
|
2576
|
+
return emotion
|
|
2577
|
+
except Exception as e:
|
|
2578
|
+
print(f"❌ Ошибка DeepFace: {e}")
|
|
2579
|
+
return None
|
|
2580
|
+
|
|
2581
|
+
@staticmethod
|
|
2582
|
+
def detect_smile():
|
|
2583
|
+
"""
|
|
2584
|
+
😁 ОБНАРУЖЕНИЕ УЛЫБКИ: Проверяет, улыбается ли человек в кадре.
|
|
2585
|
+
|
|
2586
|
+
Возвращает:
|
|
2587
|
+
bool: True если обнаружена улыбка, False если нет или ошибка
|
|
2588
|
+
|
|
2589
|
+
📝 Пример:
|
|
2590
|
+
if Face.detect_smile():
|
|
2591
|
+
print("Отличная улыбка!")
|
|
2592
|
+
Auto.screenshot("smile_capture.png")
|
|
2593
|
+
|
|
2594
|
+
🎯 Возможности:
|
|
2595
|
+
- Автоматическая фотосъёмка при улыбке
|
|
2596
|
+
- Игровые механики с отслеживанием улыбки
|
|
2597
|
+
- Интерактивные приложения
|
|
2598
|
+
"""
|
|
2599
|
+
import cv2
|
|
2600
|
+
|
|
2601
|
+
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
|
|
2602
|
+
smile_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_smile.xml')
|
|
2603
|
+
|
|
2604
|
+
cap = cv2.VideoCapture(0)
|
|
2605
|
+
ret, frame = cap.read()
|
|
2606
|
+
cap.release()
|
|
2607
|
+
|
|
2608
|
+
if not ret:
|
|
2609
|
+
return False
|
|
2610
|
+
|
|
2611
|
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
|
2612
|
+
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
|
|
2613
|
+
|
|
2614
|
+
for (x, y, w, h) in faces:
|
|
2615
|
+
roi = gray[y:y+h, x:x+w]
|
|
2616
|
+
smiles = smile_cascade.detectMultiScale(roi, 1.8, 20)
|
|
2617
|
+
if len(smiles) > 0:
|
|
2618
|
+
return True
|
|
2619
|
+
|
|
2620
|
+
return False
|
|
2621
|
+
|
|
2622
|
+
@staticmethod
|
|
2623
|
+
def music_by_mood():
|
|
2624
|
+
"""
|
|
2625
|
+
🎵 МУЗЫКА ПО НАСТРОЕНИЮ: Определяет эмоцию и включает подходящий плейлист.
|
|
2626
|
+
|
|
2627
|
+
📝 Пример:
|
|
2628
|
+
Face.music_by_mood()
|
|
2629
|
+
# Определит, что ты грустишь, и включит спокойную музыку
|
|
2630
|
+
|
|
2631
|
+
🎯 Возможности:
|
|
2632
|
+
- Автоматический подбор музыки
|
|
2633
|
+
- Создание атмосферы под настроение
|
|
2634
|
+
|
|
2635
|
+
Таблица соответствия эмоций и плейлистов:
|
|
2636
|
+
- happy → happy (весёлая музыка)
|
|
2637
|
+
- sad → sad (грустная музыка)
|
|
2638
|
+
- angry → energy (энергичная музыка)
|
|
2639
|
+
- surprise → happy (весёлая музыка)
|
|
2640
|
+
- fear → calm (спокойная музыка)
|
|
2641
|
+
- neutral → calm (спокойная музыка)
|
|
2642
|
+
"""
|
|
2643
|
+
emotion = Face.detect_emotion()
|
|
2644
|
+
if not emotion:
|
|
2645
|
+
print("Не удалось определить эмоцию")
|
|
2646
|
+
return
|
|
2647
|
+
|
|
2648
|
+
mood_map = {
|
|
2649
|
+
'happy': 'happy',
|
|
2650
|
+
'sad': 'sad',
|
|
2651
|
+
'angry': 'energy',
|
|
2652
|
+
'surprise': 'happy',
|
|
2653
|
+
'fear': 'calm',
|
|
2654
|
+
'neutral': 'calm'
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
music = Music()
|
|
2658
|
+
music.mood(mood_map.get(emotion, 'calm'))
|
|
2659
|
+
|
|
2660
|
+
|
|
2661
|
+
# =============================================================================
|
|
2662
|
+
# КЛАСС Auto – АВТОМАТИЗАЦИЯ КЛАВИШ И МЫШИ
|
|
2663
|
+
# =============================================================================
|
|
2664
|
+
class Auto:
|
|
2665
|
+
"""
|
|
2666
|
+
🖱️ АВТОМАТИЗАЦИЯ: клики, клавиши, скриншоты, уведомления.
|
|
2667
|
+
|
|
2668
|
+
Все методы статические — вызывай напрямую:
|
|
2669
|
+
Auto.click()
|
|
2670
|
+
Auto.press('enter')
|
|
2671
|
+
Auto.write("Привет!")
|
|
2672
|
+
|
|
2673
|
+
🎯 Возможности класса:
|
|
2674
|
+
- Эмуляция клавиатуры (нажатие, удержание, печать текста)
|
|
2675
|
+
- Эмуляция мыши (клики, двойные клики, перетаскивание, скроллинг)
|
|
2676
|
+
- Создание скриншотов
|
|
2677
|
+
- Системные уведомления
|
|
2678
|
+
"""
|
|
2679
|
+
|
|
2680
|
+
# =========================== КЛАВИАТУРА ===========================
|
|
2681
|
+
|
|
2682
|
+
@staticmethod
|
|
2683
|
+
def key_down(key: str):
|
|
2684
|
+
"""
|
|
2685
|
+
⬇️ ЗАЖАТЬ КЛАВИШУ: Зажимает и удерживает клавишу (не отпускает, пока не вызван key_up).
|
|
2686
|
+
|
|
2687
|
+
Параметры:
|
|
2688
|
+
key (str): название клавиши, например 'shift', 'ctrl', 'a', 'win'
|
|
2689
|
+
|
|
2690
|
+
📝 Пример:
|
|
2691
|
+
Auto.key_down('ctrl') # зажали Ctrl
|
|
2692
|
+
Auto.click(100, 200) # кликнули мышью с зажатым Ctrl
|
|
2693
|
+
Auto.key_up('ctrl') # отпустили Ctrl
|
|
2694
|
+
|
|
2695
|
+
🎯 Возможности:
|
|
2696
|
+
- Создание горячих клавиш (Ctrl+C, Alt+Tab)
|
|
2697
|
+
- Комбинации с мышью
|
|
2698
|
+
"""
|
|
2699
|
+
import keyboard
|
|
2700
|
+
keyboard.press(key)
|
|
2701
|
+
|
|
2702
|
+
@staticmethod
|
|
2703
|
+
def key_up(key: str):
|
|
2704
|
+
"""
|
|
2705
|
+
⬆️ ОТПУСТИТЬ КЛАВИШУ: Отпускает указанную клавишу, которая была зажата через key_down.
|
|
2706
|
+
|
|
2707
|
+
Параметры:
|
|
2708
|
+
key (str): название клавиши, которую нужно отпустить
|
|
2709
|
+
|
|
2710
|
+
📝 Пример:
|
|
2711
|
+
Auto.key_down('shift')
|
|
2712
|
+
# ... что-то делаем ...
|
|
2713
|
+
Auto.key_up('shift')
|
|
2714
|
+
|
|
2715
|
+
🎯 Возможности:
|
|
2716
|
+
- Завершение комбинаций клавиш
|
|
2717
|
+
- Возврат клавиатуры в обычный режим
|
|
2718
|
+
"""
|
|
2719
|
+
import keyboard
|
|
2720
|
+
keyboard.release(key)
|
|
2721
|
+
|
|
2722
|
+
@staticmethod
|
|
2723
|
+
def press(key: str, duration: float = 0.05):
|
|
2724
|
+
"""
|
|
2725
|
+
⌨️ НАЖАТЬ КЛАВИШУ: Нажимает и сразу отпускает клавишу. Аналог быстрого тыка по кнопке.
|
|
2726
|
+
|
|
2727
|
+
Параметры:
|
|
2728
|
+
key (str): клавиша (например, 'enter', 'tab', 'esc', 'win+r')
|
|
2729
|
+
duration (float): время в секундах между нажатием и отпусканием
|
|
2730
|
+
|
|
2731
|
+
📝 Пример:
|
|
2732
|
+
Auto.press('enter') # быстрый Enter
|
|
2733
|
+
Auto.press('win+r', 0.1) # открыть окно «Выполнить»
|
|
2734
|
+
Auto.press('volume_up') # увеличить громкость
|
|
2735
|
+
|
|
2736
|
+
🎯 Возможности:
|
|
2737
|
+
- Имитация нажатий пользователя
|
|
2738
|
+
- Управление системой через скрипт
|
|
2739
|
+
"""
|
|
2740
|
+
import keyboard
|
|
2741
|
+
keyboard.press(key)
|
|
2742
|
+
time.sleep(duration)
|
|
2743
|
+
keyboard.release(key)
|
|
2744
|
+
|
|
2745
|
+
@staticmethod
|
|
2746
|
+
def is_pressed(key: str) -> bool:
|
|
2747
|
+
"""
|
|
2748
|
+
❓ ПРОВЕРИТЬ НАЖАТИЕ: Проверяет, зажата ли клавиша в данный момент.
|
|
2749
|
+
|
|
2750
|
+
Параметры:
|
|
2751
|
+
key (str): название клавиши
|
|
2752
|
+
|
|
2753
|
+
Возвращает:
|
|
2754
|
+
bool: True, если клавиша нажата
|
|
2755
|
+
|
|
2756
|
+
📝 Пример:
|
|
2757
|
+
if Auto.is_pressed('esc'):
|
|
2758
|
+
print("Клавиша Escape нажата! Выходим...")
|
|
2759
|
+
break
|
|
2760
|
+
|
|
2761
|
+
🎯 Возможности:
|
|
2762
|
+
- Создание горячих клавиш выхода
|
|
2763
|
+
- Обработка пользовательского ввода в реальном времени
|
|
2764
|
+
"""
|
|
2765
|
+
import keyboard
|
|
2766
|
+
return keyboard.is_pressed(key)
|
|
2767
|
+
|
|
2768
|
+
@staticmethod
|
|
2769
|
+
def write(text: str, delay: float = 0.05):
|
|
2770
|
+
"""
|
|
2771
|
+
📝 ПЕЧАТАТЬ ТЕКСТ: Печатает текст так, как будто его вводит человек с клавиатуры.
|
|
2772
|
+
|
|
2773
|
+
Параметры:
|
|
2774
|
+
text (str): строка для ввода
|
|
2775
|
+
delay (float): задержка между нажатиями символов в секундах
|
|
2776
|
+
|
|
2777
|
+
📝 Пример:
|
|
2778
|
+
Auto.write("Привет, мир!")
|
|
2779
|
+
Auto.click(200, 300) # клик в поле ввода
|
|
2780
|
+
Auto.write("username")
|
|
2781
|
+
Auto.press('tab')
|
|
2782
|
+
Auto.write("password123")
|
|
2783
|
+
Auto.press('enter')
|
|
2784
|
+
|
|
2785
|
+
🎯 Возможности:
|
|
2786
|
+
- Автозаполнение форм
|
|
2787
|
+
- Ввод логинов и паролей
|
|
2788
|
+
- Имитация живого пользователя (можно менять delay)
|
|
2789
|
+
"""
|
|
2790
|
+
import keyboard
|
|
2791
|
+
for ch in text:
|
|
2792
|
+
keyboard.write(ch)
|
|
2793
|
+
time.sleep(delay)
|
|
2794
|
+
|
|
2795
|
+
# =========================== МЫШЬ ===========================
|
|
2796
|
+
|
|
2797
|
+
@staticmethod
|
|
2798
|
+
def mouse_down(button: str = 'left'):
|
|
2799
|
+
"""
|
|
2800
|
+
🖱️⬇️ ЗАЖАТЬ КНОПКУ МЫШИ: Зажимает кнопку мыши (не отпускает, пока не вызван mouse_up).
|
|
2801
|
+
|
|
2802
|
+
Параметры:
|
|
2803
|
+
button (str): 'left', 'right' или 'middle'
|
|
2804
|
+
|
|
2805
|
+
📝 Пример:
|
|
2806
|
+
Auto.mouse_down('left') # зажали левую кнопку
|
|
2807
|
+
Auto.move(0, 0, 100, 100, 0.5) # перетащили объект
|
|
2808
|
+
Auto.mouse_up('left') # отпустили
|
|
2809
|
+
|
|
2810
|
+
🎯 Возможности:
|
|
2811
|
+
- Перетаскивание объектов
|
|
2812
|
+
- Выделение текста мышью
|
|
2813
|
+
"""
|
|
2814
|
+
import pyautogui
|
|
2815
|
+
pyautogui.mouseDown(button=button)
|
|
2816
|
+
|
|
2817
|
+
@staticmethod
|
|
2818
|
+
def mouse_up(button: str = 'left'):
|
|
2819
|
+
"""
|
|
2820
|
+
🖱️⬆️ ОТПУСТИТЬ КНОПКУ МЫШИ: Отпускает ранее зажатую кнопку мыши.
|
|
2821
|
+
|
|
2822
|
+
Параметры:
|
|
2823
|
+
button (str): 'left', 'right' или 'middle'
|
|
2824
|
+
|
|
2825
|
+
📝 Пример:
|
|
2826
|
+
Auto.mouse_down('left')
|
|
2827
|
+
# ... перетаскивание ...
|
|
2828
|
+
Auto.mouse_up('left')
|
|
2829
|
+
|
|
2830
|
+
🎯 Возможности:
|
|
2831
|
+
- Завершение перетаскивания
|
|
2832
|
+
- Отпускание после длительного удержания
|
|
2833
|
+
"""
|
|
2834
|
+
import pyautogui
|
|
2835
|
+
pyautogui.mouseUp(button=button)
|
|
2836
|
+
|
|
2837
|
+
@staticmethod
|
|
2838
|
+
def click(x=None, y=None, button: str = 'left', clicks: int = 1):
|
|
2839
|
+
"""
|
|
2840
|
+
🖱️ КЛИК МЫШЬЮ: Кликает в указанных координатах или в текущем положении.
|
|
2841
|
+
|
|
2842
|
+
Параметры:
|
|
2843
|
+
x, y (int, optional): координаты клика. Можно передать кортеж (x, y) в x.
|
|
2844
|
+
button (str): 'left', 'right', 'middle'
|
|
2845
|
+
clicks (int): количество кликов (1 — одиночный, 2 — двойной)
|
|
2846
|
+
|
|
2847
|
+
📝 Пример:
|
|
2848
|
+
Auto.click() # клик левой в текущей позиции
|
|
2849
|
+
Auto.click(500, 300) # клик в точку (500, 300)
|
|
2850
|
+
Auto.click((500, 300), button='right') # правый клик туда же
|
|
2851
|
+
Auto.click(clicks=2) # двойной клик в текущей позиции
|
|
2852
|
+
|
|
2853
|
+
🎯 Возможности:
|
|
2854
|
+
- Автоматизация интерфейсов
|
|
2855
|
+
- Клики по кнопкам и ссылкам
|
|
2856
|
+
- Поддержка двойных и правых кликов
|
|
2857
|
+
"""
|
|
2858
|
+
import pyautogui
|
|
2859
|
+
if x is not None and y is not None:
|
|
2860
|
+
pyautogui.click(x, y, button=button, clicks=clicks)
|
|
2861
|
+
elif isinstance(x, (tuple, list)) and len(x) >= 2:
|
|
2862
|
+
pyautogui.click(x[0], x[1], button=button, clicks=clicks)
|
|
2863
|
+
elif x is None and y is None:
|
|
2864
|
+
pyautogui.click(button=button, clicks=clicks)
|
|
2865
|
+
else:
|
|
2866
|
+
raise ValueError("Передай и x, и y, или кортеж (x, y), или ничего")
|
|
2867
|
+
|
|
2868
|
+
@staticmethod
|
|
2869
|
+
def double_click(x=None, y=None, button: str = 'left'):
|
|
2870
|
+
"""
|
|
2871
|
+
🖱️🖱️ ДВОЙНОЙ КЛИК: Делает двойной клик мышью.
|
|
2872
|
+
|
|
2873
|
+
Параметры:
|
|
2874
|
+
x, y (int, optional): координаты клика
|
|
2875
|
+
button (str): 'left', 'right', 'middle'
|
|
2876
|
+
|
|
2877
|
+
📝 Пример:
|
|
2878
|
+
Auto.double_click() # двойной клик в текущей позиции
|
|
2879
|
+
Auto.double_click(400, 250) # двойной клик в точку
|
|
2880
|
+
|
|
2881
|
+
🎯 Возможности:
|
|
2882
|
+
- Открытие файлов и папок
|
|
2883
|
+
- Выделение слов двойным кликом
|
|
2884
|
+
"""
|
|
2885
|
+
Auto.click(x, y, button, clicks=2)
|
|
2886
|
+
|
|
2887
|
+
@staticmethod
|
|
2888
|
+
def move(x, y=None, duration: float = 0.0):
|
|
2889
|
+
"""
|
|
2890
|
+
➡️ ДВИЖЕНИЕ МЫШЬЮ: Перемещает курсор в указанную точку.
|
|
2891
|
+
|
|
2892
|
+
Параметры:
|
|
2893
|
+
x, y (int): координаты (или кортеж (x, y) в параметре x)
|
|
2894
|
+
duration (float): время перемещения в секундах (0 — мгновенно, 0.5 — плавно)
|
|
2895
|
+
|
|
2896
|
+
📝 Пример:
|
|
2897
|
+
Auto.move(300, 400) # мгновенно в (300, 400)
|
|
2898
|
+
Auto.move((300, 400)) # то же самое кортежем
|
|
2899
|
+
Auto.move(300, 400, 0.5) # плавно за полсекунды
|
|
2900
|
+
|
|
2901
|
+
🎯 Возможности:
|
|
2902
|
+
- Плавная анимация движения
|
|
2903
|
+
- Точное позиционирование мыши
|
|
2904
|
+
"""
|
|
2905
|
+
import pyautogui
|
|
2906
|
+
if isinstance(x, (tuple, list)) and len(x) >= 2:
|
|
2907
|
+
pyautogui.moveTo(x[0], x[1], duration=duration)
|
|
2908
|
+
elif y is not None:
|
|
2909
|
+
pyautogui.moveTo(x, y, duration=duration)
|
|
2910
|
+
else:
|
|
2911
|
+
print("⚠️ Координаты не указаны")
|
|
2912
|
+
|
|
2913
|
+
@staticmethod
|
|
2914
|
+
def scroll(amount: int, x=None, y=None):
|
|
2915
|
+
"""
|
|
2916
|
+
🖱️🔄 ПРОКРУТКА: Прокручивает колёсико мыши.
|
|
2917
|
+
|
|
2918
|
+
Параметры:
|
|
2919
|
+
amount (int): положительное — вверх, отрицательное — вниз
|
|
2920
|
+
x, y (int, optional): координаты курсора перед прокруткой
|
|
2921
|
+
|
|
2922
|
+
📝 Пример:
|
|
2923
|
+
Auto.scroll(3) # прокрутить вверх на 3 щелчка
|
|
2924
|
+
Auto.scroll(-5) # прокрутить вниз на 5 щелчков
|
|
2925
|
+
Auto.scroll(2, 500, 300) # переместиться в (500, 300) и прокрутить вверх
|
|
2926
|
+
|
|
2927
|
+
🎯 Возможности:
|
|
2928
|
+
- Скроллинг веб-страниц
|
|
2929
|
+
- Навигация по спискам
|
|
2930
|
+
"""
|
|
2931
|
+
import pyautogui
|
|
2932
|
+
pyautogui.scroll(amount, x, y)
|
|
2933
|
+
|
|
2934
|
+
@staticmethod
|
|
2935
|
+
def drag(start_x, start_y, end_x, end_y, button='left', duration=0.5):
|
|
2936
|
+
"""
|
|
2937
|
+
🖱️↔️ ПЕРЕТАСКИВАНИЕ: Перетаскивает объект от начальных координат к конечным.
|
|
2938
|
+
|
|
2939
|
+
Параметры:
|
|
2940
|
+
start_x, start_y (int): начальные координаты
|
|
2941
|
+
end_x, end_y (int): конечные координаты
|
|
2942
|
+
button (str): кнопка мыши для перетаскивания
|
|
2943
|
+
duration (float): время перетаскивания в секундах
|
|
2944
|
+
|
|
2945
|
+
📝 Пример:
|
|
2946
|
+
Auto.drag(100, 100, 300, 300) # перетащить по диагонали
|
|
2947
|
+
Auto.drag(100, 100, 300, 300, duration=0.2) # быстро
|
|
2948
|
+
|
|
2949
|
+
🎯 Возможности:
|
|
2950
|
+
- Перемещение окон и файлов
|
|
2951
|
+
- Drag-and-drop интерфейсы
|
|
2952
|
+
"""
|
|
2953
|
+
import pyautogui
|
|
2954
|
+
pyautogui.moveTo(start_x, start_y)
|
|
2955
|
+
pyautogui.drag(end_x - start_x, end_y - start_y, duration=duration, button=button)
|
|
2956
|
+
|
|
2957
|
+
# =========================== ПРОЧЕЕ ===========================
|
|
2958
|
+
|
|
2959
|
+
@staticmethod
|
|
2960
|
+
def screenshot(save_path: str = "screen.png"):
|
|
2961
|
+
"""
|
|
2962
|
+
📸 СКРИНШОТ: Делает снимок всего экрана и сохраняет в файл.
|
|
2963
|
+
|
|
2964
|
+
Параметры:
|
|
2965
|
+
save_path (str): путь для сохранения относительно папки скрипта
|
|
2966
|
+
|
|
2967
|
+
📝 Пример:
|
|
2968
|
+
Auto.screenshot() # screen.png в папке скрипта
|
|
2969
|
+
Auto.screenshot("my_screen.png") # своё имя файла
|
|
2970
|
+
Auto.screenshot("screenshots/game.png") # в подпапке
|
|
2971
|
+
|
|
2972
|
+
🎯 Возможности:
|
|
2973
|
+
- Сохранение состояний интерфейса
|
|
2974
|
+
- Создание отчётов с картинками
|
|
2975
|
+
- Отладка автоматизации
|
|
2976
|
+
"""
|
|
2977
|
+
import pyautogui
|
|
2978
|
+
full = Manager.search_file_path(save_path)
|
|
2979
|
+
img = pyautogui.screenshot()
|
|
2980
|
+
img.save(full)
|
|
2981
|
+
|
|
2982
|
+
@staticmethod
|
|
2983
|
+
def notify(title: str, message: str, timeout: int = 5):
|
|
2984
|
+
"""
|
|
2985
|
+
🔔 УВЕДОМЛЕНИЕ: Показывает системное уведомление в правом нижнем углу экрана.
|
|
2986
|
+
|
|
2987
|
+
Параметры:
|
|
2988
|
+
title (str): заголовок уведомления
|
|
2989
|
+
message (str): текст уведомления
|
|
2990
|
+
timeout (int): сколько секунд показывать
|
|
2991
|
+
|
|
2992
|
+
📝 Пример:
|
|
2993
|
+
Auto.notify("Напоминание", "Пора сделать зарядку!")
|
|
2994
|
+
Auto.notify("Кеша", "Музыка поставлена на паузу", 3)
|
|
2995
|
+
Auto.notify("Загрузка", "Файл успешно скачан!", 5)
|
|
2996
|
+
|
|
2997
|
+
🎯 Возможности:
|
|
2998
|
+
- Ненавязчивые оповещения
|
|
2999
|
+
- Уведомления о завершении операций
|
|
3000
|
+
- Напоминания пользователю
|
|
3001
|
+
"""
|
|
3002
|
+
try:
|
|
3003
|
+
from plyer import notification
|
|
3004
|
+
notification.notify(title=title, message=message, timeout=timeout)
|
|
3005
|
+
except:
|
|
3006
|
+
print(f"⚠️ {title}: {message}")
|
|
3007
|
+
|
|
3008
|
+
|
|
3009
|
+
# =============================================================================
|
|
3010
|
+
# КЛАСС Web – БРАУЗЕР И ОТКРЫТИЕ ССЫЛОК
|
|
3011
|
+
# =============================================================================
|
|
3012
|
+
class Web:
|
|
3013
|
+
"""
|
|
3014
|
+
🌐 РАБОТА С БРАУЗЕРОМ: открытие ссылок, поиск в Яндексе и на YouTube.
|
|
3015
|
+
|
|
3016
|
+
Все методы статические — вызывай напрямую:
|
|
3017
|
+
Web.open("https://google.com")
|
|
3018
|
+
Web.search("погода Москва")
|
|
3019
|
+
Web.youtube("python уроки")
|
|
3020
|
+
|
|
3021
|
+
🎯 Возможности класса:
|
|
3022
|
+
- Открытие любых URL в браузере по умолчанию
|
|
3023
|
+
- Быстрый поиск в Яндексе
|
|
3024
|
+
- Открытие результатов поиска на YouTube
|
|
3025
|
+
"""
|
|
3026
|
+
|
|
3027
|
+
@staticmethod
|
|
3028
|
+
def open(url):
|
|
3029
|
+
"""
|
|
3030
|
+
🌐 ОТКРЫТЬ ССЫЛКУ: Открывает URL в браузере по умолчанию.
|
|
3031
|
+
|
|
3032
|
+
Параметры:
|
|
3033
|
+
url (str): полный URL для открытия
|
|
3034
|
+
|
|
3035
|
+
📝 Пример:
|
|
3036
|
+
Web.open("https://google.com")
|
|
3037
|
+
Web.open("https://github.com/Naruto456y")
|
|
3038
|
+
|
|
3039
|
+
🎯 Возможности:
|
|
3040
|
+
- Быстрый переход на сайты
|
|
3041
|
+
- Интеграция с веб-сервисами
|
|
3042
|
+
"""
|
|
3043
|
+
webbrowser.open(url)
|
|
3044
|
+
print(f"🌐 Открыто: {url}")
|
|
3045
|
+
|
|
3046
|
+
@staticmethod
|
|
3047
|
+
def search(query):
|
|
3048
|
+
"""
|
|
3049
|
+
🔍 ПОИСК В ЯНДЕКСЕ: Открывает результаты поиска в Яндексе.
|
|
3050
|
+
|
|
3051
|
+
Параметры:
|
|
3052
|
+
query (str): поисковый запрос
|
|
3053
|
+
|
|
3054
|
+
📝 Пример:
|
|
3055
|
+
Web.search("как сделать бота на Python")
|
|
3056
|
+
Web.search("погода в Москве")
|
|
3057
|
+
|
|
3058
|
+
🎯 Возможности:
|
|
3059
|
+
- Быстрый поиск информации
|
|
3060
|
+
- Голосовой ассистент может искать в интернете
|
|
3061
|
+
"""
|
|
3062
|
+
url = f"https://yandex.ru/search/?text={quote(query)}"
|
|
3063
|
+
webbrowser.open(url)
|
|
3064
|
+
print(f"🔍 Поиск: {query}")
|
|
3065
|
+
|
|
3066
|
+
@staticmethod
|
|
3067
|
+
def youtube(query):
|
|
3068
|
+
"""
|
|
3069
|
+
🎬 ПОИСК НА YOUTUBE: Открывает результаты поиска видео на YouTube.
|
|
3070
|
+
|
|
3071
|
+
Параметры:
|
|
3072
|
+
query (str): поисковый запрос
|
|
3073
|
+
|
|
3074
|
+
📝 Пример:
|
|
3075
|
+
Web.youtube("python для начинающих")
|
|
3076
|
+
Web.youtube("Morgenshtern последний клип")
|
|
3077
|
+
|
|
3078
|
+
🎯 Возможности:
|
|
3079
|
+
- Быстрый поиск видео
|
|
3080
|
+
- Интеграция с голосовым ассистентом
|
|
3081
|
+
"""
|
|
3082
|
+
url = f"https://www.youtube.com/results?search_query={quote(query)}"
|
|
3083
|
+
webbrowser.open(url)
|
|
3084
|
+
print(f"🎬 YouTube: {query}")
|
|
3085
|
+
|
|
3086
|
+
|
|
3087
|
+
# =============================================================================
|
|
3088
|
+
# КЛАСС System – СИСТЕМНЫЕ КОМАНДЫ
|
|
3089
|
+
# =============================================================================
|
|
3090
|
+
class System:
|
|
3091
|
+
"""
|
|
3092
|
+
💻 СИСТЕМНЫЕ КОМАНДЫ: выключение, перезагрузка, сон, блокировка.
|
|
3093
|
+
|
|
3094
|
+
Все методы статические. Будь осторожен с shutdown и restart!
|
|
3095
|
+
|
|
3096
|
+
🎯 Возможности класса:
|
|
3097
|
+
- Выключение компьютера
|
|
3098
|
+
- Перезагрузка системы
|
|
3099
|
+
- Переход в спящий режим
|
|
3100
|
+
- Блокировка экрана
|
|
3101
|
+
"""
|
|
3102
|
+
|
|
3103
|
+
@staticmethod
|
|
3104
|
+
def shutdown(delay=0):
|
|
3105
|
+
"""
|
|
3106
|
+
🔌 ВЫКЛЮЧЕНИЕ КОМПЬЮТЕРА: Выключает ПК через указанное количество секунд.
|
|
3107
|
+
|
|
3108
|
+
Параметры:
|
|
3109
|
+
delay (int): задержка в секундах перед выключением
|
|
3110
|
+
|
|
3111
|
+
📝 Пример:
|
|
3112
|
+
System.shutdown(10) # Выключится через 10 секунд
|
|
3113
|
+
System.shutdown() # Выключится немедленно
|
|
3114
|
+
|
|
3115
|
+
⚠️ Внимание: Эта команда реально выключает компьютер! Сохраните все данные.
|
|
3116
|
+
"""
|
|
3117
|
+
if delay > 0:
|
|
3118
|
+
print(f"⚠️ Выключение через {delay} секунд...")
|
|
3119
|
+
time.sleep(delay)
|
|
3120
|
+
os.system("shutdown /s /t 0")
|
|
3121
|
+
print("🔌 Выключение...")
|
|
3122
|
+
|
|
3123
|
+
@staticmethod
|
|
3124
|
+
def restart(delay=0):
|
|
3125
|
+
"""
|
|
3126
|
+
🔄 ПЕРЕЗАГРУЗКА: Перезагружает компьютер через указанное количество секунд.
|
|
3127
|
+
|
|
3128
|
+
Параметры:
|
|
3129
|
+
delay (int): задержка в секундах перед перезагрузкой
|
|
3130
|
+
|
|
3131
|
+
📝 Пример:
|
|
3132
|
+
System.restart(30) # Перезагрузится через 30 секунд
|
|
3133
|
+
System.restart() # Перезагрузится немедленно
|
|
3134
|
+
|
|
3135
|
+
⚠️ Внимание: Эта команда реально перезагружает компьютер! Сохраните все данные.
|
|
3136
|
+
"""
|
|
3137
|
+
if delay > 0:
|
|
3138
|
+
print(f"⚠️ Перезагрузка через {delay} секунд...")
|
|
3139
|
+
time.sleep(delay)
|
|
3140
|
+
os.system("shutdown /r /t 0")
|
|
3141
|
+
print("🔄 Перезагрузка...")
|
|
3142
|
+
|
|
3143
|
+
@staticmethod
|
|
3144
|
+
def sleep():
|
|
3145
|
+
"""
|
|
3146
|
+
💤 СПЯЩИЙ РЕЖИМ: Переводит компьютер в спящий режим.
|
|
3147
|
+
|
|
3148
|
+
📝 Пример:
|
|
3149
|
+
System.sleep()
|
|
3150
|
+
# Компьютер уснёт
|
|
3151
|
+
|
|
3152
|
+
🎯 Возможности:
|
|
3153
|
+
- Экономия энергии
|
|
3154
|
+
- Быстрое прерывание работы с сохранением состояния
|
|
3155
|
+
"""
|
|
3156
|
+
os.system("rundll32.exe powrprof.dll,SetSuspendState 0,1,0")
|
|
3157
|
+
print("💤 Спящий режим")
|
|
3158
|
+
|
|
3159
|
+
@staticmethod
|
|
3160
|
+
def lock():
|
|
3161
|
+
"""
|
|
3162
|
+
🔒 ЗАБЛОКИРОВАТЬ ЭКРАН: Блокирует рабочую станцию (экран входа в систему).
|
|
3163
|
+
|
|
3164
|
+
📝 Пример:
|
|
3165
|
+
System.lock()
|
|
3166
|
+
# Экран заблокирован, нужно ввести пароль для входа
|
|
3167
|
+
|
|
3168
|
+
🎯 Возможности:
|
|
3169
|
+
- Быстрая блокировка при отходе от компьютера
|
|
3170
|
+
- Безопасность данных
|
|
3171
|
+
"""
|
|
3172
|
+
os.system("rundll32.exe user32.dll,LockWorkStation")
|
|
3173
|
+
print("🔒 Экран заблокирован")
|
|
3174
|
+
|
|
3175
|
+
|
|
3176
|
+
# =============================================================================
|
|
3177
|
+
# КЛАСС Info – ИНФОРМАЦИЯ О СИСТЕМЕ
|
|
3178
|
+
# =============================================================================
|
|
3179
|
+
class Info:
|
|
3180
|
+
"""
|
|
3181
|
+
ℹ️ ИНФОРМАЦИЯ О СИСТЕМЕ: ОС, батарея, процессор, память.
|
|
3182
|
+
|
|
3183
|
+
Все методы статические — вызывай напрямую:
|
|
3184
|
+
battery, plugged = Info.battery()
|
|
3185
|
+
os_name = Info.os()
|
|
3186
|
+
cpu_load = Info.cpu()
|
|
3187
|
+
ram_used, ram_total = Info.ram()
|
|
3188
|
+
|
|
3189
|
+
🎯 Возможности класса:
|
|
3190
|
+
- Мониторинг заряда батареи ноутбука
|
|
3191
|
+
- Определение операционной системы
|
|
3192
|
+
- Загрузка процессора в процентах
|
|
3193
|
+
- Использование оперативной памяти
|
|
3194
|
+
"""
|
|
3195
|
+
|
|
3196
|
+
@staticmethod
|
|
3197
|
+
def battery():
|
|
3198
|
+
"""
|
|
3199
|
+
🔋 БАТАРЕЯ: Возвращает уровень заряда и статус подключения к сети.
|
|
3200
|
+
|
|
3201
|
+
Возвращает:
|
|
3202
|
+
tuple: (процент заряда, подключена ли зарядка) или (None, None) при ошибке
|
|
3203
|
+
|
|
3204
|
+
📝 Пример:
|
|
3205
|
+
percent, plugged = Info.battery()
|
|
3206
|
+
if percent is not None:
|
|
3207
|
+
print(f"Заряд: {percent}%")
|
|
3208
|
+
if plugged:
|
|
3209
|
+
print("Ноутбук на зарядке")
|
|
3210
|
+
elif percent < 20:
|
|
3211
|
+
print("⚠️ Пора заряжать!")
|
|
3212
|
+
|
|
3213
|
+
🎯 Возможности:
|
|
3214
|
+
- Мониторинг батареи в приложениях
|
|
3215
|
+
- Предупреждения о низком заряде
|
|
3216
|
+
"""
|
|
3217
|
+
try:
|
|
3218
|
+
import psutil
|
|
3219
|
+
battery = psutil.sensors_battery()
|
|
3220
|
+
if battery:
|
|
3221
|
+
return battery.percent, battery.power_plugged
|
|
3222
|
+
except:
|
|
3223
|
+
pass
|
|
3224
|
+
return None, None
|
|
3225
|
+
|
|
3226
|
+
@staticmethod
|
|
3227
|
+
def os():
|
|
3228
|
+
"""
|
|
3229
|
+
🖥️ ОПЕРАЦИОННАЯ СИСТЕМА: Возвращает тип операционной системы.
|
|
3230
|
+
|
|
3231
|
+
Возвращает:
|
|
3232
|
+
str: 'win32' для Windows, 'linux' для Linux, 'darwin' для Mac
|
|
3233
|
+
|
|
3234
|
+
📝 Пример:
|
|
3235
|
+
os_name = Info.os()
|
|
3236
|
+
if os_name == 'win32':
|
|
3237
|
+
print("Работаем на Windows")
|
|
3238
|
+
elif os_name == 'linux':
|
|
3239
|
+
print("Работаем на Linux")
|
|
3240
|
+
|
|
3241
|
+
🎯 Возможности:
|
|
3242
|
+
- Кроссплатформенная разработка
|
|
3243
|
+
- Выбор системных команд под ОС
|
|
3244
|
+
"""
|
|
3245
|
+
return sys.platform
|
|
3246
|
+
|
|
3247
|
+
@staticmethod
|
|
3248
|
+
def cpu():
|
|
3249
|
+
"""
|
|
3250
|
+
🧠 ПРОЦЕССОР: Возвращает загрузку CPU в процентах.
|
|
3251
|
+
|
|
3252
|
+
Возвращает:
|
|
3253
|
+
float или None: процент загрузки процессора (измеряется 1 секунду)
|
|
3254
|
+
|
|
3255
|
+
📝 Пример:
|
|
3256
|
+
cpu_load = Info.cpu()
|
|
3257
|
+
if cpu_load and cpu_load > 90:
|
|
3258
|
+
print(f"⚠️ Процессор сильно загружен: {cpu_load}%")
|
|
3259
|
+
else:
|
|
3260
|
+
print(f"CPU: {cpu_load}%")
|
|
3261
|
+
|
|
3262
|
+
🎯 Возможности:
|
|
3263
|
+
- Мониторинг производительности
|
|
3264
|
+
- Оптимизация тяжёлых вычислений
|
|
3265
|
+
"""
|
|
3266
|
+
try:
|
|
3267
|
+
import psutil
|
|
3268
|
+
return psutil.cpu_percent(interval=1)
|
|
3269
|
+
except:
|
|
3270
|
+
return None
|
|
3271
|
+
|
|
3272
|
+
@staticmethod
|
|
3273
|
+
def ram():
|
|
3274
|
+
"""
|
|
3275
|
+
💾 ОПЕРАТИВНАЯ ПАМЯТЬ: Возвращает использование RAM в мегабайтах.
|
|
3276
|
+
|
|
3277
|
+
Возвращает:
|
|
3278
|
+
tuple: (использовано МБ, всего МБ) или (None, None) при ошибке
|
|
3279
|
+
|
|
3280
|
+
📝 Пример:
|
|
3281
|
+
used, total = Info.ram()
|
|
3282
|
+
if used and total:
|
|
3283
|
+
print(f"RAM: {used}/{total} MB")
|
|
3284
|
+
print(f"Свободно: {total - used} MB ({100 - used/total*100:.0f}%)")
|
|
3285
|
+
|
|
3286
|
+
🎯 Возможности:
|
|
3287
|
+
- Мониторинг потребления памяти
|
|
3288
|
+
- Диагностика утечек памяти
|
|
3289
|
+
"""
|
|
3290
|
+
try:
|
|
3291
|
+
import psutil
|
|
3292
|
+
mem = psutil.virtual_memory()
|
|
3293
|
+
return mem.used // (1024*1024), mem.total // (1024*1024)
|
|
3294
|
+
except:
|
|
3295
|
+
return None, None
|
|
3296
|
+
|
|
3297
|
+
|
|
3298
|
+
# =============================================================================
|
|
3299
|
+
# КЛАСС ThreadManager – УПРАВЛЕНИЕ ПОТОКАМИ
|
|
3300
|
+
# =============================================================================
|
|
3301
|
+
class ThreadManager:
|
|
3302
|
+
"""
|
|
3303
|
+
🧵 УПРАВЛЕНИЕ ПОТОКАМИ — запуск, остановка, мониторинг фоновых задач.
|
|
3304
|
+
|
|
3305
|
+
📝 Пример:
|
|
3306
|
+
tm = ThreadManager()
|
|
3307
|
+
tm.start(my_function, args=(1,2))
|
|
3308
|
+
tm.stop_all()
|
|
3309
|
+
|
|
3310
|
+
🎯 Возможности:
|
|
3311
|
+
- Запуск фоновых задач без блокировки основного кода
|
|
3312
|
+
- Остановка потоков по ID или всех сразу
|
|
3313
|
+
- Мониторинг активных потоков
|
|
3314
|
+
- Автоматическая обработка событий остановки
|
|
3315
|
+
"""
|
|
3316
|
+
|
|
3317
|
+
def __init__(self):
|
|
3318
|
+
"""
|
|
3319
|
+
🏗️ ИНИЦИАЛИЗАЦИЯ: Создаёт менеджер потоков с пустыми словарями.
|
|
3320
|
+
"""
|
|
3321
|
+
self.threads = {}
|
|
3322
|
+
self.stop_events = {}
|
|
3323
|
+
|
|
3324
|
+
def start(self, target, args=(), name=None, daemon=True):
|
|
3325
|
+
"""
|
|
3326
|
+
▶️ ЗАПУСК ПОТОКА: Запускает функцию в отдельном фоновом потоке.
|
|
3327
|
+
|
|
3328
|
+
Параметры:
|
|
3329
|
+
target (callable): функция для выполнения
|
|
3330
|
+
args (tuple): кортеж аргументов функции
|
|
3331
|
+
name (str, optional): имя потока (если не указано, генерируется автоматически)
|
|
3332
|
+
daemon (bool): фоновый поток (умрёт при завершении программы)
|
|
3333
|
+
|
|
3334
|
+
Возвращает:
|
|
3335
|
+
str: ID потока
|
|
3336
|
+
|
|
3337
|
+
📝 Пример:
|
|
3338
|
+
def worker(stop_event):
|
|
3339
|
+
while not stop_event.is_set():
|
|
3340
|
+
print("Работаю...")
|
|
3341
|
+
time.sleep(1)
|
|
3342
|
+
|
|
3343
|
+
tm = ThreadManager()
|
|
3344
|
+
thread_id = tm.start(worker, name="my_worker")
|
|
3345
|
+
time.sleep(5)
|
|
3346
|
+
tm.stop(thread_id)
|
|
3347
|
+
|
|
3348
|
+
🎯 Возможности:
|
|
3349
|
+
- Фоновая обработка данных
|
|
3350
|
+
- Параллельное выполнение задач
|
|
3351
|
+
"""
|
|
3352
|
+
stop_event = threading.Event()
|
|
3353
|
+
thread_id = name or f"Thread_{len(self.threads) + 1}"
|
|
3354
|
+
|
|
3355
|
+
def wrapper():
|
|
3356
|
+
target(*args, stop_event=stop_event)
|
|
3357
|
+
|
|
3358
|
+
thread = threading.Thread(target=wrapper, name=thread_id, daemon=daemon)
|
|
3359
|
+
self.threads[thread_id] = thread
|
|
3360
|
+
self.stop_events[thread_id] = stop_event
|
|
3361
|
+
thread.start()
|
|
3362
|
+
print(f"✅ Поток '{thread_id}' запущен")
|
|
3363
|
+
return thread_id
|
|
3364
|
+
|
|
3365
|
+
def stop(self, thread_id):
|
|
3366
|
+
"""
|
|
3367
|
+
⏹️ ОСТАНОВИТЬ ПОТОК: Останавливает поток по его ID.
|
|
3368
|
+
|
|
3369
|
+
Параметры:
|
|
3370
|
+
thread_id (str): ID потока, который нужно остановить
|
|
3371
|
+
|
|
3372
|
+
Возвращает:
|
|
3373
|
+
bool: True если поток остановлен, False если не найден
|
|
3374
|
+
|
|
3375
|
+
📝 Пример:
|
|
3376
|
+
tm.stop("my_worker")
|
|
3377
|
+
"""
|
|
3378
|
+
if thread_id in self.stop_events:
|
|
3379
|
+
self.stop_events[thread_id].set()
|
|
3380
|
+
print(f"⏹️ Поток '{thread_id}' остановлен")
|
|
3381
|
+
return True
|
|
3382
|
+
print(f"❌ Поток '{thread_id}' не найден")
|
|
3383
|
+
return False
|
|
3384
|
+
|
|
3385
|
+
def stop_all(self):
|
|
3386
|
+
"""
|
|
3387
|
+
⏹️⏹️ ОСТАНОВИТЬ ВСЁ: Останавливает все запущенные потоки.
|
|
3388
|
+
|
|
3389
|
+
📝 Пример:
|
|
3390
|
+
tm.stop_all()
|
|
3391
|
+
print("Все фоновые задачи остановлены")
|
|
3392
|
+
"""
|
|
3393
|
+
for thread_id in self.stop_events:
|
|
3394
|
+
self.stop_events[thread_id].set()
|
|
3395
|
+
print(f"⏹️ Остановлено {len(self.stop_events)} потоков")
|
|
3396
|
+
|
|
3397
|
+
def is_alive(self, thread_id):
|
|
3398
|
+
"""
|
|
3399
|
+
❓ ПОТОК ЖИВ?: Проверяет, активен ли поток.
|
|
3400
|
+
|
|
3401
|
+
Параметры:
|
|
3402
|
+
thread_id (str): ID потока
|
|
3403
|
+
|
|
3404
|
+
Возвращает:
|
|
3405
|
+
bool: True если поток жив, False если завершился или не найден
|
|
3406
|
+
|
|
3407
|
+
📝 Пример:
|
|
3408
|
+
if tm.is_alive("my_worker"):
|
|
3409
|
+
print("Поток ещё работает")
|
|
3410
|
+
else:
|
|
3411
|
+
print("Поток завершён")
|
|
3412
|
+
"""
|
|
3413
|
+
return thread_id in self.threads and self.threads[thread_id].is_alive()
|
|
3414
|
+
|
|
3415
|
+
def list_threads(self):
|
|
3416
|
+
"""
|
|
3417
|
+
📋 СПИСОК ПОТОКОВ: Возвращает список имён всех активных потоков.
|
|
3418
|
+
|
|
3419
|
+
Возвращает:
|
|
3420
|
+
list[str]: имена активных потоков
|
|
3421
|
+
|
|
3422
|
+
📝 Пример:
|
|
3423
|
+
active = tm.list_threads()
|
|
3424
|
+
print(f"Активных потоков: {len(active)}")
|
|
3425
|
+
for name in active:
|
|
3426
|
+
print(f" - {name}")
|
|
3427
|
+
"""
|
|
3428
|
+
return [name for name, t in self.threads.items() if t.is_alive()]
|
|
3429
|
+
|
|
3430
|
+
def active_count(self):
|
|
3431
|
+
"""
|
|
3432
|
+
🔢 КОЛИЧЕСТВО ПОТОКОВ: Возвращает общее количество активных потоков в программе.
|
|
3433
|
+
|
|
3434
|
+
Возвращает:
|
|
3435
|
+
int: количество активных потоков
|
|
3436
|
+
|
|
3437
|
+
📝 Пример:
|
|
3438
|
+
total = tm.active_count()
|
|
3439
|
+
print(f"Всего потоков в программе: {total}")
|
|
3440
|
+
"""
|
|
3441
|
+
return threading.active_count()
|
|
3442
|
+
|
|
3443
|
+
|
|
3444
|
+
# =============================================================================
|
|
3445
|
+
# КЛАСС ScreenReader – РАСПОЗНАВАНИЕ ЭКРАНА
|
|
3446
|
+
# =============================================================================
|
|
3447
|
+
class ScreenReader:
|
|
3448
|
+
"""
|
|
3449
|
+
🖥️ РАСПОЗНАВАНИЕ ЭКРАНА — поиск изображений, цветов, текста на экране.
|
|
3450
|
+
|
|
3451
|
+
Требуется установка: opencv-python, pillow, easyocr, mss, numpy
|
|
3452
|
+
|
|
3453
|
+
📝 Примеры:
|
|
3454
|
+
# Найти картинку на экране
|
|
3455
|
+
pos = ScreenReader.find_image("button.png")
|
|
3456
|
+
if pos:
|
|
3457
|
+
pyautogui.click(pos)
|
|
3458
|
+
|
|
3459
|
+
# Найти цвет
|
|
3460
|
+
pos = ScreenReader.find_color((255, 0, 0)) # красный
|
|
3461
|
+
|
|
3462
|
+
# Прочитать текст с экрана
|
|
3463
|
+
result = ScreenReader.find_text_coordinates("Принять")
|
|
3464
|
+
if result['found']:
|
|
3465
|
+
pyautogui.click(result['x'], result['y'])
|
|
3466
|
+
|
|
3467
|
+
🎯 Возможности:
|
|
3468
|
+
- Поиск изображения на экране (для автоматизации кликов)
|
|
3469
|
+
- Поиск пикселя по цвету
|
|
3470
|
+
- Чтение текста с экрана (OCR)
|
|
3471
|
+
- Получение цвета пикселя в координатах
|
|
3472
|
+
- Ожидание появления изображения
|
|
3473
|
+
- Выделение найденных изображений рамкой
|
|
3474
|
+
"""
|
|
3475
|
+
|
|
3476
|
+
@staticmethod
|
|
3477
|
+
def find_image(template_path, confidence=0.8, region=None):
|
|
3478
|
+
"""
|
|
3479
|
+
🔍 НАЙТИ ИЗОБРАЖЕНИЕ: Ищет изображение-шаблон на текущем экране.
|
|
3480
|
+
|
|
3481
|
+
Параметры:
|
|
3482
|
+
template_path (str): путь к файлу-шаблону (.png рекомендуется)
|
|
3483
|
+
confidence (float): порог совпадения (0.7–0.95, чем выше — тем точнее)
|
|
3484
|
+
region (tuple): область поиска (x, y, width, height)
|
|
3485
|
+
|
|
3486
|
+
Возвращает:
|
|
3487
|
+
tuple или None: (x, y) — центр найденного изображения, или None если не найдено
|
|
3488
|
+
|
|
3489
|
+
📝 Пример:
|
|
3490
|
+
pos = ScreenReader.find_image("button.png", confidence=0.9)
|
|
3491
|
+
if pos:
|
|
3492
|
+
print(f"Кнопка найдена в {pos}")
|
|
3493
|
+
Auto.click(pos)
|
|
3494
|
+
|
|
3495
|
+
# Искать только в левом верхнем углу
|
|
3496
|
+
pos = ScreenReader.find_image("icon.png", region=(0, 0, 500, 500))
|
|
3497
|
+
|
|
3498
|
+
🎯 Возможности:
|
|
3499
|
+
- Автоматизация кликов по кнопкам
|
|
3500
|
+
- Проверка наличия элементов интерфейса
|
|
3501
|
+
- Поиск в ограниченной области для ускорения
|
|
3502
|
+
"""
|
|
3503
|
+
import cv2
|
|
3504
|
+
import numpy as np
|
|
3505
|
+
import pyautogui
|
|
3506
|
+
|
|
3507
|
+
if region:
|
|
3508
|
+
screenshot = pyautogui.screenshot(region=region)
|
|
3509
|
+
else:
|
|
3510
|
+
screenshot = pyautogui.screenshot()
|
|
3511
|
+
|
|
3512
|
+
screenshot = cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR)
|
|
3513
|
+
template = cv2.imread(template_path)
|
|
3514
|
+
|
|
3515
|
+
if template is None:
|
|
3516
|
+
print(f"❌ Не удалось загрузить {template_path}")
|
|
3517
|
+
return None
|
|
3518
|
+
|
|
3519
|
+
result = cv2.matchTemplate(screenshot, template, cv2.TM_CCOEFF_NORMED)
|
|
3520
|
+
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
|
3521
|
+
|
|
3522
|
+
if max_val >= confidence:
|
|
3523
|
+
h, w = template.shape[:2]
|
|
3524
|
+
center_x = max_loc[0] + w // 2
|
|
3525
|
+
center_y = max_loc[1] + h // 2
|
|
3526
|
+
if region:
|
|
3527
|
+
center_x += region[0]
|
|
3528
|
+
center_y += region[1]
|
|
3529
|
+
print(f"✅ Изображение найдено в ({center_x}, {center_y}) (совпадение: {max_val:.2f})")
|
|
3530
|
+
return (center_x, center_y)
|
|
3531
|
+
else:
|
|
3532
|
+
print(f"❌ Изображение не найдено (совпадение: {max_val:.2f})")
|
|
3533
|
+
return None
|
|
3534
|
+
|
|
3535
|
+
@staticmethod
|
|
3536
|
+
def find_color(target_color, tolerance=30, region=None):
|
|
3537
|
+
"""
|
|
3538
|
+
🎨 НАЙТИ ЦВЕТ: Ищет первый пиксель заданного цвета на экране.
|
|
3539
|
+
|
|
3540
|
+
Параметры:
|
|
3541
|
+
target_color (tuple): кортеж (R, G, B) — цвет для поиска
|
|
3542
|
+
tolerance (int): допустимое отклонение (0 — точное совпадение, 30 — похожий оттенок)
|
|
3543
|
+
region (tuple): область поиска (x, y, width, height)
|
|
3544
|
+
|
|
3545
|
+
Возвращает:
|
|
3546
|
+
tuple или None: (x, y) координаты пикселя, или None если не найден
|
|
3547
|
+
|
|
3548
|
+
📝 Пример:
|
|
3549
|
+
# Найти красный крестик закрытия окна
|
|
3550
|
+
pos = ScreenReader.find_color((255, 0, 0), tolerance=20)
|
|
3551
|
+
if pos:
|
|
3552
|
+
Auto.click(pos)
|
|
3553
|
+
|
|
3554
|
+
# Искать только в строке состояния
|
|
3555
|
+
pos = ScreenReader.find_color((0, 255, 0), region=(0, 900, 1920, 1080))
|
|
3556
|
+
|
|
3557
|
+
🎯 Возможности:
|
|
3558
|
+
- Поиск элементов по цвету
|
|
3559
|
+
- Проверка статусов (красный/зелёный индикатор)
|
|
3560
|
+
"""
|
|
3561
|
+
import pyautogui
|
|
3562
|
+
import numpy as np
|
|
3563
|
+
|
|
3564
|
+
screenshot = pyautogui.screenshot(region=region) if region else pyautogui.screenshot()
|
|
3565
|
+
offset_x = region[0] if region else 0
|
|
3566
|
+
offset_y = region[1] if region else 0
|
|
3567
|
+
|
|
3568
|
+
img = np.array(screenshot)
|
|
3569
|
+
for y in range(img.shape[0]):
|
|
3570
|
+
for x in range(img.shape[1]):
|
|
3571
|
+
pixel = img[y, x][:3]
|
|
3572
|
+
if all(abs(pixel[i] - target_color[i]) <= tolerance for i in range(3)):
|
|
3573
|
+
result_x = x + offset_x
|
|
3574
|
+
result_y = y + offset_y
|
|
3575
|
+
print(f"✅ Цвет найден в ({result_x}, {result_y})")
|
|
3576
|
+
return (result_x, result_y)
|
|
3577
|
+
|
|
3578
|
+
print(f"❌ Цвет {target_color} не найден")
|
|
3579
|
+
return None
|
|
3580
|
+
|
|
3581
|
+
@staticmethod
|
|
3582
|
+
def get_pixel_color(x, y):
|
|
3583
|
+
"""
|
|
3584
|
+
🎨 ЦВЕТ ПИКСЕЛЯ: Получает цвет пикселя в указанных координатах.
|
|
3585
|
+
|
|
3586
|
+
Параметры:
|
|
3587
|
+
x (int): координата X
|
|
3588
|
+
y (int): координата Y
|
|
3589
|
+
|
|
3590
|
+
Возвращает:
|
|
3591
|
+
tuple: (R, G, B) — цвет пикселя
|
|
3592
|
+
|
|
3593
|
+
📝 Пример:
|
|
3594
|
+
color = ScreenReader.get_pixel_color(100, 200)
|
|
3595
|
+
print(f"Цвет в (100, 200): {color}")
|
|
3596
|
+
# Цвет в (100, 200): (255, 128, 64)
|
|
3597
|
+
"""
|
|
3598
|
+
import pyautogui
|
|
3599
|
+
pixel = pyautogui.pixel(x, y)
|
|
3600
|
+
print(f"🖱️ Цвет в ({x}, {y}): {pixel}")
|
|
3601
|
+
return pixel
|
|
3602
|
+
|
|
3603
|
+
@staticmethod
|
|
3604
|
+
def find_text_coordinates(search_text, languages=['en', 'ru'], monitor_index=1, confidence_threshold=0.5, partial_match=True):
|
|
3605
|
+
"""
|
|
3606
|
+
📖 НАЙТИ ТЕКСТ: Ищет координаты текста на экране с помощью OCR.
|
|
3607
|
+
|
|
3608
|
+
Параметры:
|
|
3609
|
+
search_text (str): текст для поиска на экране
|
|
3610
|
+
languages (list): языки распознавания (по умолчанию ['en', 'ru'])
|
|
3611
|
+
monitor_index (int): индекс монитора (1 — основной)
|
|
3612
|
+
confidence_threshold (float): минимальная уверенность распознавания (0.0–1.0)
|
|
3613
|
+
partial_match (bool): искать частичное совпадение (True) или точное (False)
|
|
3614
|
+
|
|
3615
|
+
Возвращает:
|
|
3616
|
+
dict: {'found': True/False, 'x': ..., 'y': ..., 'confidence': ...}
|
|
3617
|
+
|
|
3618
|
+
📝 Пример:
|
|
3619
|
+
result = ScreenReader.find_text_coordinates("Принять")
|
|
3620
|
+
if result['found']:
|
|
3621
|
+
Auto.click(result['x'], result['y'])
|
|
3622
|
+
print(f"Нажали на текст (уверенность: {result['confidence']:.2f})")
|
|
3623
|
+
|
|
3624
|
+
🎯 Возможности:
|
|
3625
|
+
- Поиск текста в интерфейсах
|
|
3626
|
+
- Автоклики по надписям
|
|
3627
|
+
- Многоязычное распознавание
|
|
3628
|
+
"""
|
|
3629
|
+
import easyocr
|
|
3630
|
+
import mss
|
|
3631
|
+
import numpy as np
|
|
3632
|
+
from PIL import Image
|
|
3633
|
+
|
|
3634
|
+
reader = easyocr.Reader(languages)
|
|
3635
|
+
|
|
3636
|
+
with mss.mss() as sct:
|
|
3637
|
+
monitor = sct.monitors[monitor_index]
|
|
3638
|
+
screenshot = sct.grab(monitor)
|
|
3639
|
+
img = Image.frombytes("RGB", screenshot.size, screenshot.bgra, "raw", "BGRX")
|
|
3640
|
+
img_array = np.array(img)
|
|
3641
|
+
|
|
3642
|
+
results = reader.readtext(img_array, detail=1, text_threshold=0.7)
|
|
3643
|
+
|
|
3644
|
+
best_match = None
|
|
3645
|
+
for detection in results:
|
|
3646
|
+
bbox = detection[0]
|
|
3647
|
+
text = detection[1].lower()
|
|
3648
|
+
confidence = detection[2]
|
|
3649
|
+
|
|
3650
|
+
match = False
|
|
3651
|
+
if partial_match:
|
|
3652
|
+
if search_text in text:
|
|
3653
|
+
match = True
|
|
3654
|
+
else:
|
|
3655
|
+
if search_text == text:
|
|
3656
|
+
match = True
|
|
3657
|
+
|
|
3658
|
+
if match and confidence >= confidence_threshold:
|
|
3659
|
+
x_coords = [point[0] for point in bbox]
|
|
3660
|
+
y_coords = [point[1] for point in bbox]
|
|
3661
|
+
center_x = int(sum(x_coords) / len(x_coords))
|
|
3662
|
+
center_y = int(sum(y_coords) / len(y_coords))
|
|
3663
|
+
|
|
3664
|
+
if best_match is None or confidence > best_match['confidence']:
|
|
3665
|
+
best_match = {
|
|
3666
|
+
'found': True,
|
|
3667
|
+
'x': center_x,
|
|
3668
|
+
'y': center_y,
|
|
3669
|
+
'confidence': confidence,
|
|
3670
|
+
'matched_text': detection[1],
|
|
3671
|
+
'bbox': bbox
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
if best_match:
|
|
3675
|
+
return best_match
|
|
3676
|
+
return {'found': False, 'error': f'Текст "{search_text}" не найден'}
|
|
3677
|
+
|
|
3678
|
+
@staticmethod
|
|
3679
|
+
def wait_for_image(template_path, timeout=30, confidence=0.8):
|
|
3680
|
+
"""
|
|
3681
|
+
⏳ ЖДАТЬ ИЗОБРАЖЕНИЕ: Ждёт появления изображения на экране в течение заданного времени.
|
|
3682
|
+
|
|
3683
|
+
Параметры:
|
|
3684
|
+
template_path (str): путь к изображению
|
|
3685
|
+
timeout (int): максимальное время ожидания в секундах
|
|
3686
|
+
confidence (float): порог совпадения
|
|
3687
|
+
|
|
3688
|
+
Возвращает:
|
|
3689
|
+
tuple или None: (x, y) центр найденного изображения, или None если таймаут
|
|
3690
|
+
|
|
3691
|
+
📝 Пример:
|
|
3692
|
+
print("Ждём загрузку...")
|
|
3693
|
+
pos = ScreenReader.wait_for_image("loading_complete.png", timeout=60)
|
|
3694
|
+
if pos:
|
|
3695
|
+
print("Загрузка завершена!")
|
|
3696
|
+
else:
|
|
3697
|
+
print("Таймаут!")
|
|
3698
|
+
|
|
3699
|
+
🎯 Возможности:
|
|
3700
|
+
- Ожидание загрузки интерфейса
|
|
3701
|
+
- Синхронизация с медленными программами
|
|
3702
|
+
"""
|
|
3703
|
+
start = time.time()
|
|
3704
|
+
while time.time() - start < timeout:
|
|
3705
|
+
pos = ScreenReader.find_image(template_path, confidence)
|
|
3706
|
+
if pos:
|
|
3707
|
+
print(f"✅ Изображение появилось через {time.time() - start:.1f} сек")
|
|
3708
|
+
return pos
|
|
3709
|
+
time.sleep(0.5)
|
|
3710
|
+
print(f"❌ Изображение не появилось за {timeout} сек")
|
|
3711
|
+
return None
|
|
3712
|
+
|
|
3713
|
+
@staticmethod
|
|
3714
|
+
def highlight_image(template_path, confidence=0.8, duration=2000, color='lime', thickness=4):
|
|
3715
|
+
"""
|
|
3716
|
+
🟩 ВЫДЕЛИТЬ ИЗОБРАЖЕНИЕ: Находит изображение на экране и обводит его рамкой.
|
|
3717
|
+
|
|
3718
|
+
Параметры:
|
|
3719
|
+
template_path (str): путь к картинке-образцу (png)
|
|
3720
|
+
confidence (float): порог совпадения (0.7–0.95)
|
|
3721
|
+
duration (int): сколько миллисекунд держать рамку
|
|
3722
|
+
color (str): цвет рамки ('lime', 'red', 'blue', ...)
|
|
3723
|
+
thickness (int): толщина линии рамки в пикселях
|
|
3724
|
+
|
|
3725
|
+
Возвращает:
|
|
3726
|
+
tuple или None: (x, y) координаты найденного изображения
|
|
3727
|
+
|
|
3728
|
+
📝 Пример:
|
|
3729
|
+
ScreenReader.highlight_image("button.png", duration=3000, color='red')
|
|
3730
|
+
# Найдёт кнопку и обведёт её красной рамкой на 3 секунды
|
|
3731
|
+
|
|
3732
|
+
🎯 Возможности:
|
|
3733
|
+
- Визуальная отладка поиска изображений
|
|
3734
|
+
- Демонстрация найденных элементов
|
|
3735
|
+
"""
|
|
3736
|
+
import cv2
|
|
3737
|
+
import numpy as np
|
|
3738
|
+
import pyautogui
|
|
3739
|
+
import tkinter as tk
|
|
3740
|
+
|
|
3741
|
+
screenshot = pyautogui.screenshot()
|
|
3742
|
+
screenshot = cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR)
|
|
3743
|
+
template = cv2.imread(template_path)
|
|
3744
|
+
|
|
3745
|
+
if template is None:
|
|
3746
|
+
print(f"❌ Не найден файл: {template_path}")
|
|
3747
|
+
return
|
|
3748
|
+
|
|
3749
|
+
result = cv2.matchTemplate(screenshot, template, cv2.TM_CCOEFF_NORMED)
|
|
3750
|
+
_, max_val, _, max_loc = cv2.minMaxLoc(result)
|
|
3751
|
+
|
|
3752
|
+
if max_val < confidence:
|
|
3753
|
+
print(f"❌ Изображение не найдено (совпадение {max_val:.2f})")
|
|
3754
|
+
return
|
|
3755
|
+
|
|
3756
|
+
h, w = template.shape[:2]
|
|
3757
|
+
x, y = max_loc
|
|
3758
|
+
print(f"✅ Найдено: ({x}, {y}) размер {w}x{h} (совпадение {max_val:.2f})")
|
|
3759
|
+
|
|
3760
|
+
root = tk.Tk()
|
|
3761
|
+
root.overrideredirect(True)
|
|
3762
|
+
root.lift()
|
|
3763
|
+
root.wm_attributes("-topmost", True)
|
|
3764
|
+
root.wm_attributes("-transparentcolor", "white")
|
|
3765
|
+
|
|
3766
|
+
canvas = tk.Canvas(root, width=w, height=h, highlightthickness=0, bg='white')
|
|
3767
|
+
canvas.pack()
|
|
3768
|
+
canvas.create_rectangle(0, 0, w-1, h-1, outline=color, width=thickness)
|
|
3769
|
+
|
|
3770
|
+
root.geometry(f"{w}x{h}+{x}+{y}")
|
|
3771
|
+
root.after(duration, root.destroy)
|
|
3772
|
+
root.mainloop()
|
|
3773
|
+
|
|
3774
|
+
return (x, y)
|
|
3775
|
+
|
|
3776
|
+
|
|
3777
|
+
# =============================================================================
|
|
3778
|
+
# КЛАСС Clipboard – БУФЕР ОБМЕНА
|
|
3779
|
+
# =============================================================================
|
|
3780
|
+
class Clipboard:
|
|
3781
|
+
"""
|
|
3782
|
+
📋 РАБОТА С БУФЕРОМ ОБМЕНА: копирование и вставка текста.
|
|
3783
|
+
|
|
3784
|
+
Требуется установка: pip install pyperclip
|
|
3785
|
+
|
|
3786
|
+
📝 Пример:
|
|
3787
|
+
Clipboard.copy("Привет, мир!")
|
|
3788
|
+
text = Clipboard.paste()
|
|
3789
|
+
print(text) # "Привет, мир!"
|
|
3790
|
+
"""
|
|
3791
|
+
|
|
3792
|
+
@staticmethod
|
|
3793
|
+
def copy(text: str):
|
|
3794
|
+
"""
|
|
3795
|
+
📋📤 КОПИРОВАТЬ: Копирует текст в буфер обмена.
|
|
3796
|
+
|
|
3797
|
+
Параметры:
|
|
3798
|
+
text (str): текст для копирования
|
|
3799
|
+
|
|
3800
|
+
📝 Пример:
|
|
3801
|
+
Clipboard.copy("Важный текст")
|
|
3802
|
+
# Теперь можно вставить его через Ctrl+V где угодно
|
|
3803
|
+
|
|
3804
|
+
🎯 Возможности:
|
|
3805
|
+
- Программное копирование текста
|
|
3806
|
+
- Подготовка данных для вставки
|
|
3807
|
+
"""
|
|
3808
|
+
import pyperclip
|
|
3809
|
+
pyperclip.copy(text)
|
|
3810
|
+
|
|
3811
|
+
@staticmethod
|
|
3812
|
+
def paste() -> str:
|
|
3813
|
+
"""
|
|
3814
|
+
📋📥 ВСТАВИТЬ: Извлекает текст из буфера обмена.
|
|
3815
|
+
|
|
3816
|
+
Возвращает:
|
|
3817
|
+
str: текст из буфера обмена
|
|
3818
|
+
|
|
3819
|
+
📝 Пример:
|
|
3820
|
+
text = Clipboard.paste()
|
|
3821
|
+
print(f"В буфере: {text}")
|
|
3822
|
+
|
|
3823
|
+
🎯 Возможности:
|
|
3824
|
+
- Получение скопированных данных
|
|
3825
|
+
- Автоматическая обработка текста из буфера
|
|
3826
|
+
"""
|
|
3827
|
+
import pyperclip
|
|
3828
|
+
return pyperclip.paste()
|
|
3829
|
+
|
|
3830
|
+
|
|
3831
|
+
# =============================================================================
|
|
3832
|
+
# КЛАСС MathHelper – БЫСТРЫЕ ВЫЧИСЛЕНИЯ
|
|
3833
|
+
# =============================================================================
|
|
3834
|
+
class MathHelper:
|
|
3835
|
+
"""
|
|
3836
|
+
🧮 ПРОСТЫЕ МАТЕМАТИЧЕСКИЕ ОПЕРАЦИИ: факториал, среднее, медиана.
|
|
3837
|
+
|
|
3838
|
+
📝 Примеры:
|
|
3839
|
+
MathHelper.factorial(5) # 120
|
|
3840
|
+
MathHelper.mean([1, 2, 3]) # 2.0
|
|
3841
|
+
MathHelper.median([1, 2, 3, 4]) # 2.5
|
|
3842
|
+
MathHelper.to_binary(42) # '101010'
|
|
3843
|
+
MathHelper.to_hex(42) # '2a'
|
|
3844
|
+
MathHelper.school_round(3.5) # 4
|
|
3845
|
+
"""
|
|
3846
|
+
|
|
3847
|
+
@staticmethod
|
|
3848
|
+
def factorial(n: int) -> int:
|
|
3849
|
+
"""
|
|
3850
|
+
🔢 ФАКТОРИАЛ: Вычисляет факториал числа n! = 1 × 2 × ... × n.
|
|
3851
|
+
|
|
3852
|
+
Параметры:
|
|
3853
|
+
n (int): неотрицательное целое число
|
|
3854
|
+
|
|
3855
|
+
Возвращает:
|
|
3856
|
+
int: значение факториала
|
|
3857
|
+
|
|
3858
|
+
📝 Пример:
|
|
3859
|
+
MathHelper.factorial(5) # 120 (1×2×3×4×5)
|
|
3860
|
+
MathHelper.factorial(0) # 1
|
|
3861
|
+
"""
|
|
3862
|
+
return math.factorial(n)
|
|
3863
|
+
|
|
3864
|
+
@staticmethod
|
|
3865
|
+
def mean(numbers: list) -> float:
|
|
3866
|
+
"""
|
|
3867
|
+
📊 СРЕДНЕЕ АРИФМЕТИЧЕСКОЕ: Сумма элементов, делённая на их количество.
|
|
3868
|
+
|
|
3869
|
+
Параметры:
|
|
3870
|
+
numbers (list): список чисел
|
|
3871
|
+
|
|
3872
|
+
Возвращает:
|
|
3873
|
+
float: среднее значение
|
|
3874
|
+
|
|
3875
|
+
📝 Пример:
|
|
3876
|
+
MathHelper.mean([1, 2, 3, 4, 5]) # 3.0
|
|
3877
|
+
MathHelper.mean([10, 20]) # 15.0
|
|
3878
|
+
"""
|
|
3879
|
+
return sum(numbers) / len(numbers)
|
|
3880
|
+
|
|
3881
|
+
@staticmethod
|
|
3882
|
+
def median(numbers: list) -> float:
|
|
3883
|
+
"""
|
|
3884
|
+
📈 МЕДИАНА: Значение, которое находится посередине отсортированного списка.
|
|
3885
|
+
|
|
3886
|
+
Параметры:
|
|
3887
|
+
numbers (list): список чисел
|
|
3888
|
+
|
|
3889
|
+
Возвращает:
|
|
3890
|
+
float: медиана
|
|
3891
|
+
|
|
3892
|
+
📝 Пример:
|
|
3893
|
+
MathHelper.median([1, 2, 3, 4, 5]) # 3
|
|
3894
|
+
MathHelper.median([1, 2, 3, 4]) # 2.5
|
|
3895
|
+
"""
|
|
3896
|
+
return statistics.median(numbers)
|
|
3897
|
+
|
|
3898
|
+
@staticmethod
|
|
3899
|
+
def to_binary(n: int) -> str:
|
|
3900
|
+
"""
|
|
3901
|
+
0️⃣1️⃣ В ДВОИЧНУЮ: Переводит целое число в двоичную систему счисления.
|
|
3902
|
+
|
|
3903
|
+
Параметры:
|
|
3904
|
+
n (int): целое число
|
|
3905
|
+
|
|
3906
|
+
Возвращает:
|
|
3907
|
+
str: строка с двоичным представлением
|
|
3908
|
+
|
|
3909
|
+
📝 Пример:
|
|
3910
|
+
MathHelper.to_binary(42) # '101010'
|
|
3911
|
+
MathHelper.to_binary(255) # '11111111'
|
|
3912
|
+
"""
|
|
3913
|
+
return bin(n)[2:]
|
|
3914
|
+
|
|
3915
|
+
@staticmethod
|
|
3916
|
+
def to_hex(n: int) -> str:
|
|
3917
|
+
"""
|
|
3918
|
+
🔣 В ШЕСТНАДЦАТЕРИЧНУЮ: Переводит целое число в шестнадцатеричную систему.
|
|
3919
|
+
|
|
3920
|
+
Параметры:
|
|
3921
|
+
n (int): целое число
|
|
3922
|
+
|
|
3923
|
+
Возвращает:
|
|
3924
|
+
str: строка с шестнадцатеричным представлением
|
|
3925
|
+
|
|
3926
|
+
📝 Пример:
|
|
3927
|
+
MathHelper.to_hex(42) # '2a'
|
|
3928
|
+
MathHelper.to_hex(255) # 'ff'
|
|
3929
|
+
"""
|
|
3930
|
+
return hex(n)[2:]
|
|
3931
|
+
|
|
3932
|
+
@staticmethod
|
|
3933
|
+
def school_round(number, digits=0):
|
|
3934
|
+
"""
|
|
3935
|
+
🎓 ШКОЛЬНОЕ ОКРУГЛЕНИЕ: Работает как на уроках математики.
|
|
3936
|
+
3.5 → 4, 3.15 (до десятых) → 3.2
|
|
3937
|
+
|
|
3938
|
+
Отличается от встроенного round(), который использует банковское округление.
|
|
3939
|
+
|
|
3940
|
+
Параметры:
|
|
3941
|
+
number (float): число для округления
|
|
3942
|
+
digits (int): сколько знаков после запятой оставить (по умолчанию 0)
|
|
3943
|
+
|
|
3944
|
+
Возвращает:
|
|
3945
|
+
float: округлённое число
|
|
3946
|
+
|
|
3947
|
+
📝 Пример:
|
|
3948
|
+
MathHelper.school_round(3.5) # 4.0
|
|
3949
|
+
MathHelper.school_round(4.5) # 5.0
|
|
3950
|
+
MathHelper.school_round(3.15, 1) # 3.2
|
|
3951
|
+
MathHelper.school_round(3.14, 1) # 3.1
|
|
3952
|
+
|
|
3953
|
+
🎯 Возможности:
|
|
3954
|
+
- Правильное округление для школьных задач
|
|
3955
|
+
- Финансовые расчёты
|
|
3956
|
+
"""
|
|
3957
|
+
multiplier = 10 ** digits
|
|
3958
|
+
return (number * multiplier + 0.5 * (1 if number >= 0 else -1)) // 1 * (1 / multiplier)
|
|
3959
|
+
|
|
3960
|
+
|
|
3961
|
+
# =============================================================================
|
|
3962
|
+
# КЛАСС FileUtils – ПРОСТЫЕ ОПЕРАЦИИ С ФАЙЛАМИ
|
|
3963
|
+
# =============================================================================
|
|
3964
|
+
class FileUtils:
|
|
3965
|
+
"""
|
|
3966
|
+
📄 ЧТЕНИЕ, ЗАПИСЬ И ПОДСЧЁТ СТРОК.
|
|
3967
|
+
|
|
3968
|
+
📝 Примеры:
|
|
3969
|
+
content = FileUtils.read_text("notes.txt")
|
|
3970
|
+
FileUtils.write_text("hello.txt", "Привет!")
|
|
3971
|
+
lines = FileUtils.count_lines("log.txt")
|
|
3972
|
+
"""
|
|
3973
|
+
|
|
3974
|
+
@staticmethod
|
|
3975
|
+
def read_text(path: str) -> str:
|
|
3976
|
+
"""
|
|
3977
|
+
📖 ЧИТАТЬ ФАЙЛ: Читает весь текстовый файл и возвращает содержимое.
|
|
3978
|
+
|
|
3979
|
+
Параметры:
|
|
3980
|
+
path (str): путь к текстовому файлу
|
|
3981
|
+
|
|
3982
|
+
Возвращает:
|
|
3983
|
+
str: содержимое файла
|
|
3984
|
+
|
|
3985
|
+
📝 Пример:
|
|
3986
|
+
content = FileUtils.read_text("config.txt")
|
|
3987
|
+
print(content)
|
|
3988
|
+
|
|
3989
|
+
🎯 Возможности:
|
|
3990
|
+
- Чтение конфигурационных файлов
|
|
3991
|
+
- Загрузка текстовых данных
|
|
3992
|
+
"""
|
|
3993
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
3994
|
+
return f.read()
|
|
3995
|
+
|
|
3996
|
+
@staticmethod
|
|
3997
|
+
def write_text(path: str, content: str):
|
|
3998
|
+
"""
|
|
3999
|
+
✍️ ЗАПИСАТЬ ФАЙЛ: Записывает текст в файл (перезаписывает, если существует).
|
|
4000
|
+
|
|
4001
|
+
Параметры:
|
|
4002
|
+
path (str): путь к файлу
|
|
4003
|
+
content (str): текст для записи
|
|
4004
|
+
|
|
4005
|
+
📝 Пример:
|
|
4006
|
+
FileUtils.write_text("output.txt", "Результаты:\\n...")
|
|
4007
|
+
print("Файл сохранён!")
|
|
4008
|
+
|
|
4009
|
+
🎯 Возможности:
|
|
4010
|
+
- Сохранение отчётов и логов
|
|
4011
|
+
- Создание новых текстовых файлов
|
|
4012
|
+
"""
|
|
4013
|
+
with open(path, 'w', encoding='utf-8') as f:
|
|
4014
|
+
f.write(content)
|
|
4015
|
+
|
|
4016
|
+
@staticmethod
|
|
4017
|
+
def count_lines(path: str) -> int:
|
|
4018
|
+
"""
|
|
4019
|
+
🔢 ПОДСЧЁТ СТРОК: Считает количество строк в текстовом файле.
|
|
4020
|
+
|
|
4021
|
+
Параметры:
|
|
4022
|
+
path (str): путь к файлу
|
|
4023
|
+
|
|
4024
|
+
Возвращает:
|
|
4025
|
+
int: количество строк
|
|
4026
|
+
|
|
4027
|
+
📝 Пример:
|
|
4028
|
+
lines = FileUtils.count_lines("log.txt")
|
|
4029
|
+
print(f"В лог-файле {lines} строк")
|
|
4030
|
+
|
|
4031
|
+
🎯 Возможности:
|
|
4032
|
+
- Анализ размера логов
|
|
4033
|
+
- Проверка количества записей
|
|
4034
|
+
"""
|
|
4035
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
4036
|
+
return sum(1 for _ in f)
|
|
4037
|
+
|
|
4038
|
+
|
|
4039
|
+
# =============================================================================
|
|
4040
|
+
# КЛАСС ImageUtils – ПРОСТЫЕ ОПЕРАЦИИ С ИЗОБРАЖЕНИЯМИ (Pillow)
|
|
4041
|
+
# =============================================================================
|
|
4042
|
+
class ImageUtils:
|
|
4043
|
+
"""
|
|
4044
|
+
🖼️ ИЗМЕНЕНИЕ РАЗМЕРА, ОБРЕЗКА, ПОВОРОТ ИЗОБРАЖЕНИЙ.
|
|
4045
|
+
|
|
4046
|
+
Требуется установка: pip install pillow
|
|
4047
|
+
|
|
4048
|
+
📝 Примеры:
|
|
4049
|
+
ImageUtils.resize("input.jpg", "small.jpg", 100, 100)
|
|
4050
|
+
ImageUtils.crop("input.jpg", "cropped.jpg", 10, 10, 50, 50)
|
|
4051
|
+
ImageUtils.rotate("input.jpg", "rotated.jpg", 90)
|
|
4052
|
+
"""
|
|
4053
|
+
|
|
4054
|
+
@staticmethod
|
|
4055
|
+
def resize(input_path, output_path, width, height):
|
|
4056
|
+
"""
|
|
4057
|
+
📏 ИЗМЕНИТЬ РАЗМЕР: Изменяет размер изображения.
|
|
4058
|
+
|
|
4059
|
+
Параметры:
|
|
4060
|
+
input_path (str): путь к исходному изображению
|
|
4061
|
+
output_path (str): путь для сохранения
|
|
4062
|
+
width (int): новая ширина в пикселях
|
|
4063
|
+
height (int): новая высота в пикселях
|
|
4064
|
+
|
|
4065
|
+
📝 Пример:
|
|
4066
|
+
ImageUtils.resize("photo.jpg", "photo_small.jpg", 200, 150)
|
|
4067
|
+
print("Миниатюра создана!")
|
|
4068
|
+
|
|
4069
|
+
🎯 Возможности:
|
|
4070
|
+
- Создание миниатюр
|
|
4071
|
+
- Оптимизация размера для веба
|
|
4072
|
+
"""
|
|
4073
|
+
from PIL import Image
|
|
4074
|
+
img = Image.open(input_path)
|
|
4075
|
+
img = img.resize((width, height))
|
|
4076
|
+
img.save(output_path)
|
|
4077
|
+
|
|
4078
|
+
@staticmethod
|
|
4079
|
+
def crop(input_path, output_path, left, top, right, bottom):
|
|
4080
|
+
"""
|
|
4081
|
+
✂️ ОБРЕЗАТЬ: Обрезает изображение по указанным границам.
|
|
4082
|
+
|
|
4083
|
+
Параметры:
|
|
4084
|
+
input_path (str): путь к исходному изображению
|
|
4085
|
+
output_path (str): путь для сохранения
|
|
4086
|
+
left, top, right, bottom (int): границы обрезки в пикселях
|
|
4087
|
+
|
|
4088
|
+
📝 Пример:
|
|
4089
|
+
ImageUtils.crop("photo.jpg", "face.jpg", 100, 50, 300, 300)
|
|
4090
|
+
print("Обрезано!")
|
|
4091
|
+
|
|
4092
|
+
🎯 Возможности:
|
|
4093
|
+
- Вырезание фрагментов изображений
|
|
4094
|
+
- Удаление лишних краёв
|
|
4095
|
+
"""
|
|
4096
|
+
from PIL import Image
|
|
4097
|
+
img = Image.open(input_path)
|
|
4098
|
+
cropped = img.crop((left, top, right, bottom))
|
|
4099
|
+
cropped.save(output_path)
|
|
4100
|
+
|
|
4101
|
+
@staticmethod
|
|
4102
|
+
def rotate(input_path, output_path, degrees):
|
|
4103
|
+
"""
|
|
4104
|
+
🔄 ПОВЕРНУТЬ: Поворачивает изображение на указанный угол.
|
|
4105
|
+
|
|
4106
|
+
Параметры:
|
|
4107
|
+
input_path (str): путь к исходному изображению
|
|
4108
|
+
output_path (str): путь для сохранения
|
|
4109
|
+
degrees (int): угол поворота в градусах
|
|
4110
|
+
|
|
4111
|
+
📝 Пример:
|
|
4112
|
+
ImageUtils.rotate("photo.jpg", "photo_90.jpg", 90)
|
|
4113
|
+
ImageUtils.rotate("photo.jpg", "photo_flip.jpg", 180)
|
|
4114
|
+
|
|
4115
|
+
🎯 Возможности:
|
|
4116
|
+
- Исправление ориентации фото
|
|
4117
|
+
- Создание повёрнутых копий
|
|
4118
|
+
"""
|
|
4119
|
+
from PIL import Image
|
|
4120
|
+
img = Image.open(input_path)
|
|
4121
|
+
rotated = img.rotate(degrees, expand=True)
|
|
4122
|
+
rotated.save(output_path)
|
|
4123
|
+
|
|
4124
|
+
|
|
4125
|
+
# =============================================================================
|
|
4126
|
+
# КЛАСС PDFUtils – ИЗВЛЕЧЕНИЕ ТЕКСТА ИЗ PDF (PyPDF2)
|
|
4127
|
+
# =============================================================================
|
|
4128
|
+
class PDFUtils:
|
|
4129
|
+
"""
|
|
4130
|
+
📑 ЧТЕНИЕ ТЕКСТА ИЗ PDF-ФАЙЛОВ.
|
|
4131
|
+
|
|
4132
|
+
Требуется установка: pip install PyPDF2
|
|
4133
|
+
|
|
4134
|
+
📝 Пример:
|
|
4135
|
+
text = PDFUtils.extract_text("document.pdf")
|
|
4136
|
+
print(text)
|
|
4137
|
+
"""
|
|
4138
|
+
|
|
4139
|
+
@staticmethod
|
|
4140
|
+
def extract_text(pdf_path: str) -> str:
|
|
4141
|
+
"""
|
|
4142
|
+
📄 ИЗВЛЕЧЬ ТЕКСТ: Извлекает весь текст из PDF-файла.
|
|
4143
|
+
|
|
4144
|
+
Параметры:
|
|
4145
|
+
pdf_path (str): путь к PDF-файлу
|
|
4146
|
+
|
|
4147
|
+
Возвращает:
|
|
4148
|
+
str: извлечённый текст (страницы разделены переносом строки)
|
|
4149
|
+
|
|
4150
|
+
📝 Пример:
|
|
4151
|
+
text = PDFUtils.extract_text("report.pdf")
|
|
4152
|
+
print(f"В документе {len(text)} символов")
|
|
4153
|
+
print(text[:200]) # первые 200 символов
|
|
4154
|
+
|
|
4155
|
+
🎯 Возможности:
|
|
4156
|
+
- Обработка документов
|
|
4157
|
+
- Извлечение данных из PDF-отчётов
|
|
4158
|
+
"""
|
|
4159
|
+
import PyPDF2
|
|
4160
|
+
text = []
|
|
4161
|
+
with open(pdf_path, 'rb') as f:
|
|
4162
|
+
reader = PyPDF2.PdfReader(f)
|
|
4163
|
+
for page in reader.pages:
|
|
4164
|
+
text.append(page.extract_text() or '')
|
|
4165
|
+
return '\n'.join(text)
|
|
4166
|
+
|
|
4167
|
+
|
|
4168
|
+
# =============================================================================
|
|
4169
|
+
# КЛАСС PasswordGen – ГЕНЕРАТОР ПАРОЛЕЙ
|
|
4170
|
+
# =============================================================================
|
|
4171
|
+
class PasswordGen:
|
|
4172
|
+
"""
|
|
4173
|
+
🔐 ГЕНЕРАЦИЯ СЛУЧАЙНЫХ ПАРОЛЕЙ.
|
|
4174
|
+
|
|
4175
|
+
📝 Примеры:
|
|
4176
|
+
PasswordGen.generate() # 12 символов, с цифрами и спецсимволами
|
|
4177
|
+
PasswordGen.generate(8, False) # только буквы, 8 символов
|
|
4178
|
+
PasswordGen.generate(16) # длинный надёжный пароль
|
|
4179
|
+
"""
|
|
4180
|
+
|
|
4181
|
+
@staticmethod
|
|
4182
|
+
def generate(length=12, use_digits=True, use_special=True) -> str:
|
|
4183
|
+
"""
|
|
4184
|
+
🔑 СОЗДАТЬ ПАРОЛЬ: Генерирует случайный пароль из букв, цифр и спецсимволов.
|
|
4185
|
+
|
|
4186
|
+
Параметры:
|
|
4187
|
+
length (int): длина пароля (по умолчанию 12)
|
|
4188
|
+
use_digits (bool): включать ли цифры (0-9)
|
|
4189
|
+
use_special (bool): включать ли спецсимволы (!@#$%^&*)
|
|
4190
|
+
|
|
4191
|
+
Возвращает:
|
|
4192
|
+
str: сгенерированный пароль
|
|
4193
|
+
|
|
4194
|
+
📝 Пример:
|
|
4195
|
+
pwd1 = PasswordGen.generate() # "aB3$xK9@mP2q"
|
|
4196
|
+
pwd2 = PasswordGen.generate(8, False) # "aBcDeFgH" (только буквы)
|
|
4197
|
+
pwd3 = PasswordGen.generate(16) # 16 символов, надёжный
|
|
4198
|
+
|
|
4199
|
+
🎯 Возможности:
|
|
4200
|
+
- Создание паролей для новых аккаунтов
|
|
4201
|
+
- Генерация токенов и ключей
|
|
4202
|
+
- Настраиваемая сложность
|
|
4203
|
+
"""
|
|
4204
|
+
chars = string.ascii_letters
|
|
4205
|
+
if use_digits:
|
|
4206
|
+
chars += string.digits
|
|
4207
|
+
if use_special:
|
|
4208
|
+
chars += "!@#$%^&*"
|
|
4209
|
+
return ''.join(random.choice(chars) for _ in range(length))
|
|
4210
|
+
|
|
4211
|
+
|
|
4212
|
+
# =============================================================================
|
|
4213
|
+
# КЛАСС UnitConverter – КОНВЕРТАЦИЯ ВЕЛИЧИН
|
|
4214
|
+
# =============================================================================
|
|
4215
|
+
class UnitConverter:
|
|
4216
|
+
"""
|
|
4217
|
+
📏 КОНВЕРТАЦИЯ МЕЖДУ ЕДИНИЦАМИ ИЗМЕРЕНИЯ.
|
|
4218
|
+
|
|
4219
|
+
📝 Примеры:
|
|
4220
|
+
UnitConverter.celsius_to_fahrenheit(0) # 32.0
|
|
4221
|
+
UnitConverter.km_to_miles(10) # 6.21371
|
|
4222
|
+
UnitConverter.kg_to_lbs(5) # 11.0231
|
|
4223
|
+
"""
|
|
4224
|
+
|
|
4225
|
+
@staticmethod
|
|
4226
|
+
def celsius_to_fahrenheit(c: float) -> float:
|
|
4227
|
+
"""
|
|
4228
|
+
🌡️ °C → °F: Переводит температуру из Цельсия в Фаренгейт.
|
|
4229
|
+
|
|
4230
|
+
Формула: F = C × 9/5 + 32
|
|
4231
|
+
|
|
4232
|
+
Параметры:
|
|
4233
|
+
c (float): температура в градусах Цельсия
|
|
4234
|
+
|
|
4235
|
+
Возвращает:
|
|
4236
|
+
float: температура в градусах Фаренгейта
|
|
4237
|
+
|
|
4238
|
+
📝 Пример:
|
|
4239
|
+
UnitConverter.celsius_to_fahrenheit(0) # 32.0 (замерзание воды)
|
|
4240
|
+
UnitConverter.celsius_to_fahrenheit(100) # 212.0 (кипение воды)
|
|
4241
|
+
UnitConverter.celsius_to_fahrenheit(37) # 98.6 (температура тела)
|
|
4242
|
+
"""
|
|
4243
|
+
return c * 9/5 + 32
|
|
4244
|
+
|
|
4245
|
+
@staticmethod
|
|
4246
|
+
def fahrenheit_to_celsius(f: float) -> float:
|
|
4247
|
+
"""
|
|
4248
|
+
🌡️ °F → °C: Переводит температуру из Фаренгейта в Цельсий.
|
|
4249
|
+
|
|
4250
|
+
Формула: C = (F - 32) × 5/9
|
|
4251
|
+
|
|
4252
|
+
Параметры:
|
|
4253
|
+
f (float): температура в градусах Фаренгейта
|
|
4254
|
+
|
|
4255
|
+
Возвращает:
|
|
4256
|
+
float: температура в градусах Цельсия
|
|
4257
|
+
|
|
4258
|
+
📝 Пример:
|
|
4259
|
+
UnitConverter.fahrenheit_to_celsius(32) # 0.0
|
|
4260
|
+
UnitConverter.fahrenheit_to_celsius(212) # 100.0
|
|
4261
|
+
"""
|
|
4262
|
+
return (f - 32) * 5/9
|
|
4263
|
+
|
|
4264
|
+
@staticmethod
|
|
4265
|
+
def km_to_miles(km: float) -> float:
|
|
4266
|
+
"""
|
|
4267
|
+
🛣️ КМ → МИЛИ: Переводит километры в мили.
|
|
4268
|
+
|
|
4269
|
+
Параметры:
|
|
4270
|
+
km (float): расстояние в километрах
|
|
4271
|
+
|
|
4272
|
+
Возвращает:
|
|
4273
|
+
float: расстояние в милях
|
|
4274
|
+
|
|
4275
|
+
📝 Пример:
|
|
4276
|
+
UnitConverter.km_to_miles(10) # 6.21371
|
|
4277
|
+
UnitConverter.km_to_miles(100) # 62.1371
|
|
4278
|
+
"""
|
|
4279
|
+
return km * 0.621371
|
|
4280
|
+
|
|
4281
|
+
@staticmethod
|
|
4282
|
+
def miles_to_km(miles: float) -> float:
|
|
4283
|
+
"""
|
|
4284
|
+
🛣️ МИЛИ → КМ: Переводит мили в километры.
|
|
4285
|
+
|
|
4286
|
+
Параметры:
|
|
4287
|
+
miles (float): расстояние в милях
|
|
4288
|
+
|
|
4289
|
+
Возвращает:
|
|
4290
|
+
float: расстояние в километрах
|
|
4291
|
+
|
|
4292
|
+
📝 Пример:
|
|
4293
|
+
UnitConverter.miles_to_km(10) # 16.0934
|
|
4294
|
+
"""
|
|
4295
|
+
return miles / 0.621371
|
|
4296
|
+
|
|
4297
|
+
@staticmethod
|
|
4298
|
+
def kg_to_lbs(kg: float) -> float:
|
|
4299
|
+
"""
|
|
4300
|
+
⚖️ КГ → ФУНТЫ: Переводит килограммы в фунты.
|
|
4301
|
+
|
|
4302
|
+
Параметры:
|
|
4303
|
+
kg (float): вес в килограммах
|
|
4304
|
+
|
|
4305
|
+
Возвращает:
|
|
4306
|
+
float: вес в фунтах
|
|
4307
|
+
|
|
4308
|
+
📝 Пример:
|
|
4309
|
+
UnitConverter.kg_to_lbs(5) # 11.0231
|
|
4310
|
+
UnitConverter.kg_to_lbs(70) # 154.3234
|
|
4311
|
+
"""
|
|
4312
|
+
return kg * 2.20462
|
|
4313
|
+
|
|
4314
|
+
@staticmethod
|
|
4315
|
+
def lbs_to_kg(lbs: float) -> float:
|
|
4316
|
+
"""
|
|
4317
|
+
⚖️ ФУНТЫ → КГ: Переводит фунты в килограммы.
|
|
4318
|
+
|
|
4319
|
+
Параметры:
|
|
4320
|
+
lbs (float): вес в фунтах
|
|
4321
|
+
|
|
4322
|
+
Возвращает:
|
|
4323
|
+
float: вес в килограммах
|
|
4324
|
+
|
|
4325
|
+
📝 Пример:
|
|
4326
|
+
UnitConverter.lbs_to_kg(10) # 4.5359
|
|
4327
|
+
"""
|
|
4328
|
+
return lbs / 2.20462
|
|
4329
|
+
|
|
4330
|
+
|
|
4331
|
+
# =============================================================================
|
|
4332
|
+
# ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ
|
|
4333
|
+
# =============================================================================
|
|
4334
|
+
if __name__ == "__main__":
|
|
4335
|
+
Manager.open_file("test_help_manager.py")
|