From a6a5920311a5cf7715d3c4a82ae399edd6de8c87 Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Thu, 31 Jul 2025 10:06:53 +0100
Subject: [PATCH 01/21] Update WIPBOT.py
---
WIPBOT.py | 120 +++++++++++++++++++++++++++++-------------------------
1 file changed, 65 insertions(+), 55 deletions(-)
diff --git a/WIPBOT.py b/WIPBOT.py
index 833755e..84ad1d9 100644
--- a/WIPBOT.py
+++ b/WIPBOT.py
@@ -96,23 +96,6 @@ CLASSES_POR_ROLE = {
("Frost Mage", "https://wow.zamimg.com/images/wow/icons/large/spell_frost_frostbolt02.jpg")
]
}
-
-class ClasseDropdown(Select):
- def __init__(self, role, jogador):
- self.jogador = jogador
- options = [discord.SelectOption(label=nome, value=nome, description="", emoji=None) for nome, _ in CLASSES_POR_ROLE[role]]
- super().__init__(placeholder=f"Escolhe a classe ({role})", min_values=1, max_values=1, options=options)
-
- async def callback(self, interaction: discord.Interaction):
- classes_escolhidas[self.jogador] = self.values[0]
- mensagem = await interaction.channel.fetch_message(grupo_mensagem_id)
- await mensagem.edit(embed=format_grupo_embed())
- await interaction.response.send_message(f"Classe escolhida: **{self.values[0]}**", ephemeral=True)
-
-class ClasseView(View):
- def __init__(self, role, jogador):
- super().__init__(timeout=60)
- self.add_item(ClasseDropdown(role, jogador))
class DungeonDropdown(Select):
def __init__(self):
options = [discord.SelectOption(label=d, value=d) for d in DUNGEONS]
@@ -131,7 +114,7 @@ class DungeonDropdown(Select):
if all(alteracoes_feitas.values()):
nova_view.clear_items()
await mensagem.edit(embed=format_grupo_embed(), view=nova_view)
- await interaction.response.send_message(f"Dungeon atualizada para **{dungeon_escolhida}**", ephemeral=True)
+ await interaction.response.defer()
class DificuldadeDropdown(Select):
def __init__(self):
@@ -151,7 +134,7 @@ class DificuldadeDropdown(Select):
if all(alteracoes_feitas.values()):
nova_view.clear_items()
await mensagem.edit(embed=format_grupo_embed(), view=nova_view)
- await interaction.response.send_message(f"Dificuldade atualizada para **+{dificuldade_escolhida}**", ephemeral=True)
+ await interaction.response.defer()
class DataModal(Modal, title="Definir Data da Dungeon"):
dia = TextInput(label="Dia do mês (1-31)", placeholder="Ex: 6", max_length=2)
hora = TextInput(label="Hora (HH:MM)", placeholder="Ex: 22:30", max_length=5)
@@ -190,7 +173,7 @@ class DataModal(Modal, title="Definir Data da Dungeon"):
if all(alteracoes_feitas.values()):
nova_view.clear_items()
await mensagem.edit(embed=format_grupo_embed(), view=nova_view)
- await interaction.response.send_message(f"Data agendada: **{data_formatada} às {hora_escolhida}**", ephemeral=True)
+ await interaction.response.defer()
class BotaoData(Button):
def __init__(self):
@@ -208,6 +191,25 @@ class DungeonView(View):
self.add_item(DificuldadeDropdown())
if not alteracoes_feitas["data"]:
self.add_item(BotaoData())
+class ClasseDropdown(Select):
+ def __init__(self, role, jogador):
+ self.jogador = jogador
+ options = [discord.SelectOption(label=nome, value=nome) for nome, _ in CLASSES_POR_ROLE[role]]
+ super().__init__(placeholder=f"Escolhe a classe ({role})", options=options)
+
+ async def callback(self, interaction: discord.Interaction):
+ classes_escolhidas[self.jogador] = self.values[0]
+ canal = discord.utils.get(bot.get_all_channels(), id=self.ctx_channel_id)
+ mensagem = await canal.fetch_message(grupo_mensagem_id)
+ await mensagem.edit(embed=format_grupo_embed())
+ await interaction.response.defer()
+
+class ClasseView(View):
+ def __init__(self, role, jogador, ctx_channel_id):
+ super().__init__()
+ dropdown = ClasseDropdown(role, jogador)
+ dropdown.ctx_channel_id = ctx_channel_id
+ self.add_item(dropdown)
@bot.command(name="criargrupo")
async def criar_grupo(ctx):
@@ -231,63 +233,71 @@ async def criar_grupo(ctx):
async def on_raw_reaction_add(payload):
if payload.message_id != grupo_mensagem_id or payload.user_id == bot.user.id:
return
+
guild = bot.get_guild(payload.guild_id)
- member = guild.get_member(payload.user_id)
- if member is None:
- member = await guild.fetch_member(payload.user_id)
+ member = guild.get_member(payload.user_id) or await guild.fetch_member(payload.user_id)
nome = member.display_name
emoji = str(payload.emoji)
- role = None
- if emoji == EMOJI_TANK:
- role = "Tank"
- elif emoji == EMOJI_HEALER:
- role = "Healer"
- elif emoji == EMOJI_DPS:
- role = "DPS"
- if role:
- if len(inscritos[role]) >= LIMITES[role] and nome not in inscritos[role]:
- channel = bot.get_channel(payload.channel_id)
- mensagem = await channel.fetch_message(payload.message_id)
- await mensagem.remove_reaction(emoji, member)
- return
- for r in inscritos:
- if nome in inscritos[r]:
- inscritos[r].remove(nome)
- inscritos[role].append(nome)
+ role = {EMOJI_TANK: "Tank", EMOJI_HEALER: "Healer", EMOJI_DPS: "DPS"}.get(emoji)
+
+ if not role:
+ return
+
+ if len(inscritos[role]) >= LIMITES[role] and nome not in inscritos[role]:
channel = bot.get_channel(payload.channel_id)
mensagem = await channel.fetch_message(payload.message_id)
- await mensagem.edit(embed=format_grupo_embed())
- if role in CLASSES_POR_ROLE:
- await channel.send(f"{member.mention}, escolhe a tua classe:", view=ClasseView(role, nome))
+ await mensagem.remove_reaction(emoji, member)
+ return
+
+ for r in inscritos:
+ if nome in inscritos[r]:
+ inscritos[r].remove(nome)
+ inscritos[role].append(nome)
+
+ canal = bot.get_channel(payload.channel_id)
+ mensagem = await canal.fetch_message(payload.message_id)
+ await mensagem.edit(embed=format_grupo_embed())
+
+ if role in CLASSES_POR_ROLE:
+ try:
+ dm_channel = await member.create_dm()
+ await dm_channel.send(f"Olá {member.display_name}, escolhe a tua classe para {role}:",
+ view=ClasseView(role, nome, payload.channel_id))
+ except discord.Forbidden:
+ await canal.send(f"{member.mention}, ativa as DMs para receber opções do bot.")
@bot.event
async def on_raw_reaction_remove(payload):
if payload.message_id != grupo_mensagem_id or payload.user_id == bot.user.id:
return
+
guild = bot.get_guild(payload.guild_id)
- member = guild.get_member(payload.user_id)
- if member is None:
- member = await guild.fetch_member(payload.user_id)
+ member = guild.get_member(payload.user_id) or await guild.fetch_member(payload.user_id)
nome = member.display_name
emoji = str(payload.emoji)
- role = None
- if emoji == EMOJI_TANK:
- role = "Tank"
- elif emoji == EMOJI_HEALER:
- role = "Healer"
- elif emoji == EMOJI_DPS:
- role = "DPS"
+ role = {EMOJI_TANK: "Tank", EMOJI_HEALER: "Healer", EMOJI_DPS: "DPS"}.get(emoji)
+
if role and nome in inscritos[role]:
inscritos[role].remove(nome)
classes_escolhidas.pop(nome, None)
channel = bot.get_channel(payload.channel_id)
mensagem = await channel.fetch_message(payload.message_id)
await mensagem.edit(embed=format_grupo_embed())
+
if role in CLASSES_POR_ROLE:
- await channel.send(f"{member.mention}, escolhe a tua classe:", view=ClasseView(role, nome))
- if (len(inscritos["Tank"]) == LIMITES["Tank"] and len(inscritos["Healer"]) == LIMITES["Healer"] and len(inscritos["DPS"]) == LIMITES["DPS"]):
+ try:
+ await member.send(f"Escolhe a tua classe:", view=ClasseView(role, nome))
+ except discord.Forbidden:
+ await channel.send(f"{member.mention}, ativa as DMs para escolher a classe.")
+
+ if (
+ len(inscritos["Tank"]) == LIMITES["Tank"]
+ and len(inscritos["Healer"]) == LIMITES["Healer"]
+ and len(inscritos["DPS"]) == LIMITES["DPS"]
+ ):
await channel.send("🎉 Dungeon Encerrada! Parabéns <@Rixa>, felicidades 🎉")
+
@bot.event
async def on_ready():
print(f"Bot ligado como {bot.user}")
From 8db01e1cd69cb5dd4cd2214050dd0457c5e0acb5 Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Thu, 31 Jul 2025 10:08:17 +0100
Subject: [PATCH 02/21] Add files via upload
---
Procfile | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Procfile b/Procfile
index 02dab60..ff32438 100644
--- a/Procfile
+++ b/Procfile
@@ -1 +1,9 @@
+<<<<<<< HEAD
+<<<<<<< HEAD
python WIPBOT.py
+=======
+python3 WIPBOT.py
+>>>>>>> 8839ffd (first commit of Discord bot)
+=======
+python WIPBOT.py
+>>>>>>> 462e052 (Salvando alterações locais antes de rebase)
From 118f1eab7b630b6f88d73268ad8c9e446165b9ff Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Thu, 31 Jul 2025 10:09:29 +0100
Subject: [PATCH 03/21] Update Procfile
---
Procfile | 8 --------
1 file changed, 8 deletions(-)
diff --git a/Procfile b/Procfile
index ff32438..02dab60 100644
--- a/Procfile
+++ b/Procfile
@@ -1,9 +1 @@
-<<<<<<< HEAD
-<<<<<<< HEAD
python WIPBOT.py
-=======
-python3 WIPBOT.py
->>>>>>> 8839ffd (first commit of Discord bot)
-=======
-python WIPBOT.py
->>>>>>> 462e052 (Salvando alterações locais antes de rebase)
From cd50591edba07264485ea52c2abe3aab4bcda9bc Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Thu, 31 Jul 2025 10:10:27 +0100
Subject: [PATCH 04/21] Update Procfile
---
Procfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Procfile b/Procfile
index 02dab60..e593d15 100644
--- a/Procfile
+++ b/Procfile
@@ -1 +1 @@
-python WIPBOT.py
+web: python WIPBOT.py
From bb38308b43bcd6101f9388ed852b74ff8541f098 Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Thu, 31 Jul 2025 10:19:39 +0100
Subject: [PATCH 05/21] Update WIPBOT.py
---
WIPBOT.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/WIPBOT.py b/WIPBOT.py
index 84ad1d9..733d135 100644
--- a/WIPBOT.py
+++ b/WIPBOT.py
@@ -297,9 +297,11 @@ async def on_raw_reaction_remove(payload):
):
await channel.send("🎉 Dungeon Encerrada! Parabéns <@Rixa>, felicidades 🎉")
-
@bot.event
async def on_ready():
print(f"Bot ligado como {bot.user}")
-
+if os.getenv("RAILWAY_ENVIRONMENT") is None:
+ from dotenv import load_dotenv
+ load_dotenv()
bot.run(os.getenv("DISCORD_TOKEN"))
+
From c597b12ffecd596204cbbe55f4e7ec410f573d2a Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Fri, 1 Aug 2025 14:18:45 +0100
Subject: [PATCH 06/21] Update WIPBOT.py
---
WIPBOT.py | 237 ++++++++++++++++++++++++++++++++++--------------------
1 file changed, 148 insertions(+), 89 deletions(-)
diff --git a/WIPBOT.py b/WIPBOT.py
index 733d135..1e97b09 100644
--- a/WIPBOT.py
+++ b/WIPBOT.py
@@ -1,3 +1,4 @@
+#!pip install discord.py python-dotenv
import discord
from discord.ext import commands
from discord.ui import View, Select, Button, Modal, TextInput
@@ -5,6 +6,7 @@ from datetime import datetime, timedelta
import os
from dotenv import load_dotenv
load_dotenv()
+jogador_em_progresso = None
intents = discord.Intents.default()
intents.message_content = True
intents.reactions = True
@@ -54,9 +56,71 @@ def format_grupo_embed():
embed.add_field(name=f"{EMOJI_DPS} DPS", value=format_role("DPS"), inline=False)
embed.set_footer(text="Bot created by Kl3z")
return embed
+
+async def criar_grupo_customizado(canal):
+ global grupo_mensagem_id, inscritos, dungeon_escolhida, dificuldade_escolhida, data_formatada, hora_escolhida, alteracoes_feitas
+
+ inscritos = {"Tank": [], "Healer": [], "DPS": []}
+ classes_escolhidas.clear()
+ dungeon_escolhida = DUNGEONS[2]
+ dificuldade_escolhida = "10"
+ data_formatada = "06/08/2025"
+ hora_escolhida = "22:00"
+ alteracoes_feitas = {"dungeon": False, "dificuldade": False, "data": False}
+
+ embed = format_grupo_embed()
+ mensagem = await canal.send(embed=embed, view=DungeonView())
+ grupo_mensagem_id = mensagem.id
+
+ await canal.send(
+ "Escolher a tua **Role**:",
+ view=RoleView("Novo jogador", canal.id, grupo_mensagem_id)
+ )
+
+class StackDropdown(Select):
+ def __init__(self):
+ options = [
+ discord.SelectOption(label="Plate Stack"),
+ discord.SelectOption(label="Leather Stack"),
+ discord.SelectOption(label="Mail Stack"),
+ discord.SelectOption(label="Cloth Stack")
+ ]
+ super().__init__(placeholder="Escolhe o tipo de stack", options=options)
+
+ async def callback(self, interaction: discord.Interaction):
+ escolha = self.values[0]
+ forum_channel = discord.utils.get(interaction.guild.channels, name="lfg", type=discord.ChannelType.forum)
+
+ if not forum_channel:
+ await interaction.response.send_message("❌ Canal de fórum 'lfg' não encontrado ou não é do tipo fórum.", ephemeral=True)
+ return
+
+ # Cria o post no fórum
+ thread = await forum_channel.create_thread(name=escolha, content=escolha)
+
+ # Apaga a mensagem original com a combobox
+ await interaction.message.delete()
+
+ # Evita erro de timeout
+ await interaction.response.defer()
+
+
+
+
+
+@bot.command(name="bot")
+async def bot_command(ctx):
+ await ctx.send(view=StackView())
+
+class StackView(View):
+ def __init__(self):
+ super().__init__()
+ self.add_item(StackDropdown())
+
CLASSES_POR_ROLE = {
"Tank": [
("Protection Paladin", "https://wow.zamimg.com/images/wow/icons/large/spell_holy_avengershield.jpg"),
+ ("Protection Warrior", "https://wow.zamimg.com/images/wow/icons/large/spell_warrior_protetion_spec.jpg"),
("Blood Death Knight", "https://wow.zamimg.com/images/wow/icons/large/spell_deathknight_bloodpresence.jpg"),
("Vengeance Demon Hunter","https://wow.zamimg.com/images/wow/icons/large/ability_demonhunter_specdps.jpg"),
("Brewmaster Monk", "https://wow.zamimg.com/images/wow/icons/large/spell_monk_brewmaster_spec.jpg"),
@@ -113,7 +177,11 @@ class DungeonDropdown(Select):
nova_view = DungeonView()
if all(alteracoes_feitas.values()):
nova_view.clear_items()
+
await mensagem.edit(embed=format_grupo_embed(), view=nova_view)
+ if isinstance(interaction.channel, discord.Thread):
+ await interaction.channel.edit(name=f"{dungeon_escolhida} - Key {dificuldade_escolhida}")
+
await interaction.response.defer()
class DificuldadeDropdown(Select):
@@ -128,17 +196,19 @@ class DificuldadeDropdown(Select):
return
dificuldade_escolhida = self.values[0]
alteracoes_feitas["dificuldade"] = True
-
mensagem = await interaction.channel.fetch_message(grupo_mensagem_id)
nova_view = DungeonView()
if all(alteracoes_feitas.values()):
nova_view.clear_items()
await mensagem.edit(embed=format_grupo_embed(), view=nova_view)
+ if isinstance(interaction.channel, discord.Thread):
+ await interaction.channel.edit(name=f"{dungeon_escolhida} - Key {dificuldade_escolhida}")
+
await interaction.response.defer()
+
class DataModal(Modal, title="Definir Data da Dungeon"):
dia = TextInput(label="Dia do mês (1-31)", placeholder="Ex: 6", max_length=2)
hora = TextInput(label="Hora (HH:MM)", placeholder="Ex: 22:30", max_length=5)
-
async def on_submit(self, interaction: discord.Interaction):
global data_formatada, hora_escolhida
if alteracoes_feitas["data"]:
@@ -149,7 +219,6 @@ class DataModal(Modal, title="Definir Data da Dungeon"):
if not 1 <= dia_num <= 31:
raise ValueError("Dia fora do intervalo")
hora_val = datetime.strptime(self.hora.value, "%H:%M").strftime("%H:%M")
-
hoje = datetime.now()
mes = hoje.month
ano = hoje.year
@@ -159,29 +228,23 @@ class DataModal(Modal, title="Definir Data da Dungeon"):
mes = 1
ano += 1
data_completa = datetime(year=ano, month=mes, day=dia_num)
-
except Exception:
await interaction.response.send_message("❌ Data ou hora inválida.", ephemeral=True)
return
-
data_formatada = data_completa.strftime("%d/%m/%Y")
hora_escolhida = hora_val
alteracoes_feitas["data"] = True
-
mensagem = await interaction.channel.fetch_message(grupo_mensagem_id)
nova_view = DungeonView()
if all(alteracoes_feitas.values()):
nova_view.clear_items()
await mensagem.edit(embed=format_grupo_embed(), view=nova_view)
await interaction.response.defer()
-
class BotaoData(Button):
def __init__(self):
super().__init__(label="🗓️ Definir Data", style=discord.ButtonStyle.primary)
-
async def callback(self, interaction: discord.Interaction):
await interaction.response.send_modal(DataModal())
-
class DungeonView(View):
def __init__(self):
super().__init__(timeout=None)
@@ -191,26 +254,88 @@ class DungeonView(View):
self.add_item(DificuldadeDropdown())
if not alteracoes_feitas["data"]:
self.add_item(BotaoData())
+class RoleDropdown(Select):
+ def __init__(self, jogador, ctx_channel_id, role_msg_id):
+ self.jogador = jogador
+ self.ctx_channel_id = ctx_channel_id
+ self.role_msg_id = role_msg_id
+ options = []
+ for role in LIMITES:
+ if len(inscritos[role]) < LIMITES[role]:
+ options.append(discord.SelectOption(label=role, description="Escolher esta role"))
+
+ placeholder = "Escolhe a tua role" if options else "Todas as roles preenchidas"
+ super().__init__(placeholder=placeholder, options=options, disabled=(len(options) == 0))
+
+ async def callback(self, interaction: discord.Interaction):
+ global jogador_em_progresso
+ jogador = interaction.user.display_name
+ if jogador_em_progresso and jogador_em_progresso != jogador:
+ await interaction.response.send_message(
+ f"⏳ Aguarda que {jogador_em_progresso} termine a escolha da classe.",
+ ephemeral=True
+ )
+ return
+ jogador_em_progresso = jogador
+ role = self.values[0]
+ for r in inscritos:
+ if jogador in inscritos[r]:
+ inscritos[r].remove(jogador)
+ if jogador not in inscritos[role] and len(inscritos[role]) < LIMITES[role]:
+ inscritos[role].append(jogador)
+ canal = bot.get_channel(self.ctx_channel_id)
+ if isinstance(canal, discord.Thread):
+ await canal.join()
+ await interaction.message.delete()
+ mensagem = await canal.fetch_message(grupo_mensagem_id)
+ await mensagem.edit(embed=format_grupo_embed())
+ await canal.send(
+ f"{jogador}, agora escolhe a tua classe para **{role}**:",
+ view=ClasseView(role, jogador, self.ctx_channel_id)
+ )
+ await interaction.response.defer()
+ else:
+ jogador_em_progresso = None
+ await interaction.response.send_message("❌ Esta role já está cheia ou ocorreu um erro.", ephemeral=True)
class ClasseDropdown(Select):
def __init__(self, role, jogador):
self.jogador = jogador
options = [discord.SelectOption(label=nome, value=nome) for nome, _ in CLASSES_POR_ROLE[role]]
super().__init__(placeholder=f"Escolhe a classe ({role})", options=options)
-
async def callback(self, interaction: discord.Interaction):
+ global jogador_em_progresso
classes_escolhidas[self.jogador] = self.values[0]
- canal = discord.utils.get(bot.get_all_channels(), id=self.ctx_channel_id)
+ canal = interaction.channel
mensagem = await canal.fetch_message(grupo_mensagem_id)
await mensagem.edit(embed=format_grupo_embed())
+ await interaction.message.delete()
+ jogador_em_progresso = None
+ if any(len(inscritos[r]) < LIMITES[r] for r in LIMITES):
+ await canal.send(
+ "Escolher a tua **Role**:",
+ view=RoleView(interaction.user.display_name, canal.id, grupo_mensagem_id)
+ )
await interaction.response.defer()
-
class ClasseView(View):
def __init__(self, role, jogador, ctx_channel_id):
super().__init__()
dropdown = ClasseDropdown(role, jogador)
dropdown.ctx_channel_id = ctx_channel_id
self.add_item(dropdown)
-
+import asyncio
+async def agendar_remocao_thread(thread: discord.Thread, data_str: str, hora_str: str):
+ try:
+ alvo = datetime.strptime(f"{data_str} {hora_str}", "%d/%m/%Y %H:%M")
+ agora = datetime.now()
+ segundos_ate_apagar = (alvo + timedelta(minutes=30) - agora).total_seconds()
+ if segundos_ate_apagar > 0:
+ await asyncio.sleep(segundos_ate_apagar)
+ await thread.delete()
+ print(f"[INFO] Thread '{thread.name}' apagada automaticamente após 30 minutos.")
+ else:
+ print(f"[INFO] Tempo já passou, thread '{thread.name}' não foi agendada.")
+ except Exception as e:
+ print(f"[ERRO] ao agendar remoção: {e}")
@bot.command(name="criargrupo")
async def criar_grupo(ctx):
global grupo_mensagem_id, inscritos, dungeon_escolhida, dificuldade_escolhida, data_formatada, hora_escolhida, alteracoes_feitas
@@ -221,87 +346,21 @@ async def criar_grupo(ctx):
data_formatada = "06/08/2025"
hora_escolhida = "22:00"
alteracoes_feitas = {"dungeon": False, "dificuldade": False, "data": False}
-
embed = format_grupo_embed()
mensagem = await ctx.send(embed=embed, view=DungeonView())
grupo_mensagem_id = mensagem.id
- await mensagem.add_reaction(EMOJI_TANK)
- await mensagem.add_reaction(EMOJI_HEALER)
- await mensagem.add_reaction(EMOJI_DPS)
-
-@bot.event
-async def on_raw_reaction_add(payload):
- if payload.message_id != grupo_mensagem_id or payload.user_id == bot.user.id:
- return
-
- guild = bot.get_guild(payload.guild_id)
- member = guild.get_member(payload.user_id) or await guild.fetch_member(payload.user_id)
- nome = member.display_name
- emoji = str(payload.emoji)
- role = {EMOJI_TANK: "Tank", EMOJI_HEALER: "Healer", EMOJI_DPS: "DPS"}.get(emoji)
-
- if not role:
- return
-
- if len(inscritos[role]) >= LIMITES[role] and nome not in inscritos[role]:
- channel = bot.get_channel(payload.channel_id)
- mensagem = await channel.fetch_message(payload.message_id)
- await mensagem.remove_reaction(emoji, member)
- return
-
- for r in inscritos:
- if nome in inscritos[r]:
- inscritos[r].remove(nome)
- inscritos[role].append(nome)
-
- canal = bot.get_channel(payload.channel_id)
- mensagem = await canal.fetch_message(payload.message_id)
- await mensagem.edit(embed=format_grupo_embed())
-
- if role in CLASSES_POR_ROLE:
- try:
- dm_channel = await member.create_dm()
- await dm_channel.send(f"Olá {member.display_name}, escolhe a tua classe para {role}:",
- view=ClasseView(role, nome, payload.channel_id))
- except discord.Forbidden:
- await canal.send(f"{member.mention}, ativa as DMs para receber opções do bot.")
-
-@bot.event
-async def on_raw_reaction_remove(payload):
- if payload.message_id != grupo_mensagem_id or payload.user_id == bot.user.id:
- return
-
- guild = bot.get_guild(payload.guild_id)
- member = guild.get_member(payload.user_id) or await guild.fetch_member(payload.user_id)
- nome = member.display_name
- emoji = str(payload.emoji)
- role = {EMOJI_TANK: "Tank", EMOJI_HEALER: "Healer", EMOJI_DPS: "DPS"}.get(emoji)
-
- if role and nome in inscritos[role]:
- inscritos[role].remove(nome)
- classes_escolhidas.pop(nome, None)
- channel = bot.get_channel(payload.channel_id)
- mensagem = await channel.fetch_message(payload.message_id)
- await mensagem.edit(embed=format_grupo_embed())
-
- if role in CLASSES_POR_ROLE:
- try:
- await member.send(f"Escolhe a tua classe:", view=ClasseView(role, nome))
- except discord.Forbidden:
- await channel.send(f"{member.mention}, ativa as DMs para escolher a classe.")
-
- if (
- len(inscritos["Tank"]) == LIMITES["Tank"]
- and len(inscritos["Healer"]) == LIMITES["Healer"]
- and len(inscritos["DPS"]) == LIMITES["DPS"]
- ):
- await channel.send("🎉 Dungeon Encerrada! Parabéns <@Rixa>, felicidades 🎉")
-
+ if isinstance(ctx.channel, discord.Thread):
+ asyncio.create_task(agendar_remocao_thread(ctx.channel, data_formatada, hora_escolhida))
+ role_msg = await ctx.send(
+ "Escolher a tua **Role**:",
+ view=RoleView(ctx.author.display_name, ctx.channel.id, grupo_mensagem_id)
+ )
+class RoleView(View):
+ def __init__(self, jogador, ctx_channel_id, role_msg_id):
+ super().__init__(timeout=None)
+ self.add_item(RoleDropdown(jogador, ctx_channel_id, role_msg_id))
@bot.event
async def on_ready():
print(f"Bot ligado como {bot.user}")
-if os.getenv("RAILWAY_ENVIRONMENT") is None:
- from dotenv import load_dotenv
- load_dotenv()
bot.run(os.getenv("DISCORD_TOKEN"))
From 850cc6b655c54a870525d323728a7fcd70402884 Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Fri, 1 Aug 2025 14:22:39 +0100
Subject: [PATCH 07/21] Update README.md
---
README.md | 103 ++++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 85 insertions(+), 18 deletions(-)
diff --git a/README.md b/README.md
index 33d4604..ee020ce 100644
--- a/README.md
+++ b/README.md
@@ -1,31 +1,98 @@
-# 🛡️ WIP Dungeon Organizer Bot — by kl3z
+🧙 World of Warcraft Dungeon Group Bot (WoW LFG Bot)
+Este é um bot desenvolvido em Python com discord.py, desenhado para automatizar e organizar a criação de grupos de dungeons míticas+ (Mythic+) no Discord, facilitando a marcação, inscrição e gestão de jogadores segundo as suas roles e classes no World of Warcraft.
-Bot para Discord criado para a comunidade **Work-in-Progress (WIP)**, que permite aos jogadores de **World of Warcraft** organizarem-se automaticamente em grupos para dungeons, de forma prática e interativa.
+⚙️ Funcionalidades principais
+🎯 Criação de Grupos com embed interativo que mostra:
----
+Dungeon escolhida
-## ✨ Funcionalidades
+Nível da chave (+X)
-- 🎭 **Reações por role** – jogadores escolhem ser:
- - 🛡️ **Tank**
- - 💚 **Healer**
- - ⚔️ **DPS**
+Data e hora da marcação
-- 📊 **Limites automáticos por role**:
- - 1 Tank, 1 Healer e 3 DPS por grupo.
+Lista dinâmica de jogadores por role e classe
-- 🧙 **Seleção de classes** com dropdowns personalizados, incluindo ícones das classes.
+🧩 Inscrição por Role (Tank, Healer, DPS) com limite automático:
-- 🏰 **Escolha da dungeon e dificuldade da mesma** (de 0 a 20+) via menus interativos.
+1 Tank
-- 📅 **Definição de data e hora** através de modal intuitivo.
+1 Healer
-- ♻️ **Atualização dinâmica** do embed com os inscritos e respetivas classes.
+3 DPS
----
+🧙 Escolha de Classe apenas após escolher a role, com ícones e nomes específicos de cada especialização.
-## ⚙️ Comando principal
+⛔ Sistema de bloqueio por jogador:
-```bash
-!criargrupo
+Apenas um jogador pode inscrever-se de cada vez.
+
+O processo de escolha de role é bloqueado até que a classe seja selecionada.
+
+🏰 Seleção de Dungeon e Dificuldade com menus suspensos (SelectDropdown)
+
+Apenas configurável uma vez
+
+O título do post (thread) é atualizado com a dungeon e dificuldade
+
+📆 Definição de Data e Hora com Modal
+
+Garante que os grupos sejam marcados para o futuro
+
+🧹 Autoapagamento do Post
+
+O post do grupo é automaticamente apagado 30 minutos após a hora marcada
+
+📬 Criação de Posts no canal de fórum
+
+Permite criar threads com nomes customizados e eliminar menus após uso
+
+💡 Tecnologias utilizadas
+discord.py (API de bots do Discord)
+
+python-dotenv (Gestão de tokens e variáveis de ambiente)
+
+asyncio (tarefas agendadas como apagar post)
+
+datetime (gestão de datas e marcações)
+
+Discord UI components (botões, dropdowns, modals)
+
+🚀 Como usar
+Cria um servidor com canal do tipo fórum chamado lfg
+
+Usa o comando /criargrupo ou !criargrupo para iniciar um novo grupo
+
+Os jogadores devem escolher a role → classe → e ficam inscritos
+
+Dungeon, dificuldade e data são definidas com menus intuitivos
+
+O grupo é removido automaticamente 30 minutos após a hora da run
+
+📌 Exemplo visual
+plaintext
+Copy
+Edit
+Dungeon: The Dawnbreaker
+Dificuldade: 12
+Marcação: 08/08/2025 às 21:30
+
+🛡️ Tank
+- João (Protection Paladin)
+
+💚 Healer
+- Maria (Restoration Druid)
+
+⚔️ DPS
+- Ana (Fire Mage)
+- Rui (Outlaw Rogue)
+- Pedro (Marksmanship Hunter)
+🔐 Segurança
+O bot respeita os limites de cada role
+
+Impede que mais do que um jogador escolha role em simultâneo
+
+Elimina interações após uso para evitar spam
+
+👨💻 Desenvolvido por
+Kl3z – este projeto é open-source e pode ser adaptado para qualquer comunidade WoW.
From 616df57af318bbcbac882d15caf11962c79b23e2 Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Fri, 1 Aug 2025 14:23:10 +0100
Subject: [PATCH 08/21] Update README.md
---
README.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index ee020ce..39ba73a 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,8 @@
-🧙 World of Warcraft Dungeon Group Bot (WoW LFG Bot)
+#🧙 World of Warcraft Dungeon Group Bot (WoW LFG Bot)
+
Este é um bot desenvolvido em Python com discord.py, desenhado para automatizar e organizar a criação de grupos de dungeons míticas+ (Mythic+) no Discord, facilitando a marcação, inscrição e gestão de jogadores segundo as suas roles e classes no World of Warcraft.
-⚙️ Funcionalidades principais
+##⚙️ Funcionalidades principais
🎯 Criação de Grupos com embed interativo que mostra:
Dungeon escolhida
From 33b62cd3095e0341b27bfc3e112e86d434b735da Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Fri, 1 Aug 2025 14:27:07 +0100
Subject: [PATCH 09/21] Update README.md
---
README.md | 39 +++++++++++++++++++--------------------
1 file changed, 19 insertions(+), 20 deletions(-)
diff --git a/README.md b/README.md
index 39ba73a..7b35327 100644
--- a/README.md
+++ b/README.md
@@ -1,51 +1,50 @@
-#🧙 World of Warcraft Dungeon Group Bot (WoW LFG Bot)
+# World of Warcraft Dungeon Group Bot (WoW LFG Bot)
Este é um bot desenvolvido em Python com discord.py, desenhado para automatizar e organizar a criação de grupos de dungeons míticas+ (Mythic+) no Discord, facilitando a marcação, inscrição e gestão de jogadores segundo as suas roles e classes no World of Warcraft.
-##⚙️ Funcionalidades principais
+## Funcionalidades principais
+
🎯 Criação de Grupos com embed interativo que mostra:
-Dungeon escolhida
+- *Dungeon escolhida*
-Nível da chave (+X)
+- *Nível da chave (+X)*
-Data e hora da marcação
+- *Data e hora da marcação*
-Lista dinâmica de jogadores por role e classe
+- *Lista dinâmica de jogadores por role e classe*
🧩 Inscrição por Role (Tank, Healer, DPS) com limite automático:
-1 Tank
+- *1 Tank*
-1 Healer
+- *1 Healer*
-3 DPS
+- *3 DPS*
-🧙 Escolha de Classe apenas após escolher a role, com ícones e nomes específicos de cada especialização.
+ 🧙 Escolha de Classe apenas após escolher a role, com ícones e nomes específicos de cada especialização.
⛔ Sistema de bloqueio por jogador:
-Apenas um jogador pode inscrever-se de cada vez.
+- *Apenas um jogador pode inscrever-se de cada vez.*
-O processo de escolha de role é bloqueado até que a classe seja selecionada.
+- *O processo de escolha de role é bloqueado até que a classe seja selecionada.*
🏰 Seleção de Dungeon e Dificuldade com menus suspensos (SelectDropdown)
-Apenas configurável uma vez
+- *Apenas configurável uma vez*
-O título do post (thread) é atualizado com a dungeon e dificuldade
+- *O título do post (thread) é atualizado com a dungeon e dificuldade*
📆 Definição de Data e Hora com Modal
-Garante que os grupos sejam marcados para o futuro
-
-🧹 Autoapagamento do Post
-
-O post do grupo é automaticamente apagado 30 minutos após a hora marcada
+- *Garante que os grupos sejam marcados para o futuro*
📬 Criação de Posts no canal de fórum
-Permite criar threads com nomes customizados e eliminar menus após uso
+- *Permite criar threads com nomes customizados e eliminar menus após uso*
+
+ *Comando !bot*
💡 Tecnologias utilizadas
discord.py (API de bots do Discord)
From 467ed43752ea61626640cdd730350cebab0ed4e1 Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Fri, 1 Aug 2025 14:35:20 +0100
Subject: [PATCH 10/21] Update README.md
---
README.md | 49 ++++++++++++++++---------------------------------
1 file changed, 16 insertions(+), 33 deletions(-)
diff --git a/README.md b/README.md
index 7b35327..68ae08d 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ Este é um bot desenvolvido em Python com discord.py, desenhado para automatizar
- *3 DPS*
- 🧙 Escolha de Classe apenas após escolher a role, com ícones e nomes específicos de cada especialização.
+ 🧙 Escolha de Classe apenas após escolher a role, com ícones e nomes específicos de cada especialização.
⛔ Sistema de bloqueio por jogador:
@@ -43,55 +43,38 @@ Este é um bot desenvolvido em Python com discord.py, desenhado para automatizar
📬 Criação de Posts no canal de fórum
- *Permite criar threads com nomes customizados e eliminar menus após uso*
+- *Comando*
- *Comando !bot*
+ !bot
-💡 Tecnologias utilizadas
-discord.py (API de bots do Discord)
+💡 Bibliotecas utilizadas
+- [discord.py](https://discordpy.readthedocs.io/en/stable/) (API de bots do Discord)
-python-dotenv (Gestão de tokens e variáveis de ambiente)
+- [python-dotenv](https://pypi.org/project/python-dotenv/) (Gestão de tokens e variáveis de ambiente)
-asyncio (tarefas agendadas como apagar post)
+- [asyncio](https://docs.python.org/3/library/asyncio.html) (tarefas agendadas como apagar post)
-datetime (gestão de datas e marcações)
-
-Discord UI components (botões, dropdowns, modals)
+- [datetime](https://docs.python.org/3/library/datetime.html) (gestão de datas e marcações)
🚀 Como usar
-Cria um servidor com canal do tipo fórum chamado lfg
+1. Cria um servidor com canal do tipo fórum chamado lfg
-Usa o comando /criargrupo ou !criargrupo para iniciar um novo grupo
+2. Usa o comando /criargrupo ou !criargrupo para iniciar um novo grupo
-Os jogadores devem escolher a role → classe → e ficam inscritos
+3. Os jogadores devem escolher a role → classe → e ficam inscritos
-Dungeon, dificuldade e data são definidas com menus intuitivos
-
-O grupo é removido automaticamente 30 minutos após a hora da run
+4. Dungeon, dificuldade e data são definidas com menus intuitivos
📌 Exemplo visual
-plaintext
-Copy
-Edit
-Dungeon: The Dawnbreaker
-Dificuldade: 12
-Marcação: 08/08/2025 às 21:30
-🛡️ Tank
-- João (Protection Paladin)
+
-💚 Healer
-- Maria (Restoration Druid)
-
-⚔️ DPS
-- Ana (Fire Mage)
-- Rui (Outlaw Rogue)
-- Pedro (Marksmanship Hunter)
🔐 Segurança
-O bot respeita os limites de cada role
+- *O bot respeita os limites de cada role*
-Impede que mais do que um jogador escolha role em simultâneo
+- *Impede que mais do que um jogador escolha role em simultâneo*
-Elimina interações após uso para evitar spam
+- *Elimina interações após uso para evitar spam*
👨💻 Desenvolvido por
Kl3z – este projeto é open-source e pode ser adaptado para qualquer comunidade WoW.
From b0e6a28b3d1392ecc24da427d80d2224a7296683 Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Fri, 1 Aug 2025 14:41:30 +0100
Subject: [PATCH 11/21] Update README.md
---
README.md | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 68ae08d..5392651 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ Este é um bot desenvolvido em Python com discord.py, desenhado para automatizar
🚀 Como usar
1. Cria um servidor com canal do tipo fórum chamado lfg
-2. Usa o comando /criargrupo ou !criargrupo para iniciar um novo grupo
+2. Usa o comando `/criargrupo` ou `!criargrupo` para iniciar um novo grupo
3. Os jogadores devem escolher a role → classe → e ficam inscritos
@@ -69,6 +69,13 @@ Este é um bot desenvolvido em Python com discord.py, desenhado para automatizar
+5. Adicionalmente pode ser adicionado o bot num canal do discord e com o comando `/bot` ou `!bot` ele cria um post no forum lfg.
+
+6. Com esta opção permite ainda ao user introduzir o tipo de stack que enventualmente poderá querer.
+
+
+
+
🔐 Segurança
- *O bot respeita os limites de cada role*
From 7695fedc6b336e680d4292e1e9fbbde74c7b1088 Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Fri, 1 Aug 2025 14:46:02 +0100
Subject: [PATCH 12/21] Update README.md
---
README.md | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 5392651..5aa27f5 100644
--- a/README.md
+++ b/README.md
@@ -61,17 +61,25 @@ Este é um bot desenvolvido em Python com discord.py, desenhado para automatizar
2. Usa o comando `/criargrupo` ou `!criargrupo` para iniciar um novo grupo
-3. Os jogadores devem escolher a role → classe → e ficam inscritos
+```bash
+!criargrupo
+```
-4. Dungeon, dificuldade e data são definidas com menus intuitivos
+4. Os jogadores devem escolher a role → classe → e ficam inscritos
+
+5. Dungeon, dificuldade e data são definidas com menus intuitivos
📌 Exemplo visual
5. Adicionalmente pode ser adicionado o bot num canal do discord e com o comando `/bot` ou `!bot` ele cria um post no forum lfg.
+
+```bash
+!bot
+```
-6. Com esta opção permite ainda ao user introduzir o tipo de stack que enventualmente poderá querer.
+7. Com esta opção permite ainda ao user introduzir o tipo de stack que enventualmente poderá querer.
From b7f0a65644cdab9fd9bf667d77b6b9dec5d9418b Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Fri, 1 Aug 2025 21:29:19 +0100
Subject: [PATCH 13/21] Update README.md
---
README.md | 105 +++++++++++++++++++++++++++---------------------------
1 file changed, 53 insertions(+), 52 deletions(-)
diff --git a/README.md b/README.md
index 5aa27f5..c091be6 100644
--- a/README.md
+++ b/README.md
@@ -1,96 +1,97 @@
-# World of Warcraft Dungeon Group Bot (WoW LFG Bot)
+World of Warcraft Dungeon Group Bot (WoW LFG Bot)
+This is a Python bot developed with discord.py, designed to automate and organize the creation of Mythic+ dungeon groups in Discord, simplifying the scheduling, sign-up, and player management process based on their roles and classes in World of Warcraft.
-Este é um bot desenvolvido em Python com discord.py, desenhado para automatizar e organizar a criação de grupos de dungeons míticas+ (Mythic+) no Discord, facilitando a marcação, inscrição e gestão de jogadores segundo as suas roles e classes no World of Warcraft.
+Main Features
+🎯 Group creation with interactive embed displaying:
-## Funcionalidades principais
+Selected dungeon
-🎯 Criação de Grupos com embed interativo que mostra:
+Keystone level (+X)
-- *Dungeon escolhida*
+Scheduled date and time
-- *Nível da chave (+X)*
+Dynamic player list by role and class
-- *Data e hora da marcação*
+🧩 Role signup (Tank, Healer, DPS) with automatic limits:
-- *Lista dinâmica de jogadores por role e classe*
+1 Tank
-🧩 Inscrição por Role (Tank, Healer, DPS) com limite automático:
+1 Healer
-- *1 Tank*
+3 DPS
-- *1 Healer*
+🧙 Class selection is only available after choosing a role, with specific icons and names for each specialization.
-- *3 DPS*
+⛔ Player lock system:
- 🧙 Escolha de Classe apenas após escolher a role, com ícones e nomes específicos de cada especialização.
+Only one player can sign up at a time.
-⛔ Sistema de bloqueio por jogador:
+The role selection process is blocked until the class is chosen.
-- *Apenas um jogador pode inscrever-se de cada vez.*
+🏰 Dungeon and Difficulty selection via dropdown menus (SelectDropdown):
-- *O processo de escolha de role é bloqueado até que a classe seja selecionada.*
+Configurable only once
-🏰 Seleção de Dungeon e Dificuldade com menus suspensos (SelectDropdown)
+The thread title is updated with the selected dungeon and difficulty
-- *Apenas configurável uma vez*
+📆 Date and Time setup via Modal:
-- *O título do post (thread) é atualizado com a dungeon e dificuldade*
+Ensures the groups are scheduled for future times
-📆 Definição de Data e Hora com Modal
+📬 Forum channel post creation:
-- *Garante que os grupos sejam marcados para o futuro*
+Allows threads to be created with custom names and deletes dropdowns after use
-📬 Criação de Posts no canal de fórum
+Command:
-- *Permite criar threads com nomes customizados e eliminar menus após uso*
-- *Comando*
+yaml
+Copy
+Edit
+ !bot
+💡 Libraries Used
- !bot
+discord.py (Discord bot API)
-💡 Bibliotecas utilizadas
-- [discord.py](https://discordpy.readthedocs.io/en/stable/) (API de bots do Discord)
+python-dotenv (Management of tokens and environment variables)
-- [python-dotenv](https://pypi.org/project/python-dotenv/) (Gestão de tokens e variáveis de ambiente)
+asyncio (Scheduled tasks like deleting threads)
-- [asyncio](https://docs.python.org/3/library/asyncio.html) (tarefas agendadas como apagar post)
+datetime (Date and time management)
-- [datetime](https://docs.python.org/3/library/datetime.html) (gestão de datas e marcações)
+🚀 How to Use
-🚀 Como usar
-1. Cria um servidor com canal do tipo fórum chamado lfg
+Create a server with a forum channel named lfg
-2. Usa o comando `/criargrupo` ou `!criargrupo` para iniciar um novo grupo
+Use the command /criargrupo or !criargrupo to start a new group:
-```bash
+bash
+Copy
+Edit
!criargrupo
-```
+Players must choose their role → class → and they are signed up
-4. Os jogadores devem escolher a role → classe → e ficam inscritos
+Dungeon, difficulty, and date are selected through intuitive menus
-5. Dungeon, dificuldade e data são definidas com menus intuitivos
-
-📌 Exemplo visual
+📌 Visual Example
+Additionally, the bot can be added to a channel, and using the command /bot or !bot, it creates a post in the lfg forum:
-5. Adicionalmente pode ser adicionado o bot num canal do discord e com o comando `/bot` ou `!bot` ele cria um post no forum lfg.
-
-```bash
+bash
+Copy
+Edit
!bot
-```
-
-7. Com esta opção permite ainda ao user introduzir o tipo de stack que enventualmente poderá querer.
+This option also allows the user to select the type of stack they may want:
+🔐 Security
+The bot respects the limits for each role
-🔐 Segurança
-- *O bot respeita os limites de cada role*
+Prevents more than one player from selecting a role at the same time
-- *Impede que mais do que um jogador escolha role em simultâneo*
+Removes interaction elements after use to avoid spam
-- *Elimina interações após uso para evitar spam*
-
-👨💻 Desenvolvido por
-Kl3z – este projeto é open-source e pode ser adaptado para qualquer comunidade WoW.
+👨💻 Developed by
+Kl3z – This project is open-source and can be adapted for any WoW community.
From 76c0efe1c422a96496a1557eee05b7c1cfbced76 Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Fri, 1 Aug 2025 21:34:59 +0100
Subject: [PATCH 14/21] Update README.md
---
README.md | 91 +++++++++++++++++++++++++++----------------------------
1 file changed, 45 insertions(+), 46 deletions(-)
diff --git a/README.md b/README.md
index c091be6..d82a619 100644
--- a/README.md
+++ b/README.md
@@ -1,97 +1,96 @@
-World of Warcraft Dungeon Group Bot (WoW LFG Bot)
+# World of Warcraft Dungeon Group Bot (WoW LFG Bot)
+
This is a Python bot developed with discord.py, designed to automate and organize the creation of Mythic+ dungeon groups in Discord, simplifying the scheduling, sign-up, and player management process based on their roles and classes in World of Warcraft.
-Main Features
-🎯 Group creation with interactive embed displaying:
+## Main Features
+###🎯 Group creation with interactive embed displaying:
-Selected dungeon
+- *Selected dungeon*
-Keystone level (+X)
+- *Keystone level (+X)*
-Scheduled date and time
+- *Scheduled date and time*
-Dynamic player list by role and class
+- *Dynamic player list by role and class*
-🧩 Role signup (Tank, Healer, DPS) with automatic limits:
+### 🧩 Role signup (Tank, Healer, DPS) with automatic limits:
-1 Tank
+- *1 Tank*
-1 Healer
+- *1 Healer*
-3 DPS
+- *3 DPS*
🧙 Class selection is only available after choosing a role, with specific icons and names for each specialization.
-⛔ Player lock system:
+### ⛔ Player lock system:
-Only one player can sign up at a time.
+- *Only one player can sign up at a time.*
-The role selection process is blocked until the class is chosen.
+- *The role selection process is blocked until the class is chosen.*
-🏰 Dungeon and Difficulty selection via dropdown menus (SelectDropdown):
+### 🏰 Dungeon and Difficulty selection via dropdown menus (SelectDropdown):
-Configurable only once
+- *Configurable only once*
-The thread title is updated with the selected dungeon and difficulty
+- *The thread title is updated with the selected dungeon and difficulty*
-📆 Date and Time setup via Modal:
+### 📆 Date and Time setup via Modal:
-Ensures the groups are scheduled for future times
+- *Ensures the groups are scheduled for future times*
-📬 Forum channel post creation:
+## 📬 Forum channel post creation:
-Allows threads to be created with custom names and deletes dropdowns after use
+- *Allows threads to be created with custom names and deletes dropdowns after use*
Command:
-yaml
-Copy
-Edit
+
!bot
-💡 Libraries Used
-discord.py (Discord bot API)
-python-dotenv (Management of tokens and environment variables)
+## 💡 Libraries Used
-asyncio (Scheduled tasks like deleting threads)
+- *discord.py (Discord bot API)*
-datetime (Date and time management)
+- *python-dotenv (Management of tokens and environment variables)*
-🚀 How to Use
+- *asyncio (Scheduled tasks like deleting threads)*
-Create a server with a forum channel named lfg
+- *datetime (Date and time management)*
-Use the command /criargrupo or !criargrupo to start a new group:
+## 🚀 How to Use
-bash
-Copy
-Edit
+- *Create a server with a forum channel named lfg*
+
+- *Use the command /criargrupo or !criargrupo to start a new group:*
+
+```bash
!criargrupo
-Players must choose their role → class → and they are signed up
+```
+- *Players must choose their role → class → and they are signed up*
-Dungeon, difficulty, and date are selected through intuitive menus
+- *Dungeon, difficulty, and date are selected through intuitive menus*
-📌 Visual Example
+## 📌 Visual Example
Additionally, the bot can be added to a channel, and using the command /bot or !bot, it creates a post in the lfg forum:
-bash
-Copy
-Edit
+```bash
!bot
+```
This option also allows the user to select the type of stack they may want:
-🔐 Security
+## 🔐 Security
-The bot respects the limits for each role
+- *The bot respects the limits for each role*
-Prevents more than one player from selecting a role at the same time
+- *Prevents more than one player from selecting a role at the same time*
-Removes interaction elements after use to avoid spam
+- *Removes interaction elements after use to avoid spam*
-👨💻 Developed by
+## 👨💻 Developed by
Kl3z – This project is open-source and can be adapted for any WoW community.
From e7e6c2ebc0ed061597cf81748df841d9d556673f Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Fri, 1 Aug 2025 21:35:10 +0100
Subject: [PATCH 15/21] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index d82a619..a4e8fa7 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
This is a Python bot developed with discord.py, designed to automate and organize the creation of Mythic+ dungeon groups in Discord, simplifying the scheduling, sign-up, and player management process based on their roles and classes in World of Warcraft.
## Main Features
-###🎯 Group creation with interactive embed displaying:
+### 🎯 Group creation with interactive embed displaying:
- *Selected dungeon*
From ca3623c71ee7a29b969fd07a7170a19e839e87f1 Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Fri, 1 Aug 2025 21:35:34 +0100
Subject: [PATCH 16/21] Update README.md
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index a4e8fa7..f8c9e30 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@
This is a Python bot developed with discord.py, designed to automate and organize the creation of Mythic+ dungeon groups in Discord, simplifying the scheduling, sign-up, and player management process based on their roles and classes in World of Warcraft.
## Main Features
+
### 🎯 Group creation with interactive embed displaying:
- *Selected dungeon*
From 22c369479abe2cf21bb02a57916031c253b36cd0 Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Fri, 1 Aug 2025 21:36:08 +0100
Subject: [PATCH 17/21] Update README.md
---
README.md | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index f8c9e30..f0fc7a3 100644
--- a/README.md
+++ b/README.md
@@ -44,11 +44,10 @@ This is a Python bot developed with discord.py, designed to automate and organiz
- *Allows threads to be created with custom names and deletes dropdowns after use*
-Command:
-
+```bash
!bot
-
+```
## 💡 Libraries Used
From 3e5838a1ca00ad85d651603ccc1100dbd0d53746 Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Sun, 3 Aug 2025 17:09:53 +0100
Subject: [PATCH 18/21] Update README.md
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index f0fc7a3..11d8c9b 100644
--- a/README.md
+++ b/README.md
@@ -94,3 +94,5 @@ This option also allows the user to select the type of stack they may want:
## 👨💻 Developed by
Kl3z – This project is open-source and can be adapted for any WoW community.
+[Bot](https://discord.com/oauth2/authorize?client_id=1400140850029133834&permissions=0&integration_type=0&scope=bot)
+
From 563d950e36f342ea06128f532c4f4a72dc3f56b3 Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Tue, 5 Aug 2025 08:50:53 +0100
Subject: [PATCH 19/21] Update README.md
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 11d8c9b..7116ac8 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,7 @@ Additionally, the bot can be added to a channel, and using the command /bot or !
This option also allows the user to select the type of stack they may want:
+
## 🔐 Security
- *The bot respects the limits for each role*
From fb4d4ee0257ff8e0fc827141d32aa319cef1d467 Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Tue, 5 Aug 2025 08:52:08 +0100
Subject: [PATCH 20/21] Update WIPBOT.py
---
WIPBOT.py | 435 ++++++++++++++++++++++++++++++++++--------------------
1 file changed, 276 insertions(+), 159 deletions(-)
diff --git a/WIPBOT.py b/WIPBOT.py
index 1e97b09..1b588a4 100644
--- a/WIPBOT.py
+++ b/WIPBOT.py
@@ -28,14 +28,16 @@ DUNGEONS = [
]
DIFICULDADES = [str(i) for i in range(0, 21)] + ["20+"]
LIMITES = {"Tank": 1, "Healer": 1, "DPS": 3}
-grupo_mensagem_id = None
-inscritos = {"Tank": [], "Healer": [], "DPS": []}
-classes_escolhidas = {}
-dungeon_escolhida = DUNGEONS[2]
-dificuldade_escolhida = "10"
-data_formatada = "06/08/2025"
-hora_escolhida = "22:00"
-alteracoes_feitas = {"dungeon": False, "dificuldade": False, "data": False}
+# grupo_mensagem_id = None
+# inscritos = {"Tank": [], "Healer": [], "DPS": []}
+# classes_escolhidas = {}
+# dungeon_escolhida = DUNGEONS[2]
+# dificuldade_escolhida = "10"
+# data_formatada = "06/08/2025"
+# hora_escolhida = "22:00"
+# alteracoes_feitas = {"dungeon": False, "dificuldade": False, "data": False}
+# jogador_em_progresso = None
+
def format_grupo_embed():
def format_role(role):
players = inscritos[role]
@@ -59,7 +61,6 @@ def format_grupo_embed():
async def criar_grupo_customizado(canal):
global grupo_mensagem_id, inscritos, dungeon_escolhida, dificuldade_escolhida, data_formatada, hora_escolhida, alteracoes_feitas
-
inscritos = {"Tank": [], "Healer": [], "DPS": []}
classes_escolhidas.clear()
dungeon_escolhida = DUNGEONS[2]
@@ -67,18 +68,16 @@ async def criar_grupo_customizado(canal):
data_formatada = "06/08/2025"
hora_escolhida = "22:00"
alteracoes_feitas = {"dungeon": False, "dificuldade": False, "data": False}
-
embed = format_grupo_embed()
mensagem = await canal.send(embed=embed, view=DungeonView())
grupo_mensagem_id = mensagem.id
-
await canal.send(
"Escolher a tua **Role**:",
view=RoleView("Novo jogador", canal.id, grupo_mensagem_id)
)
-
class StackDropdown(Select):
- def __init__(self):
+ def __init__(self, grupo):
+ self.grupo = grupo
options = [
discord.SelectOption(label="Plate Stack"),
discord.SelectOption(label="Leather Stack"),
@@ -90,128 +89,166 @@ class StackDropdown(Select):
async def callback(self, interaction: discord.Interaction):
escolha = self.values[0]
forum_channel = discord.utils.get(interaction.guild.channels, name="lfg", type=discord.ChannelType.forum)
-
if not forum_channel:
await interaction.response.send_message("❌ Canal de fórum 'lfg' não encontrado ou não é do tipo fórum.", ephemeral=True)
return
-
- # Cria o post no fórum
thread = await forum_channel.create_thread(name=escolha, content=escolha)
-
- # Apaga a mensagem original com a combobox
await interaction.message.delete()
-
- # Evita erro de timeout
await interaction.response.defer()
-
-
-
+class StackView(View):
+ def __init__(self, grupo):
+ super().__init__()
+ self.grupo = grupo
+ self.add_item(StackDropdown(grupo))
+
@bot.command(name="bot")
async def bot_command(ctx):
- await ctx.send(view=StackView())
-
-class StackView(View):
- def __init__(self):
- super().__init__()
- self.add_item(StackDropdown())
+ grupo_temporario = GrupoDungeon()
+ await ctx.send(view=StackView(grupo_temporario))
+
+ membro = discord.utils.get(ctx.guild.members, name="carlitosqt")
+ if membro:
+ await ctx.send(f"Parabéns {membro.mention}!")
CLASSES_POR_ROLE = {
"Tank": [
- ("Protection Paladin", "https://wow.zamimg.com/images/wow/icons/large/spell_holy_avengershield.jpg"),
- ("Protection Warrior", "https://wow.zamimg.com/images/wow/icons/large/spell_warrior_protetion_spec.jpg"),
- ("Blood Death Knight", "https://wow.zamimg.com/images/wow/icons/large/spell_deathknight_bloodpresence.jpg"),
- ("Vengeance Demon Hunter","https://wow.zamimg.com/images/wow/icons/large/ability_demonhunter_specdps.jpg"),
- ("Brewmaster Monk", "https://wow.zamimg.com/images/wow/icons/large/spell_monk_brewmaster_spec.jpg"),
- ("Guardian Druid", "https://wow.zamimg.com/images/wow/icons/large/ability_racial_bearform.jpg")
+ ("Paladin", "https://wow.zamimg.com/images/wow/icons/large/spell_holy_avengershield.jpg"),
+ ("Warrior", "https://wow.zamimg.com/images/wow/icons/large/spell_warrior_protetion_spec.jpg"),
+ ("Death Knight", "https://wow.zamimg.com/images/wow/icons/large/spell_deathknight_bloodpresence.jpg"),
+ ("Demon Hunter","https://wow.zamimg.com/images/wow/icons/large/ability_demonhunter_specdps.jpg"),
+ ("Monk", "https://wow.zamimg.com/images/wow/icons/large/spell_monk_brewmaster_spec.jpg"),
+ ("Druid", "https://wow.zamimg.com/images/wow/icons/large/ability_racial_bearform.jpg")
],
"Healer": [
- ("Discipline Priest", "https://wow.zamimg.com/images/wow/icons/large/spell_holy_powerwordshield.jpg"),
- ("Restoration Shaman", "https://wow.zamimg.com/images/wow/icons/large/spell_nature_magicimmunity.jpg"),
- ("Mistweaver Monk", "https://wow.zamimg.com/images/wow/icons/large/spell_monk_mistweaver_spec.jpg"),
- ("Restoration Druid", "https://wow.zamimg.com/images/wow/icons/large/spell_nature_healingtouch.jpg"),
- ("Holy Paladin", "https://wow.zamimg.com/images/wow/icons/large/spell_holy_holybolt.jpg"),
- ("Preservation Evoker", "https://wow.zamimg.com/images/wow/icons/large/spell_spec_evoker_preservation.jpg")
+ ("Priest", "https://wow.zamimg.com/images/wow/icons/large/spell_holy_powerwordshield.jpg"),
+ ("Shaman", "https://wow.zamimg.com/images/wow/icons/large/spell_nature_magicimmunity.jpg"),
+ ("Monk", "https://wow.zamimg.com/images/wow/icons/large/spell_monk_mistweaver_spec.jpg"),
+ ("Druid", "https://wow.zamimg.com/images/wow/icons/large/spell_nature_healingtouch.jpg"),
+ ("Paladin", "https://wow.zamimg.com/images/wow/icons/large/spell_holy_holybolt.jpg"),
+ ("Evoker", "https://wow.zamimg.com/images/wow/icons/large/spell_spec_evoker_preservation.jpg")
],
"DPS": [
- ("Arcane Mage", "https://wow.zamimg.com/images/wow/icons/large/spell_holy_magicalsentry.jpg"),
- ("Balance Druid", "https://wow.zamimg.com/images/wow/icons/large/spell_nature_starfall.jpg"),
- ("Unholy Death Knight", "https://wow.zamimg.com/images/wow/icons/large/spell_deathknight_unholypresence.png"),
- ("Retribution Paladin", "https://wow.zamimg.com/images/wow/icons/large/spell_holy_auraoflight.jpg"),
- ("Fire Mage", "https://wow.zamimg.com/images/wow/icons/large/spell_fire_flamebolt.jpg"),
- ("Elemental Shaman", "https://wow.zamimg.com/images/wow/icons/large/spell_nature_lightning.jpg"),
- ("Enhancement Shaman", "https://wow.zamimg.com/images/wow/icons/large/spell_shaman_improvedstormstrike.jpg"),
- ("Marksmanship Hunter", "https://wow.zamimg.com/images/wow/icons/large/ability_hunter_focusedaim.jpg"),
- ("Havoc Demon Hunter", "https://wow.zamimg.com/images/wow/icons/large/ability_demonhunter_specdps.jpg"),
- ("Beast Mastery Hunter", "https://wow.zamimg.com/images/wow/icons/large/ability_hunter_bestialdiscipline.jpg"),
- ("Assassination Rogue", "https://wow.zamimg.com/images/wow/icons/large/ability_rogue_deadlybrew.jpg"),
- ("Outlaw Rogue", "https://wow.zamimg.com/images/wow/icons/large/ability_rogue_waylay.jpg"),
- ("Shadow Priest", "https://wow.zamimg.com/images/wow/icons/large/spell_shadow_shadowwordpain.jpg"),
- ("Frost Death Knight", "https://wow.zamimg.com/images/wow/icons/large/spell_deathknight_frostpresence.png"),
- ("Windwalker Monk", "https://wow.zamimg.com/images/wow/icons/large/spell_monk_windwalkerspec.jpg"),
- ("Fury Warrior", "https://wow.zamimg.com/images/wow/icons/large/ability_warrior_innerrage.jpg"),
- ("Arms Warrior", "https://wow.zamimg.com/images/wow/icons/large/ability_warrior_savageblow.jpg"),
- ("Affliction Warlock", "https://wow.zamimg.com/images/wow/icons/large/spell_shadow_deathcoil.jpg"),
- ("Destruction Warlock", "https://wow.zamimg.com/images/wow/icons/large/spell_shadow_rainoffire.jpg"),
- ("Feral Druid", "https://wow.zamimg.com/images/wow/icons/large/ability_druid_catform.jpg"),
- ("Devastation Evoker", "https://wow.zamimg.com/images/wow/icons/large/spell_spec_evoker_devastation.jpg"),
- ("Survival Hunter", "https://wow.zamimg.com/images/wow/icons/large/ability_hunter_camouflage.jpg"),
- ("Frost Mage", "https://wow.zamimg.com/images/wow/icons/large/spell_frost_frostbolt02.jpg")
+ ("Mage", "https://wow.zamimg.com/images/wow/icons/large/spell_holy_magicalsentry.jpg"),
+ ("Druid", "https://wow.zamimg.com/images/wow/icons/large/spell_nature_starfall.jpg"),
+ ("Paladin", "https://wow.zamimg.com/images/wow/icons/large/spell_holy_auraoflight.jpg"),
+ ("Shaman", "https://wow.zamimg.com/images/wow/icons/large/spell_nature_lightning.jpg"),
+ ("Hunter", "https://wow.zamimg.com/images/wow/icons/large/ability_hunter_bestialdiscipline.jpg"),
+ ("Rogue", "https://wow.zamimg.com/images/wow/icons/large/ability_rogue_deadlybrew.jpg"),
+ ("Demon Hunter","https://wow.zamimg.com/images/wow/icons/large/ability_demonhunter_specdps.jpg"),
+ ("Priest", "https://wow.zamimg.com/images/wow/icons/large/spell_shadow_shadowwordpain.jpg"),
+ ("Death Knight", "https://wow.zamimg.com/images/wow/icons/large/spell_deathknight_frostpresence.png"),
+ ("Monk", "https://wow.zamimg.com/images/wow/icons/large/spell_monk_windwalkerspec.jpg"),
+ ("Warrior", "https://wow.zamimg.com/images/wow/icons/large/ability_warrior_innerrage.jpg"),
+ ("Warlock", "https://wow.zamimg.com/images/wow/icons/large/spell_shadow_deathcoil.jpg"),
+ ("Evoker", "https://wow.zamimg.com/images/wow/icons/large/spell_spec_evoker_devastation.jpg")
]
}
-class DungeonDropdown(Select):
+
+class GrupoDungeon:
def __init__(self):
+ self.inscritos = {"Tank": [], "Healer": [], "DPS": []}
+ self.classes_escolhidas = {}
+ self.dungeon_escolhida = DUNGEONS[2]
+ self.dificuldade_escolhida = "10"
+ self.data_formatada = "06/08/2025"
+ self.hora_escolhida = "22:00"
+ self.alteracoes_feitas = {"dungeon": False, "dificuldade": False, "data": False}
+ self.jogador_em_progresso = None
+ self.mensagem_id = None
+ self.canal_id = None
+ self.thread = None
+
+ def format_grupo_embed(self):
+ def format_role(role):
+ players = self.inscritos[role]
+ if not players:
+ return "None"
+ return "\n".join(f"{p} ({self.classes_escolhidas.get(p, 'sem classe')})" for p in players)
+
+ embed = discord.Embed(
+ title=f"Dungeon: {self.dungeon_escolhida}",
+ description=(
+ f"Dificuldade: {self.dificuldade_escolhida}\n"
+ f"Marcação: {self.data_formatada} às {self.hora_escolhida}"
+ ),
+ color=0x00ffcc
+ )
+ embed.add_field(name=f"{EMOJI_TANK} Tank", value=format_role("Tank"), inline=False)
+ embed.add_field(name=f"{EMOJI_HEALER} Healer", value=format_role("Healer"), inline=False)
+ embed.add_field(name=f"{EMOJI_DPS} DPS", value=format_role("DPS"), inline=False)
+ embed.set_footer(text="Bot created by Kl3z")
+ return embed
+
+grupos_ativos = {}
+
+class DungeonDropdown(Select):
+ def __init__(self, grupo: GrupoDungeon):
+ self.grupo = grupo
options = [discord.SelectOption(label=d, value=d) for d in DUNGEONS]
super().__init__(placeholder="Escolhe a dungeon", min_values=1, max_values=1, options=options)
async def callback(self, interaction: discord.Interaction):
- global dungeon_escolhida
- if alteracoes_feitas["dungeon"]:
+ if self.grupo.alteracoes_feitas["dungeon"]:
await interaction.response.send_message("❌ A dungeon já foi escolhida e não pode ser alterada novamente.", ephemeral=True)
return
- dungeon_escolhida = self.values[0]
- alteracoes_feitas["dungeon"] = True
+
+ self.grupo.dungeon_escolhida = self.values[0]
+ self.grupo.alteracoes_feitas["dungeon"] = True
- mensagem = await interaction.channel.fetch_message(grupo_mensagem_id)
- nova_view = DungeonView()
- if all(alteracoes_feitas.values()):
+ canal = bot.get_channel(self.grupo.canal_id)
+ mensagem = await canal.fetch_message(self.grupo.mensagem_id)
+
+ nova_view = DungeonView(self.grupo)
+ if all(self.grupo.alteracoes_feitas.values()):
nova_view.clear_items()
- await mensagem.edit(embed=format_grupo_embed(), view=nova_view)
- if isinstance(interaction.channel, discord.Thread):
- await interaction.channel.edit(name=f"{dungeon_escolhida} - Key {dificuldade_escolhida}")
+ await mensagem.edit(embed=self.grupo.format_grupo_embed(), view=nova_view)
+
+ if self.grupo.thread:
+ await self.grupo.thread.edit(name=f"{self.grupo.dungeon_escolhida} - Key {self.grupo.dificuldade_escolhida}")
await interaction.response.defer()
class DificuldadeDropdown(Select):
- def __init__(self):
+ def __init__(self, grupo):
+ self.grupo = grupo
options = [discord.SelectOption(label=f"Chave {d}", value=d) for d in DIFICULDADES]
super().__init__(placeholder="Escolhe a dificuldade", min_values=1, max_values=1, options=options)
async def callback(self, interaction: discord.Interaction):
- global dificuldade_escolhida
- if alteracoes_feitas["dificuldade"]:
+ if self.grupo.alteracoes_feitas["dificuldade"]:
await interaction.response.send_message("❌ A dificuldade já foi definida e não pode ser alterada novamente.", ephemeral=True)
return
- dificuldade_escolhida = self.values[0]
- alteracoes_feitas["dificuldade"] = True
- mensagem = await interaction.channel.fetch_message(grupo_mensagem_id)
- nova_view = DungeonView()
- if all(alteracoes_feitas.values()):
+
+ self.grupo.dificuldade_escolhida = self.values[0]
+ self.grupo.alteracoes_feitas["dificuldade"] = True
+
+ canal = bot.get_channel(self.grupo.canal_id)
+ mensagem = await canal.fetch_message(self.grupo.mensagem_id)
+ nova_view = DungeonView(self.grupo)
+ if all(self.grupo.alteracoes_feitas.values()):
nova_view.clear_items()
- await mensagem.edit(embed=format_grupo_embed(), view=nova_view)
- if isinstance(interaction.channel, discord.Thread):
- await interaction.channel.edit(name=f"{dungeon_escolhida} - Key {dificuldade_escolhida}")
+
+ await mensagem.edit(embed=self.grupo.format_grupo_embed(), view=nova_view)
+
+ if self.grupo.thread:
+ await self.grupo.thread.edit(name=f"{self.grupo.dungeon_escolhida} - Key {self.grupo.dificuldade_escolhida}")
await interaction.response.defer()
class DataModal(Modal, title="Definir Data da Dungeon"):
- dia = TextInput(label="Dia do mês (1-31)", placeholder="Ex: 6", max_length=2)
- hora = TextInput(label="Hora (HH:MM)", placeholder="Ex: 22:30", max_length=5)
+ def __init__(self, grupo):
+ super().__init__()
+ self.grupo = grupo
+ self.dia = TextInput(label="Dia do mês (1-31)", placeholder="Ex: 6", max_length=2)
+ self.hora = TextInput(label="Hora (HH:MM)", placeholder="Ex: 22:30", max_length=5)
+ self.add_item(self.dia)
+ self.add_item(self.hora)
+
async def on_submit(self, interaction: discord.Interaction):
- global data_formatada, hora_escolhida
- if alteracoes_feitas["data"]:
+ if self.grupo.alteracoes_feitas["data"]:
await interaction.response.send_message("❌ A data e hora já foram definidas e não podem ser alteradas novamente.", ephemeral=True)
return
try:
@@ -219,6 +256,7 @@ class DataModal(Modal, title="Definir Data da Dungeon"):
if not 1 <= dia_num <= 31:
raise ValueError("Dia fora do intervalo")
hora_val = datetime.strptime(self.hora.value, "%H:%M").strftime("%H:%M")
+
hoje = datetime.now()
mes = hoje.month
ano = hoje.year
@@ -228,106 +266,135 @@ class DataModal(Modal, title="Definir Data da Dungeon"):
mes = 1
ano += 1
data_completa = datetime(year=ano, month=mes, day=dia_num)
+
+ self.grupo.data_formatada = data_completa.strftime("%d/%m/%Y")
+ self.grupo.hora_escolhida = hora_val
+ self.grupo.alteracoes_feitas["data"] = True
+
+ canal = bot.get_channel(self.grupo.canal_id)
+ mensagem = await canal.fetch_message(self.grupo.mensagem_id)
+ nova_view = DungeonView(self.grupo)
+ if all(self.grupo.alteracoes_feitas.values()):
+ nova_view.clear_items()
+ await mensagem.edit(embed=self.grupo.format_grupo_embed(), view=nova_view)
+ await interaction.response.defer()
+
except Exception:
await interaction.response.send_message("❌ Data ou hora inválida.", ephemeral=True)
- return
- data_formatada = data_completa.strftime("%d/%m/%Y")
- hora_escolhida = hora_val
- alteracoes_feitas["data"] = True
- mensagem = await interaction.channel.fetch_message(grupo_mensagem_id)
- nova_view = DungeonView()
- if all(alteracoes_feitas.values()):
- nova_view.clear_items()
- await mensagem.edit(embed=format_grupo_embed(), view=nova_view)
- await interaction.response.defer()
+
class BotaoData(Button):
- def __init__(self):
+ def __init__(self, grupo):
super().__init__(label="🗓️ Definir Data", style=discord.ButtonStyle.primary)
+ self.grupo = grupo
+
async def callback(self, interaction: discord.Interaction):
- await interaction.response.send_modal(DataModal())
+ await interaction.response.send_modal(DataModal(self.grupo))
+
class DungeonView(View):
- def __init__(self):
+ def __init__(self, grupo):
super().__init__(timeout=None)
- if not alteracoes_feitas["dungeon"]:
- self.add_item(DungeonDropdown())
- if not alteracoes_feitas["dificuldade"]:
- self.add_item(DificuldadeDropdown())
- if not alteracoes_feitas["data"]:
- self.add_item(BotaoData())
+ self.grupo = grupo
+ if not self.grupo.alteracoes_feitas["dungeon"]:
+ self.add_item(DungeonDropdown(self.grupo))
+ if not self.grupo.alteracoes_feitas["dificuldade"]:
+ self.add_item(DificuldadeDropdown(self.grupo))
+ if not self.grupo.alteracoes_feitas["data"]:
+ self.add_item(BotaoData(self.grupo))
+
class RoleDropdown(Select):
- def __init__(self, jogador, ctx_channel_id, role_msg_id):
+ def __init__(self, jogador, ctx_channel_id, role_msg_id, grupo):
self.jogador = jogador
self.ctx_channel_id = ctx_channel_id
self.role_msg_id = role_msg_id
+ self.grupo = grupo
options = []
for role in LIMITES:
- if len(inscritos[role]) < LIMITES[role]:
+ if len(self.grupo.inscritos[role]) < LIMITES[role]:
options.append(discord.SelectOption(label=role, description="Escolher esta role"))
placeholder = "Escolhe a tua role" if options else "Todas as roles preenchidas"
super().__init__(placeholder=placeholder, options=options, disabled=(len(options) == 0))
async def callback(self, interaction: discord.Interaction):
- global jogador_em_progresso
- jogador = interaction.user.display_name
- if jogador_em_progresso and jogador_em_progresso != jogador:
+ if self.grupo.jogador_em_progresso and self.grupo.jogador_em_progresso != interaction.user.display_name:
await interaction.response.send_message(
- f"⏳ Aguarda que {jogador_em_progresso} termine a escolha da classe.",
+ f"⏳ Aguarda que {self.grupo.jogador_em_progresso} termine a escolha da classe.",
ephemeral=True
)
return
- jogador_em_progresso = jogador
+
+ self.grupo.jogador_em_progresso = interaction.user.display_name
role = self.values[0]
- for r in inscritos:
- if jogador in inscritos[r]:
- inscritos[r].remove(jogador)
- if jogador not in inscritos[role] and len(inscritos[role]) < LIMITES[role]:
- inscritos[role].append(jogador)
+
+ # Remover jogador de qualquer role anterior
+ for r in self.grupo.inscritos:
+ if interaction.user.display_name in self.grupo.inscritos[r]:
+ self.grupo.inscritos[r].remove(interaction.user.display_name)
+
+ if interaction.user.display_name not in self.grupo.inscritos[role] and len(self.grupo.inscritos[role]) < LIMITES[role]:
+ self.grupo.inscritos[role].append(interaction.user.display_name)
canal = bot.get_channel(self.ctx_channel_id)
+
if isinstance(canal, discord.Thread):
await canal.join()
+
await interaction.message.delete()
- mensagem = await canal.fetch_message(grupo_mensagem_id)
- await mensagem.edit(embed=format_grupo_embed())
+
+ # Usar self.grupo.mensagem_id em vez da variável global
+ mensagem = await canal.fetch_message(self.grupo.mensagem_id)
+ await mensagem.edit(embed=self.grupo.format_grupo_embed())
+
await canal.send(
- f"{jogador}, agora escolhe a tua classe para **{role}**:",
- view=ClasseView(role, jogador, self.ctx_channel_id)
+ f"{interaction.user.display_name}, agora escolhe a tua classe para **{role}**:",
+ view=ClasseView(role, interaction.user.display_name, self.ctx_channel_id, self.grupo)
)
+
await interaction.response.defer()
else:
- jogador_em_progresso = None
+ self.grupo.jogador_em_progresso = None
await interaction.response.send_message("❌ Esta role já está cheia ou ocorreu um erro.", ephemeral=True)
+
class ClasseDropdown(Select):
- def __init__(self, role, jogador):
+ def __init__(self, role, jogador, grupo):
self.jogador = jogador
+ self.role = role
+ self.grupo = grupo
options = [discord.SelectOption(label=nome, value=nome) for nome, _ in CLASSES_POR_ROLE[role]]
super().__init__(placeholder=f"Escolhe a classe ({role})", options=options)
+
async def callback(self, interaction: discord.Interaction):
- global jogador_em_progresso
- classes_escolhidas[self.jogador] = self.values[0]
+ self.grupo.classes_escolhidas[self.jogador] = self.values[0]
canal = interaction.channel
- mensagem = await canal.fetch_message(grupo_mensagem_id)
- await mensagem.edit(embed=format_grupo_embed())
+
+ # Usar self.grupo.mensagem_id em vez da variável global
+ mensagem = await canal.fetch_message(self.grupo.mensagem_id)
+ await mensagem.edit(embed=self.grupo.format_grupo_embed())
+
await interaction.message.delete()
- jogador_em_progresso = None
- if any(len(inscritos[r]) < LIMITES[r] for r in LIMITES):
+ self.grupo.jogador_em_progresso = None
+
+ if any(len(self.grupo.inscritos[r]) < LIMITES[r] for r in LIMITES):
await canal.send(
"Escolher a tua **Role**:",
- view=RoleView(interaction.user.display_name, canal.id, grupo_mensagem_id)
+ view=RoleView(interaction.user.display_name, canal.id, self.grupo.mensagem_id, self.grupo)
)
+
await interaction.response.defer()
+
class ClasseView(View):
- def __init__(self, role, jogador, ctx_channel_id):
- super().__init__()
- dropdown = ClasseDropdown(role, jogador)
- dropdown.ctx_channel_id = ctx_channel_id
- self.add_item(dropdown)
+ def __init__(self, role, jogador, ctx_channel_id, grupo):
+ super().__init__(timeout=None)
+ self.grupo = grupo
+ self.add_item(ClasseDropdown(role, jogador, grupo))
+
import asyncio
+
async def agendar_remocao_thread(thread: discord.Thread, data_str: str, hora_str: str):
try:
alvo = datetime.strptime(f"{data_str} {hora_str}", "%d/%m/%Y %H:%M")
agora = datetime.now()
segundos_ate_apagar = (alvo + timedelta(minutes=30) - agora).total_seconds()
+
if segundos_ate_apagar > 0:
await asyncio.sleep(segundos_ate_apagar)
await thread.delete()
@@ -336,31 +403,81 @@ async def agendar_remocao_thread(thread: discord.Thread, data_str: str, hora_str
print(f"[INFO] Tempo já passou, thread '{thread.name}' não foi agendada.")
except Exception as e:
print(f"[ERRO] ao agendar remoção: {e}")
+
@bot.command(name="criargrupo")
async def criar_grupo(ctx):
- global grupo_mensagem_id, inscritos, dungeon_escolhida, dificuldade_escolhida, data_formatada, hora_escolhida, alteracoes_feitas
- inscritos = {"Tank": [], "Healer": [], "DPS": []}
- classes_escolhidas.clear()
- dungeon_escolhida = DUNGEONS[2]
- dificuldade_escolhida = "10"
- data_formatada = "06/08/2025"
- hora_escolhida = "22:00"
- alteracoes_feitas = {"dungeon": False, "dificuldade": False, "data": False}
- embed = format_grupo_embed()
- mensagem = await ctx.send(embed=embed, view=DungeonView())
- grupo_mensagem_id = mensagem.id
- if isinstance(ctx.channel, discord.Thread):
- asyncio.create_task(agendar_remocao_thread(ctx.channel, data_formatada, hora_escolhida))
- role_msg = await ctx.send(
- "Escolher a tua **Role**:",
- view=RoleView(ctx.author.display_name, ctx.channel.id, grupo_mensagem_id)
- )
+ # Verificar se o comando foi usado em um local válido
+ if not (isinstance(ctx.channel, discord.ForumChannel) or
+ (isinstance(ctx.channel, discord.Thread) and
+ isinstance(ctx.channel.parent, discord.ForumChannel))):
+ await ctx.send("❌ Este comando só pode ser usado em um canal de fórum ou em posts de fórum!", ephemeral=True)
+ return
+
+ # Determinar o canal de fórum principal
+ forum_channel = ctx.channel if isinstance(ctx.channel, discord.ForumChannel) else ctx.channel.parent
+
+ # Criar novo grupo
+ novo_grupo = GrupoDungeon()
+
+ try:
+ # Criar novo post no fórum com tags apropriadas
+ tags = []
+ dungeon_tag = discord.utils.get(forum_channel.available_tags, name=novo_grupo.dungeon_escolhida)
+ if dungeon_tag:
+ tags.append(dungeon_tag)
+
+ # Criar o post no fórum
+ thread, message = await forum_channel.create_thread(
+ name=f"{novo_grupo.dungeon_escolhida} - Key {novo_grupo.dificuldade_escolhida}",
+ content=f"Dungeon group created by {ctx.author.mention}",
+ embed=novo_grupo.format_grupo_embed(),
+ view=DungeonView(novo_grupo),
+ applied_tags=tags
+ )
+
+ # Configurar o grupo
+ novo_grupo.thread = thread
+ novo_grupo.canal_id = thread.id
+ novo_grupo.mensagem_id = message.id
+ grupos_ativos[message.id] = novo_grupo
+
+ # Enviar mensagem para escolher role
+ await thread.send(
+ f"{ctx.author.mention}, escolhe a tua **Role**:",
+ view=RoleView(ctx.author.display_name, thread.id, message.id, novo_grupo)
+ )
+
+ # Agendar remoção automática
+ asyncio.create_task(
+ agendar_remocao_thread(
+ thread,
+ novo_grupo.data_formatada,
+ novo_grupo.hora_escolhida
+ )
+ )
+
+ # Confirmar criação
+ if ctx.channel.id != thread.id: # Só responde se não estiver já na thread criada
+ await ctx.send(f"✅ Grupo criado com sucesso! {thread.mention}", ephemeral=True)
+
+ except discord.HTTPException as e:
+ await ctx.send("❌ Erro ao criar post no fórum. Verifique as permissões do bot.", ephemeral=True)
+ print(f"Erro HTTP ao criar post: {e}")
+ except Exception as e:
+ await ctx.send("❌ Ocorreu um erro inesperado ao criar o grupo.", ephemeral=True)
+ print(f"Erro ao criar grupo: {e}")
+ if 'thread' in locals() and thread: # Limpeza em caso de erro
+ await thread.delete()
+
class RoleView(View):
- def __init__(self, jogador, ctx_channel_id, role_msg_id):
+ def __init__(self, jogador, ctx_channel_id, role_msg_id, grupo):
super().__init__(timeout=None)
- self.add_item(RoleDropdown(jogador, ctx_channel_id, role_msg_id))
+ self.grupo = grupo
+ self.add_item(RoleDropdown(jogador, ctx_channel_id, role_msg_id, grupo))
+
+
@bot.event
async def on_ready():
print(f"Bot ligado como {bot.user}")
-bot.run(os.getenv("DISCORD_TOKEN"))
+bot.run(os.getenv("DISCORD_TOKEN"))
From d74e7ede443dd80ee62c43fde4fce12225656485 Mon Sep 17 00:00:00 2001
From: kl3z <136390977+kl3z@users.noreply.github.com>
Date: Tue, 5 Aug 2025 08:53:14 +0100
Subject: [PATCH 21/21] Update WIPBOT.py
---
WIPBOT.py | 28 ++++------------------------
1 file changed, 4 insertions(+), 24 deletions(-)
diff --git a/WIPBOT.py b/WIPBOT.py
index 1b588a4..7be677d 100644
--- a/WIPBOT.py
+++ b/WIPBOT.py
@@ -325,8 +325,6 @@ class RoleDropdown(Select):
self.grupo.jogador_em_progresso = interaction.user.display_name
role = self.values[0]
-
- # Remover jogador de qualquer role anterior
for r in self.grupo.inscritos:
if interaction.user.display_name in self.grupo.inscritos[r]:
self.grupo.inscritos[r].remove(interaction.user.display_name)
@@ -339,8 +337,7 @@ class RoleDropdown(Select):
await canal.join()
await interaction.message.delete()
-
- # Usar self.grupo.mensagem_id em vez da variável global
+
mensagem = await canal.fetch_message(self.grupo.mensagem_id)
await mensagem.edit(embed=self.grupo.format_grupo_embed())
@@ -365,8 +362,7 @@ class ClasseDropdown(Select):
async def callback(self, interaction: discord.Interaction):
self.grupo.classes_escolhidas[self.jogador] = self.values[0]
canal = interaction.channel
-
- # Usar self.grupo.mensagem_id em vez da variável global
+
mensagem = await canal.fetch_message(self.grupo.mensagem_id)
await mensagem.edit(embed=self.grupo.format_grupo_embed())
@@ -406,27 +402,19 @@ async def agendar_remocao_thread(thread: discord.Thread, data_str: str, hora_str
@bot.command(name="criargrupo")
async def criar_grupo(ctx):
- # Verificar se o comando foi usado em um local válido
if not (isinstance(ctx.channel, discord.ForumChannel) or
(isinstance(ctx.channel, discord.Thread) and
isinstance(ctx.channel.parent, discord.ForumChannel))):
await ctx.send("❌ Este comando só pode ser usado em um canal de fórum ou em posts de fórum!", ephemeral=True)
return
-
- # Determinar o canal de fórum principal
forum_channel = ctx.channel if isinstance(ctx.channel, discord.ForumChannel) else ctx.channel.parent
-
- # Criar novo grupo
novo_grupo = GrupoDungeon()
try:
- # Criar novo post no fórum com tags apropriadas
tags = []
dungeon_tag = discord.utils.get(forum_channel.available_tags, name=novo_grupo.dungeon_escolhida)
if dungeon_tag:
tags.append(dungeon_tag)
-
- # Criar o post no fórum
thread, message = await forum_channel.create_thread(
name=f"{novo_grupo.dungeon_escolhida} - Key {novo_grupo.dificuldade_escolhida}",
content=f"Dungeon group created by {ctx.author.mention}",
@@ -434,20 +422,14 @@ async def criar_grupo(ctx):
view=DungeonView(novo_grupo),
applied_tags=tags
)
-
- # Configurar o grupo
novo_grupo.thread = thread
novo_grupo.canal_id = thread.id
novo_grupo.mensagem_id = message.id
grupos_ativos[message.id] = novo_grupo
-
- # Enviar mensagem para escolher role
await thread.send(
f"{ctx.author.mention}, escolhe a tua **Role**:",
view=RoleView(ctx.author.display_name, thread.id, message.id, novo_grupo)
)
-
- # Agendar remoção automática
asyncio.create_task(
agendar_remocao_thread(
thread,
@@ -455,9 +437,7 @@ async def criar_grupo(ctx):
novo_grupo.hora_escolhida
)
)
-
- # Confirmar criação
- if ctx.channel.id != thread.id: # Só responde se não estiver já na thread criada
+ if ctx.channel.id != thread.id:
await ctx.send(f"✅ Grupo criado com sucesso! {thread.mention}", ephemeral=True)
except discord.HTTPException as e:
@@ -466,7 +446,7 @@ async def criar_grupo(ctx):
except Exception as e:
await ctx.send("❌ Ocorreu um erro inesperado ao criar o grupo.", ephemeral=True)
print(f"Erro ao criar grupo: {e}")
- if 'thread' in locals() and thread: # Limpeza em caso de erro
+ if 'thread' in locals() and thread:
await thread.delete()
class RoleView(View):