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


да, я понимаю, это спагетти-код, но если можете, то прошу помощи
  • Вопрос задан
  • 169 просмотров
Решения вопроса 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. Но общий принцип примерно такой.
Время ожидания команды можно увеличить - это замедлит время реакции пре передаче данных между потоками, но уменьшит холостую нагрузку на систему.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

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

Войти через центр авторизации
Похожие вопросы