fix(schedule): Remove members after session.

This commit is contained in:
2023-10-01 02:22:09 +03:00
parent 5c057278e7
commit e2a2e7be8a
3 changed files with 131 additions and 8 deletions

View File

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

View File

@@ -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}"

View File

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