fix(schedule): Remove members after session.
This commit is contained in:
@@ -12,7 +12,7 @@ from utils.lib import MessageArgs
|
||||
|
||||
from .. import babel, logger
|
||||
from ..data import ScheduleData as Data
|
||||
from ..lib import slotid_to_utc
|
||||
from ..lib import slotid_to_utc, vacuum_channel
|
||||
from ..settings import ScheduleSettings as Settings
|
||||
from ..settings import ScheduleConfig
|
||||
from ..ui.sessionui import SessionUI
|
||||
@@ -289,12 +289,16 @@ class ScheduledSession:
|
||||
Remove overwrites for non-members.
|
||||
"""
|
||||
async with self.lock:
|
||||
if not (members := list(self.members.values())):
|
||||
return
|
||||
if not (guild := self.guild):
|
||||
return
|
||||
if not (room := self.room_channel):
|
||||
# Nothing to do
|
||||
self.prepared = True
|
||||
self.opened = True
|
||||
return
|
||||
members = list(self.members.values())
|
||||
|
||||
t = self.bot.translator.t
|
||||
|
||||
if room.permissions_for(guild.me) >= my_room_permissions:
|
||||
# Replace the member overwrites
|
||||
@@ -314,17 +318,36 @@ class ScheduledSession:
|
||||
if mobj:
|
||||
overwrites[mobj] = discord.PermissionOverwrite(connect=True, view_channel=True)
|
||||
try:
|
||||
await room.edit(overwrites=overwrites)
|
||||
await room.edit(
|
||||
overwrites=overwrites,
|
||||
reason=t(_p(
|
||||
'session|open|update_perms|audit_reason',
|
||||
"Opening configured scheduled session room."
|
||||
))
|
||||
)
|
||||
except discord.HTTPException:
|
||||
logger.exception(
|
||||
f"Unhandled discord exception received while opening schedule session room {self!r}"
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
f"Opened schedule session room for session {self!r}"
|
||||
f"Opened schedule session room for session {self!r} with overwrites {overwrites}"
|
||||
)
|
||||
|
||||
# Cleanup members who should not be in the channel(s)
|
||||
if room.type is discord.enums.ChannelType.category:
|
||||
channels = room.voice_channels
|
||||
else:
|
||||
channels = [room]
|
||||
for channel in channels:
|
||||
await vacuum_channel(
|
||||
channel,
|
||||
reason=t(_p(
|
||||
'session|open|clean_room|audit_reason',
|
||||
"Removing extra member from scheduled session room."
|
||||
))
|
||||
)
|
||||
else:
|
||||
t = self.bot.translator.t
|
||||
await self.send(
|
||||
t(_p(
|
||||
'session|open|error:room_permissions',
|
||||
@@ -344,6 +367,8 @@ class ScheduledSession:
|
||||
await asyncio.sleep(ping_wait)
|
||||
except asyncio.CancelledError:
|
||||
return
|
||||
|
||||
# Ghost ping alert for missing members
|
||||
missing = [mid for mid, m in self.members.items() if m.total_clock == 0 and m.clock_start is None]
|
||||
if missing:
|
||||
ping = ''.join(f"<@{mid}>" for mid in missing)
|
||||
@@ -361,6 +386,7 @@ class ScheduledSession:
|
||||
# In case we somehow left the guild in the meantime
|
||||
return
|
||||
|
||||
# DM alert for _still_ missing members
|
||||
missing = [mid for mid, m in self.members.items() if m.total_clock == 0 and m.clock_start is None]
|
||||
for mid in missing:
|
||||
member = self.guild.get_member(mid)
|
||||
|
||||
@@ -19,7 +19,7 @@ from modules.economy.data import EconomyData, TransactionType
|
||||
|
||||
from .. import babel, logger
|
||||
from ..data import ScheduleData as Data
|
||||
from ..lib import slotid_to_utc, batchrun_per_second, limit_concurrency
|
||||
from ..lib import slotid_to_utc, batchrun_per_second, limit_concurrency, vacuum_channel
|
||||
from ..settings import ScheduleSettings
|
||||
|
||||
from .session import ScheduledSession
|
||||
@@ -439,6 +439,75 @@ class TimeSlot:
|
||||
f"Closed {len(sessions)} for scheduled session timeslot: {self!r}"
|
||||
)
|
||||
|
||||
@log_wrap(action='Tidy session rooms')
|
||||
async def tidy_rooms(self, sessions: list[ScheduledSession]):
|
||||
"""
|
||||
'Tidy Up' after sessions have been closed.
|
||||
|
||||
This cleans up permissions for sessions which do not have another running session,
|
||||
and vacuums the channel.
|
||||
|
||||
Somewhat temporary measure,
|
||||
workaround for the design flaw that channel permissions are only updated during open,
|
||||
and hence are never cleared unless there is a next session.
|
||||
Limitations include not clearing after a manual close.
|
||||
"""
|
||||
t = self.bot.translator.t
|
||||
|
||||
for session in sessions:
|
||||
if not session.guild:
|
||||
# Can no longer access the session guild, nothing to clean up
|
||||
logger.debug(f"Not tidying {session!r} because guild gone.")
|
||||
continue
|
||||
if not (room := session.room_channel):
|
||||
# Session did not have a room to clean up
|
||||
logger.debug(f"Not tidying {session!r} because room channel gone.")
|
||||
continue
|
||||
if not session.opened or session.cancelled:
|
||||
# Not an active session, don't try to tidy up
|
||||
logger.debug(f"Not tidying {session!r} because cancelled or not opened.")
|
||||
continue
|
||||
if (active := self.cog.get_active_session(session.guild.id)) is not None:
|
||||
# Rely on the active session to set permissions and vacuum channel
|
||||
logger.debug(f"Not tidying {session!r} because guild has active session {active!r}.")
|
||||
continue
|
||||
logger.debug(f"Tidying {session!r}.")
|
||||
|
||||
me = session.guild.me
|
||||
if room.permissions_for(me).manage_roles:
|
||||
overwrites = {
|
||||
target: overwrite for target, overwrite in room.overwrites.items()
|
||||
if not isinstance(target, discord.Member)
|
||||
}
|
||||
try:
|
||||
await room.edit(
|
||||
overwrites=overwrites,
|
||||
reason=t(_p(
|
||||
"session|closing|audit_reason",
|
||||
"Removing previous scheduled session member permissions."
|
||||
))
|
||||
)
|
||||
except discord.HTTPException:
|
||||
logger.warning(
|
||||
f"Unexpected exception occurred while tidying after sessions {session!r}",
|
||||
exc_info=True
|
||||
)
|
||||
else:
|
||||
logger.debug(f"Updated room permissions while tidying {session!r}.")
|
||||
if room.type is discord.enums.ChannelType.category:
|
||||
channels = room.voice_channels
|
||||
else:
|
||||
channels = [room]
|
||||
for channel in channels:
|
||||
await vacuum_channel(
|
||||
channel,
|
||||
reason=t(_p(
|
||||
"session|closing|disconnecting|audit_reason",
|
||||
"Disconnecting previous scheduled session members."
|
||||
))
|
||||
)
|
||||
logger.debug(f"Finished tidying {session!r}.")
|
||||
|
||||
def launch(self) -> asyncio.Task:
|
||||
self.run_task = asyncio.create_task(self.run(), name=f"TimeSlot {self.slotid}")
|
||||
return self.run_task
|
||||
@@ -475,6 +544,9 @@ class TimeSlot:
|
||||
logger.info(f"Active timeslot closing. {self!r}")
|
||||
await self.close(list(self.sessions.values()), consequences=True)
|
||||
logger.info(f"Active timeslot closed. {self!r}")
|
||||
await asyncio.sleep(30)
|
||||
await self.tidy_rooms(list(self.sessions.values()))
|
||||
logger.info(f"Previous active timeslot tidied up. {self!r}")
|
||||
except asyncio.CancelledError:
|
||||
logger.info(
|
||||
f"Deactivating active time slot: {self!r}"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import asyncio
|
||||
import itertools
|
||||
import datetime as dt
|
||||
from typing import Optional
|
||||
|
||||
from . import logger, babel
|
||||
import discord
|
||||
|
||||
from meta.logger import log_wrap
|
||||
from utils.ratelimits import Bucket
|
||||
from . import logger, babel
|
||||
|
||||
_p, _np = babel._p, babel._np
|
||||
|
||||
@@ -88,3 +92,24 @@ def format_until(t, distance):
|
||||
'ui:schedule|format_until|now',
|
||||
"right now!"
|
||||
))
|
||||
|
||||
|
||||
@log_wrap(action='Vacuum Channel')
|
||||
async def vacuum_channel(channel: discord.VoiceChannel, reason: Optional[str] = None):
|
||||
"""
|
||||
Launch disconnect tasks for each voice channel member who does not have permission to connect.
|
||||
"""
|
||||
me = channel.guild.me
|
||||
if not channel.permissions_for(me).move_members:
|
||||
# Nothing we can do
|
||||
return
|
||||
|
||||
to_remove = [member for member in channel.members if not channel.permissions_for(member).connect]
|
||||
for member in to_remove:
|
||||
# Disconnect member from voice
|
||||
# Extra check here since members may come and go while we are trying to remove
|
||||
if member in channel.members:
|
||||
try:
|
||||
await member.edit(voice_channel=None, reason=reason)
|
||||
except discord.HTTPException:
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user