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).
This commit is contained in:
everestmcarthur 2026-05-08 14:48:42 +00:00
parent ec7767079e
commit a4ba2040ec
2 changed files with 41 additions and 21 deletions

View file

@ -341,8 +341,10 @@ class VoiceMeisterPanel(ui.View):
cog, member, vc, owner_id = await _require_owner(interaction) cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None: if cog is None:
return return
overwrite = vc.overwrites_for(vc.guild.default_role)
overwrite.connect = False
await vc.set_permissions( await vc.set_permissions(
vc.guild.default_role, connect=False, vc.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: locked by {member}", reason=f"VoiceMeister: locked by {member}",
) )
await interaction.response.send_message( await interaction.response.send_message(
@ -356,8 +358,10 @@ class VoiceMeisterPanel(ui.View):
cog, member, vc, owner_id = await _require_owner(interaction) cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None: if cog is None:
return return
overwrite = vc.overwrites_for(vc.guild.default_role)
overwrite.connect = None
await vc.set_permissions( await vc.set_permissions(
vc.guild.default_role, connect=None, vc.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: unlocked by {member}", reason=f"VoiceMeister: unlocked by {member}",
) )
await interaction.response.send_message( await interaction.response.send_message(
@ -371,8 +375,11 @@ class VoiceMeisterPanel(ui.View):
cog, member, vc, owner_id = await _require_owner(interaction) cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None: if cog is None:
return return
overwrite = vc.overwrites_for(vc.guild.default_role)
overwrite.connect = False
overwrite.view_channel = False
await vc.set_permissions( await vc.set_permissions(
vc.guild.default_role, connect=False, view_channel=False, vc.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: ghosted by {member}", reason=f"VoiceMeister: ghosted by {member}",
) )
await interaction.response.send_message( await interaction.response.send_message(
@ -386,8 +393,11 @@ class VoiceMeisterPanel(ui.View):
cog, member, vc, owner_id = await _require_owner(interaction) cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None: if cog is None:
return return
overwrite = vc.overwrites_for(vc.guild.default_role)
overwrite.connect = None
overwrite.view_channel = None
await vc.set_permissions( await vc.set_permissions(
vc.guild.default_role, connect=None, view_channel=None, vc.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: revealed by {member}", reason=f"VoiceMeister: revealed by {member}",
) )
await interaction.response.send_message( await interaction.response.send_message(
@ -401,8 +411,10 @@ class VoiceMeisterPanel(ui.View):
cog, member, vc, owner_id = await _require_owner(interaction) cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None: if cog is None:
return return
overwrite = vc.overwrites_for(vc.guild.default_role)
overwrite.view_channel = False
await vc.set_permissions( await vc.set_permissions(
vc.guild.default_role, view_channel=False, vc.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: hidden by {member}", reason=f"VoiceMeister: hidden by {member}",
) )
await interaction.response.send_message( await interaction.response.send_message(
@ -462,8 +474,10 @@ class VoiceMeisterPanel(ui.View):
cog, member, vc, owner_id = await _require_owner(interaction) cog, member, vc, owner_id = await _require_owner(interaction)
if cog is None: if cog is None:
return return
overwrite = vc.overwrites_for(vc.guild.default_role)
overwrite.view_channel = None
await vc.set_permissions( await vc.set_permissions(
vc.guild.default_role, view_channel=None, vc.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: unhidden by {member}", reason=f"VoiceMeister: unhidden by {member}",
) )
await interaction.response.send_message( await interaction.response.send_message(

View file

@ -7,9 +7,8 @@ name templates, game detection, logging, blacklist/whitelist, cooldowns.
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
import datetime
import time import time
from typing import Dict, Optional, Set from typing import Dict, Optional
import discord import discord
from redbot.core import Config, commands, checks from redbot.core import Config, commands, checks
@ -64,11 +63,8 @@ class VoiceMeister(commands.Cog):
self.bot.add_view(self._panel_view) self.bot.add_view(self._panel_view)
await self._load_all_guilds() await self._load_all_guilds()
self._bg_tasks.append(asyncio.create_task(self._cleanup_loop())) self._bg_tasks.append(asyncio.create_task(self._cleanup_loop()))
if any( # Always start the game rename loop — it checks per-guild config each cycle
(await self.config.guild_from_id(gid).auto_rename_game()) self._bg_tasks.append(asyncio.create_task(self._game_rename_loop()))
for gid in [g.id for g in self.bot.guilds]
):
self._bg_tasks.append(asyncio.create_task(self._game_rename_loop()))
async def cog_unload(self): async def cog_unload(self):
for task in self._bg_tasks: for task in self._bg_tasks:
@ -97,14 +93,13 @@ class VoiceMeister(commands.Cog):
gid = channel.guild.id gid = channel.guild.id
guild_map.setdefault(gid, {})[str(ch_id)] = owner_id guild_map.setdefault(gid, {})[str(ch_id)] = owner_id
# Also handle guilds that might have had all channels removed # Clear guilds that no longer have temp channels
all_guilds = await self.config.all_guilds() all_guilds = await self.config.all_guilds()
for gid in all_guilds: for gid in all_guilds:
if gid not in guild_map: if gid not in guild_map:
await self.config.guild_from_id(gid).temp_channels.set({}) await self.config.guild_from_id(gid).temp_channels.set({})
else:
await self.config.guild_from_id(gid).temp_channels.set(guild_map[gid])
# Save guilds that have temp channels
for gid, data in guild_map.items(): for gid, data in guild_map.items():
await self.config.guild_from_id(gid).temp_channels.set(data) await self.config.guild_from_id(gid).temp_channels.set(data)
@ -965,7 +960,9 @@ class VoiceMeister(commands.Cog):
vc, _ = await self._get_user_vc(ctx) vc, _ = await self._get_user_vc(ctx)
if not vc: if not vc:
return return
await vc.set_permissions(ctx.guild.default_role, connect=False, overwrite = vc.overwrites_for(ctx.guild.default_role)
overwrite.connect = False
await vc.set_permissions(ctx.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: locked by {ctx.author}") reason=f"VoiceMeister: locked by {ctx.author}")
await ctx.send(embed=ok_embed("Channel **locked** 🔒.")) await ctx.send(embed=ok_embed("Channel **locked** 🔒."))
await self._log_action(ctx.guild, "🔒 Locked", member=ctx.author, channel=vc) await self._log_action(ctx.guild, "🔒 Locked", member=ctx.author, channel=vc)
@ -976,7 +973,9 @@ class VoiceMeister(commands.Cog):
vc, _ = await self._get_user_vc(ctx) vc, _ = await self._get_user_vc(ctx)
if not vc: if not vc:
return return
await vc.set_permissions(ctx.guild.default_role, connect=None, overwrite = vc.overwrites_for(ctx.guild.default_role)
overwrite.connect = None
await vc.set_permissions(ctx.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: unlocked by {ctx.author}") reason=f"VoiceMeister: unlocked by {ctx.author}")
await ctx.send(embed=ok_embed("Channel **unlocked** 🔓.")) await ctx.send(embed=ok_embed("Channel **unlocked** 🔓."))
await self._log_action(ctx.guild, "🔓 Unlocked", member=ctx.author, channel=vc) await self._log_action(ctx.guild, "🔓 Unlocked", member=ctx.author, channel=vc)
@ -987,7 +986,9 @@ class VoiceMeister(commands.Cog):
vc, _ = await self._get_user_vc(ctx) vc, _ = await self._get_user_vc(ctx)
if not vc: if not vc:
return return
await vc.set_permissions(ctx.guild.default_role, view_channel=False, overwrite = vc.overwrites_for(ctx.guild.default_role)
overwrite.view_channel = False
await vc.set_permissions(ctx.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: hidden by {ctx.author}") reason=f"VoiceMeister: hidden by {ctx.author}")
await ctx.send(embed=ok_embed("Channel **hidden** 👤.")) await ctx.send(embed=ok_embed("Channel **hidden** 👤."))
await self._log_action(ctx.guild, "👤 Hidden", member=ctx.author, channel=vc) await self._log_action(ctx.guild, "👤 Hidden", member=ctx.author, channel=vc)
@ -998,7 +999,9 @@ class VoiceMeister(commands.Cog):
vc, _ = await self._get_user_vc(ctx) vc, _ = await self._get_user_vc(ctx)
if not vc: if not vc:
return return
await vc.set_permissions(ctx.guild.default_role, view_channel=None, overwrite = vc.overwrites_for(ctx.guild.default_role)
overwrite.view_channel = None
await vc.set_permissions(ctx.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: unhidden by {ctx.author}") reason=f"VoiceMeister: unhidden by {ctx.author}")
await ctx.send(embed=ok_embed("Channel **visible** 👁️.")) await ctx.send(embed=ok_embed("Channel **visible** 👁️."))
await self._log_action(ctx.guild, "👁️ Unhidden", member=ctx.author, channel=vc) await self._log_action(ctx.guild, "👁️ Unhidden", member=ctx.author, channel=vc)
@ -1009,7 +1012,10 @@ class VoiceMeister(commands.Cog):
vc, _ = await self._get_user_vc(ctx) vc, _ = await self._get_user_vc(ctx)
if not vc: if not vc:
return return
await vc.set_permissions(ctx.guild.default_role, connect=False, view_channel=False, overwrite = vc.overwrites_for(ctx.guild.default_role)
overwrite.connect = False
overwrite.view_channel = False
await vc.set_permissions(ctx.guild.default_role, overwrite=overwrite,
reason=f"VoiceMeister: ghosted by {ctx.author}") reason=f"VoiceMeister: ghosted by {ctx.author}")
await ctx.send(embed=ok_embed("Channel **ghosted** 👻 — hidden and locked.")) await ctx.send(embed=ok_embed("Channel **ghosted** 👻 — hidden and locked."))
await self._log_action(ctx.guild, "👻 Ghosted", member=ctx.author, channel=vc) await self._log_action(ctx.guild, "👻 Ghosted", member=ctx.author, channel=vc)