feat: Добавлены массовые операции и разделение чатов на вкладки
- Добавлена кнопка "Список" для одновременной обработки нескольких пользователей.
- Реализовано разделение чатов на вкладки "Офис", "Розница" и "Прочие".
fix: Улучшен интерфейс и исправлены ошибки
- Область списка чатов теперь корректно увеличивается после авторизации.
- В окне подтверждения отображаются имена всех пользователей.
- Исправлены грамматика и склонения в диалоговых окнах.
- Кнопки в диалогах заменены на русскоязычные ("Да"/"Нет", "ОК"/"Отмена").
This commit is contained in:
786
main.py
786
main.py
@@ -5,10 +5,11 @@ import time
|
|||||||
import os
|
import os
|
||||||
from PySide6.QtWidgets import (QApplication, QMainWindow, QLabel, QLineEdit,
|
from PySide6.QtWidgets import (QApplication, QMainWindow, QLabel, QLineEdit,
|
||||||
QPushButton, QVBoxLayout, QWidget, QMessageBox,
|
QPushButton, QVBoxLayout, QWidget, QMessageBox,
|
||||||
QTextBrowser, QScrollArea, QCheckBox, QHBoxLayout, QSizePolicy, QDialog)
|
QTextBrowser, QScrollArea, QCheckBox, QHBoxLayout,
|
||||||
|
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.QtWebEngineWidgets import QWebEngineView
|
from PySide6.QtWebEngineWidgets import QWebEngineView
|
||||||
from PySide6.QtWebEngineCore import QWebEnginePage, QWebEngineProfile # Добавлен QWebEngineProfile
|
from PySide6.QtWebEngineCore import QWebEnginePage, QWebEngineProfile
|
||||||
from urllib.parse import urlparse, parse_qs, unquote
|
from urllib.parse import urlparse, parse_qs, unquote
|
||||||
from vk_api.exceptions import VkApiError
|
from vk_api.exceptions import VkApiError
|
||||||
from PySide6.QtCore import QStandardPaths
|
from PySide6.QtCore import QStandardPaths
|
||||||
@@ -16,7 +17,6 @@ from PySide6.QtCore import QStandardPaths
|
|||||||
# --- Управление токенами и настройками ---
|
# --- Управление токенами и настройками ---
|
||||||
APP_DATA_DIR = os.path.join(QStandardPaths.writableLocation(QStandardPaths.AppDataLocation), "AnabasisVKChatManager")
|
APP_DATA_DIR = os.path.join(QStandardPaths.writableLocation(QStandardPaths.AppDataLocation), "AnabasisVKChatManager")
|
||||||
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")
|
||||||
|
|
||||||
|
|
||||||
@@ -205,12 +205,28 @@ class AuthBrowserWindow(QDialog):
|
|||||||
super().closeEvent(event)
|
super().closeEvent(event)
|
||||||
|
|
||||||
|
|
||||||
class VkChatManager(QMainWindow):
|
class MultiLinkDialog(QDialog):
|
||||||
"""
|
def __init__(self, parent=None):
|
||||||
Главное окно приложения VK Chat Manager.
|
super().__init__(parent)
|
||||||
Позволяет авторизоваться через VK и удалять/добавлять пользователей в чаты.
|
self.setWindowTitle("Ввод нескольких ссылок")
|
||||||
"""
|
self.setMinimumSize(400, 300)
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
label = QLabel("Вставьте ссылки на страницы VK, каждая с новой строки:")
|
||||||
|
layout.addWidget(label)
|
||||||
|
self.links_text_edit = QTextEdit()
|
||||||
|
layout.addWidget(self.links_text_edit)
|
||||||
|
button_box = QDialogButtonBox()
|
||||||
|
button_box.addButton("ОК", QDialogButtonBox.AcceptRole)
|
||||||
|
button_box.addButton("Отмена", QDialogButtonBox.RejectRole)
|
||||||
|
button_box.accepted.connect(self.accept)
|
||||||
|
button_box.rejected.connect(self.reject)
|
||||||
|
layout.addWidget(button_box)
|
||||||
|
|
||||||
|
def get_links(self):
|
||||||
|
return [line.strip() for line in self.links_text_edit.toPlainText().strip().split('\n') if line.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
class VkChatManager(QMainWindow):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.setWindowTitle("Anabasis Chat Manager")
|
self.setWindowTitle("Anabasis Chat Manager")
|
||||||
@@ -219,35 +235,39 @@ class VkChatManager(QMainWindow):
|
|||||||
self.token = None
|
self.token = None
|
||||||
self.token_expiration_time = None
|
self.token_expiration_time = None
|
||||||
self.chats = []
|
self.chats = []
|
||||||
self.chat_checkboxes = []
|
self.office_chat_checkboxes = []
|
||||||
|
self.retail_chat_checkboxes = []
|
||||||
|
self.other_chat_checkboxes = []
|
||||||
self.vk_session = None
|
self.vk_session = None
|
||||||
self.vk = None
|
self.vk = None
|
||||||
self.user_id_to_process = None # Новое поле для хранения ID пользователя из ссылки
|
self.user_ids_to_process = []
|
||||||
|
|
||||||
|
self.resolve_timer = QTimer(self)
|
||||||
|
self.resolve_timer.setSingleShot(True)
|
||||||
|
self.resolve_timer.setInterval(750)
|
||||||
|
self.resolve_timer.timeout.connect(self.resolve_single_user_id_from_input)
|
||||||
|
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
self.load_saved_token_on_startup()
|
self.load_saved_token_on_startup()
|
||||||
self.setup_token_timer()
|
self.setup_token_timer()
|
||||||
|
|
||||||
def init_ui(self):
|
def init_ui(self):
|
||||||
"""Инициализирует пользовательский интерфейс приложения."""
|
|
||||||
central_widget = QWidget()
|
central_widget = QWidget()
|
||||||
self.setCentralWidget(central_widget)
|
self.setCentralWidget(central_widget)
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout(central_widget)
|
||||||
layout.setContentsMargins(10, 10, 10, 10)
|
layout.setContentsMargins(10, 10, 10, 10)
|
||||||
layout.setSpacing(5)
|
layout.setSpacing(5)
|
||||||
|
|
||||||
self.instructions = QTextBrowser()
|
self.instructions = QTextBrowser()
|
||||||
self.instructions.setPlainText(
|
self.instructions.setPlainText(
|
||||||
"Инструкция:\n"
|
"Инструкция:\n"
|
||||||
"1. Нажмите 'Авторизоваться через VK'\n"
|
"1. Авторизуйтесь через VK.\n"
|
||||||
"2. В открывшемся окне войдите в свой аккаунт VK\n"
|
"2. Выберите чаты.\n"
|
||||||
"3. Разрешите доступ приложению\n"
|
"3. Вставьте ссылку на пользователя в поле ниже. ID определится автоматически.\n"
|
||||||
"4. Токен автоматически сохранится на 1 час\n"
|
"4. Для массовых операций, нажмите кнопку 'Список' и вставьте ссылки в окне.\n"
|
||||||
"5. Выберите один или несколько чатов, установив галочки\n"
|
"5. Нажмите 'ИСКЛЮЧИТЬ' или 'ПРИГЛАСИТЬ'."
|
||||||
"6. Введите или вставьте ссылку на страницу пользователя VK (например, vk.com/id123 или vk.com/durov). ID будет получен автоматически.\n"
|
|
||||||
"7. Нажмите соответствующую кнопку 'ИСКЛЮЧИТЬ ПОЛЬЗОВАТЕЛЯ' или 'ПРИГЛАСИТЬ ПОЛЬЗОВАТЕЛЯ'."
|
|
||||||
)
|
)
|
||||||
self.instructions.setFixedHeight(180)
|
self.instructions.setFixedHeight(120)
|
||||||
layout.addWidget(self.instructions)
|
layout.addWidget(self.instructions)
|
||||||
|
|
||||||
layout.addWidget(QLabel("Access Token VK:"))
|
layout.addWidget(QLabel("Access Token VK:"))
|
||||||
@@ -258,547 +278,397 @@ class VkChatManager(QMainWindow):
|
|||||||
|
|
||||||
self.token_timer_label = QLabel("Срок действия токена: Н/Д")
|
self.token_timer_label = QLabel("Срок действия токена: Н/Д")
|
||||||
self.token_timer_label.setAlignment(Qt.AlignRight)
|
self.token_timer_label.setAlignment(Qt.AlignRight)
|
||||||
self.token_timer_label.setStyleSheet("font-weight: bold; color: #555;")
|
|
||||||
layout.addWidget(self.token_timer_label)
|
layout.addWidget(self.token_timer_label)
|
||||||
|
|
||||||
self.auth_btn = QPushButton("Авторизоваться через VK")
|
self.auth_btn = QPushButton("Авторизоваться через VK")
|
||||||
self.auth_btn.clicked.connect(self.start_auth)
|
self.auth_btn.clicked.connect(self.start_auth)
|
||||||
layout.addWidget(self.auth_btn)
|
layout.addWidget(self.auth_btn)
|
||||||
|
|
||||||
|
self.chat_tabs = QTabWidget()
|
||||||
|
self.chat_tabs.hide()
|
||||||
|
self.office_tab = self.create_chat_tab()
|
||||||
|
self.chat_tabs.addTab(self.office_tab, "AG Офис")
|
||||||
|
self.retail_tab = self.create_chat_tab()
|
||||||
|
self.chat_tabs.addTab(self.retail_tab, "AG Розница")
|
||||||
|
self.other_tab = self.create_chat_tab()
|
||||||
|
self.chat_tabs.addTab(self.other_tab, "Прочие")
|
||||||
layout.addWidget(QLabel("Выберите чаты:"))
|
layout.addWidget(QLabel("Выберите чаты:"))
|
||||||
|
select_buttons_layout = QHBoxLayout()
|
||||||
self.chat_checkbox_layout = QVBoxLayout()
|
self.select_all_btn = QPushButton("Выбрать все на вкладке")
|
||||||
self.chat_checkbox_layout.setSpacing(2)
|
self.select_all_btn.clicked.connect(lambda: self.set_all_checkboxes_on_current_tab(True))
|
||||||
self.chat_checkbox_widget = QWidget()
|
self.deselect_all_btn = QPushButton("Снять выбор на вкладке")
|
||||||
self.chat_checkbox_widget.setLayout(self.chat_checkbox_layout)
|
self.deselect_all_btn.clicked.connect(lambda: self.set_all_checkboxes_on_current_tab(False))
|
||||||
|
|
||||||
self.chat_scroll_area = QScrollArea()
|
|
||||||
self.chat_scroll_area.setWidgetResizable(True)
|
|
||||||
self.chat_scroll_area.setWidget(self.chat_checkbox_widget)
|
|
||||||
self.chat_scroll_area.setFixedHeight(240)
|
|
||||||
self.chat_scroll_area.hide()
|
|
||||||
|
|
||||||
self.select_all_btn = QPushButton("Выбрать все")
|
|
||||||
self.select_all_btn.clicked.connect(lambda: self.set_all_checkboxes(True))
|
|
||||||
self.select_all_btn.setEnabled(False)
|
|
||||||
|
|
||||||
self.deselect_all_btn = QPushButton("Снять выбор со всех")
|
|
||||||
self.deselect_all_btn.clicked.connect(lambda: self.set_all_checkboxes(False))
|
|
||||||
self.deselect_all_btn.setEnabled(False)
|
|
||||||
|
|
||||||
self.refresh_chats_btn = QPushButton("Обновить чаты")
|
self.refresh_chats_btn = QPushButton("Обновить чаты")
|
||||||
self.refresh_chats_btn.clicked.connect(self.load_chats)
|
self.refresh_chats_btn.clicked.connect(self.load_chats)
|
||||||
self.refresh_chats_btn.setEnabled(False)
|
|
||||||
|
|
||||||
select_buttons_layout = QHBoxLayout()
|
|
||||||
select_buttons_layout.addWidget(self.select_all_btn)
|
select_buttons_layout.addWidget(self.select_all_btn)
|
||||||
select_buttons_layout.addWidget(self.deselect_all_btn)
|
select_buttons_layout.addWidget(self.deselect_all_btn)
|
||||||
select_buttons_layout.addWidget(self.refresh_chats_btn)
|
select_buttons_layout.addWidget(self.refresh_chats_btn)
|
||||||
layout.addLayout(select_buttons_layout)
|
layout.addLayout(select_buttons_layout)
|
||||||
layout.addWidget(self.chat_scroll_area)
|
layout.addWidget(self.chat_tabs)
|
||||||
|
|
||||||
layout.addWidget(QLabel("Введите или вставьте ссылку на страницу VK (ID будет получен автоматически):"))
|
layout.addWidget(QLabel("Ссылка на страницу VK (ID определится автоматически):"))
|
||||||
|
link_input_layout = QHBoxLayout()
|
||||||
self.vk_url_input = QLineEdit()
|
self.vk_url_input = QLineEdit()
|
||||||
self.vk_url_input.setPlaceholderText("Например: vk.com/id123 или vk.com/durov")
|
self.vk_url_input.setPlaceholderText("https://vk.com/id1")
|
||||||
self.vk_url_input.textChanged.connect(self.on_vk_url_input_changed) # Подключаем сигнал
|
self.vk_url_input.textChanged.connect(self.on_vk_url_input_changed)
|
||||||
layout.addWidget(self.vk_url_input)
|
link_input_layout.addWidget(self.vk_url_input)
|
||||||
|
|
||||||
# Кнопки для исключения/приглашения пользователя
|
self.multi_link_btn = QPushButton("Список")
|
||||||
self.remove_user_btn = QPushButton("ИСКЛЮЧИТЬ ПОЛЬЗОВАТЕЛЯ")
|
self.multi_link_btn.setToolTip("Ввести несколько ссылок списком")
|
||||||
self.remove_user_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
self.multi_link_btn.clicked.connect(self.open_multi_link_dialog)
|
||||||
self.remove_user_btn.setMinimumHeight(60)
|
link_input_layout.addWidget(self.multi_link_btn)
|
||||||
|
layout.addLayout(link_input_layout)
|
||||||
|
|
||||||
|
self.remove_user_btn = QPushButton("ИСКЛЮЧИТЬ ПОЛЬЗОВАТЕЛЕЙ")
|
||||||
|
self.remove_user_btn.setMinimumHeight(50)
|
||||||
self.remove_user_btn.clicked.connect(self.remove_user)
|
self.remove_user_btn.clicked.connect(self.remove_user)
|
||||||
self.remove_user_btn.setEnabled(False) # Изначально отключена
|
|
||||||
layout.addWidget(self.remove_user_btn)
|
layout.addWidget(self.remove_user_btn)
|
||||||
|
|
||||||
self.visible_messages_checkbox = QCheckBox("Показать 250 последних сообщений при добавлении")
|
self.visible_messages_checkbox = QCheckBox("Показать 250 последних сообщений при добавлении")
|
||||||
self.visible_messages_checkbox.setChecked(False)
|
|
||||||
layout.addWidget(self.visible_messages_checkbox)
|
layout.addWidget(self.visible_messages_checkbox)
|
||||||
|
self.add_user_btn = QPushButton("ПРИГЛАСИТЬ ПОЛЬЗОВАТЕЛЕЙ")
|
||||||
self.add_user_btn = QPushButton("ПРИГЛАСИТЬ ПОЛЬЗОВАТЕЛЯ")
|
self.add_user_btn.setMinimumHeight(50)
|
||||||
self.add_user_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
||||||
self.add_user_btn.setMinimumHeight(60)
|
|
||||||
self.add_user_btn.clicked.connect(self.add_user_to_chat)
|
self.add_user_btn.clicked.connect(self.add_user_to_chat)
|
||||||
self.add_user_btn.setEnabled(False) # Изначально отключена
|
|
||||||
layout.addWidget(self.add_user_btn)
|
layout.addWidget(self.add_user_btn)
|
||||||
|
|
||||||
self.status_label = QLabel("Статус: не авторизован")
|
self.status_label = QLabel("Статус: не авторизован")
|
||||||
self.status_label.setAlignment(Qt.AlignCenter)
|
self.status_label.setAlignment(Qt.AlignCenter)
|
||||||
layout.addWidget(self.status_label)
|
layout.addWidget(self.status_label)
|
||||||
|
|
||||||
layout.addStretch(1)
|
layout.addStretch(1)
|
||||||
|
|
||||||
central_widget.setLayout(layout)
|
self.set_ui_state(False)
|
||||||
|
|
||||||
|
def on_vk_url_input_changed(self, text):
|
||||||
|
if self.vk_url_input.hasFocus():
|
||||||
|
self.resolve_timer.start()
|
||||||
|
|
||||||
|
def open_multi_link_dialog(self):
|
||||||
|
dialog = MultiLinkDialog(self)
|
||||||
|
if dialog.exec():
|
||||||
|
links = dialog.get_links()
|
||||||
|
if links:
|
||||||
|
self.vk_url_input.clear()
|
||||||
|
self._process_links_list(links)
|
||||||
|
else:
|
||||||
|
QMessageBox.information(self, "Информация", "Список ссылок пуст.")
|
||||||
|
|
||||||
|
def resolve_single_user_id_from_input(self):
|
||||||
|
url = self.vk_url_input.text().strip()
|
||||||
|
if not url:
|
||||||
|
self.user_ids_to_process.clear()
|
||||||
|
self.status_label.setText("Статус: Введите ссылку или откройте список.")
|
||||||
|
self.set_ui_state(self.token is not None)
|
||||||
|
return
|
||||||
|
self._process_links_list([url])
|
||||||
|
|
||||||
|
def _process_links_list(self, links_list):
|
||||||
|
if not self.vk:
|
||||||
|
QMessageBox.warning(self, "Ошибка", "Сначала авторизуйтесь.")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.user_ids_to_process.clear()
|
||||||
|
resolved_ids = []
|
||||||
|
failed_links = []
|
||||||
|
|
||||||
|
self.status_label.setText("Статус: Определяю ID...")
|
||||||
|
QApplication.processEvents()
|
||||||
|
|
||||||
|
for link in links_list:
|
||||||
|
try:
|
||||||
|
path = urlparse(link).path
|
||||||
|
screen_name = path.split('/')[-1] if path else ''
|
||||||
|
if not screen_name and len(path.split('/')) > 1:
|
||||||
|
screen_name = path.split('/')[-2]
|
||||||
|
|
||||||
|
if not screen_name:
|
||||||
|
failed_links.append(link)
|
||||||
|
continue
|
||||||
|
|
||||||
|
resolved_object = self.vk.utils.resolveScreenName(screen_name=screen_name)
|
||||||
|
if resolved_object and resolved_object.get('type') == 'user':
|
||||||
|
resolved_ids.append(resolved_object['object_id'])
|
||||||
|
else:
|
||||||
|
failed_links.append(link)
|
||||||
|
except Exception:
|
||||||
|
failed_links.append(link)
|
||||||
|
|
||||||
|
self.user_ids_to_process = resolved_ids
|
||||||
|
|
||||||
|
status_message = f"Статус: Готово к работе с {len(resolved_ids)} пользовател(ем/ями)."
|
||||||
|
if len(links_list) > 1:
|
||||||
|
self.vk_url_input.setText(f"Загружено {len(resolved_ids)}/{len(links_list)} из списка")
|
||||||
|
|
||||||
|
if failed_links:
|
||||||
|
QMessageBox.warning(self, "Ошибка получения ID",
|
||||||
|
f"Не удалось получить ID для следующих ссылок:\n" + "\n".join(failed_links))
|
||||||
|
|
||||||
|
self.status_label.setText(status_message)
|
||||||
|
self.set_ui_state(self.token is not None)
|
||||||
|
|
||||||
|
def create_chat_tab(self):
|
||||||
|
# This implementation correctly creates a scrollable area for chat lists.
|
||||||
|
tab_content_widget = QWidget()
|
||||||
|
tab_layout = QVBoxLayout(tab_content_widget)
|
||||||
|
tab_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
tab_layout.setSpacing(0)
|
||||||
|
|
||||||
|
scroll_area = QScrollArea()
|
||||||
|
scroll_area.setWidgetResizable(True)
|
||||||
|
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||||
|
scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||||
|
|
||||||
|
tab_layout.addWidget(scroll_area)
|
||||||
|
|
||||||
|
checkbox_container_widget = QWidget()
|
||||||
|
checkbox_layout = QVBoxLayout(checkbox_container_widget)
|
||||||
|
checkbox_layout.setContentsMargins(5, 5, 5, 5)
|
||||||
|
checkbox_layout.setSpacing(2)
|
||||||
|
checkbox_layout.addStretch()
|
||||||
|
|
||||||
|
scroll_area.setWidget(checkbox_container_widget)
|
||||||
|
|
||||||
|
return tab_content_widget
|
||||||
|
|
||||||
def setup_token_timer(self):
|
def setup_token_timer(self):
|
||||||
"""Настраивает QTimer для обновления отображения обратного отсчета токена."""
|
|
||||||
self.token_countdown_timer = QTimer(self)
|
self.token_countdown_timer = QTimer(self)
|
||||||
self.token_countdown_timer.timeout.connect(self.update_token_timer_display)
|
self.token_countdown_timer.timeout.connect(self.update_token_timer_display)
|
||||||
self.token_countdown_timer.start(1000)
|
self.token_countdown_timer.start(1000)
|
||||||
|
|
||||||
def update_token_timer_display(self):
|
def update_token_timer_display(self):
|
||||||
"""
|
|
||||||
Обновляет QLabel с оставшимся временем до истечения срока действия токена.
|
|
||||||
"""
|
|
||||||
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
|
||||||
|
|
||||||
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 and self.token_countdown_timer.isActive():
|
if self.token_countdown_timer.isActive(): self.token_countdown_timer.stop()
|
||||||
self.token_countdown_timer.stop()
|
|
||||||
self.auth_btn.setEnabled(True)
|
|
||||||
self.set_ui_state(False)
|
self.set_ui_state(False)
|
||||||
self.status_label.setText("Статус: Срок действия токена истек, пожалуйста, авторизуйтесь заново.")
|
self.status_label.setText("Статус: Срок действия токена истек, авторизуйтесь заново.")
|
||||||
self.token = None
|
self.token, self.token_expiration_time = None, None
|
||||||
self.token_expiration_time = 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}с")
|
||||||
time_str = ""
|
|
||||||
if hours > 0:
|
|
||||||
time_str += f"{hours:02d}ч "
|
|
||||||
if minutes > 0 or hours > 0:
|
|
||||||
time_str += f"{minutes:02d}м "
|
|
||||||
time_str += f"{seconds:02d}с"
|
|
||||||
|
|
||||||
self.token_timer_label.setText(f"Срок действия токена: {time_str}")
|
|
||||||
|
|
||||||
def set_ui_state(self, authorized):
|
def set_ui_state(self, authorized):
|
||||||
"""Устанавливает состояние элементов пользовательского интерфейса в зависимости от статуса авторизации."""
|
self.auth_btn.setEnabled(not authorized)
|
||||||
self.select_all_btn.setEnabled(authorized)
|
for btn in [self.select_all_btn, self.deselect_all_btn, self.refresh_chats_btn,
|
||||||
self.deselect_all_btn.setEnabled(authorized)
|
self.vk_url_input, self.multi_link_btn,
|
||||||
self.refresh_chats_btn.setEnabled(authorized)
|
self.visible_messages_checkbox]:
|
||||||
self.vk_url_input.setEnabled(authorized)
|
btn.setEnabled(authorized)
|
||||||
# Состояние кнопок теперь зависит от наличия user_id_to_process
|
|
||||||
self.remove_user_btn.setEnabled(authorized and self.user_id_to_process is not None)
|
|
||||||
self.visible_messages_checkbox.setEnabled(authorized)
|
|
||||||
self.add_user_btn.setEnabled(authorized and self.user_id_to_process is not None)
|
|
||||||
|
|
||||||
|
has_ids = authorized and bool(self.user_ids_to_process)
|
||||||
|
self.remove_user_btn.setEnabled(has_ids)
|
||||||
|
self.add_user_btn.setEnabled(has_ids)
|
||||||
|
|
||||||
|
self.chat_tabs.setVisible(authorized)
|
||||||
if authorized:
|
if authorized:
|
||||||
self.chat_scroll_area.show()
|
# Когда авторизованы, задаем минимальную высоту, достаточную для ~10-12 чатов
|
||||||
|
self.chat_tabs.setMinimumHeight(300)
|
||||||
else:
|
else:
|
||||||
self.chat_scroll_area.hide()
|
# Когда не авторизованы, сбрасываем минимальную высоту
|
||||||
|
self.chat_tabs.setMinimumHeight(0)
|
||||||
|
|
||||||
|
if not authorized:
|
||||||
|
self.user_ids_to_process.clear()
|
||||||
|
self.vk_url_input.clear()
|
||||||
|
|
||||||
def load_saved_token_on_startup(self):
|
def load_saved_token_on_startup(self):
|
||||||
"""Пытается загрузить сохраненный токен при запуске приложения."""
|
|
||||||
loaded_token, expiration_time = load_token()
|
loaded_token, expiration_time = load_token()
|
||||||
if loaded_token:
|
if loaded_token:
|
||||||
self.token = loaded_token
|
self.handle_auth_token_on_load(loaded_token, expiration_time)
|
||||||
self.token_expiration_time = expiration_time
|
|
||||||
self.token_input.setText(self.token[:50] + "...")
|
|
||||||
self.status_label.setText("Статус: авторизован (токен загружен из файла)")
|
|
||||||
self.auth_btn.setEnabled(False)
|
|
||||||
self.set_ui_state(True)
|
|
||||||
self.update_token_timer_display()
|
|
||||||
|
|
||||||
self.vk_session = vk_api.VkApi(token=self.token)
|
|
||||||
self.vk = self.vk_session.get_api()
|
|
||||||
self.load_chats()
|
|
||||||
else:
|
else:
|
||||||
self.status_label.setText("Статус: не авторизован (токен не найден или просрочен)")
|
|
||||||
self.set_ui_state(False)
|
self.set_ui_state(False)
|
||||||
self.update_token_timer_display()
|
|
||||||
|
|
||||||
def set_all_checkboxes(self, checked):
|
def set_all_checkboxes_on_current_tab(self, checked):
|
||||||
"""
|
current_index = self.chat_tabs.currentIndex()
|
||||||
Устанавливает состояние (выбран/не выбран) для всех чекбоксов чатов.
|
checkbox_lists = [self.office_chat_checkboxes, self.retail_chat_checkboxes, self.other_chat_checkboxes]
|
||||||
"""
|
if 0 <= current_index < len(checkbox_lists):
|
||||||
for checkbox in self.chat_checkboxes:
|
for checkbox in checkbox_lists[current_index]:
|
||||||
checkbox.setChecked(checked)
|
checkbox.setChecked(checked)
|
||||||
|
|
||||||
def start_auth(self):
|
def start_auth(self):
|
||||||
"""
|
self.status_label.setText("Статус: ожидание авторизации...")
|
||||||
Запускает процесс OAuth авторизации VK в новом окне.
|
|
||||||
"""
|
|
||||||
self.status_label.setText("Статус: ожидание авторизации в новом окне...")
|
|
||||||
auth_window = AuthBrowserWindow(self)
|
auth_window = AuthBrowserWindow(self)
|
||||||
auth_window.token_extracted_signal.connect(self.handle_auth_token)
|
auth_window.token_extracted_signal.connect(self.handle_new_auth_token)
|
||||||
auth_window.start_auth_flow()
|
auth_window.start_auth_flow()
|
||||||
auth_window.exec() # Изменено с exec_() на exec()
|
auth_window.exec()
|
||||||
|
|
||||||
|
def handle_new_auth_token(self, token, expires_in):
|
||||||
|
if not token:
|
||||||
|
self.status_label.setText("Статус: Авторизация не удалась")
|
||||||
|
self.set_ui_state(False)
|
||||||
|
return
|
||||||
|
|
||||||
def handle_auth_token(self, token, expires_in):
|
|
||||||
"""
|
|
||||||
Обрабатывает полученный токен авторизации после закрытия окна браузера.
|
|
||||||
"""
|
|
||||||
if token:
|
|
||||||
self.token = token
|
self.token = token
|
||||||
self.token_expiration_time = time.time() + expires_in
|
self.token_expiration_time = time.time() + expires_in
|
||||||
save_token(self.token, expires_in)
|
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.auth_btn.setEnabled(False)
|
|
||||||
self.set_ui_state(True)
|
|
||||||
self.update_token_timer_display()
|
|
||||||
|
|
||||||
self.vk_session = vk_api.VkApi(token=self.token)
|
self.vk_session = vk_api.VkApi(token=self.token)
|
||||||
self.vk = self.vk_session.get_api()
|
self.vk = self.vk_session.get_api()
|
||||||
|
self.set_ui_state(True)
|
||||||
self.load_chats()
|
self.load_chats()
|
||||||
else:
|
|
||||||
QMessageBox.warning(self, "Ошибка", "Не удалось получить токен. Попробуйте еще раз.")
|
def handle_auth_token_on_load(self, token, expiration_time):
|
||||||
self.status_label.setText("Статус: Авторизация не удалась")
|
self.token = token
|
||||||
self.set_ui_state(False)
|
self.token_expiration_time = expiration_time
|
||||||
self.update_token_timer_display()
|
|
||||||
|
self.token_input.setText(self.token[:50] + "...")
|
||||||
|
self.status_label.setText("Статус: авторизован (токен загружен)")
|
||||||
|
self.vk_session = vk_api.VkApi(token=self.token)
|
||||||
|
self.vk = self.vk_session.get_api()
|
||||||
|
self.set_ui_state(True)
|
||||||
|
self.load_chats()
|
||||||
|
|
||||||
|
def _clear_chat_tabs(self):
|
||||||
|
self.chats.clear()
|
||||||
|
for chk_list in [self.office_chat_checkboxes, self.retail_chat_checkboxes, self.other_chat_checkboxes]:
|
||||||
|
for checkbox in chk_list:
|
||||||
|
checkbox.setParent(None)
|
||||||
|
checkbox.deleteLater()
|
||||||
|
chk_list.clear()
|
||||||
|
|
||||||
def load_chats(self):
|
def load_chats(self):
|
||||||
"""
|
self._clear_chat_tabs()
|
||||||
Загружает список чатов пользователя из VK API и заполняет их чекбоксами.
|
|
||||||
"""
|
# Get the checkbox layouts from each tab
|
||||||
for i in reversed(range(self.chat_checkbox_layout.count())):
|
layouts = [
|
||||||
widget_to_remove = self.chat_checkbox_layout.itemAt(i).widget()
|
self.office_tab.findChild(QWidget).findChild(QVBoxLayout),
|
||||||
if widget_to_remove:
|
self.retail_tab.findChild(QWidget).findChild(QVBoxLayout),
|
||||||
widget_to_remove.setParent(None)
|
self.other_tab.findChild(QWidget).findChild(QVBoxLayout)
|
||||||
widget_to_remove.deleteLater()
|
]
|
||||||
self.chat_checkboxes.clear()
|
|
||||||
self.chats.clear()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conversations = self.vk.messages.getConversations(count=200)['items']
|
conversations = self.vk.messages.getConversations(count=200, filter="all")['items']
|
||||||
|
|
||||||
for conv in conversations:
|
for conv in conversations:
|
||||||
if conv['conversation']['peer']['type'] == 'chat':
|
if conv['conversation']['peer']['type'] == 'chat':
|
||||||
chat_id = conv['conversation']['peer']['local_id']
|
chat_id = conv['conversation']['peer']['local_id']
|
||||||
title = conv['conversation']['chat_settings']['title']
|
title = conv['conversation']['chat_settings']['title']
|
||||||
chat_data = {'id': chat_id, 'title': title}
|
self.chats.append({'id': chat_id, 'title': title})
|
||||||
self.chats.append(chat_data)
|
|
||||||
|
|
||||||
checkbox = QCheckBox(f"{title} (id: {chat_id})")
|
checkbox = QCheckBox(f"{title} (id: {chat_id})")
|
||||||
checkbox.setChecked(False)
|
|
||||||
checkbox.setProperty("chat_id", chat_id)
|
checkbox.setProperty("chat_id", chat_id)
|
||||||
self.chat_checkbox_layout.addWidget(checkbox)
|
|
||||||
self.chat_checkboxes.append(checkbox)
|
|
||||||
|
|
||||||
if not self.chats:
|
# Insert checkbox at the top of the layout (before the stretch)
|
||||||
QMessageBox.information(self, "Информация", "У вас нет доступных чатов.")
|
if "AG офис" in title:
|
||||||
self.chat_scroll_area.hide()
|
layouts[0].insertWidget(layouts[0].count() - 1, checkbox)
|
||||||
self.select_all_btn.setEnabled(False)
|
self.office_chat_checkboxes.append(checkbox)
|
||||||
self.deselect_all_btn.setEnabled(False)
|
elif "AG розница" in title:
|
||||||
self.refresh_chats_btn.setEnabled(False)
|
layouts[1].insertWidget(layouts[1].count() - 1, checkbox)
|
||||||
|
self.retail_chat_checkboxes.append(checkbox)
|
||||||
else:
|
else:
|
||||||
self.chat_scroll_area.show()
|
layouts[2].insertWidget(layouts[2].count() - 1, checkbox)
|
||||||
self.select_all_btn.setEnabled(True)
|
self.other_chat_checkboxes.append(checkbox)
|
||||||
self.deselect_all_btn.setEnabled(True)
|
|
||||||
self.refresh_chats_btn.setEnabled(True)
|
|
||||||
|
|
||||||
|
self.chat_tabs.setTabText(0, f"AG Офис ({len(self.office_chat_checkboxes)})")
|
||||||
|
self.chat_tabs.setTabText(1, f"AG Розница ({len(self.retail_chat_checkboxes)})")
|
||||||
|
self.chat_tabs.setTabText(2, f"Прочие ({len(self.other_chat_checkboxes)})")
|
||||||
except VkApiError as e:
|
except VkApiError as e:
|
||||||
error_message = str(e)
|
QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить чаты: {e}")
|
||||||
if "[5] User authorization failed: access_token was given to another ip address" in error_message:
|
|
||||||
QMessageBox.critical(self, "Ошибка авторизации VK",
|
|
||||||
"Ваш IP-адрес изменился, и токен стал недействительным. "
|
|
||||||
"Пожалуйста, авторизуйтесь заново.")
|
|
||||||
self.token = None
|
|
||||||
self.token_expiration_time = None
|
|
||||||
self.token_input.clear()
|
|
||||||
self.auth_btn.setEnabled(True)
|
|
||||||
self.set_ui_state(False)
|
self.set_ui_state(False)
|
||||||
self.status_label.setText("Статус: требуется повторная авторизация (IP изменен)")
|
|
||||||
if self.token_countdown_timer and self.token_countdown_timer.isActive():
|
|
||||||
self.token_countdown_timer.stop()
|
|
||||||
if os.path.exists(TOKEN_FILE):
|
|
||||||
os.remove(TOKEN_FILE)
|
|
||||||
else:
|
|
||||||
QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить чаты: {e}\n"
|
|
||||||
"Убедитесь, что у приложения есть необходимые права доступа.")
|
|
||||||
self.chat_scroll_area.hide()
|
|
||||||
self.select_all_btn.setEnabled(False)
|
|
||||||
self.deselect_all_btn.setEnabled(False)
|
|
||||||
self.refresh_chats_btn.setEnabled(False)
|
|
||||||
|
|
||||||
def on_vk_url_input_changed(self, text):
|
|
||||||
"""
|
|
||||||
Слот, вызываемый при изменении текста в поле vk_url_input.
|
|
||||||
Автоматически пытается получить ID пользователя и сохраняет его в self.user_id_to_process.
|
|
||||||
"""
|
|
||||||
if not self.vk or not text:
|
|
||||||
self.user_id_to_process = None
|
|
||||||
self.set_ui_state(self.token is not None)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.resolve_user_id_from_url(text)
|
|
||||||
|
|
||||||
def get_user_info_by_id(self, user_id):
|
def get_user_info_by_id(self, user_id):
|
||||||
"""
|
|
||||||
Получает имя и фамилию пользователя по его ID.
|
|
||||||
Возвращает строку "Имя Фамилия" или "Неизвестный пользователь".
|
|
||||||
"""
|
|
||||||
if not self.vk:
|
|
||||||
return "Неизвестный пользователь"
|
|
||||||
try:
|
try:
|
||||||
users = self.vk.users.get(user_ids=user_id)
|
user = self.vk.users.get(user_ids=user_id)[0]
|
||||||
if users and len(users) > 0:
|
|
||||||
user = users[0]
|
|
||||||
return f"{user.get('first_name', '')} {user.get('last_name', '')}"
|
return f"{user.get('first_name', '')} {user.get('last_name', '')}"
|
||||||
|
except Exception:
|
||||||
|
return f"Пользователь {user_id}"
|
||||||
|
|
||||||
|
def _get_selected_chats(self):
|
||||||
|
selected = []
|
||||||
|
for chk in self.office_chat_checkboxes + self.retail_chat_checkboxes + self.other_chat_checkboxes:
|
||||||
|
if chk.isChecked():
|
||||||
|
chat_id = chk.property("chat_id")
|
||||||
|
title = next((c['title'] for c in self.chats if c['id'] == chat_id), "")
|
||||||
|
selected.append({'id': chat_id, 'title': title})
|
||||||
|
return selected
|
||||||
|
|
||||||
|
def _execute_user_action(self, action_type):
|
||||||
|
if not self.user_ids_to_process:
|
||||||
|
QMessageBox.warning(self, "Ошибка", f"Нет ID пользователей для операции.")
|
||||||
|
return
|
||||||
|
selected_chats = self._get_selected_chats()
|
||||||
|
if not selected_chats:
|
||||||
|
QMessageBox.warning(self, "Ошибка", "Выберите хотя бы один чат.")
|
||||||
|
return
|
||||||
|
|
||||||
|
user_infos = {uid: self.get_user_info_by_id(uid) for uid in self.user_ids_to_process}
|
||||||
|
|
||||||
|
action_verb = "исключить" if action_type == "remove" else "пригласить"
|
||||||
|
preposition = "из" if action_type == "remove" else "в"
|
||||||
|
|
||||||
|
user_names_list = list(user_infos.values())
|
||||||
|
user_names_str = "\n".join([f"• {name}" for name in user_names_list])
|
||||||
|
|
||||||
|
chat_count = len(selected_chats)
|
||||||
|
chat_str = ""
|
||||||
|
|
||||||
|
# Финальная логика склонения с учетом падежа (для "из" и "в")
|
||||||
|
if chat_count % 10 == 1 and chat_count % 100 != 11:
|
||||||
|
if action_type == 'remove':
|
||||||
|
# из 1 выбранного чата (Родительный падеж, ед.ч.)
|
||||||
|
chat_str = f"{chat_count} выбранного чата"
|
||||||
else:
|
else:
|
||||||
return "Неизвестный пользователь"
|
# в 1 выбранный чат (Винительный падеж, ед.ч.)
|
||||||
except VkApiError as e:
|
chat_str = f"{chat_count} выбранный чат"
|
||||||
print(f"Ошибка получения информации о пользователе {user_id}: {e}")
|
elif 2 <= chat_count % 10 <= 4 and (chat_count % 100 < 10 or chat_count % 100 >= 20):
|
||||||
return "Неизвестный пользователь"
|
if action_type == 'remove':
|
||||||
except Exception as e:
|
# из 3 выбранных чатов (Родительный падеж, мн.ч.)
|
||||||
print(f"Неизвестная ошибка при получении информации о пользователе {user_id}: {e}")
|
chat_str = f"{chat_count} выбранных чатов"
|
||||||
return "Неизвестный пользователь"
|
else:
|
||||||
|
# в 3 выбранных чата (Родительный падеж, ед.ч.)
|
||||||
|
chat_str = f"{chat_count} выбранных чата"
|
||||||
|
else:
|
||||||
|
# из 5 выбранных чатов / в 5 выбранных чатов (Родительный падеж, мн.ч.)
|
||||||
|
chat_str = f"{chat_count} выбранных чатов"
|
||||||
|
|
||||||
def resolve_user_id_from_url(self, vk_url_to_resolve):
|
msg = (
|
||||||
"""
|
f"Вы уверены, что хотите {action_verb} следующих пользователей:\n\n"
|
||||||
Разрешает ID пользователя VK из введенной ссылки и сохраняет его в self.user_id_to_process.
|
f"{user_names_str}\n\n"
|
||||||
"""
|
f"{preposition} {chat_str}?"
|
||||||
if not self.vk:
|
)
|
||||||
self.user_id_to_process = None
|
|
||||||
self.set_ui_state(self.token is not None)
|
confirm_dialog = QMessageBox(self)
|
||||||
return
|
confirm_dialog.setWindowTitle("Подтверждение действия")
|
||||||
|
confirm_dialog.setText(msg)
|
||||||
vk_url = vk_url_to_resolve.strip()
|
confirm_dialog.setIcon(QMessageBox.Question)
|
||||||
if not vk_url:
|
yes_button = confirm_dialog.addButton("Да", QMessageBox.YesRole)
|
||||||
self.user_id_to_process = None
|
no_button = confirm_dialog.addButton("Нет", QMessageBox.NoRole)
|
||||||
self.set_ui_state(self.token is not None)
|
confirm_dialog.setDefaultButton(no_button) # "Нет" - кнопка по умолчанию
|
||||||
|
|
||||||
|
confirm_dialog.exec()
|
||||||
|
|
||||||
|
if confirm_dialog.clickedButton() != yes_button:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for chat in selected_chats:
|
||||||
|
for user_id, user_info in user_infos.items():
|
||||||
try:
|
try:
|
||||||
parsed_url = urlparse(vk_url)
|
if action_type == "remove":
|
||||||
path_parts = parsed_url.path.split('/')
|
self.vk.messages.removeChatUser(chat_id=chat['id'], member_id=user_id)
|
||||||
|
results.append(f"✓ '{user_info}' исключен из '{chat['title']}'.")
|
||||||
screen_name = ''
|
|
||||||
if path_parts:
|
|
||||||
screen_name = path_parts[-1]
|
|
||||||
if not screen_name and len(path_parts) > 1:
|
|
||||||
screen_name = path_parts[-2]
|
|
||||||
|
|
||||||
if not screen_name:
|
|
||||||
self.user_id_to_process = None
|
|
||||||
self.status_label.setText("Статус: Не удалось извлечь имя страницы из ссылки.")
|
|
||||||
self.set_ui_state(self.token is not None)
|
|
||||||
return
|
|
||||||
|
|
||||||
resolved_object = self.vk.utils.resolveScreenName(screen_name=screen_name)
|
|
||||||
|
|
||||||
if resolved_object and 'object_id' in resolved_object and resolved_object['type'] == 'user':
|
|
||||||
self.user_id_to_process = resolved_object['object_id']
|
|
||||||
self.status_label.setText(f"Статус: ID пользователя {self.user_id_to_process} успешно получен.")
|
|
||||||
elif resolved_object and 'type' in resolved_object and resolved_object['type'] != 'user':
|
|
||||||
self.user_id_to_process = None
|
|
||||||
self.status_label.setText(f"Статус: Ссылка ведет на {resolved_object['type']}, а не на пользователя.")
|
|
||||||
else:
|
else:
|
||||||
self.user_id_to_process = None
|
params = {'chat_id': chat['id'], 'user_id': user_id}
|
||||||
self.status_label.setText("Статус: Не удалось найти пользователя по этой ссылке.")
|
if self.visible_messages_checkbox.isChecked():
|
||||||
|
params['visible_messages_count'] = 250
|
||||||
|
self.vk.messages.addChatUser(**params)
|
||||||
|
results.append(f"✓ '{user_info}' приглашен в '{chat['title']}'.")
|
||||||
except VkApiError as e:
|
except VkApiError as e:
|
||||||
self.user_id_to_process = None
|
results.append(f"✗ Ошибка для '{user_info}' в '{chat['title']}': {e}")
|
||||||
error_message = str(e)
|
|
||||||
if "[5] User authorization failed: access_token was given to another ip address" in error_message:
|
QMessageBox.information(self, "Результаты", "\n".join(results))
|
||||||
self.status_label.setText("Статус: Требуется повторная авторизация (IP изменен).")
|
self.vk_url_input.clear()
|
||||||
QMessageBox.critical(self, "Ошибка авторизации VK",
|
self.user_ids_to_process.clear()
|
||||||
"Ваш IP-адрес изменился, и токен стал недействительным. "
|
self.set_ui_state(self.token is not None)
|
||||||
"Пожалуйста, авторизуйтесь заново.")
|
|
||||||
self.token = None
|
|
||||||
self.token_expiration_time = None
|
|
||||||
self.token_input.clear()
|
|
||||||
self.auth_btn.setEnabled(True)
|
|
||||||
self.set_ui_state(False)
|
|
||||||
if self.token_countdown_timer and self.token_countdown_timer.isActive():
|
|
||||||
self.token_countdown_timer.stop()
|
|
||||||
if os.path.exists(TOKEN_FILE):
|
|
||||||
os.remove(TOKEN_FILE)
|
|
||||||
else:
|
|
||||||
self.status_label.setText(f"Статус: Ошибка VK API при получении ID: {e}")
|
|
||||||
except Exception as e:
|
|
||||||
self.user_id_to_process = None
|
|
||||||
self.status_label.setText(f"Статус: Неизвестная ошибка при получении ID: {e}")
|
|
||||||
finally:
|
|
||||||
self.set_ui_state(self.token is not None) # Обновляем состояние кнопок после попытки разрешения ID
|
|
||||||
|
|
||||||
def remove_user(self):
|
def remove_user(self):
|
||||||
"""
|
self._execute_user_action("remove")
|
||||||
Исключает пользователя из всех выбранных чатов.
|
|
||||||
Перед исключением выводит окно подтверждения.
|
|
||||||
"""
|
|
||||||
if not self.token:
|
|
||||||
QMessageBox.warning(self, "Ошибка", "Сначала авторизуйтесь!")
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.user_id_to_process is None:
|
|
||||||
QMessageBox.warning(self, "Ошибка",
|
|
||||||
"ID пользователя не получен. Пожалуйста, введите корректную ссылку и убедитесь, что ID определился.")
|
|
||||||
return
|
|
||||||
|
|
||||||
user_id = self.user_id_to_process
|
|
||||||
user_info = self.get_user_info_by_id(user_id)
|
|
||||||
|
|
||||||
selected_chat_ids = []
|
|
||||||
selected_chat_titles = []
|
|
||||||
for checkbox in self.chat_checkboxes:
|
|
||||||
if checkbox.isChecked():
|
|
||||||
chat_id = checkbox.property("chat_id")
|
|
||||||
chat_title = next((chat['title'] for chat in self.chats if chat['id'] == chat_id),
|
|
||||||
f"Чат с ID: {chat_id}")
|
|
||||||
selected_chat_ids.append(chat_id)
|
|
||||||
selected_chat_titles.append(chat_title)
|
|
||||||
|
|
||||||
if not selected_chat_ids:
|
|
||||||
QMessageBox.warning(self, "Ошибка", "Выберите хотя бы один чат для исключения пользователя!")
|
|
||||||
return
|
|
||||||
|
|
||||||
confirmation_message = (
|
|
||||||
f"Вы точно хотите исключить пользователя '{user_info}' (ID: {user_id}) из следующих чатов?\n\n"
|
|
||||||
"Выбранные чаты:\n" + "\n".join([f"- {title}" for title in selected_chat_titles]) +
|
|
||||||
"\n\nЭто действие необратимо."
|
|
||||||
)
|
|
||||||
|
|
||||||
reply = QMessageBox.question(self, "Подтверждение исключения",
|
|
||||||
confirmation_message,
|
|
||||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
|
||||||
|
|
||||||
if reply == QMessageBox.Yes:
|
|
||||||
results = []
|
|
||||||
for chat_id, chat_title in zip(selected_chat_ids, selected_chat_titles):
|
|
||||||
try:
|
|
||||||
self.vk.messages.removeChatUser(chat_id=chat_id, user_id=user_id)
|
|
||||||
results.append(
|
|
||||||
f"✓ Пользователь '{user_info}' (ID: {user_id}) успешно исключен из чата '{chat_title}' (ID: {chat_id}).")
|
|
||||||
except VkApiError as e:
|
|
||||||
error_message = str(e)
|
|
||||||
if "[15] Access denied: user cannot be removed from this chat" in error_message:
|
|
||||||
results.append(
|
|
||||||
f"✗ Ошибка: Не удалось исключить пользователя '{user_info}' (ID: {user_id}) из чата '{chat_title}' (ID: {chat_id}). Доступ запрещен (нет прав админа или пользователь - создатель чата).")
|
|
||||||
elif "[100] One of the parameters specified was missing or invalid: user_id is invalid" in error_message:
|
|
||||||
results.append(
|
|
||||||
f"✗ Ошибка: Не удалось исключить пользователя '{user_info}' (ID: {user_id}) из чата '{chat_title}' (ID: {chat_id}). Некорректный ID пользователя.")
|
|
||||||
elif "[900] Cannot remove yourself" in error_message:
|
|
||||||
results.append(
|
|
||||||
f"✗ Ошибка: Не удалось исключить пользователя '{user_info}' (ID: {user_id}) из чата '{chat_title}' (ID: {chat_id}). Невозможно исключить самого себя.")
|
|
||||||
elif "[5] User authorization failed: access_token was given to another ip address" in error_message:
|
|
||||||
results.append(
|
|
||||||
f"✗ Ошибка: Не удалось исключить пользователя '{user_info}' (ID: {user_id}) из чата '{chat_title}' (ID: {chat_id}). "
|
|
||||||
"Токен недействителен из-за смены IP-адреса. Требуется повторная авторизация.")
|
|
||||||
self.token = None
|
|
||||||
self.token_expiration_time = None
|
|
||||||
self.token_input.clear()
|
|
||||||
self.auth_btn.setEnabled(True)
|
|
||||||
self.set_ui_state(False)
|
|
||||||
self.status_label.setText("Статус: требуется повторная авторизация (IP изменен)")
|
|
||||||
if self.token_countdown_timer and self.token_countdown_timer.isActive():
|
|
||||||
self.token_countdown_timer.stop()
|
|
||||||
if os.path.exists(TOKEN_FILE):
|
|
||||||
os.remove(TOKEN_FILE)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
results.append(
|
|
||||||
f"✗ Ошибка: Не удалось исключить пользователя '{user_info}' (ID: {user_id}) из чата '{chat_title}' (ID: {chat_id}): {e}")
|
|
||||||
except Exception as e:
|
|
||||||
results.append(f"✗ Неизвестная ошибка при исключении из чата '{chat_title}' (ID: {chat_id}): {e}")
|
|
||||||
|
|
||||||
QMessageBox.information(self, "Результаты исключения", "\n".join(results))
|
|
||||||
self.vk_url_input.clear()
|
|
||||||
self.user_id_to_process = None
|
|
||||||
self.set_ui_state(self.token is not None)
|
|
||||||
else:
|
|
||||||
QMessageBox.information(self, "Отмена", "Операция исключения отменена.")
|
|
||||||
|
|
||||||
def add_user_to_chat(self):
|
def add_user_to_chat(self):
|
||||||
"""
|
self._execute_user_action("add")
|
||||||
Приглашает пользователя во все выбранные чаты.
|
|
||||||
Перед приглашением выводит окно подтверждения.
|
|
||||||
"""
|
|
||||||
if not self.token:
|
|
||||||
QMessageBox.warning(self, "Ошибка", "Сначала авторизуйтесь!")
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.user_id_to_process is None:
|
|
||||||
QMessageBox.warning(self, "Ошибка",
|
|
||||||
"ID пользователя не получен. Пожалуйста, введите корректную ссылку и убедитесь, что ID определился.")
|
|
||||||
return
|
|
||||||
|
|
||||||
user_id = self.user_id_to_process
|
|
||||||
user_info = self.get_user_info_by_id(user_id)
|
|
||||||
|
|
||||||
visible_messages_count = None
|
|
||||||
if self.visible_messages_checkbox.isChecked():
|
|
||||||
visible_messages_count = 250
|
|
||||||
|
|
||||||
selected_chat_ids = []
|
|
||||||
selected_chat_titles = []
|
|
||||||
for checkbox in self.chat_checkboxes:
|
|
||||||
if checkbox.isChecked():
|
|
||||||
chat_id = checkbox.property("chat_id")
|
|
||||||
chat_title = next((chat['title'] for chat in self.chats if chat['id'] == chat_id),
|
|
||||||
f"Чат с ID: {chat_id}")
|
|
||||||
selected_chat_ids.append(chat_id)
|
|
||||||
selected_chat_titles.append(chat_title)
|
|
||||||
|
|
||||||
if not selected_chat_ids:
|
|
||||||
QMessageBox.warning(self, "Ошибка", "Выберите хотя бы один чат для приглашения пользователя!")
|
|
||||||
return
|
|
||||||
|
|
||||||
confirmation_message = (
|
|
||||||
f"Вы точно хотите пригласить пользователя '{user_info}' (ID: {user_id}) в следующие чаты?\n"
|
|
||||||
f"(Параметр 'Показать 250 последних сообщений' установлен: {'ДА' if visible_messages_count is not None else 'НЕТ'})\n\n"
|
|
||||||
"Выбранные чаты:\n" + "\n".join([f"- {title}" for title in selected_chat_titles]) +
|
|
||||||
"\n\nЭто действие может привести к отправке уведомлений пользователю."
|
|
||||||
)
|
|
||||||
|
|
||||||
reply = QMessageBox.question(self, "Подтверждение приглашения",
|
|
||||||
confirmation_message,
|
|
||||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
|
||||||
|
|
||||||
if reply == QMessageBox.Yes:
|
|
||||||
results = []
|
|
||||||
for chat_id, chat_title in zip(selected_chat_ids, selected_chat_titles):
|
|
||||||
try:
|
|
||||||
params = {
|
|
||||||
'chat_id': chat_id,
|
|
||||||
'user_id': user_id
|
|
||||||
}
|
|
||||||
if visible_messages_count is not None:
|
|
||||||
params['visible_messages_count'] = visible_messages_count
|
|
||||||
results.append(
|
|
||||||
f"Внимание: Параметр 'visible_messages_count' (значение: {visible_messages_count}) передан в messages.addChatUser для чата '{chat_title}' (ID: {chat_id}). Согласно официальной документации VK API, этот параметр не поддерживается для данного метода и может быть проигнорирован или привести к ошибке.")
|
|
||||||
|
|
||||||
self.vk.messages.addChatUser(**params)
|
|
||||||
results.append(
|
|
||||||
f"✓ Пользователь '{user_info}' (ID: {user_id}) успешно приглашен в чат '{chat_title}' (ID: {chat_id}).")
|
|
||||||
except VkApiError as e:
|
|
||||||
error_message = str(e)
|
|
||||||
if "[917] You don't have access to this chat" in error_message:
|
|
||||||
results.append(
|
|
||||||
f"✗ Ошибка: Не удалось пригласить пользователя '{user_info}' (ID: {user_id}) в чат '{chat_title}' (ID: {chat_id}). Нет доступа к чату или недостаточно прав.")
|
|
||||||
elif "[935] User has been invited to this chat" in error_message:
|
|
||||||
results.append(
|
|
||||||
f"✗ Ошибка: Пользователь '{user_info}' (ID: {user_id}) уже приглашен в чат '{chat_title}' (ID: {chat_id}) или уже был добавлен.")
|
|
||||||
elif "[100] One of the parameters specified was missing or invalid: user_id is invalid" in error_message:
|
|
||||||
results.append(
|
|
||||||
f"✗ Ошибка: Не удалось пригласить пользователя '{user_info}' (ID: {user_id}) из чата '{chat_title}' (ID: {chat_id}). Некорректный ID пользователя.")
|
|
||||||
elif "[5] User authorization failed: access_token was given to another ip address" in error_message:
|
|
||||||
results.append(
|
|
||||||
f"✗ Ошибка: Не удалось пригласить пользователя '{user_info}' (ID: {user_id}) в чат '{chat_title}' (ID: {chat_id}). "
|
|
||||||
"Токен недействителен из-за смены IP-адреса. Требуется повторная авторизация.")
|
|
||||||
self.token = None
|
|
||||||
self.token_expiration_time = None
|
|
||||||
self.token_input.clear()
|
|
||||||
self.auth_btn.setEnabled(True)
|
|
||||||
self.set_ui_state(False)
|
|
||||||
self.status_label.setText("Статус: требуется повторная авторизация (IP изменен)")
|
|
||||||
if self.token_countdown_timer and self.token_countdown_timer.isActive():
|
|
||||||
self.token_countdown_timer.stop()
|
|
||||||
if os.path.exists(TOKEN_FILE):
|
|
||||||
os.remove(TOKEN_FILE)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
results.append(
|
|
||||||
f"✗ Ошибка: Не удалось пригласить пользователя '{user_info}' (ID: {user_id}) в чат '{chat_title}' (ID: {chat_id}): {e}")
|
|
||||||
except Exception as e:
|
|
||||||
results.append(f"✗ Неизвестная ошибка при приглашении в чат '{chat_title}' (ID: {chat_id}): {e}")
|
|
||||||
|
|
||||||
QMessageBox.information(self, "Результаты приглашения", "\n".join(results))
|
|
||||||
self.visible_messages_checkbox.setChecked(False)
|
|
||||||
self.vk_url_input.clear()
|
|
||||||
self.user_id_to_process = None
|
|
||||||
self.set_ui_state(self.token is not None)
|
|
||||||
else:
|
|
||||||
QMessageBox.information(self, "Отмена", "Операция приглашения отменена.")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user