Search code examples
pythondiscordpyside6

Discord Python, After a client.close() call i want to be able to call client.start() again, but somehow the client loggs in multiple times


I'm trying to build a Discord Bot with a GUI, which is based on Qt. I want to have two Buttons, one to start the Bot and one to stop it. When I restart after a client.close() the Bot logs in with multiple instances and i don't know how to fix it.

Code:

import bot # some discord bot that can login on a server and send a message
import asyncio
from PySide6.QtWidgets import QLabel, QWidget, QPushButton, QTextBrowser, QComboBox
from qasync import QEventLoop, asyncSlot, QApplication
from PySide6.QtCore import QFile
from PySide6.QtUiTools import QUiLoader
import PySide6.QtGui as QtGui

class main(QWidget):
    def __init__(self):
        super(main, self).__init__(Parent=None)
        self.load_ui()
        self.btn_startbot = self.findChild(QPushButton, 'btn_startbot')
        self.btn_startbot.clicked.connect(self.on_btn_startbot_clicked)
        self.btn_stopbot = self.findChild(QPushButton, 'btn_stopbot')
        self.btn_stopbot.clicked.connect(self.on_btn_stopbot_clicked)

    def load_ui(self):
        loader = QUiLoader()
        ui_file = QFile("form.ui") # some ui, that has buttons
        ui_file.open(QFile.ReadOnly)
        loader.load(ui_file, self)
        ui_file.close()


    @asyncSlot()
    async def on_btn_startbot_clicked(self):
        print("clicked start bot")
        self.bot = bot.MyBot(command_prefix='-', self_bot=False)
        self.bot.sig_slot_handler.sig_send_log.connect(self.slot_log_msg)
        await self.bot.start('Token') 
    
    @asyncSlot()
    async def on_btn_stopbot_clicked(self):
        await self.bot.close()

    @asyncSlot(str)
    async def slot_log_msg(self, logmsg):
        print(logmsg) 

I don't know if it is maybe something about the event loop, but I needed to use qasync because Qt event loop and Discord's async event loop conflicted.

if __name__ == "__main__":
    app = QApplication([])
    loop = QEventLoop(app)
    asyncio.set_event_loop(loop)
    widget = main()
    widget.show()
    loop.run_forever()

Minimal Bot class:

class MyBot(commands.Bot):
    sig_slot_handler = q_signal_slot_handler()

    def __init__(self, command_prefix, self_bot, parent=None):
        commands.Bot.__init__(self, command_prefix=command_prefix, self_bot=self_bot)
        self.add_commands()
        self.add_events()
    
    def add_events(self):

        @self.event
        async def on_ready():
            self.sig_slot_handler.send_log_signal(f'PyBot login') 

    def add_commands(self):
        @self.command(brief="Send a Message", usage="<MESSAGE WORDS>", help="Send a Message in your active Text Channel. Type: -send <MESSAGE WORDS>")
        async def send(ctx, *, message):
            await ctx.message.delete()
            self.sig_slot_handler.send_log_signal(f'{ctx.author} used send in {ctx.message.channel}.')
            await ctx.send(message)

Also I made a class to handle Qt signals and slots:

class q_signal_slot_handler(QObject):
    sig_send_log    = Signal(str)

    def __init__(self, Parent=None):
        super(q_signal_slot_handler, self).__init__(Parent=Parent)
    
    def send_log_signal(self, logmsg):
        self.sig_send_log.emit(logmsg)

What happens is after I press stop and then start again, the output is something like:

  • PyBot login
  • PyBot login

Solution

  • The problem is that sig_slot_handler is a single object for all MyBot so you are connecting the same signal multiple times so when a signal is issued the slot will be invoked multiple times. The solution is to make an attribute of the class to sig_slot_handler:

    class MyBot(commands.Bot):
        def __init__(self, command_prefix, self_bot, parent=None):
            commands.Bot.__init__(self, command_prefix=command_prefix, self_bot=self_bot)
            self.sig_slot_handler = q_signal_slot_handler()
            self.add_commands()
            self.add_events()