@soks_of_fate

Как исправить ошибку взаимодействия окна и дискорд бота?

Я хочу чтоб бот управлялся с помощью окна из python. Проблема в том, что команда "^test" работает только по нажатию кнопки. Я так пытался чтоб бот работал во втором потоке, но тогда перестает работать кнопка "рассчитать". Я пытался получить совет от gpt, но она только сильнее все ломает. Прошу помощи...

import time
import discord
from discord.ext import commands
from tkinter import *
from tkinter import ttk
import asyncio
from tkinter import Tk, Label
from PIL import Image, ImageTk
from io import BytesIO
from urllib.request import urlopen

intents = discord.Intents.all()
intents.voice_states = True
intents.typing = False

bot = commands.Bot(command_prefix='^', intents=intents)


@bot.event
async def on_ready():
    print(f'We have logged in as {bot.user}')


# Добавляем асинхронную функцию main, которая будет запускать бота
async def main():
    await bot.start('MTExMDk0MDc4OTA5OTQ3OTE3MQ.GG6-v_.d-y5UvyEap556faMPw9YS6hx89HDAm5zE94jdw')


# Остальной код остается без изменений


@bot.command()
async def test(ctx):
    await ctx.send("its work")
    print(f'We have logged in as')



def click_button():
    loop = asyncio.get_event_loop()


def click_button3(channel_name, nickname, label):
    loop = asyncio.get_event_loop()
    task = loop.create_task(move_user_to_channel(channel_name, nickname, label))
    loop.run_until_complete(task)




def click_button2():

    loop = asyncio.get_event_loop()
    task = loop.create_task(run_async2())
    loop.run_until_complete(task)


async def run_async2():
    await get_voice_list()

async def get_voice_list():
    await bot.wait_until_ready()
    www = entry2.get()
    try:
        if "music" in www:
            print("нашел")
        else:
            guild_id = int(www)
            guild = bot.get_guild(guild_id)
            if guild is not None:
                voice_channels = guild.voice_channels

                channel_names = []

                for channel in voice_channels:
                    channel_names.append(channel.name)
                    formatted_text = "\n".join(channel_names)
                    create_channel_buttons(channel_names)
    except ValueError:
        print("Некорректное значение в поле ввода.")

def create_channel_buttons(channel_names):
    for i, channel_name in enumerate(channel_names):
        btn = ttk.Button(root, text=channel_name, command=lambda name=channel_name: open_new_window(name))
        btn.pack()
        btn.place(x=10, y=150 + i * 30)


def open_new_window(channel_name):
    new_window = Toplevel(root)
    new_window.title(channel_name)
    new_window.geometry("500x500")

    label_nickname = ttk.Label(new_window, text="Nickname:")
    label_nickname.pack()
    label_nickname.place(x=10, y=10)

    entry_nickname = ttk.Entry(new_window)
    entry_nickname.pack()
    entry_nickname.place(x=80, y=10)

    btn_move = ttk.Button(new_window, text="Move to channel", command=lambda: click_button3(channel_name, entry_nickname.get(), label3))
    btn_move.pack()
    btn_move.place(x=200, y=10)

    label3 = ttk.Label(new_window)
    label3.pack()
    label3.place(x=10, y=50)

    voice_channel = discord.utils.get(bot.get_all_channels(), name=channel_name)
    if voice_channel is not None:
        participants = voice_channel.members
        participant_names = [member.display_name for member in participants]
        participant_list = "\n".join(participant_names)
        label_participants = ttk.Label(new_window, text=f'Участники в канале {channel_name}:\n{participant_list}')
        label_participants.pack()
        label_participants.place(x=10, y=90)


def black_theme():
    root["bg"] = "black"







async def move_user_to_channel(channel_name, nickname, label):
    await bot.wait_until_ready()
    voice_channel = discord.utils.get(bot.get_all_channels(), name=channel_name)
    if voice_channel is not None:
        member = discord.utils.get(voice_channel.guild.members, display_name=nickname)
        if member is not None:
            await member.move_to(voice_channel)
            label["text"] = f'{nickname} был перемещен в голосовой канал {channel_name}.'
        else:
            label["text"] = f'Участник с ником {nickname} не найден.'
    else:
        label["text"] = f'Голосовой канал {channel_name} не найден.'




async def main():
    await bot.start('MTExMDk0MDc4OTA5OTQ3OTE3MQ.GG6-v_.d-y5UvyEap556faMPw9YS6hx89HDAm5zE94jdw')




loop = asyncio.get_event_loop()
loop.create_task(main())

root = Tk()
root.title("dicord_bit")
root.geometry("700x400")
root.resizable(False, False)

btn2 = ttk.Button(text="рассчитать", command=click_button2)
btn2.pack()
btn2.place(x=310, y=30)
entry2 = ttk.Entry()
entry2.pack()
entry2.place(x=300, y=60, width=300, height=50)


r_var = BooleanVar()
r_var.set(0)
r1 = Radiobutton(text='обычный режим', variable=r_var, value=0)
r2 = Radiobutton(text='тихий режим', variable=r_var, value=1)
r1.pack()
r2.pack()
r1.place(x=30, y=30)
r2.place(x=30, y=50)


response = urlopen("https://cs14.pikabu.ru/post_img/2021/12/03/9/1638544827126780665.jpg")
image_data = response.read()
image = Image.open(BytesIO(image_data))

image = image.resize((400, 400))


photo = ImageTk.PhotoImage(image)


labelg = Label(root)
labelg.image = photo
labelg.configure(image=photo)
labelg.pack()
labelg.place(x=700, y=400)



root.mainloop()


да, я понимаю, это спагетти-код, но если можете, то прошу помощи
  • Вопрос задан
  • 184 просмотра
Решения вопроса 1
Vindicar
@Vindicar
RTFM!
В коде нет работы с потоками, ты даже не импортируешь threading.
Нужно понимать три вещи:
1. Рабочий цикл бота займёт весь поток, равно как и рабочий цикл tkinter. Совместить их практически невозможно (технически можно, но это очень нетривиально).
2. Элементы GUI должны создаваться и использоваться строго в одном и том же потоке.
3. Нельзя делать await вызовы из одного потока в другой.

Поэтому я бы посоветовал такое грубое, но простое решение:
В одном потоке потоке создавай event loop (сам! это важно!) и запускай бота. В другом потоке создавай и запускай GUI.
Какой поток должен быть главным - скорее всего не принципиально. Попробуй оба варианта.
Используй пару очередей (queue.Queue) для синхронизации между потоками.

Одна очередь будет периодически проверяться в потоке GUI с помощью root.after(). Почитай документацию на этот метод, но если коротко - он позволяет запланировать вызов функции в потоке GUI через время. Эта очередь будет содержать сведения о том, что нужно обновить в GUI.
Формат и смысл сведений определи сам. Это может быть что-то высокоуровневое в духе "есть сообщение от пользователя такого-то с таким-то текстом", и пусть поток gui сам разбирается, что с этим делать. Это может быть и что-то более низкоуровневое, типа "задай такому-то свойству у такого-то элемента управления такое-то значение". Я бы посоветовал первый вариант - он позволит разделить логику программы чётко на две части.
Код проверки будет примерно вида
def check_gui_queue():
    try:
        while True:
            command = gui_queue.get_nowait()  # проверяем, есть ли команда для GUI
            gui_queue.task_done()  # на каждый успешный вызов get() - один вызов task_done()
            pass  # как-то обрабатываем команду
    except queue.Empty:  # команды нет
        root.after(100, check_gui_queue)  # даём GUI поработать спокойно 100 мс

Разумеется, если ты завернёшь GUI в класс (что имеет смысл) код немного изменится. Но суть останется прежней.

Другая очередь будет содержать команды для бота. Периодически проверяй её в потоке asyncio с помощью простого кода вида
async def check_bot_queue():
    while True:
        try:
            command = bot_queue.get_nowait()  # проверяем, есть ли новая команда для бота
        except queue.Empty:
            await asyncio.sleep(0.1)  # нет - даём другим корутинам поработать 100 мс
        else:
            bot_queue.task_done()  # на каждый успешный вызов get() - один вызов task_done()
            pass  # есть - как-то её обрабатываем

Эту корутину запустишь через create_task(), и она позволит коду GUI организовывать вызовы в коде бота.

Разумеется, придётся подумать, как аккуратно сделать, чтобы эти две функции не разрослись в дикую простыню из if-elif-else. Но общий принцип примерно такой.
Время ожидания команды можно увеличить - это замедлит время реакции пре передаче данных между потоками, но уменьшит холостую нагрузку на систему.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы