feat: автоматизация сборки и поддержка бессрочных токенов
- Исправлена ошибка ImportError: QtWebEngineCore путем перехода на PyInstaller. - Добавлен скрипт build.py для автоматической сборки, очистки DLL и создания ZIP-архива. - Реализована поддержка бессрочного доступа (offline_access) для VK Access Token. - Обновлен README.md: добавлены разделы для разработчиков и описание структуры данных. - Оптимизирован размер билда за счет удаления неиспользуемых библиотек Qt и папок локализации.
This commit is contained in:
128
README.md
128
README.md
@@ -1,37 +1,42 @@
|
|||||||
# Anabasis VK Chat Manager
|
# 🚀 Anabasis VK Chat Manager
|
||||||
|
|
||||||
## Описание проекта
|
**Anabasis VK Chat Manager** — специализированное десктопное приложение для HR-менеджеров и администраторов сообществ, предназначенное для автоматизации управления участниками в чатах ВКонтакте. Избавьтесь от рутины и управляйте всеми беседами из одного удобного интерфейса.
|
||||||
|
|
||||||
**Anabasis VK Chat Manager** — это десктопное приложение на Python с графическим интерфейсом, разработанное для упрощения управления пользователями в чатах ВКонтакте. Оно позволяет авторизоваться через VK OAuth, просматривать список своих чатов, а также исключать или приглашать пользователей в выбранные чаты.
|
---
|
||||||
|
|
||||||
Приложение спроектировано для минимизации ручных операций и повышения удобства управления групповыми беседами VK.
|
## ✨ Основные возможности
|
||||||
|
|
||||||
## Возможности
|
* **🔐 Безопасная авторизация:** Вход через официальный VK OAuth во встроенном защищенном браузере.
|
||||||
|
* **💾 Умное сохранение сессий:** Поддержка Persistent Cookies — не нужно вводить пароль при каждом запуске.
|
||||||
|
* **⏳ Таймер токена:** Наглядное отображение времени действия сессии прямо в интерфейсе.
|
||||||
|
* **📊 Массовые операции:**
|
||||||
|
* Моментальная загрузка всех доступных чатов пользователя.
|
||||||
|
* Групповой выбор чатов («Выбрать все» / «Снять выбор»).
|
||||||
|
* Быстрое обновление списка бесед.
|
||||||
|
* **👤 Интеллектуальный поиск ID:** Автоматическое распознавание ID пользователя из ссылок любого формата (например, `vk.com/id123`, `vk.com/durov` или просто `durov`).
|
||||||
|
* **🛠 Управление в один клик:** Кнопки для мгновенного исключения или приглашения пользователя во все выбранные чаты одновременно.
|
||||||
|
* **🛡 Стабильность:** Улучшенная обработка ошибок VK API и автоматическая реакция на смену IP-адреса.
|
||||||
|
|
||||||
* **Авторизация через VK OAuth:** Безопасный процесс входа через официальный VK API.
|
---
|
||||||
* **Сохранение сессии:** Поддержка сохранения куки-файлов браузера для длительной авторизации в `QWebEngineView`.
|
|
||||||
* **Управление токенами:** Автоматическое сохранение и загрузка VK Access Token для удобства использования.
|
|
||||||
* **Список чатов:** Загрузка и отображение списка доступных чатов пользователя.
|
|
||||||
* **Выбор чатов:** Возможность выбора одного или нескольких чатов для выполнения операций.
|
|
||||||
* **Автоматическое определение ID пользователя:** Получение ID пользователя VK из различных форматов ссылок (например, `vk.com/id123`, `vk.com/durov`).
|
|
||||||
* **Исключение пользователей:** Удаление пользователя из выбранных чатов.
|
|
||||||
* **Приглашение пользователей:** Добавление пользователя в выбранные чаты.
|
|
||||||
* **Визуальный таймер токена:** Отображение оставшегося времени действия Access Token.
|
|
||||||
* **Информативные сообщения:** Детальные статусы операций и сообщения об ошибках.
|
|
||||||
* **Обработка ошибок:** Улучшенная обработка ошибок VK API, включая смену IP-адреса.
|
|
||||||
|
|
||||||
## Установка
|
## 📦 Установка и запуск
|
||||||
|
|
||||||
### Готовый билд
|
### Вариант 1: Готовый билд (Windows)
|
||||||
|
1. Перейдите в раздел **Releases** на GitHub.
|
||||||
|
2. Скачайте архив формата `AnabasisManager-1.x.zip`.
|
||||||
|
3. Распакуйте архив в удобную папку.
|
||||||
|
4. Запустите файл **AnabasisManager.exe**.
|
||||||
|
|
||||||
Скачайте последнюю доступную версию из релизов и распакуйте архив
|
### Вариант 2: Запуск из исходного кода
|
||||||
|
Вам потребуется **Python 3.10** или выше.
|
||||||
|
|
||||||
### Ручная установка
|
1. **Клонируйте репозиторий:**
|
||||||
Для запуска приложения вам потребуется Python 3 и библиотеки `PySide6` и `vk_api`.
|
```bash
|
||||||
|
git clone [https://github.com/your-username/AnabasisVKChatManager.git](https://github.com/your-username/AnabasisVKChatManager.git)
|
||||||
|
cd AnabasisVKChatManager
|
||||||
|
```
|
||||||
|
|
||||||
1. **Клонируйте репозиторий**
|
2. **Настройте виртуальное окружение:**
|
||||||
|
|
||||||
2. **Создайте и активируйте виртуальное окружение (рекомендуется):**
|
|
||||||
```bash
|
```bash
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
# Для Windows:
|
# Для Windows:
|
||||||
@@ -45,50 +50,55 @@
|
|||||||
pip install PySide6 vk_api
|
pip install PySide6 vk_api
|
||||||
```
|
```
|
||||||
|
|
||||||
## Использование
|
4. **Запустите приложение:**
|
||||||
|
```bash
|
||||||
|
python main.py
|
||||||
|
```
|
||||||
|
|
||||||
1. **Запустите приложение:**
|
---
|
||||||
* *Готовый билд:*
|
|
||||||
* Запустите **AnabasisHRChatManager.exe**
|
|
||||||
* *Ручная установка:*
|
|
||||||
* ```bash
|
|
||||||
python main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Авторизация:**
|
## 🕹 Инструкция по использованию
|
||||||
* Нажмите кнопку "Авторизоваться через VK".
|
|
||||||
* В открывшемся окне браузера войдите в свой аккаунт ВКонтакте.
|
|
||||||
* Разрешите доступ приложению, если потребуется.
|
|
||||||
* После успешной авторизации окно закроется, и токен доступа будет сохранен.
|
|
||||||
|
|
||||||
3. **Выбор чатов:**
|
1. **Вход:** Нажмите кнопку «Авторизоваться через VK». Введите данные в открывшемся окне браузера.
|
||||||
* После авторизации приложение автоматически загрузит список ваших чатов.
|
2. **Выбор целей:** Отметьте галочками чаты, в которых нужно произвести изменения.
|
||||||
* Отметьте галочками те чаты, с которыми хотите работать. Используйте кнопки "Выбрать все" / "Снять выбор со всех" для удобства.
|
3. **Данные пользователя:** Вставьте ссылку на профиль VK человека, которого нужно добавить или удалить.
|
||||||
* Кнопка "Обновить чаты" позволяет перезагрузить список чатов.
|
4. **Действие:** Нажмите кнопку нужной операции. Следите за прогрессом в окне системных сообщений.
|
||||||
|
|
||||||
4. **Управление пользователями:**
|
---
|
||||||
* В поле "Введите или вставьте ссылку на страницу VK" вставьте ссылку на страницу пользователя ВКонтакте (например, `vk.com/id123` или `vk.com/durov`). Приложение автоматически определит ID пользователя.
|
|
||||||
* Нажмите кнопку "ИСКЛЮЧИТЬ ПОЛЬЗОВАТЕЛЯ" для удаления пользователя из выбранных чатов.
|
|
||||||
* Нажмите кнопку "ПРИГЛАСИТЬ ПОЛЬЗОВАТЕЛЯ" для добавления пользователя в выбранные чаты.
|
|
||||||
* Опция "Показать 250 последних сообщений при добавлении" позволяет управлять видимостью истории сообщений для нового участника (Примечание: VK API может игнорировать этот параметр для `messages.addChatUser`).
|
|
||||||
|
|
||||||
## Структура данных и конфигурация
|
## 📂 Техническая информация
|
||||||
|
|
||||||
Приложение хранит данные в директории, специфичной для данных приложения, что соответствует рекомендациям операционных систем.
|
### Сборка проекта (для разработчиков)
|
||||||
|
Проект использует кастомный скрипт автоматизации `build.py`, который оптимизирует зависимости `PySide6` и корректно упаковывает `QtWebEngineCore`.
|
||||||
|
|
||||||
* **`token.json`**: Файл для сохранения VK Access Token. Находится в `[AppDataLocation]/AnabasisVKChatManager/token.json`.
|
**Команда для сборки:**
|
||||||
* **`web_engine_cache/`**: Директория для хранения куки-файлов и кэша `QWebEngineProfile`, обеспечивающая сохранение сессии внутри встроенного браузера. Находится в `[AppDataLocation]/AnabasisVKChatManager/web_engine_cache/`.
|
```bash
|
||||||
|
python build.py
|
||||||
|
```
|
||||||
|
|
||||||
`[AppDataLocation]` соответствует:
|
Скрипт автоматически:
|
||||||
* Windows: `%APPDATA%` (например, `C:\Users\YourUser\AppData\Roaming`)
|
|
||||||
* macOS: `~/Library/Application Support`
|
|
||||||
* Linux: `~/.local/share`
|
|
||||||
|
|
||||||
## Известные проблемы / Ограничения
|
Собирает .exe через PyInstaller с использованием --collect-all для модулей WebEngine.
|
||||||
|
|
||||||
* Параметр `visible_messages_count` для `messages.addChatUser` может быть проигнорирован VK API согласно официальной документации. Приложение уведомит вас об этом при попытке использования.
|
Удаляет лишние библиотеки (PDF, Multimedia, Designer) и папки переводов, сокращая размер сборки на ~100 МБ.
|
||||||
* При смене IP-адреса, токен авторизации VK может стать недействительным, потребуется повторная авторизация. Приложение автоматически предложит её.
|
|
||||||
|
|
||||||
## Лицензия
|
Создает готовый ZIP-архив с актуальной версией в названии.
|
||||||
|
|
||||||
Этот проект распространяется под лицензией MIT.
|
Хранение данных
|
||||||
|
|
||||||
|
Приложение использует системные папки AppData для изоляции пользовательских данных:
|
||||||
|
|
||||||
|
Windows: %APPDATA%/AnabasisVKChatManager
|
||||||
|
|
||||||
|
macOS: ~/Library/Application Support/AnabasisVKChatManager
|
||||||
|
|
||||||
|
В этих папках хранятся token.json (доступ к API) и web_engine_cache/ (сессия браузера).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📜 Лицензия
|
||||||
|
|
||||||
|
---
|
||||||
|
Проект распространяется под лицензией MIT.
|
||||||
|
|
||||||
|
Сэкономьте часы ручного труда с Anabasis VK Chat Manager.
|
||||||
96
build.py
Normal file
96
build.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# --- Конфигурация ---
|
||||||
|
APP_NAME = "AnabasisManager"
|
||||||
|
VERSION = "1.3" # Ваша версия
|
||||||
|
MAIN_SCRIPT = "main.py"
|
||||||
|
ICON_PATH = "icon.ico"
|
||||||
|
DIST_DIR = os.path.join("dist", APP_NAME)
|
||||||
|
ARCHIVE_NAME = f"{APP_NAME}-{VERSION}" # Формат Название-Версия
|
||||||
|
|
||||||
|
|
||||||
|
def run_build():
|
||||||
|
print(f"--- 1. Запуск PyInstaller для {APP_NAME} v{VERSION} ---")
|
||||||
|
|
||||||
|
command = [
|
||||||
|
"pyinstaller",
|
||||||
|
"--noconfirm",
|
||||||
|
"--onedir",
|
||||||
|
"--windowed",
|
||||||
|
f"--name={APP_NAME}",
|
||||||
|
f"--icon={ICON_PATH}" if os.path.exists(ICON_PATH) else "",
|
||||||
|
f"--add-data={ICON_PATH}{os.pathsep}." if os.path.exists(ICON_PATH) else "",
|
||||||
|
"--collect-all", "PySide6.QtWebEngineCore",
|
||||||
|
"--collect-all", "PySide6.QtWebEngineWidgets",
|
||||||
|
MAIN_SCRIPT
|
||||||
|
]
|
||||||
|
|
||||||
|
command = [arg for arg in command if arg]
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.check_call(command)
|
||||||
|
print("\n[OK] Сборка PyInstaller завершена.")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"\n[ERROR] Ошибка при сборке: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def run_cleanup():
|
||||||
|
print(f"\n--- 2. Оптимизация папки {APP_NAME} ---")
|
||||||
|
|
||||||
|
# Пытаемся найти папку PySide6 внутри сборки
|
||||||
|
pyside_path = os.path.join(DIST_DIR, "PySide6")
|
||||||
|
if not os.path.exists(pyside_path):
|
||||||
|
pyside_path = DIST_DIR
|
||||||
|
|
||||||
|
to_remove = [
|
||||||
|
"Qt6Pdf.dll", "Qt6PdfQuick.dll", "Qt6PdfWidgets.dll",
|
||||||
|
"Qt6VirtualKeyboard.dll", "Qt6Positioning.dll",
|
||||||
|
"Qt6PrintSupport.dll", "Qt6Svg.dll", "Qt6Sql.dll",
|
||||||
|
"Qt6Charts.dll", "Qt6Multimedia.dll", "Qt63DCore.dll",
|
||||||
|
"translations",
|
||||||
|
"Qt6QuickTemplates2.dll"
|
||||||
|
]
|
||||||
|
|
||||||
|
for item in to_remove:
|
||||||
|
path = os.path.join(pyside_path, item)
|
||||||
|
if os.path.exists(path):
|
||||||
|
try:
|
||||||
|
if os.path.isdir(path):
|
||||||
|
shutil.rmtree(path)
|
||||||
|
else:
|
||||||
|
os.remove(path)
|
||||||
|
print(f"Удалено: {item}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Пропуск {item}: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def create_archive():
|
||||||
|
print(f"\n--- 3. Создание архива {ARCHIVE_NAME}.zip ---")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Создаем zip-архив из папки DIST_DIR
|
||||||
|
# base_name - имя файла без расширения, format - 'zip', root_dir - что упаковываем
|
||||||
|
shutil.make_archive(os.path.join("dist", ARCHIVE_NAME), 'zip', DIST_DIR)
|
||||||
|
print(f"[OK] Архив создан: dist/{ARCHIVE_NAME}.zip")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] Не удалось создать архив: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Предварительная очистка
|
||||||
|
for folder in ["build", "dist"]:
|
||||||
|
if os.path.exists(folder):
|
||||||
|
shutil.rmtree(folder)
|
||||||
|
|
||||||
|
run_build()
|
||||||
|
run_cleanup()
|
||||||
|
create_archive()
|
||||||
|
|
||||||
|
print("\n" + "=" * 30)
|
||||||
|
print(f"ПРОЦЕСС ЗАВЕРШЕН")
|
||||||
|
print(f"Файл для отправки: dist/{ARCHIVE_NAME}.zip")
|
||||||
|
print("=" * 30)
|
||||||
80
main.py
80
main.py
@@ -1,5 +1,5 @@
|
|||||||
import sys
|
import sys
|
||||||
import vk_api
|
from vk_api import VkApi
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
@@ -8,6 +8,7 @@ from PySide6.QtWidgets import (QApplication, QMainWindow, QLabel, QLineEdit,
|
|||||||
QTextBrowser, QScrollArea, QCheckBox, QHBoxLayout,
|
QTextBrowser, QScrollArea, QCheckBox, QHBoxLayout,
|
||||||
QSizePolicy, QDialog, QTextEdit, QTabWidget, QDialogButtonBox)
|
QSizePolicy, QDialog, QTextEdit, QTabWidget, QDialogButtonBox)
|
||||||
from PySide6.QtCore import Qt, QUrl, QDateTime, Signal, QTimer
|
from PySide6.QtCore import Qt, QUrl, QDateTime, Signal, QTimer
|
||||||
|
from PySide6.QtGui import QIcon
|
||||||
from PySide6.QtWebEngineWidgets import QWebEngineView
|
from PySide6.QtWebEngineWidgets import QWebEngineView
|
||||||
from PySide6.QtWebEngineCore import QWebEnginePage, QWebEngineProfile
|
from PySide6.QtWebEngineCore import QWebEnginePage, QWebEngineProfile
|
||||||
from urllib.parse import urlparse, parse_qs, unquote
|
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")
|
TOKEN_FILE = os.path.join(APP_DATA_DIR, "token.json")
|
||||||
WEB_ENGINE_CACHE_DIR = os.path.join(APP_DATA_DIR, "web_engine_cache")
|
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)
|
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 = {
|
data = {
|
||||||
"token": token,
|
"token": token,
|
||||||
"expiration_time": expiration_time
|
"expiration_time": expiration_time
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(TOKEN_FILE, "w") as f:
|
with open(TOKEN_FILE, "w") as f:
|
||||||
json.dump(data, 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:
|
except IOError as e:
|
||||||
print(f"Ошибка сохранения токена: {e}")
|
print(f"Ошибка сохранения токена: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def load_token():
|
def load_token():
|
||||||
"""
|
"""Загружает токен и проверяет его валидность."""
|
||||||
Загружает VK access токен из JSON файла, если он еще действителен.
|
|
||||||
Возвращает (токен, время_истечения_unix) или (None, None).
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
if not os.path.exists(TOKEN_FILE):
|
if not os.path.exists(TOKEN_FILE):
|
||||||
print(f"Файл токена не найден по пути {TOKEN_FILE}.")
|
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
with open(TOKEN_FILE, "r") as f:
|
with open(TOKEN_FILE, "r") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
|
|
||||||
token = data.get("token")
|
token = data.get("token")
|
||||||
expiration_time = data.get("expiration_time")
|
expiration_time = data.get("expiration_time")
|
||||||
|
|
||||||
if token and expiration_time and expiration_time > time.time():
|
# ИСПРАВЛЕНИЕ: токен валиден, если время истечения 0 ИЛИ оно больше текущего
|
||||||
print(
|
if token and (expiration_time == 0 or expiration_time > time.time()):
|
||||||
f"Токен загружен из {TOKEN_FILE}. Действителен до {QDateTime.fromSecsSinceEpoch(int(expiration_time)).toString()}")
|
|
||||||
return token, expiration_time
|
return token, expiration_time
|
||||||
else:
|
else:
|
||||||
print("Токен просрочен или недействителен.")
|
|
||||||
if os.path.exists(TOKEN_FILE):
|
if os.path.exists(TOKEN_FILE):
|
||||||
os.remove(TOKEN_FILE)
|
os.remove(TOKEN_FILE)
|
||||||
return None, None
|
return None, None
|
||||||
except (IOError, json.JSONDecodeError) as e:
|
except Exception as e:
|
||||||
print(f"Ошибка загрузки токена: {e}")
|
print(f"Ошибка загрузки: {e}")
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
class WebEnginePage(QWebEnginePage):
|
class WebEnginePage(QWebEnginePage):
|
||||||
"""
|
"""
|
||||||
Класс для обработки навигационных запросов в QWebEngineView,
|
Класс для обработки навигационных запросов в QWebEngineView,
|
||||||
@@ -443,15 +453,24 @@ class VkChatManager(QMainWindow):
|
|||||||
if self.token_expiration_time is None:
|
if self.token_expiration_time is None:
|
||||||
self.token_timer_label.setText("Срок действия токена: Н/Д")
|
self.token_timer_label.setText("Срок действия токена: Н/Д")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# ИСПРАВЛЕНИЕ: обрабатываем бессрочный токен
|
||||||
|
if self.token_expiration_time == 0:
|
||||||
|
self.token_timer_label.setText("Срок действия: Бессрочно")
|
||||||
|
return
|
||||||
|
|
||||||
remaining_seconds = int(self.token_expiration_time - time.time())
|
remaining_seconds = int(self.token_expiration_time - time.time())
|
||||||
|
|
||||||
if remaining_seconds <= 0:
|
if remaining_seconds <= 0:
|
||||||
self.token_timer_label.setText("Срок действия токена истек!")
|
self.token_timer_label.setText("Срок действия истек!")
|
||||||
if self.token_countdown_timer.isActive(): self.token_countdown_timer.stop()
|
if self.token_countdown_timer.isActive():
|
||||||
|
self.token_countdown_timer.stop()
|
||||||
self.set_ui_state(False)
|
self.set_ui_state(False)
|
||||||
self.status_label.setText("Статус: Срок действия токена истек, авторизуйтесь заново.")
|
self.status_label.setText("Статус: Срок действия истек, авторизуйтесь заново.")
|
||||||
self.token, self.token_expiration_time = None, None
|
self.token, self.token_expiration_time = None, None
|
||||||
self.token_input.clear()
|
self.token_input.clear()
|
||||||
return
|
return
|
||||||
|
|
||||||
minutes, seconds = divmod(remaining_seconds, 60)
|
minutes, seconds = divmod(remaining_seconds, 60)
|
||||||
hours, minutes = divmod(minutes, 60)
|
hours, minutes = divmod(minutes, 60)
|
||||||
self.token_timer_label.setText(f"Срок: {hours:02d}ч {minutes:02d}м {seconds:02d}с")
|
self.token_timer_label.setText(f"Срок: {hours:02d}ч {minutes:02d}м {seconds:02d}с")
|
||||||
@@ -507,12 +526,12 @@ class VkChatManager(QMainWindow):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.token = token
|
self.token = token
|
||||||
self.token_expiration_time = time.time() + expires_in
|
# Сохраняем и получаем корректный expiration_time (0 или будущее время)
|
||||||
save_token(self.token, expires_in)
|
self.token_expiration_time = save_token(self.token, expires_in)
|
||||||
|
|
||||||
self.token_input.setText(self.token[:50] + "...")
|
self.token_input.setText(self.token[:50] + "...")
|
||||||
self.status_label.setText("Статус: авторизован")
|
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.vk = self.vk_session.get_api()
|
||||||
self.set_ui_state(True)
|
self.set_ui_state(True)
|
||||||
self.load_chats()
|
self.load_chats()
|
||||||
@@ -523,7 +542,7 @@ class VkChatManager(QMainWindow):
|
|||||||
|
|
||||||
self.token_input.setText(self.token[:50] + "...")
|
self.token_input.setText(self.token[:50] + "...")
|
||||||
self.status_label.setText("Статус: авторизован (токен загружен)")
|
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.vk = self.vk_session.get_api()
|
||||||
self.set_ui_state(True)
|
self.set_ui_state(True)
|
||||||
self.load_chats()
|
self.load_chats()
|
||||||
@@ -692,6 +711,11 @@ if __name__ == "__main__":
|
|||||||
app.setStyle("Fusion")
|
app.setStyle("Fusion")
|
||||||
app.setPalette(app.style().standardPalette())
|
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 = VkChatManager()
|
||||||
window.show()
|
window.show()
|
||||||
sys.exit(app.exec())
|
sys.exit(app.exec())
|
||||||
79
setup.py
79
setup.py
@@ -1,79 +0,0 @@
|
|||||||
# setup.py
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
from cx_Freeze import setup, Executable
|
|
||||||
|
|
||||||
# --- Основные настройки ---
|
|
||||||
|
|
||||||
# Имя вашего основного скрипта
|
|
||||||
main_script = "main.py" # Замените на имя вашего основного Python-файла
|
|
||||||
|
|
||||||
# Имя вашего приложения (исполняемого файла без расширения)
|
|
||||||
exe_name = "AnabasisHRChatManager"
|
|
||||||
|
|
||||||
# --- Платформо-зависимые настройки ---
|
|
||||||
|
|
||||||
# Определяем базовый тип приложения и имя конечного файла
|
|
||||||
base = None
|
|
||||||
target_name = exe_name
|
|
||||||
icon_path = "icon.ico" # Путь к иконке по умолчанию
|
|
||||||
|
|
||||||
if sys.platform == "win32":
|
|
||||||
# Для графических приложений на Windows (консоль не будет открываться)
|
|
||||||
base = "Win32GUI"
|
|
||||||
# Добавляем расширение .exe для Windows
|
|
||||||
target_name = f"{exe_name}.exe"
|
|
||||||
elif sys.platform == "darwin": # macOS
|
|
||||||
base = "MacOSX"
|
|
||||||
# Иконки для macOS имеют формат .icns
|
|
||||||
# icon_path = "icon.icns"
|
|
||||||
elif sys.platform.startswith("linux"): # Linux
|
|
||||||
# Для Linux обычно не требуется специальный 'base'
|
|
||||||
# Иконки могут быть в формате .png или .xpm
|
|
||||||
# icon_path = "icon.png"
|
|
||||||
pass # Оставляем base = None
|
|
||||||
|
|
||||||
# --- Опции сборки ---
|
|
||||||
|
|
||||||
# Общие опции сборки для всех платформ
|
|
||||||
build_exe_options = {
|
|
||||||
# 'packages' - список пакетов для обязательного включения.
|
|
||||||
"packages": ["os", "sys", "requests", "json", "webbrowser"],
|
|
||||||
|
|
||||||
# 'excludes' - список пакетов для исключения.
|
|
||||||
"excludes": ["tkinter", "unittest", "PyQt5.QtWebEngineWidgets"],
|
|
||||||
|
|
||||||
# 'include_files' - список дополнительных файлов или папок.
|
|
||||||
# Формат: [('источник', 'назначение_в_сборке')]
|
|
||||||
"include_files": [], # Например: ["resources/", "config.ini"]
|
|
||||||
|
|
||||||
# 'build_exe' - папка для выходных файлов
|
|
||||||
"build_exe": f"build_{sys.platform}", # Создаём отдельную папку для каждой ОС
|
|
||||||
}
|
|
||||||
|
|
||||||
# Опции, специфичные для Windows
|
|
||||||
if sys.platform == "win32":
|
|
||||||
build_exe_options["include_msvcr"] = True # Включаем C++ Runtime Library
|
|
||||||
|
|
||||||
# --- Определение исполняемого файла ---
|
|
||||||
|
|
||||||
executables = [
|
|
||||||
Executable(
|
|
||||||
script=main_script,
|
|
||||||
base=base,
|
|
||||||
target_name=target_name, # Имя конечного файла
|
|
||||||
icon=icon_path # Путь к файлу иконки
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
# --- Настройка метаданных и запуск сборки ---
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name=exe_name,
|
|
||||||
version="1.2",
|
|
||||||
description="Управление чатами для HR-менеджеров",
|
|
||||||
options={
|
|
||||||
"build_exe": build_exe_options
|
|
||||||
},
|
|
||||||
executables=executables
|
|
||||||
)
|
|
||||||
Reference in New Issue
Block a user