Как убрать зависание GUI в приложении PyQt5?

Пишу приложение для Telegram для анализа сообщений в группах.
Сам парсер работает, GUI работает, но вот после запуска бота, GUI намертво зависает.

main.py:

from telethon import TelegramClient, events
from telethon.tl.custom import Button

from PyQt5 import QtWidgets, QtCore, uic
from PyQt5.QtWidgets import QFileDialog, QWidget, QHBoxLayout, QStackedWidget, QApplication
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from PyQt5.QtWidgets import *

from PyQt5.QtCore import QTimer  # Импортируем QTimer
from PyQt5.QtGui import QColor, QIcon
import sys
import logging
import os
from py_windows.MainUI import Ui_MainWindow


# Настройка логирования (включая запись в файл)
LOG_FILE = "bot_log.txt"  # Имя файла для логов
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
    logging.FileHandler(LOG_FILE, encoding="utf-8"),  # Запись логов в файл
    logging.StreamHandler()         # Вывод логов в консоль
]
)

# Конфигурация API
API_ID = 23729732
API_HASH = '3f164a5c7c810de606912e098abb5583'
BOT_TOKEN = "7794964692:AAH2kcOC7Vu8pMg8ZkWmleRTeougEFmmbyw"

# Ключевые слова для мониторинга
KEYWORDS = ["нужен фотограф", "ищу фотографа", "фотосессия", "фото"]

# Канал или чат, куда отправлять найденные сообщения
DESTINATION_CHAT = "@pro_nik1"

# Ссылки для предопределённого сообщения
INSTAGRAM_LINK = "https://instagram.com/lebedeeww?utm_medium=copy_link"
PORTFOLIO_LINK = "https://disk.yandex.ru/d/iVbRu5w1d2XCWw"

# Предопределенное сообщение
PREDEFINED_MESSAGE = f"""Добрый день ??

Меня зовут xxx, я фотограф. Увидел ваш запрос на съёмку.

Моё портфолио: [{PORTFOLIO_LINK}]({PORTFOLIO_LINK})

? Ссылка на соцсеть: [{INSTAGRAM_LINK}]({INSTAGRAM_LINK})
"""

# Список пользователей, которым нужно ответить
USERS_TO_REPLY = []
CHANNELS_FILE = "channels.txt"

# Создание клиента Telegram
client = TelegramClient("bot_session_bot", API_ID, API_HASH).start(bot_token=BOT_TOKEN)
# Загрузка каналов для мониторинга


class Main_Screen(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(Main_Screen, self).__init__(parent)
        self.setupUi(self)
        
    
class MainWindow(QtWidgets.QMainWindow):
     def __init__(self):
        super().__init__()
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        
        #Начало класса Main_Screen
        self.main_screen = Main_Screen()
        self.main_screen.pushButton.clicked.connect(self.run_bot)
        self.main_screen.pushButton_2.clicked.connect(self.add_group)
        #self.main_screen.pushButton_3.clicked.connect(self.run_bot)
        #Конец класса Main_Screen
        
         # Таймер для автоматического обновления логов
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update_log)  # Подключаем функцию обновления логов
        self.timer.start(1000)  # Запускаем таймер с интервалом 5000 миллисекунд (5 секунд)

        self.stack = QStackedWidget()
        self.stack.addWidget(self.main_screen)
        
        self.stack.setCurrentIndex(0)
        self.nameProgramm(self.stack.currentIndex())

        self.gotomain()

        hbox = QHBoxLayout(self.centralWidget)
        hbox.addWidget(self.stack)

    def nameProgramm(self, w=0):
        self.setWindowTitle('SoundWave')
        # base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
        # icon_path = os.path.join(base_path, 'images', '11zon_cropped.png')
        # self.setWindowIcon(QIcon(icon_path))

    def gotomain(self):
        self.stack.setCurrentIndex(0)
        self.nameProgramm(self.stack.currentIndex())

    def update_log(self):
        try:
            vertical_scrollbar = self.main_screen.logEdit.verticalScrollBar()
            current_scroll_position = vertical_scrollbar.value()

            # Считываем содержимое файла логов
            with open(LOG_FILE, "r", encoding="utf-8") as file:
                log_content = file.read()

            # Обновляем текст в QTextEdit
            self.main_screen.logEdit.setPlainText(log_content)

            # Восстанавливаем положение вертикального скролла
            vertical_scrollbar.setValue(current_scroll_position)

        except FileNotFoundError:
            QMessageBox.warning(self, "Ошибка", "Файл логов не найден.")
        except Exception as e:
            QMessageBox.critical(self, "Ошибка", f"Не удалось прочитать лог: {e}")

    def run_bot(self):
        """Основная функция работы бота."""
        client = TelegramClient("bot_session_bot", API_ID, API_HASH).start(bot_token=BOT_TOKEN)

        def load_channels_from_file(file_path):
            try:
                with open(file_path, "r", encoding="utf-8") as file:
                    channels = [line.strip() for line in file if line.strip()]
                return channels
            except FileNotFoundError:
                logging.error(f"Файл {file_path} не найден.")
                return []
            except Exception as e:
                logging.error(f"Ошибка при чтении файла {file_path}: {e}")
                return []

        CHANNELS_TO_MONITOR = load_channels_from_file(CHANNELS_FILE)

        @client.on(events.NewMessage(chats=CHANNELS_TO_MONITOR))
        async def handle_new_message(event):
            message_text = event.message.message.lower()
            sender = await event.get_sender()
            channel = event.chat

            sender_username = sender.username if sender.username else f"{sender.first_name} {sender.last_name}"
            channel_title = channel.title if hasattr(channel, 'title') else 'Неизвестный канал'

            message_link = (
                f"https://t.me/{event.chat.username}/{event.message.id}" if channel_title else "Неизвестный канал"
            )

            logging.info(f"Получено сообщение: {message_text}")

            if any(keyword in message_text for keyword in KEYWORDS):
                logging.info(f"✅ Сообщение подходит, отправляем в {DESTINATION_CHAT}")
                USERS_TO_REPLY.append(sender_username)

                reply_button = Button.inline(f"Написать @{sender_username}", data=b"send_message")
                chat_button = Button.url(f"Перейти в диалог к @{sender_username}", f"t.me/{sender.username}")

                await client.send_message(
                    DESTINATION_CHAT,
                    f"? **Найдено сообщение** в канале [{channel_title}](https://t.me/{event.chat.username})\n\n"
                    f"? **Сообщение от @{sender_username}**\n\n"
                    f"**Текст:** {event.message.text}\n\n"
                    f"? [Ссылка на пост]({message_link})",
                    buttons=[reply_button, chat_button]
                )
            else:
                logging.info("❌ Сообщение не содержит ключевых слов")

        client.run_until_disconnected()

    def add_group(self):
        group_name = self.main_screen.lineEdit.text().strip()
        
        if not group_name:
            QMessageBox.warning(self, "Внимание", "Введите название группы.")
            return

        try:
            # Проверяем, существует ли файл channels.txt, если нет - создаем его
            if not os.path.exists(CHANNELS_FILE):
                with open(CHANNELS_FILE, "w", encoding="utf-8") as file:
                    pass  # Создаем пустой файл

            # Добавляем группу в файл
            with open(CHANNELS_FILE, "a", encoding="utf-8") as file:
                file.write(group_name + "\n")

            QMessageBox.information(self, "Успех", f"Группа {group_name} успешно добавлена.")
            self.main_screen.lineEdit.clear()  # Очищаем поле ввода после успешного добавления

        except PermissionError:
            QMessageBox.critical(self, "Ошибка", "Недостаточно прав для записи в файл.")
        except Exception as e:
            QMessageBox.critical(self, "Ошибка", f"Не удалось добавить группу: {e}")


def application():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    application()

MainUI.py:

from PyQt5 import QtCore, QtGui, QtWidgets
    
class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(872, 657)
        MainWindow.setMinimumSize(QtCore.QSize(872, 657))
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setMinimumSize(QtCore.QSize(872, 657))
        self.centralwidget.setStyleSheet("#centralwidget {background-color: rgb(36, 36, 36);}\n"
"\n"
"QScrollBar:vertical {\n"
"                background: rgb(50, 50, 50);\n"
"                width: 10px;\n"
"            }\n"
"            QScrollBar::handle:vertical {\n"
"                background: rgb(80, 80, 80);\n"
"                min-height: 20px;\n"
"            }\n"
"            QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {\n"
"                height: 0px;\n"
"            }\n"
"            QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {\n"
"                background: none;\n"
"            }")
        self.centralwidget.setObjectName("centralwidget")
        self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.centralwidget)
        self.horizontalLayout_3.setObjectName("horizontalLayout_3")
        self.widget = QtWidgets.QWidget(self.centralwidget)
        self.widget.setMinimumSize(QtCore.QSize(850, 635))
        self.widget.setStyleSheet("#widget {background-color: rgb(54, 54,54);}")
        self.widget.setObjectName("widget")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.widget)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.btnpanel = QtWidgets.QWidget(self.widget)
        self.btnpanel.setMinimumSize(QtCore.QSize(285, 613))
        self.btnpanel.setStyleSheet("#btnpanel{background-color: rgb(36, 36, 36);}")
        self.btnpanel.setObjectName("btnpanel")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.btnpanel)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.label = QtWidgets.QLabel(self.btnpanel)
        self.label.setMinimumSize(QtCore.QSize(181, 20))
        font = QtGui.QFont()
        font.setPointSize(12)
        font.setBold(True)
        font.setWeight(75)
        self.label.setFont(font)
        self.label.setStyleSheet("color: rgb(55, 255, 0);")
        self.label.setAlignment(QtCore.Qt.AlignCenter)
        self.label.setObjectName("label")
        self.verticalLayout.addWidget(self.label)
        self.line = QtWidgets.QFrame(self.btnpanel)
        self.line.setStyleSheet("background-color: rgb(0, 255, 0);")
        self.line.setFrameShape(QtWidgets.QFrame.HLine)
        self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
        self.line.setObjectName("line")
        self.verticalLayout.addWidget(self.line)
        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        self.verticalLayout.addItem(spacerItem)
        self.pushButton = QtWidgets.QPushButton(self.btnpanel)
        self.pushButton.setMinimumSize(QtCore.QSize(261, 41))
        font = QtGui.QFont()
        font.setBold(True)
        font.setWeight(75)
        self.pushButton.setFont(font)
        self.pushButton.setStyleSheet("background-color: rgb(236, 233, 234);\n"
"color: rgb(63, 61, 65);\n"
"border-radius: 10px;")
        self.pushButton.setObjectName("pushButton")
        self.verticalLayout.addWidget(self.pushButton)
        spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        self.verticalLayout.addItem(spacerItem1)
        self.lineEdit = QtWidgets.QLineEdit(self.btnpanel)
        self.lineEdit.setMinimumSize(QtCore.QSize(261, 31))
        self.lineEdit.setStyleSheet("background-color: rgb(236, 233, 234);\n"
"color: rgb(63, 61, 65);\n"
"border-radius: 10px;")
        self.lineEdit.setAlignment(QtCore.Qt.AlignCenter)
        self.lineEdit.setObjectName("lineEdit")
        self.verticalLayout.addWidget(self.lineEdit)
        self.pushButton_2 = QtWidgets.QPushButton(self.btnpanel)
        self.pushButton_2.setMinimumSize(QtCore.QSize(261, 41))
        font = QtGui.QFont()
        font.setBold(True)
        font.setWeight(75)
        self.pushButton_2.setFont(font)
        self.pushButton_2.setStyleSheet("background-color: rgb(236, 233, 234);\n"
"color: rgb(63, 61, 65);\n"
"border-radius: 10px;")
        self.pushButton_2.setObjectName("pushButton_2")
        self.verticalLayout.addWidget(self.pushButton_2)
        self.pushButton_3 = QtWidgets.QPushButton(self.btnpanel)
        self.pushButton_3.setMinimumSize(QtCore.QSize(261, 41))
        font = QtGui.QFont()
        font.setBold(True)
        font.setWeight(75)
        self.pushButton_3.setFont(font)
        self.pushButton_3.setStyleSheet("background-color: rgb(236, 233, 234);\n"
"color: rgb(63, 61, 65);\n"
"border-radius: 10px;")
        self.pushButton_3.setObjectName("pushButton_3")
        self.verticalLayout.addWidget(self.pushButton_3)
        spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        self.verticalLayout.addItem(spacerItem2)
        self.verticalLayout_2.addLayout(self.verticalLayout)
        self.horizontalLayout_2.addWidget(self.btnpanel, 0, QtCore.Qt.AlignHCenter)
        self.widgetLog = QtWidgets.QWidget(self.widget)
        self.widgetLog.setMinimumSize(QtCore.QSize(530, 0))
        self.widgetLog.setStyleSheet("#widgetLog{background-color: rgb(36, 36, 36);}\n"
"")
        self.widgetLog.setObjectName("widgetLog")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.widgetLog)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.logEdit = QtWidgets.QTextEdit(self.widgetLog)
        self.logEdit.setMinimumSize(QtCore.QSize(400, 590))
        self.logEdit.setStyleSheet("QTextEdit {\n"
"\n"
" \n"
" \n"
"    background-color: rgba(255, 255, 255, 0);\n"
"    \n"
" color: #d4d2d3;\n"
"    border-radius: 15px; /* Задайте радиус, чтобы сделать края овальными */\n"
"}\n"
"")
        self.logEdit.setReadOnly(True)
        self.logEdit.setObjectName("logEdit")
        self.horizontalLayout.addWidget(self.logEdit)
        self.horizontalLayout_2.addWidget(self.widgetLog)
        self.horizontalLayout_3.addWidget(self.widget)
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.label.setText(_translate("MainWindow", "Бот Активен"))
        self.pushButton.setText(_translate("MainWindow", "Запустить бота"))
        self.lineEdit.setPlaceholderText(_translate("MainWindow", "Введите ссылку на ТГ группу"))
        self.pushButton_2.setText(_translate("MainWindow", "Добавить группу в список"))
        self.pushButton_3.setText(_translate("MainWindow", "Удалить группу из списка"))
        self.logEdit.setHtml(_translate("MainWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'MS Shell Dlg 2\'; font-size:7.8pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:20:23,925 - INFO - Connecting to 149.154.167.51:443/TcpFull...</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:20:23,973 - INFO - Connection to 149.154.167.51:443/TcpFull complete!</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:20:26,849 - INFO - Получено сообщение: фото</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:20:26,850 - INFO - ✅ Сообщение подходит, отправляем в @pro_nik1</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:20:51,749 - INFO - Отправлено сообщение пользователю: pro_nik1</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:20:58,721 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:21:04,318 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,328 - INFO - Connecting to 149.154.167.51:443/TcpFull...</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,375 - INFO - Connection t5:20:51,749 - INFO - Отправлено сообщение пользователю: pro_nik1</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:20:58,721 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:21:04,318 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,328 - INFO - Connecting to 149.154.167.51:443/TcpFull...</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,375 - INFO - Connection t5:20:51,749 - INFO - Отправлено сообщение пользователю: pro_nik1</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:20:58,721 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:21:04,318 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,328 - INFO - Connecting to 149.154.167.51:443/TcpFull...</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,375 - INFO - Connection t5:20:51,749 - INFO - Отправлено сообщение пользователю: pro_nik1</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:20:58,721 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:21:04,318 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,328 - INFO - Connecting to 149.154.167.51:443/TcpFull...</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,375 - INFO - Connection t5:20:51,749 - INFO - Отправлено сообщение пользователю: pro_nik1</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:20:58,721 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:21:04,318 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,328 - INFO - Connecting to 149.154.167.51:443/TcpFull...</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,375 - INFO - Connection t5:20:51,749 - INFO - Отправлено сообщение пользователю: pro_nik1</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:20:58,721 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:21:04,318 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,328 - INFO - Connecting to 149.154.167.51:443/TcpFull...</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,375 - INFO - Connection t5:20:51,749 - INFO - Отправлено сообщение пользователю: pro_nik1</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:20:58,721 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:21:04,318 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,328 - INFO - Connecting to 149.154.167.51:443/TcpFull...</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,375 - INFO - Connection t5:20:51,749 - INFO - Отправлено сообщение пользователю: pro_nik1</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:20:58,721 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:21:04,318 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,328 - INFO - Connecting to 149.154.167.51:443/TcpFull...</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,375 - INFO - Connection t5:20:51,749 - INFO - Отправлено сообщение пользователю: pro_nik1</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:20:58,721 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:21:04,318 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,328 - INFO - Connecting to 149.154.167.51:443/TcpFull...</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,375 - INFO - Connection t5:20:51,749 - INFO - Отправлено сообщение пользователю: pro_nik1</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:20:58,721 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:21:04,318 - WARNING - Нет доступных получателей для отправки сообщения.</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,328 - INFO - Connecting to 149.154.167.51:443/TcpFull...</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:9.6pt;\">2025-03-03 15:40:23,375 - INFO - Connection to 149.154.167.51:443/TcpFull complete!</span></p></body></html>"))

После того, как я нажимаю на кнопку "Запустить бота", GUI намертво зависает.

Единственное, что я понял - это происходит из-за этой строчки кода:

client.run_until_disconnected()

(195 cтр), которая блокирует процесс GUI.
Любые попытки добавить запуск данной части кода в отдельном потоке - заканчивалось неудачей.
Помогите пожалуйста, как это можно исправить?


Ответы (1 шт):

Автор решения: S. Nick

Я не могу протестировать ваш код, но некоторые полезные совета я вам расскажу и покажу.

Для того чтобы графический интерфейс не замораживался, длительные задачи должны запускаться в дополнительном потоке.
Один из вариантов, использовать класс QThread, который предоставляет независимый от платформы способ управления потоками.

Для передачи данных между потоками, используются Signals & Slots.
Также читаем Support for Signals and Slots

Я, на всякий случай, продемонстрировал для вас как работают Сигналы и Слоты.

Скопируйте мой ответ, прочитайте комментарии по тексту примера.
Запустите мой пример и расскажите, что у вас получилось.

main.py

import sys
import logging
import os

from telethon import TelegramClient, events
from telethon.tl.custom import Button

from PyQt5.QtWidgets import QFileDialog, QWidget, QHBoxLayout, \
    QStackedWidget, QApplication, QMainWindow, QMessageBox
from PyQt5.QtCore import QTimer, QThread, pyqtSignal  
from PyQt5.QtGui import QColor, QIcon

from py_windows.MainUI import Ui_MainWindow


# Настройка логирования (включая запись в файл)
LOG_FILE = "bot_log.txt"           # Имя файла для логов
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
    handlers=[
        # Запись логов в файл
        logging.FileHandler(LOG_FILE, encoding="utf-8"), 
        # Вывод логов в консоль
        logging.StreamHandler()                          
    ]
)

# Конфигурация API
API_ID = 23729732
API_HASH = '...API_HASH...'        # <--- !!! исправьте на свое
BOT_TOKEN = "...BOT_TOKEN..."      # <--- !!! исправьте на свое

# Ключевые слова для мониторинга
KEYWORDS = [
    "нужен фотограф", 
    "ищу фотографа", 
    "фотосессия", "фото"
]

# Канал или чат, куда отправлять найденные сообщения
DESTINATION_CHAT = "@pro_nik1"

# Ссылки для предопределённого сообщения
INSTAGRAM_LINK = "https://instagram.com/lebedeeww?utm_medium=copy_link"
PORTFOLIO_LINK = "https://disk.yandex.ru/d/iVbRu5w1d2XCWw"

# Предопределенное сообщение
PREDEFINED_MESSAGE = f"""Добрый день ??

Меня зовут xxx, я фотограф. Увидел ваш запрос на съёмку.

Моё портфолио: [{PORTFOLIO_LINK}]({PORTFOLIO_LINK})

? Ссылка на соцсеть: [{INSTAGRAM_LINK}]({INSTAGRAM_LINK})
"""

# Список пользователей, которым нужно ответить
USERS_TO_REPLY = []
CHANNELS_FILE = "channels.txt"

# Создание клиента Telegram                   #?
client = TelegramClient("bot_session_bot", 
    API_ID, API_HASH).start(bot_token=BOT_TOKEN)
# Загрузка каналов для мониторинга            #?


# +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
class WorkThread(QThread):
    # какой-то сигнал для  передачи данных в основной поток
    threadSignal = pyqtSignal(str)          # Определение новых Cигналов
    
    def __init__(self):
        super().__init__()
    
    def run(self):
        """Основная функция работы бота."""
        client = TelegramClient("bot_session_bot", 
            API_ID, API_HASH).start(bot_token=BOT_TOKEN)

        def load_channels_from_file(file_path):
            # что-то отправляем в основной поток
            self.threadSignal.emit(file_path)     # Сигнал испускается
        
            try:
                with open(file_path, "r", encoding="utf-8") as file:
                    channels = [line.strip() for line in file if line.strip()]
                # что-то отправляем в основной поток
                self.threadSignal.emit(channels)
                return channels
            except FileNotFoundError:
                logging.error(f"Файл {file_path} не найден.")
                # что-то отправляем в основной поток
                self.threadSignal.emit(f"Файл {file_path} не найден.")
                return []
            except Exception as e:
                logging.error(f"Ошибка при чтении файла {file_path}: {e}")
                # что-то отправляем в основной поток
                self.threadSignal.emit(f"Ошибка при чтении файла {file_path}: {e}")
                return []

        CHANNELS_TO_MONITOR = load_channels_from_file(CHANNELS_FILE)

        @client.on(events.NewMessage(chats=CHANNELS_TO_MONITOR))
        async def handle_new_message(event):
            message_text = event.message.message.lower()
            sender = await event.get_sender()
            channel = event.chat

            sender_username = sender.username if sender.username else f"{sender.first_name} {sender.last_name}"
            channel_title = channel.title if hasattr(channel, 'title') else 'Неизвестный канал'

            message_link = (
                f"https://t.me/{event.chat.username}/{event.message.id}" if channel_title else "Неизвестный канал"
            )

            logging.info(f"Получено сообщение: {message_text}")

            # что-то отправляем в основной поток
            self.threadSignal.emit(f"Получено сообщение: {message_text}")
            # Forces the current thread to sleep for secs seconds
            self.msleep(1000)   
            
            if any(keyword in message_text for keyword in KEYWORDS):
                logging.info(f"✅ Сообщение подходит, отправляем в {DESTINATION_CHAT}")
                USERS_TO_REPLY.append(sender_username)

                reply_button = Button.inline(f"Написать @{sender_username}", data=b"send_message")
                chat_button = Button.url(f"Перейти в диалог к @{sender_username}", f"t.me/{sender.username}")

                await client.send_message(
                    DESTINATION_CHAT,
                    f"? **Найдено сообщение** в канале [{channel_title}](https://t.me/{event.chat.username})\n\n"
                    f"? **Сообщение от @{sender_username}**\n\n"
                    f"**Текст:** {event.message.text}\n\n"
                    f"? [Ссылка на пост]({message_link})",
                    buttons=[reply_button, chat_button]
                )
            else:
                logging.info("❌ Сообщение не содержит ключевых слов")

        client.run_until_disconnected()
# +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


class Main_Screen(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(Main_Screen, self).__init__(parent)
        self.setupUi(self)
        
    
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        
        #Начало класса Main_Screen
        self.main_screen = Main_Screen()
        self.main_screen.pushButton.clicked.connect(self.run_bot)
        self.main_screen.pushButton_2.clicked.connect(self.add_group)
        #self.main_screen.pushButton_3.clicked.connect(self.run_bot)
        
        # Таймер для автоматического обновления логов
        self.timer = QTimer(self)
        # Подключаем функцию обновления логов
        self.timer.timeout.connect(self.update_log)  
        # Запускаем таймер с интервалом 5000 миллисекунд (5 секунд)
        self.timer.start(5000)  

        self.stack = QStackedWidget()
        self.stack.addWidget(self.main_screen)
        self.stack.setCurrentIndex(0)
        self.nameProgramm(self.stack.currentIndex())

        self.gotomain()    # ?

        hbox = QHBoxLayout(self.centralWidget)
        hbox.addWidget(self.stack)

    def nameProgramm(self, w=0):
        self.setWindowTitle('SoundWave')
        # base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
        # icon_path = os.path.join(base_path, 'images', '11zon_cropped.png')
        # self.setWindowIcon(QIcon(icon_path))

    def gotomain(self):
        self.stack.setCurrentIndex(0)
        self.nameProgramm(self.stack.currentIndex())

    def update_log(self):
        try:
            vertical_scrollbar = self.main_screen.logEdit.verticalScrollBar()
            current_scroll_position = vertical_scrollbar.value()
            # Считываем содержимое файла логов
            with open(LOG_FILE, "r", encoding="utf-8") as file:
                log_content = file.read()
            # Обновляем текст в QTextEdit
            self.main_screen.logEdit.setPlainText(log_content)
            # Восстанавливаем положение вертикального скролла
            vertical_scrollbar.setValue(current_scroll_position)
        except FileNotFoundError:
            QMessageBox.warning(self, "Ошибка", "Файл логов не найден.")
        except Exception as e:
            QMessageBox.critical(self, "Ошибка", 
                f"Не удалось прочитать лог: {e}")

# +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    def run_bot(self):
        self.main_screen.pushButton.setText('Бот работает')
        self.main_screen.pushButton.setStyleSheet("""
            background-color: green;
            color: yellow;
            border-radius: 10px;
        """)
        self.main_screen.pushButton.setEnabled(False)
        
        self.thread = WorkThread()
        
        #подключаем Сигналvvvvvv      к  Слотуvvvvvvvvvvvvvvv
        self.thread.threadSignal.connect(self.on_threadSignal)
        self.thread.finished.connect(self.thread_finished)
        
        self.thread.start()        

    # Слот - это функция, которая вызываетсявызывается 
    # при излучении сигнала.
    def on_threadSignal(self, text):
        self.main_screen.logEdit.append(text) 

    def thread_finished(self):
        self.main_screen.pushButton.setText('Запустить бота')
        self.main_screen.pushButton.setStyleSheet("""
            background-color: rgb(236, 233, 234);
            color: rgb(63, 61, 65);
            border-radius: 10px;
        """)
        self.main_screen.pushButton.setEnabled(True)
        self.main_screen.logEdit.append(
            '''
            Этот сигнал испускается из связанного потока 
            прямо перед завершением выполнения.
            ''')
# +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        

    def add_group(self):
        group_name = self.main_screen.lineEdit.text().strip()
        if not group_name:
            QMessageBox.warning(self, "Внимание", 
                "Введите название группы.")
            return
        try:
            # Проверяем, существует ли файл channels.txt, 
            # если нет - создаем его
            if not os.path.exists(CHANNELS_FILE):
                with open(CHANNELS_FILE, "w", encoding="utf-8") as file:
                    pass  # Создаем пустой файл

            # Добавляем группу в файл
            with open(CHANNELS_FILE, "a", encoding="utf-8") as file:
                file.write(group_name + "\n")

            QMessageBox.information(self, "Успех", 
                f"Группа {group_name} успешно добавлена.")
            self.main_screen.lineEdit.clear()  # Очищаем поле ввода 
        except PermissionError:
            QMessageBox.critical(self, "Ошибка", 
                "Недостаточно прав для записи в файл.")
        except Exception as e:
            QMessageBox.critical(self, "Ошибка", 
                f"Не удалось добавить группу: {e}")


def application():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    application()

введите сюда описание изображения

введите сюда описание изображения

введите сюда описание изображения

→ Ссылка