From e4576deb29426c3608211162d6ef7c291df4c067 Mon Sep 17 00:00:00 2001 From: unknownpedestrian Date: Sat, 8 Nov 2025 22:29:33 -0500 Subject: [PATCH 1/4] Change way we determine "active" in save state (and fix modals) --- bot.py | 27 ++++++++++++++++++--------- models/models.py | 16 +++++++++------- services/state_manager.py | 8 +++++--- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/bot.py b/bot.py index 32755ba..4c64fbe 100644 --- a/bot.py +++ b/bot.py @@ -325,11 +325,11 @@ async def maint(interaction: discord.Interaction, status: bool = True): await interaction.response.send_message("๐Ÿ› ๏ธ Toggling maintenance mode... please wait") await STATE_MANAGER.set_maint(status=status) - active_guild_ids = STATE_MANAGER.all_active_guild_ids() for guild_id in active_guild_ids: text_channel = bot.get_channel(STATE_MANAGER.get_state(guild_id, 'text_channel_id')) - if status: + is_active = STATE_MANAGER.get_state(guild_id, 'is_active') or False + if status and is_active is True: embed_data = { 'title': "Maintenance", 'color': 0xfce053, @@ -337,16 +337,24 @@ async def maint(interaction: discord.Interaction, status: bool = True): 'timestamp': str(datetime.datetime.now(datetime.UTC)), } await stop_playback(bot.get_guild(guild_id)) + embed = discord.Embed.from_dict(embed_data) + await text_channel.send(embed=embed) else: - embed_data = { - 'title': "Maintenance", - 'color': 0xfce053, - 'description': "Maintenance has concluded.", - 'timestamp': str(datetime.datetime.now(datetime.UTC)), + was_active = STATE_MANAGER.get_state(guild_id, 'is_active') or False + if was_active is True and status == False: + embed_data = { + 'title': "Maintenance", + 'color': 0xfce053, + 'description': "Maintenance has concluded.", + 'timestamp': str(datetime.datetime.now(datetime.UTC)), } - embed = discord.Embed.from_dict(embed_data) - await text_channel.send(embed=embed) + embed = discord.Embed.from_dict(embed_data) + await text_channel.send(embed=embed) + if status: + await interaction.edit_original_response(content="๐Ÿ’พ saving state...") + STATE_MANAGER.save_state() + asyncio.sleep(3) # Small delay to ensure state is saved before continuing await interaction.edit_original_response(content="๐Ÿ‘ท Maintenance mode enabled") else: STATE_MANAGER.clear_state() @@ -948,6 +956,7 @@ def stream_finished_callback(error): STATE_MANAGER.set_state(interaction.guild.id, 'current_stream_url', url) STATE_MANAGER.set_state(interaction.guild.id, 'text_channel_id', interaction.channel.id) STATE_MANAGER.set_state(interaction.guild.id, 'start_time', datetime.datetime.now(datetime.UTC)) + STATE_MANAGER.set_state(interaction.guild.id, 'is_active', True) # And let the user know what song is playing await send_song_info(interaction.guild.id) diff --git a/models/models.py b/models/models.py index 8c33fc3..8a4f35a 100644 --- a/models/models.py +++ b/models/models.py @@ -23,19 +23,21 @@ class GuildState(Base): # text_channel = Text channel original play command came from # start_time = Time the current stream started playing # last_active_user_time = Time the last active user was spotted in the voice channel + # is_active = Boolean for if the bot is currently active in the guild True|None # cleaning_up = Boolean for if the bot is currently stopping/cleaning up True|None # health_error_count = Int number of times a health error occurred in a row # ffmpeg_process_pid = PID for the FFMPEG process associated with the guild __tablename__ = "guild_state" guild_id: Mapped[int] = mapped_column(primary_key=True) - current_stream_url: Mapped[str] = mapped_column(String) - private_stream: Mapped[bool] = mapped_column(Boolean) - text_channel_id: Mapped[int] = mapped_column(Integer) - start_time: Mapped[datetime] = mapped_column(DateTime) - last_active_user_time: Mapped[datetime] = mapped_column(DateTime) - cleaning_up: Mapped[bool] = mapped_column(Boolean) - ffmpeg_process_pid: Mapped[int] = mapped_column(Integer) + current_stream_url: Mapped[str] = mapped_column(String, nullable=True) + private_stream: Mapped[bool] = mapped_column(Boolean, nullable=True) + text_channel_id: Mapped[int] = mapped_column(Integer, nullable=True) + start_time: Mapped[datetime] = mapped_column(DateTime, nullable=True) + last_active_user_time: Mapped[datetime] = mapped_column(DateTime, nullable=True) + cleaning_up: Mapped[bool] = mapped_column(Boolean, nullable=True) + is_active: Mapped[bool] = mapped_column(Boolean, nullable=True) + ffmpeg_process_pid: Mapped[int] = mapped_column(Integer, nullable=True) health_error_count: list[{ErrorStates, int}] = [] class BotState(Base): diff --git a/services/state_manager.py b/services/state_manager.py index a4e3033..b1e9cbb 100644 --- a/services/state_manager.py +++ b/services/state_manager.py @@ -12,6 +12,7 @@ class StateManager: # text_channel = Text channel original play command came from # start_time = Time the current stream started playing # last_active_user_time = Time the last active user was spotted in the voice channel + # is_active = Boolean for if the bot is currently active in the guild True|None # cleaning_up = Boolean for if the bot is currently stopping/cleaning up True|None # health_error_count = Int number of times a health error occurred in a row # ffmpeg_process_pid = PID for the FFMPEG process associated with the guild @@ -72,7 +73,8 @@ def clear_state(self, guild_id: int=None, force: bool=False): saved_state = { 'text_channel_id': self.guild_state[guild_id].text_channel_id, - 'private_stream': self.guild_state[guild_id].private_stream + 'private_stream': self.guild_state[guild_id].private_stream, + 'is_active': self.guild_state[guild_id].is_active } self.guild_state[guild_id] = GuildState() if not force: @@ -94,11 +96,11 @@ def all_active_guild_ids(self): guild = self.bot.get_guild(guild_id) # Sometimes we need to exclude some state variables when considering if the guild is active - vars_to_exclude = ['cleaning_up', 'text_channel_id', 'private_stream'] + vars_to_exclude = ['cleaning_up', 'text_channel_id', 'private_stream', 'is_active'] temp_state = {key: value for key, value in self.get_state(guild_id).items() if key not in vars_to_exclude} state_active = bool(temp_state) - vc_active = guild and guild.voice_client and guild.voice_client.is_connected() + vc_active = guild and guild.voice_client and guild.voice_client.is_connected() if state_active or vc_active: active_ids.append(guild_id) return active_ids From ceb79ff137c79ea550fbccd7964b3a3aa77168c7 Mon Sep 17 00:00:00 2001 From: unknownpedestrian Date: Sat, 8 Nov 2025 22:45:01 -0500 Subject: [PATCH 2/4] remove unused code; make is_active a non-constant --- bot.py | 3 --- services/state_manager.py | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/bot.py b/bot.py index 4c64fbe..3770af4 100644 --- a/bot.py +++ b/bot.py @@ -352,9 +352,6 @@ async def maint(interaction: discord.Interaction, status: bool = True): await text_channel.send(embed=embed) if status: - await interaction.edit_original_response(content="๐Ÿ’พ saving state...") - STATE_MANAGER.save_state() - asyncio.sleep(3) # Small delay to ensure state is saved before continuing await interaction.edit_original_response(content="๐Ÿ‘ท Maintenance mode enabled") else: STATE_MANAGER.clear_state() diff --git a/services/state_manager.py b/services/state_manager.py index b1e9cbb..71996f6 100644 --- a/services/state_manager.py +++ b/services/state_manager.py @@ -73,8 +73,7 @@ def clear_state(self, guild_id: int=None, force: bool=False): saved_state = { 'text_channel_id': self.guild_state[guild_id].text_channel_id, - 'private_stream': self.guild_state[guild_id].private_stream, - 'is_active': self.guild_state[guild_id].is_active + 'private_stream': self.guild_state[guild_id].private_stream } self.guild_state[guild_id] = GuildState() if not force: From e3db5d7137e917303611f4d2bd34728cc099b0c9 Mon Sep 17 00:00:00 2001 From: unknownpedestrian Date: Sun, 9 Nov 2025 13:40:25 -0500 Subject: [PATCH 3/4] add back was_active variable to state, but working this time! (brain synapse fire) --- bot.py | 5 ++++- models/models.py | 2 ++ services/state_manager.py | 8 +++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/bot.py b/bot.py index 3770af4..6575f98 100644 --- a/bot.py +++ b/bot.py @@ -337,10 +337,11 @@ async def maint(interaction: discord.Interaction, status: bool = True): 'timestamp': str(datetime.datetime.now(datetime.UTC)), } await stop_playback(bot.get_guild(guild_id)) + STATE_MANAGER.set_state(guild_id, 'was_active', True) embed = discord.Embed.from_dict(embed_data) await text_channel.send(embed=embed) else: - was_active = STATE_MANAGER.get_state(guild_id, 'is_active') or False + was_active = STATE_MANAGER.get_state(guild_id, 'was_active') or False if was_active is True and status == False: embed_data = { 'title': "Maintenance", @@ -352,6 +353,8 @@ async def maint(interaction: discord.Interaction, status: bool = True): await text_channel.send(embed=embed) if status: + await interaction.edit_original_response(content="๐Ÿ’พ saving state...") + await STATE_MANAGER.save_state() await interaction.edit_original_response(content="๐Ÿ‘ท Maintenance mode enabled") else: STATE_MANAGER.clear_state() diff --git a/models/models.py b/models/models.py index 8a4f35a..742530b 100644 --- a/models/models.py +++ b/models/models.py @@ -24,6 +24,7 @@ class GuildState(Base): # start_time = Time the current stream started playing # last_active_user_time = Time the last active user was spotted in the voice channel # is_active = Boolean for if the bot is currently active in the guild True|None + # was_active = Boolean for if the bot was active before going into maintenance True|None # cleaning_up = Boolean for if the bot is currently stopping/cleaning up True|None # health_error_count = Int number of times a health error occurred in a row # ffmpeg_process_pid = PID for the FFMPEG process associated with the guild @@ -37,6 +38,7 @@ class GuildState(Base): last_active_user_time: Mapped[datetime] = mapped_column(DateTime, nullable=True) cleaning_up: Mapped[bool] = mapped_column(Boolean, nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, nullable=True) + was_active: Mapped[bool] = mapped_column(Boolean, nullable=True) ffmpeg_process_pid: Mapped[int] = mapped_column(Integer, nullable=True) health_error_count: list[{ErrorStates, int}] = [] diff --git a/services/state_manager.py b/services/state_manager.py index 71996f6..1151281 100644 --- a/services/state_manager.py +++ b/services/state_manager.py @@ -13,6 +13,7 @@ class StateManager: # start_time = Time the current stream started playing # last_active_user_time = Time the last active user was spotted in the voice channel # is_active = Boolean for if the bot is currently active in the guild True|None + # was_active = Boolean for if the bot was active before going into maintenance True|None # cleaning_up = Boolean for if the bot is currently stopping/cleaning up True|None # health_error_count = Int number of times a health error occurred in a row # ffmpeg_process_pid = PID for the FFMPEG process associated with the guild @@ -73,7 +74,8 @@ def clear_state(self, guild_id: int=None, force: bool=False): saved_state = { 'text_channel_id': self.guild_state[guild_id].text_channel_id, - 'private_stream': self.guild_state[guild_id].private_stream + 'private_stream': self.guild_state[guild_id].private_stream, + 'was_active': self.guild_state[guild_id].was_active } self.guild_state[guild_id] = GuildState() if not force: @@ -83,7 +85,7 @@ def clear_state(self, guild_id: int=None, force: bool=False): # Update maintenance status async def set_maint(self, status: bool): self.bot_state.maint = status - await self.save_state() + # await self.save_state() def get_maint(self): return self.bot_state.maint @@ -95,7 +97,7 @@ def all_active_guild_ids(self): guild = self.bot.get_guild(guild_id) # Sometimes we need to exclude some state variables when considering if the guild is active - vars_to_exclude = ['cleaning_up', 'text_channel_id', 'private_stream', 'is_active'] + vars_to_exclude = ['cleaning_up', 'text_channel_id', 'private_stream', 'is_active', 'was_active'] temp_state = {key: value for key, value in self.get_state(guild_id).items() if key not in vars_to_exclude} state_active = bool(temp_state) From b5fdf3e6cadfa9e4fa67dfeb41c0997e84a8e0e4 Mon Sep 17 00:00:00 2001 From: unknownpedestrian Date: Sun, 9 Nov 2025 14:14:29 -0500 Subject: [PATCH 4/4] Fix forever maintenance --- bot.py | 6 +++++- services/state_manager.py | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/bot.py b/bot.py index 6575f98..a15734d 100644 --- a/bot.py +++ b/bot.py @@ -355,10 +355,14 @@ async def maint(interaction: discord.Interaction, status: bool = True): if status: await interaction.edit_original_response(content="๐Ÿ’พ saving state...") await STATE_MANAGER.save_state() + asyncio.sleep(5) await interaction.edit_original_response(content="๐Ÿ‘ท Maintenance mode enabled") else: - STATE_MANAGER.clear_state() + await interaction.edit_original_response(content="๐Ÿงผ Purging State + DB...") + STATE_MANAGER.clear_state(force=True) await STATE_MANAGER.clear_state_db() + asyncio.sleep(5) + await STATE_MANAGER.set_maint(status=status) await interaction.edit_original_response(content="๐Ÿ‘ท Maintenance mode disabled") else: diff --git a/services/state_manager.py b/services/state_manager.py index 1151281..835c2a8 100644 --- a/services/state_manager.py +++ b/services/state_manager.py @@ -130,4 +130,5 @@ async def clear_state_db(self): async with self.ASYNC_SESSION_LOCAL() as session: stmt = delete(GuildState) await session.execute(stmt) + session.add(self.bot_state) await session.commit()