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:
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()