EveCogs/voicemeister/views.py
everestmcarthur a4ba2040ec fix: Critical bugfixes for VoiceMeister
1. CRITICAL: Permission overwrites now properly merge instead of replace.
   set_permissions with keyword args REPLACES the entire overwrite,
   so Lock→Hide would undo the Lock. Now uses overwrites_for() to
   fetch existing state and modify only the target field.
   Affects: Lock, Unlock, Hide, Unhide, Ghost, Reveal (buttons & commands)

2. CRITICAL: Removed broken async generator in any() for game rename check.
   await in generator expression creates async generator that any() can't
   consume. Game rename loop now always starts and checks config per-guild.

3. Minor: Fixed double-write in _save_temp_channels.
4. Minor: Removed unused imports (datetime, Set).
2026-05-08 14:48:42 +00:00

713 lines
32 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""VoiceMeister — Persistent views, buttons, modals, and select menus."""
from __future__ import annotations
import asyncio
from typing import Optional, TYPE_CHECKING
import discord
from discord import ui
from .utils import (
Clr, ok_embed, err_embed, info_embed, warn_embed,
is_channel_owner, can_manage, ts_relative, ts_now,
)
if TYPE_CHECKING:
from .voicemeister import VoiceMeister
# ══════════════════════════════════════════════════════════════════════════════
# HELPER — get the cog + validate the interaction
# ══════════════════════════════════════════════════════════════════════════════
async def _get_context(interaction: discord.Interaction):
"""Return (cog, member, voice_channel, owner_id) or send error and return None tuple."""
cog: VoiceMeister | None = interaction.client.get_cog("VoiceMeister")
if cog is None:
await interaction.response.send_message(
embed=err_embed("VoiceMeister cog is not loaded."), ephemeral=True,
)
return None, None, None, None
member = interaction.user
if not isinstance(member, discord.Member):
await interaction.response.send_message(
embed=err_embed("Could not resolve you as a guild member."), ephemeral=True,
)
return None, None, None, None
vc = member.voice.channel if member.voice else None
if vc is None:
await interaction.response.send_message(
embed=err_embed("You must be **in a voice channel** to use this."), ephemeral=True,
)
return None, None, None, None
owner_id = cog.temp_channels.get(vc.id)
if owner_id is None:
await interaction.response.send_message(
embed=err_embed("Your current voice channel is **not** a VoiceMeister channel."),
ephemeral=True,
)
return None, None, None, None
return cog, member, vc, owner_id
async def _require_owner(interaction: discord.Interaction):
"""Like _get_context but also requires ownership or Manage Channels."""
cog, member, vc, owner_id = await _get_context(interaction)
if cog is None:
return None, None, None, None
if not can_manage(member, owner_id):
await interaction.response.send_message(
embed=err_embed("Only the channel **owner** (or a moderator) can do this."),
ephemeral=True,
)
return None, None, None, None
return cog, member, vc, owner_id
# ══════════════════════════════════════════════════════════════════════════════
# MODALS
# ══════════════════════════════════════════════════════════════════════════════
class RenameModal(ui.Modal, title="✏️ Rename Channel"):
new_name = ui.TextInput(
label="New channel name",
placeholder="e.g. Gaming Lounge",
max_length=100,
style=discord.TextStyle.short,
)
async def on_submit(self, interaction: discord.Interaction):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
try:
await vc.edit(name=str(self.new_name), reason=f"VoiceMeister: renamed by {member}")
await interaction.response.send_message(
embed=ok_embed(f"Channel renamed to **{self.new_name}**."), ephemeral=True,
)
await cog._log_action(vc.guild, "✏️ Channel Renamed", member=member, channel=vc,
detail=f"New name: `{self.new_name}`")
except discord.HTTPException as e:
await interaction.response.send_message(
embed=err_embed(f"Failed to rename: {e}"), ephemeral=True,
)
class LimitModal(ui.Modal, title="👥 Set User Limit"):
limit = ui.TextInput(
label="User limit (0 = unlimited)",
placeholder="e.g. 5",
max_length=3,
style=discord.TextStyle.short,
)
async def on_submit(self, interaction: discord.Interaction):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
try:
val = int(str(self.limit))
if val < 0 or val > 99:
raise ValueError
except ValueError:
await interaction.response.send_message(
embed=err_embed("Enter a number between **0** and **99**."), ephemeral=True,
)
return
try:
await vc.edit(user_limit=val, reason=f"VoiceMeister: limit set by {member}")
label = f"**{val}**" if val > 0 else "**unlimited**"
await interaction.response.send_message(
embed=ok_embed(f"User limit set to {label}."), ephemeral=True,
)
await cog._log_action(vc.guild, "👥 Limit Changed", member=member, channel=vc,
detail=f"New limit: {label}")
except discord.HTTPException as e:
await interaction.response.send_message(
embed=err_embed(f"Failed to set limit: {e}"), ephemeral=True,
)
class BitrateModal(ui.Modal, title="📡 Set Bitrate"):
bitrate = ui.TextInput(
label="Bitrate in kbps (8–384)",
placeholder="e.g. 96",
max_length=3,
style=discord.TextStyle.short,
)
async def on_submit(self, interaction: discord.Interaction):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
try:
val = int(str(self.bitrate))
max_br = interaction.guild.bitrate_limit // 1000
if val < 8 or val > max_br:
raise ValueError
except (ValueError, AttributeError):
max_br = interaction.guild.bitrate_limit // 1000 if interaction.guild else 96
await interaction.response.send_message(
embed=err_embed(f"Enter a number between **8** and **{max_br}** kbps."),
ephemeral=True,
)
return
try:
await vc.edit(bitrate=val * 1000, reason=f"VoiceMeister: bitrate set by {member}")
await interaction.response.send_message(
embed=ok_embed(f"Bitrate set to **{val} kbps**."), ephemeral=True,
)
await cog._log_action(vc.guild, "📡 Bitrate Changed", member=member, channel=vc,
detail=f"New bitrate: {val} kbps")
except discord.HTTPException as e:
await interaction.response.send_message(
embed=err_embed(f"Failed to set bitrate: {e}"), ephemeral=True,
)
class RegionModal(ui.Modal, title="🌍 Set Voice Region"):
region = ui.TextInput(
label="Region (e.g. us-east, eu-west, auto)",
placeholder="auto",
max_length=30,
style=discord.TextStyle.short,
required=False,
)
async def on_submit(self, interaction: discord.Interaction):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
region_str = str(self.region).strip().lower() if self.region else ""
rtc_region = None if region_str in ("auto", "") else region_str
try:
await vc.edit(rtc_region=rtc_region, reason=f"VoiceMeister: region set by {member}")
label = rtc_region or "Automatic"
await interaction.response.send_message(
embed=ok_embed(f"Voice region set to **{label}**."), ephemeral=True,
)
await cog._log_action(vc.guild, "🌍 Region Changed", member=member, channel=vc,
detail=f"New region: {label}")
except discord.HTTPException as e:
await interaction.response.send_message(
embed=err_embed(f"Failed to set region: {e}"), ephemeral=True,
)
# ══════════════════════════════════════════════════════════════════════════════
# USER SELECT MENUS (for Kick, Ban, Permit, Reject, Transfer)
# ══════════════════════════════════════════════════════════════════════════════
class UserActionView(ui.View):
"""Ephemeral view with a user select for a specific action."""
def __init__(self, action: str):
super().__init__(timeout=60)
self.action = action
select = UserActionSelect(action=action)
self.add_item(select)
class UserActionSelect(ui.UserSelect):
def __init__(self, action: str):
labels = {
"kick": "Select user to kick",
"ban": "Select user to ban",
"permit": "Select user to permit",
"reject": "Select user to reject",
"transfer": "Select new owner",
}
super().__init__(
placeholder=labels.get(action, "Select a user"),
min_values=1,
max_values=1,
)
self.action = action
async def callback(self, interaction: discord.Interaction):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
target = self.values[0]
if not isinstance(target, discord.Member):
target = interaction.guild.get_member(target.id)
if target is None:
await interaction.response.send_message(
embed=err_embed("Could not find that member."), ephemeral=True,
)
return
if target.id == member.id:
await interaction.response.send_message(
embed=err_embed("You can't target yourself."), ephemeral=True,
)
return
if self.action == "kick":
if target.voice and target.voice.channel == vc:
await target.move_to(None, reason=f"VoiceMeister: kicked by {member}")
await interaction.response.send_message(
embed=ok_embed(f"Kicked {target.mention} from the channel."), ephemeral=True,
)
await cog._log_action(vc.guild, "👢 User Kicked", member=member, channel=vc,
detail=f"Target: {target} ({target.id})")
else:
await interaction.response.send_message(
embed=err_embed(f"{target.mention} is not in your channel."), ephemeral=True,
)
elif self.action == "ban":
# Set permission override to deny connect
await vc.set_permissions(target, connect=False, view_channel=False,
reason=f"VoiceMeister: banned by {member}")
# If they're in the channel, kick them
if target.voice and target.voice.channel == vc:
await target.move_to(None, reason=f"VoiceMeister: banned by {member}")
# Save to persistent ban list
await cog._add_channel_ban(vc.guild.id, vc.id, target.id)
await interaction.response.send_message(
embed=ok_embed(f"Banned {target.mention} from the channel."), ephemeral=True,
)
await cog._log_action(vc.guild, "🔨 User Banned", member=member, channel=vc,
detail=f"Target: {target} ({target.id})")
elif self.action == "permit":
await vc.set_permissions(target, connect=True, view_channel=True,
reason=f"VoiceMeister: permitted by {member}")
await interaction.response.send_message(
embed=ok_embed(f"Permitted {target.mention} to join the channel."), ephemeral=True,
)
await cog._log_action(vc.guild, "➕ User Permitted", member=member, channel=vc,
detail=f"Target: {target} ({target.id})")
elif self.action == "reject":
await vc.set_permissions(target, connect=False,
reason=f"VoiceMeister: rejected by {member}")
if target.voice and target.voice.channel == vc:
await target.move_to(None, reason=f"VoiceMeister: rejected by {member}")
await interaction.response.send_message(
embed=ok_embed(f"Rejected {target.mention} from the channel."), ephemeral=True,
)
await cog._log_action(vc.guild, "➖ User Rejected", member=member, channel=vc,
detail=f"Target: {target} ({target.id})")
elif self.action == "transfer":
if target.voice and target.voice.channel == vc:
cog.temp_channels[vc.id] = target.id
await cog._save_temp_channels()
await interaction.response.send_message(
embed=ok_embed(f"Ownership transferred to {target.mention}."), ephemeral=True,
)
await cog._log_action(vc.guild, "🔄 Ownership Transferred", member=member,
channel=vc, detail=f"New owner: {target} ({target.id})")
else:
await interaction.response.send_message(
embed=err_embed(f"{target.mention} must be in the channel to receive ownership."),
ephemeral=True,
)
# ══════════════════════════════════════════════════════════════════════════════
# MAIN CONTROL PANEL — PERSISTENT VIEW
# ══════════════════════════════════════════════════════════════════════════════
class VoiceMeisterPanel(ui.View):
"""The main persistent control panel. All buttons use custom_ids so the
view survives restarts. This is registered once in cog_load."""
def __init__(self):
super().__init__(timeout=None)
# ── Row 1: Access Controls ─────────────────────────────────────────────
@ui.button(label="Lock", emoji="🔒", style=discord.ButtonStyle.secondary,
custom_id="vm:lock", row=0)
async def lock_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
overwrite = vc.overwrites_for(vc.guild.default_role)
overwrite.connect = False
await vc.set_permissions(
vc.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: locked by {member}",
)
await interaction.response.send_message(
embed=ok_embed("Channel **locked** 🔒 — no one else can join."), ephemeral=True,
)
await cog._log_action(vc.guild, "🔒 Channel Locked", member=member, channel=vc)
@ui.button(label="Unlock", emoji="🔓", style=discord.ButtonStyle.secondary,
custom_id="vm:unlock", row=0)
async def unlock_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
overwrite = vc.overwrites_for(vc.guild.default_role)
overwrite.connect = None
await vc.set_permissions(
vc.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: unlocked by {member}",
)
await interaction.response.send_message(
embed=ok_embed("Channel **unlocked** 🔓 — anyone can join."), ephemeral=True,
)
await cog._log_action(vc.guild, "🔓 Channel Unlocked", member=member, channel=vc)
@ui.button(label="Ghost", emoji="👻", style=discord.ButtonStyle.secondary,
custom_id="vm:ghost", row=0)
async def ghost_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
overwrite = vc.overwrites_for(vc.guild.default_role)
overwrite.connect = False
overwrite.view_channel = False
await vc.set_permissions(
vc.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: ghosted by {member}",
)
await interaction.response.send_message(
embed=ok_embed("Channel is now **ghosted** 👻 — hidden and locked."), ephemeral=True,
)
await cog._log_action(vc.guild, "👻 Channel Ghosted", member=member, channel=vc)
@ui.button(label="Reveal", emoji="👁️", style=discord.ButtonStyle.secondary,
custom_id="vm:reveal", row=0)
async def reveal_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
overwrite = vc.overwrites_for(vc.guild.default_role)
overwrite.connect = None
overwrite.view_channel = None
await vc.set_permissions(
vc.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: revealed by {member}",
)
await interaction.response.send_message(
embed=ok_embed("Channel is now **revealed** 👁️ — visible and unlocked."), ephemeral=True,
)
await cog._log_action(vc.guild, "👁️ Channel Revealed", member=member, channel=vc)
@ui.button(label="Hide", emoji="👤", style=discord.ButtonStyle.secondary,
custom_id="vm:hide", row=0)
async def hide_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
overwrite = vc.overwrites_for(vc.guild.default_role)
overwrite.view_channel = False
await vc.set_permissions(
vc.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: hidden by {member}",
)
await interaction.response.send_message(
embed=ok_embed("Channel is now **hidden** 👤 — invisible to others."), ephemeral=True,
)
await cog._log_action(vc.guild, "👤 Channel Hidden", member=member, channel=vc)
# ── Row 2: User Management ─────────────────────────────────────────────
@ui.button(label="Permit", emoji="➕", style=discord.ButtonStyle.success,
custom_id="vm:permit", row=1)
async def permit_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
await interaction.response.send_message(
embed=info_embed("Select a user to **permit** into your channel:"),
view=UserActionView("permit"), ephemeral=True,
)
@ui.button(label="Reject", emoji="➖", style=discord.ButtonStyle.danger,
custom_id="vm:reject", row=1)
async def reject_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
await interaction.response.send_message(
embed=info_embed("Select a user to **reject** from your channel:"),
view=UserActionView("reject"), ephemeral=True,
)
@ui.button(label="Kick", emoji="👢", style=discord.ButtonStyle.danger,
custom_id="vm:kick", row=1)
async def kick_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
await interaction.response.send_message(
embed=info_embed("Select a user to **kick** from your channel:"),
view=UserActionView("kick"), ephemeral=True,
)
@ui.button(label="Ban", emoji="🔨", style=discord.ButtonStyle.danger,
custom_id="vm:ban", row=1)
async def ban_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
await interaction.response.send_message(
embed=info_embed("Select a user to **ban** from your channel:"),
view=UserActionView("ban"), ephemeral=True,
)
@ui.button(label="Unhide", emoji="👁️‍🗨️", style=discord.ButtonStyle.secondary,
custom_id="vm:unhide", row=1)
async def unhide_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
overwrite = vc.overwrites_for(vc.guild.default_role)
overwrite.view_channel = None
await vc.set_permissions(
vc.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: unhidden by {member}",
)
await interaction.response.send_message(
embed=ok_embed("Channel is now **visible** 👁️‍🗨️."), ephemeral=True,
)
await cog._log_action(vc.guild, "👁️‍🗨️ Channel Unhidden", member=member, channel=vc)
# ── Row 3: Channel Settings ────────────────────────────────────────────
@ui.button(label="Rename", emoji="✏️", style=discord.ButtonStyle.primary,
custom_id="vm:rename", row=2)
async def rename_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
await interaction.response.send_modal(RenameModal())
@ui.button(label="Limit", emoji="👥", style=discord.ButtonStyle.primary,
custom_id="vm:limit", row=2)
async def limit_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
await interaction.response.send_modal(LimitModal())
@ui.button(label="Bitrate", emoji="📡", style=discord.ButtonStyle.primary,
custom_id="vm:bitrate", row=2)
async def bitrate_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
await interaction.response.send_modal(BitrateModal())
@ui.button(label="Region", emoji="🌍", style=discord.ButtonStyle.primary,
custom_id="vm:region", row=2)
async def region_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
await interaction.response.send_modal(RegionModal())
@ui.button(label="Mute All", emoji="🔇", style=discord.ButtonStyle.secondary,
custom_id="vm:muteall", row=2)
async def muteall_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
count = 0
for m in vc.members:
if m.id != member.id and not m.voice.mute:
try:
await m.edit(mute=True, reason=f"VoiceMeister: muted by {member}")
count += 1
except discord.HTTPException:
pass
await interaction.response.send_message(
embed=ok_embed(f"Server-muted **{count}** user(s) 🔇."), ephemeral=True,
)
await cog._log_action(vc.guild, "🔇 All Muted", member=member, channel=vc,
detail=f"Muted {count} user(s)")
# ── Row 4: Ownership & Meta ────────────────────────────────────────────
@ui.button(label="Claim", emoji="👑", style=discord.ButtonStyle.success,
custom_id="vm:claim", row=3)
async def claim_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _get_context(interaction)
if cog is None:
return
# Check if owner is still in the channel
owner_in_channel = any(m.id == owner_id for m in vc.members)
if owner_in_channel and member.id != owner_id:
await interaction.response.send_message(
embed=err_embed("The channel owner is still in the channel. You can't claim it."),
ephemeral=True,
)
return
if member.id == owner_id:
await interaction.response.send_message(
embed=info_embed("You already own this channel."), ephemeral=True,
)
return
cog.temp_channels[vc.id] = member.id
await cog._save_temp_channels()
await interaction.response.send_message(
embed=ok_embed(f"You are now the **owner** of this channel 👑."), ephemeral=True,
)
await cog._log_action(vc.guild, "👑 Channel Claimed", member=member, channel=vc)
@ui.button(label="Transfer", emoji="🔄", style=discord.ButtonStyle.success,
custom_id="vm:transfer", row=3)
async def transfer_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
await interaction.response.send_message(
embed=info_embed("Select the user to **transfer ownership** to:"),
view=UserActionView("transfer"), ephemeral=True,
)
@ui.button(label="Info", emoji="ℹ️", style=discord.ButtonStyle.secondary,
custom_id="vm:info", row=3)
async def info_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _get_context(interaction)
if cog is None:
return
owner = vc.guild.get_member(owner_id)
owner_str = owner.mention if owner else f"Unknown (`{owner_id}`)"
# Channel state
overwrites = vc.overwrites_for(vc.guild.default_role)
locked = overwrites.connect is False
hidden = overwrites.view_channel is False
status_parts = []
if locked:
status_parts.append("🔒 Locked")
else:
status_parts.append("🔓 Unlocked")
if hidden:
status_parts.append("👤 Hidden")
else:
status_parts.append("👁️ Visible")
embed = discord.Embed(
title=f"ℹ️ {vc.name}",
colour=Clr.VOICE,
)
embed.add_field(name="👑 Owner", value=owner_str, inline=True)
embed.add_field(name="👥 Members", value=f"{len(vc.members)}/{vc.user_limit or '∞'}",
inline=True)
embed.add_field(name="📡 Bitrate", value=f"{vc.bitrate // 1000} kbps", inline=True)
embed.add_field(name="🔐 Status", value=" • ".join(status_parts), inline=False)
embed.add_field(name="🌍 Region", value=vc.rtc_region or "Automatic", inline=True)
embed.add_field(name="📅 Created", value=f"<t:{int(vc.created_at.timestamp())}:R>",
inline=True)
# List permitted/rejected users
permitted = []
rejected = []
for target, overwrite in vc.overwrites.items():
if isinstance(target, (discord.Member, discord.User)):
if target.id == owner_id:
continue
if overwrite.connect is True:
permitted.append(target.mention)
elif overwrite.connect is False:
rejected.append(target.mention)
if permitted:
embed.add_field(name="✅ Permitted", value=", ".join(permitted[:10]), inline=False)
if rejected:
embed.add_field(name="🚫 Rejected/Banned", value=", ".join(rejected[:10]), inline=False)
await interaction.response.send_message(embed=embed, ephemeral=True)
@ui.button(label="Unmute All", emoji="🔊", style=discord.ButtonStyle.secondary,
custom_id="vm:unmuteall", row=3)
async def unmuteall_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
count = 0
for m in vc.members:
if m.voice.mute:
try:
await m.edit(mute=False, reason=f"VoiceMeister: unmuted by {member}")
count += 1
except discord.HTTPException:
pass
await interaction.response.send_message(
embed=ok_embed(f"Unmuted **{count}** user(s) 🔊."), ephemeral=True,
)
await cog._log_action(vc.guild, "🔊 All Unmuted", member=member, channel=vc,
detail=f"Unmuted {count} user(s)")
# ── Row 4 continued ───────────────────────────────────────────────────
@ui.button(label="Delete", emoji="🗑️", style=discord.ButtonStyle.danger,
custom_id="vm:delete", row=4)
async def delete_btn(self, interaction: discord.Interaction, button: ui.Button):
cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None:
return
# Confirmation
confirm_view = DeleteConfirmView(member.id, vc.id)
await interaction.response.send_message(
embed=warn_embed(f"Are you sure you want to **delete** `{vc.name}`? This cannot be undone."),
view=confirm_view, ephemeral=True,
)
class DeleteConfirmView(ui.View):
def __init__(self, author_id: int, channel_id: int):
super().__init__(timeout=30)
self.author_id = author_id
self.channel_id = channel_id
async def interaction_check(self, interaction: discord.Interaction) -> bool:
if interaction.user.id != self.author_id:
await interaction.response.send_message("Not your confirmation.", ephemeral=True)
return False
return True
@ui.button(label="Yes, Delete", style=discord.ButtonStyle.danger, emoji="🗑️")
async def confirm(self, interaction: discord.Interaction, button: ui.Button):
cog: VoiceMeister | None = interaction.client.get_cog("VoiceMeister")
channel = interaction.guild.get_channel(self.channel_id)
if channel and cog:
await cog._log_action(interaction.guild, "🗑️ Channel Deleted",
member=interaction.user, channel=channel)
cog.temp_channels.pop(channel.id, None)
await cog._save_temp_channels()
try:
await channel.delete(reason=f"VoiceMeister: deleted by {interaction.user}")
except discord.HTTPException:
pass
await interaction.response.edit_message(
embed=ok_embed("Channel deleted."), view=None,
)
self.stop()
@ui.button(label="Cancel", style=discord.ButtonStyle.secondary, emoji="❌")
async def cancel(self, interaction: discord.Interaction, button: ui.Button):
await interaction.response.edit_message(
embed=info_embed("Deletion cancelled."), view=None,
)
self.stop()