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 .. import babel, logger
|
||||||
from ..data import ScheduleData as Data
|
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 ScheduleSettings as Settings
|
||||||
from ..settings import ScheduleConfig
|
from ..settings import ScheduleConfig
|
||||||
from ..ui.sessionui import SessionUI
|
from ..ui.sessionui import SessionUI
|
||||||
@@ -289,12 +289,16 @@ class ScheduledSession:
|
|||||||
Remove overwrites for non-members.
|
Remove overwrites for non-members.
|
||||||
"""
|
"""
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
if not (members := list(self.members.values())):
|
|
||||||
return
|
|
||||||
if not (guild := self.guild):
|
if not (guild := self.guild):
|
||||||
return
|
return
|
||||||
if not (room := self.room_channel):
|
if not (room := self.room_channel):
|
||||||
|
# Nothing to do
|
||||||
|
self.prepared = True
|
||||||
|
self.opened = True
|
||||||
return
|
return
|
||||||
|
members = list(self.members.values())
|
||||||
|
|
||||||
|
t = self.bot.translator.t
|
||||||
|
|
||||||
if room.permissions_for(guild.me) >= my_room_permissions:
|
if room.permissions_for(guild.me) >= my_room_permissions:
|
||||||
# Replace the member overwrites
|
# Replace the member overwrites
|
||||||
@@ -314,17 +318,36 @@ class ScheduledSession:
|
|||||||
if mobj:
|
if mobj:
|
||||||
overwrites[mobj] = discord.PermissionOverwrite(connect=True, view_channel=True)
|
overwrites[mobj] = discord.PermissionOverwrite(connect=True, view_channel=True)
|
||||||
try:
|
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:
|
except discord.HTTPException:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
f"Unhandled discord exception received while opening schedule session room {self!r}"
|
f"Unhandled discord exception received while opening schedule session room {self!r}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.debug(
|
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:
|
else:
|
||||||
t = self.bot.translator.t
|
|
||||||
await self.send(
|
await self.send(
|
||||||
t(_p(
|
t(_p(
|
||||||
'session|open|error:room_permissions',
|
'session|open|error:room_permissions',
|
||||||
@@ -344,6 +367,8 @@ class ScheduledSession:
|
|||||||
await asyncio.sleep(ping_wait)
|
await asyncio.sleep(ping_wait)
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
return
|
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]
|
missing = [mid for mid, m in self.members.items() if m.total_clock == 0 and m.clock_start is None]
|
||||||
if missing:
|
if missing:
|
||||||
ping = ''.join(f"<@{mid}>" for mid in 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
|
# In case we somehow left the guild in the meantime
|
||||||
return
|
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]
|
missing = [mid for mid, m in self.members.items() if m.total_clock == 0 and m.clock_start is None]
|
||||||
for mid in missing:
|
for mid in missing:
|
||||||
member = self.guild.get_member(mid)
|
member = self.guild.get_member(mid)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from modules.economy.data import EconomyData, TransactionType
|
|||||||
|
|
||||||
from .. import babel, logger
|
from .. import babel, logger
|
||||||
from ..data import ScheduleData as Data
|
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 ..settings import ScheduleSettings
|
||||||
|
|
||||||
from .session import ScheduledSession
|
from .session import ScheduledSession
|
||||||
@@ -439,6 +439,75 @@ class TimeSlot:
|
|||||||
f"Closed {len(sessions)} for scheduled session timeslot: {self!r}"
|
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:
|
def launch(self) -> asyncio.Task:
|
||||||
self.run_task = asyncio.create_task(self.run(), name=f"TimeSlot {self.slotid}")
|
self.run_task = asyncio.create_task(self.run(), name=f"TimeSlot {self.slotid}")
|
||||||
return self.run_task
|
return self.run_task
|
||||||
@@ -475,6 +544,9 @@ class TimeSlot:
|
|||||||
logger.info(f"Active timeslot closing. {self!r}")
|
logger.info(f"Active timeslot closing. {self!r}")
|
||||||
await self.close(list(self.sessions.values()), consequences=True)
|
await self.close(list(self.sessions.values()), consequences=True)
|
||||||
logger.info(f"Active timeslot closed. {self!r}")
|
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:
|
except asyncio.CancelledError:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Deactivating active time slot: {self!r}"
|
f"Deactivating active time slot: {self!r}"
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import itertools
|
import itertools
|
||||||
import datetime as dt
|
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 utils.ratelimits import Bucket
|
||||||
|
from . import logger, babel
|
||||||
|
|
||||||
_p, _np = babel._p, babel._np
|
_p, _np = babel._p, babel._np
|
||||||
|
|
||||||
@@ -88,3 +92,24 @@ def format_until(t, distance):
|
|||||||
'ui:schedule|format_until|now',
|
'ui:schedule|format_until|now',
|
||||||
"right 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