feat: автоматизация сборки и поддержка бессрочных токенов
- Исправлена ошибка ImportError: QtWebEngineCore путем перехода на PyInstaller. - Добавлен скрипт build.py для автоматической сборки, очистки DLL и создания ZIP-архива. - Реализована поддержка бессрочного доступа (offline_access) для VK Access Token. - Обновлен README.md: добавлены разделы для разработчиков и описание структуры данных. - Оптимизирован размер билда за счет удаления неиспользуемых библиотек Qt и папок локализации.
This commit is contained in:
80
main.py
80
main.py
@@ -1,5 +1,5 @@
|
||||
import sys
|
||||
import vk_api
|
||||
from vk_api import VkApi
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
@@ -8,6 +8,7 @@ from PySide6.QtWidgets import (QApplication, QMainWindow, QLabel, QLineEdit,
|
||||
QTextBrowser, QScrollArea, QCheckBox, QHBoxLayout,
|
||||
QSizePolicy, QDialog, QTextEdit, QTabWidget, QDialogButtonBox)
|
||||
from PySide6.QtCore import Qt, QUrl, QDateTime, Signal, QTimer
|
||||
from PySide6.QtGui import QIcon
|
||||
from PySide6.QtWebEngineWidgets import QWebEngineView
|
||||
from PySide6.QtWebEngineCore import QWebEnginePage, QWebEngineProfile
|
||||
from urllib.parse import urlparse, parse_qs, unquote
|
||||
@@ -19,56 +20,65 @@ APP_DATA_DIR = os.path.join(QStandardPaths.writableLocation(QStandardPaths.AppDa
|
||||
TOKEN_FILE = os.path.join(APP_DATA_DIR, "token.json")
|
||||
WEB_ENGINE_CACHE_DIR = os.path.join(APP_DATA_DIR, "web_engine_cache")
|
||||
|
||||
def get_resource_path(relative_path):
|
||||
""" Получает абсолютный путь к ресурсу, работает для скрипта и для .exe """
|
||||
if hasattr(sys, '_MEIPASS'): # Для PyInstaller (на всякий случай)
|
||||
return os.path.join(sys._MEIPASS, relative_path)
|
||||
# Для cx_Freeze и обычного запуска
|
||||
return os.path.join(os.path.abspath("."), relative_path)
|
||||
|
||||
def save_token(token, expires_in=0):
|
||||
"""Сохраняет токен. Если expires_in=0, токен считается бессрочным."""
|
||||
try:
|
||||
expires_in = int(expires_in)
|
||||
except (ValueError, TypeError):
|
||||
expires_in = 0
|
||||
|
||||
def save_token(token, expires_in=3600):
|
||||
"""
|
||||
Сохраняет VK access токен и его время истечения в JSON файл.
|
||||
По умолчанию токен действителен 1 час (3600 секунд).
|
||||
"""
|
||||
os.makedirs(APP_DATA_DIR, exist_ok=True)
|
||||
expiration_time = time.time() + expires_in
|
||||
|
||||
# ИСПРАВЛЕНИЕ: если expires_in равно 0, то и время истечения ставим 0
|
||||
expiration_time = (time.time() + expires_in) if expires_in > 0 else 0
|
||||
|
||||
data = {
|
||||
"token": token,
|
||||
"expiration_time": expiration_time
|
||||
}
|
||||
|
||||
try:
|
||||
with open(TOKEN_FILE, "w") as f:
|
||||
json.dump(data, f)
|
||||
print(
|
||||
f"Токен сохранен в {TOKEN_FILE}. Срок действия истекает {QDateTime.fromSecsSinceEpoch(int(expiration_time)).toString()}")
|
||||
|
||||
status = "Бессрочно" if expiration_time == 0 else QDateTime.fromSecsSinceEpoch(int(expiration_time)).toString()
|
||||
print(f"Токен сохранен. Срок действия: {status}")
|
||||
return expiration_time
|
||||
except IOError as e:
|
||||
print(f"Ошибка сохранения токена: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def load_token():
|
||||
"""
|
||||
Загружает VK access токен из JSON файла, если он еще действителен.
|
||||
Возвращает (токен, время_истечения_unix) или (None, None).
|
||||
"""
|
||||
"""Загружает токен и проверяет его валидность."""
|
||||
try:
|
||||
if not os.path.exists(TOKEN_FILE):
|
||||
print(f"Файл токена не найден по пути {TOKEN_FILE}.")
|
||||
return None, None
|
||||
|
||||
with open(TOKEN_FILE, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
token = data.get("token")
|
||||
expiration_time = data.get("expiration_time")
|
||||
|
||||
if token and expiration_time and expiration_time > time.time():
|
||||
print(
|
||||
f"Токен загружен из {TOKEN_FILE}. Действителен до {QDateTime.fromSecsSinceEpoch(int(expiration_time)).toString()}")
|
||||
# ИСПРАВЛЕНИЕ: токен валиден, если время истечения 0 ИЛИ оно больше текущего
|
||||
if token and (expiration_time == 0 or expiration_time > time.time()):
|
||||
return token, expiration_time
|
||||
else:
|
||||
print("Токен просрочен или недействителен.")
|
||||
if os.path.exists(TOKEN_FILE):
|
||||
os.remove(TOKEN_FILE)
|
||||
return None, None
|
||||
except (IOError, json.JSONDecodeError) as e:
|
||||
print(f"Ошибка загрузки токена: {e}")
|
||||
except Exception as e:
|
||||
print(f"Ошибка загрузки: {e}")
|
||||
return None, None
|
||||
|
||||
|
||||
class WebEnginePage(QWebEnginePage):
|
||||
"""
|
||||
Класс для обработки навигационных запросов в QWebEngineView,
|
||||
@@ -443,15 +453,24 @@ class VkChatManager(QMainWindow):
|
||||
if self.token_expiration_time is None:
|
||||
self.token_timer_label.setText("Срок действия токена: Н/Д")
|
||||
return
|
||||
|
||||
# ИСПРАВЛЕНИЕ: обрабатываем бессрочный токен
|
||||
if self.token_expiration_time == 0:
|
||||
self.token_timer_label.setText("Срок действия: Бессрочно")
|
||||
return
|
||||
|
||||
remaining_seconds = int(self.token_expiration_time - time.time())
|
||||
|
||||
if remaining_seconds <= 0:
|
||||
self.token_timer_label.setText("Срок действия токена истек!")
|
||||
if self.token_countdown_timer.isActive(): self.token_countdown_timer.stop()
|
||||
self.token_timer_label.setText("Срок действия истек!")
|
||||
if self.token_countdown_timer.isActive():
|
||||
self.token_countdown_timer.stop()
|
||||
self.set_ui_state(False)
|
||||
self.status_label.setText("Статус: Срок действия токена истек, авторизуйтесь заново.")
|
||||
self.status_label.setText("Статус: Срок действия истек, авторизуйтесь заново.")
|
||||
self.token, self.token_expiration_time = None, None
|
||||
self.token_input.clear()
|
||||
return
|
||||
|
||||
minutes, seconds = divmod(remaining_seconds, 60)
|
||||
hours, minutes = divmod(minutes, 60)
|
||||
self.token_timer_label.setText(f"Срок: {hours:02d}ч {minutes:02d}м {seconds:02d}с")
|
||||
@@ -507,12 +526,12 @@ class VkChatManager(QMainWindow):
|
||||
return
|
||||
|
||||
self.token = token
|
||||
self.token_expiration_time = time.time() + expires_in
|
||||
save_token(self.token, expires_in)
|
||||
# Сохраняем и получаем корректный expiration_time (0 или будущее время)
|
||||
self.token_expiration_time = save_token(self.token, expires_in)
|
||||
|
||||
self.token_input.setText(self.token[:50] + "...")
|
||||
self.status_label.setText("Статус: авторизован")
|
||||
self.vk_session = vk_api.VkApi(token=self.token)
|
||||
self.vk_session = VkApi(token=self.token)
|
||||
self.vk = self.vk_session.get_api()
|
||||
self.set_ui_state(True)
|
||||
self.load_chats()
|
||||
@@ -523,7 +542,7 @@ class VkChatManager(QMainWindow):
|
||||
|
||||
self.token_input.setText(self.token[:50] + "...")
|
||||
self.status_label.setText("Статус: авторизован (токен загружен)")
|
||||
self.vk_session = vk_api.VkApi(token=self.token)
|
||||
self.vk_session = VkApi(token=self.token)
|
||||
self.vk = self.vk_session.get_api()
|
||||
self.set_ui_state(True)
|
||||
self.load_chats()
|
||||
@@ -692,6 +711,11 @@ if __name__ == "__main__":
|
||||
app.setStyle("Fusion")
|
||||
app.setPalette(app.style().standardPalette())
|
||||
|
||||
# Установка иконки для ВСЕХ окон приложения
|
||||
icon_path = get_resource_path("icon.ico")
|
||||
if os.path.exists(icon_path):
|
||||
app.setWindowIcon(QIcon(icon_path))
|
||||
|
||||
window = VkChatManager()
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
Reference in New Issue
Block a user