add quiet/unquiet commands to suppress song change announcements
This commit is contained in:
parent
9c978c4773
commit
d7af7baa61
15
README.md
15
README.md
@ -73,6 +73,19 @@ admin:
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
## Manager Commands
|
||||||
|
|
||||||
|
The bot manager supports the following commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
icecast-irc-bot-manager list # List running bots
|
||||||
|
icecast-irc-bot-manager start --config FILE # Start a bot
|
||||||
|
icecast-irc-bot-manager stop BOT_ID # Stop a bot
|
||||||
|
icecast-irc-bot-manager restart BOT_ID # Restart a bot
|
||||||
|
icecast-irc-bot-manager quiet BOT_ID # Disable song announcements
|
||||||
|
icecast-irc-bot-manager unquiet BOT_ID # Enable song announcements
|
||||||
|
```
|
||||||
|
|
||||||
### Running with Automatic Restart Support
|
### Running with Automatic Restart Support
|
||||||
|
|
||||||
The recommended way to run the bot is using the provided `bot.sh` script, which handles automatic restarts when using the `!restart` command:
|
The recommended way to run the bot is using the provided `bot.sh` script, which handles automatic restarts when using the `!restart` command:
|
||||||
@ -132,6 +145,8 @@ Admin commands (only available to users listed in the `admin.users` config):
|
|||||||
|
|
||||||
- `!start` - Start stream monitoring
|
- `!start` - Start stream monitoring
|
||||||
- `!stop` - Stop stream monitoring
|
- `!stop` - Stop stream monitoring
|
||||||
|
- `!quiet` - Disable song announcements but continue monitoring
|
||||||
|
- `!unquiet` - Enable song announcements
|
||||||
- `!reconnect` - Reconnect to the stream
|
- `!reconnect` - Reconnect to the stream
|
||||||
- `!restart` - Restart the bot
|
- `!restart` - Restart the bot
|
||||||
- `!quit` - Shutdown the bot
|
- `!quit` - Shutdown the bot
|
||||||
|
|||||||
@ -17,6 +17,7 @@ announce:
|
|||||||
- "Unknown"
|
- "Unknown"
|
||||||
- "Unable to fetch metadata"
|
- "Unable to fetch metadata"
|
||||||
- "Error fetching metadata"
|
- "Error fetching metadata"
|
||||||
|
quiet_on_start: false # If true, bot starts in quiet mode (no announcements)
|
||||||
|
|
||||||
commands:
|
commands:
|
||||||
prefix: "!" # Command prefix (e.g. !np, !help)
|
prefix: "!" # Command prefix (e.g. !np, !help)
|
||||||
|
|||||||
@ -388,6 +388,76 @@ class BotManager:
|
|||||||
# Start the bot with the same config
|
# Start the bot with the same config
|
||||||
return await self.start_bot(config_path)
|
return await self.start_bot(config_path)
|
||||||
|
|
||||||
|
async def quiet_bot(self, bot_id: str) -> bool:
|
||||||
|
"""Disable song announcements for a running bot.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot_id: ID of the bot to quiet
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if command was sent successfully
|
||||||
|
"""
|
||||||
|
# Check if bot exists in state
|
||||||
|
state = self._load_state()
|
||||||
|
if bot_id not in state:
|
||||||
|
print(f"Bot {bot_id} not found")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get the socket path for this bot
|
||||||
|
socket_path = Path(tempfile.gettempdir()) / f"icecast_bot_{bot_id}.sock"
|
||||||
|
if not socket_path.exists():
|
||||||
|
print(f"Socket for bot {bot_id} not found at {socket_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Send quiet command
|
||||||
|
print(f"Sending quiet command to bot {bot_id}")
|
||||||
|
reader, writer = await asyncio.open_unix_connection(str(socket_path))
|
||||||
|
writer.write(b'quiet')
|
||||||
|
await writer.drain()
|
||||||
|
writer.close()
|
||||||
|
await writer.wait_closed()
|
||||||
|
print(f"Quiet command sent to bot {bot_id}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error sending quiet command to bot {bot_id}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def unquiet_bot(self, bot_id: str) -> bool:
|
||||||
|
"""Enable song announcements for a running bot.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot_id: ID of the bot to unquiet
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if command was sent successfully
|
||||||
|
"""
|
||||||
|
# Check if bot exists in state
|
||||||
|
state = self._load_state()
|
||||||
|
if bot_id not in state:
|
||||||
|
print(f"Bot {bot_id} not found")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get the socket path for this bot
|
||||||
|
socket_path = Path(tempfile.gettempdir()) / f"icecast_bot_{bot_id}.sock"
|
||||||
|
if not socket_path.exists():
|
||||||
|
print(f"Socket for bot {bot_id} not found at {socket_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Send unquiet command
|
||||||
|
print(f"Sending unquiet command to bot {bot_id}")
|
||||||
|
reader, writer = await asyncio.open_unix_connection(str(socket_path))
|
||||||
|
writer.write(b'unquiet')
|
||||||
|
await writer.drain()
|
||||||
|
writer.close()
|
||||||
|
await writer.wait_closed()
|
||||||
|
print(f"Unquiet command sent to bot {bot_id}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error sending unquiet command to bot {bot_id}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
async def list_bots(self) -> bool:
|
async def list_bots(self) -> bool:
|
||||||
"""List all running bots.
|
"""List all running bots.
|
||||||
|
|
||||||
@ -465,8 +535,8 @@ class BotManager:
|
|||||||
async def main():
|
async def main():
|
||||||
parser = argparse.ArgumentParser(description='Icecast IRC Bot Manager')
|
parser = argparse.ArgumentParser(description='Icecast IRC Bot Manager')
|
||||||
parser.add_argument('--config', type=str, help='Path to config file or directory')
|
parser.add_argument('--config', type=str, help='Path to config file or directory')
|
||||||
parser.add_argument('command', choices=['start', 'stop', 'restart', 'list'], help='Command to execute')
|
parser.add_argument('command', choices=['start', 'stop', 'restart', 'list', 'quiet', 'unquiet'], help='Command to execute')
|
||||||
parser.add_argument('bot_id', nargs='?', help='Bot ID for start/stop/restart commands, or "all" to stop all bots')
|
parser.add_argument('bot_id', nargs='?', help='Bot ID for start/stop/restart/quiet/unquiet commands, or "all" to stop all bots')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@ -538,6 +608,28 @@ async def main():
|
|||||||
break
|
break
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
elif args.command == 'quiet':
|
||||||
|
should_cleanup = False # Don't need cleanup for quiet command
|
||||||
|
if not args.bot_id:
|
||||||
|
print("Error: bot_id required for quiet command")
|
||||||
|
sys.exit(1)
|
||||||
|
if args.bot_id == "all":
|
||||||
|
print("Error: quiet all is not supported")
|
||||||
|
sys.exit(1)
|
||||||
|
if not await manager.quiet_bot(args.bot_id):
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
elif args.command == 'unquiet':
|
||||||
|
should_cleanup = False # Don't need cleanup for unquiet command
|
||||||
|
if not args.bot_id:
|
||||||
|
print("Error: bot_id required for unquiet command")
|
||||||
|
sys.exit(1)
|
||||||
|
if args.bot_id == "all":
|
||||||
|
print("Error: unquiet all is not supported")
|
||||||
|
sys.exit(1)
|
||||||
|
if not await manager.unquiet_bot(args.bot_id):
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
should_cleanup = True # Need cleanup for keyboard interrupt
|
should_cleanup = True # Need cleanup for keyboard interrupt
|
||||||
|
|||||||
@ -68,4 +68,6 @@ echo "Usage:"
|
|||||||
echo "icecast-irc-bot-manager list # List running bots"
|
echo "icecast-irc-bot-manager list # List running bots"
|
||||||
echo "icecast-irc-bot-manager start --config FILE # Start a bot"
|
echo "icecast-irc-bot-manager start --config FILE # Start a bot"
|
||||||
echo "icecast-irc-bot-manager stop BOT_ID # Stop a bot"
|
echo "icecast-irc-bot-manager stop BOT_ID # Stop a bot"
|
||||||
echo "icecast-irc-bot-manager restart BOT_ID # Restart a bot"
|
echo "icecast-irc-bot-manager restart BOT_ID # Restart a bot"
|
||||||
|
echo "icecast-irc-bot-manager quiet BOT_ID # Disable song announcements"
|
||||||
|
echo "icecast-irc-bot-manager unquiet BOT_ID # Enable song announcements"
|
||||||
86
main.py
86
main.py
@ -90,6 +90,8 @@ class RestartManager:
|
|||||||
self.socket_path = Path(tempfile.gettempdir()) / f"icecast_bot_{bot_id}.sock"
|
self.socket_path = Path(tempfile.gettempdir()) / f"icecast_bot_{bot_id}.sock"
|
||||||
self.server = None
|
self.server = None
|
||||||
self.should_restart = False
|
self.should_restart = False
|
||||||
|
self.quiet_requested = False
|
||||||
|
self.unquiet_requested = False
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
"""Start the restart manager server."""
|
"""Start the restart manager server."""
|
||||||
@ -119,6 +121,12 @@ class RestartManager:
|
|||||||
if data == b'restart':
|
if data == b'restart':
|
||||||
self.logger.info("Received restart request")
|
self.logger.info("Received restart request")
|
||||||
self.should_restart = True
|
self.should_restart = True
|
||||||
|
elif data == b'quiet':
|
||||||
|
self.logger.info("Received quiet request")
|
||||||
|
self.quiet_requested = True
|
||||||
|
elif data == b'unquiet':
|
||||||
|
self.logger.info("Received unquiet request")
|
||||||
|
self.unquiet_requested = True
|
||||||
writer.close()
|
writer.close()
|
||||||
await writer.wait_closed()
|
await writer.wait_closed()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -256,6 +264,9 @@ class IcecastBot:
|
|||||||
self.monitor_task = None
|
self.monitor_task = None
|
||||||
self.should_monitor = True
|
self.should_monitor = True
|
||||||
self.is_monitoring = False
|
self.is_monitoring = False
|
||||||
|
self.should_announce = not self.config.get('announce', {}).get('quiet_on_start', False) # Respect quiet_on_start config
|
||||||
|
if not self.should_announce:
|
||||||
|
self.logger.info("Starting in quiet mode (announcements disabled) due to configuration")
|
||||||
self.admin_users = self.config.get('admin', {}).get('users', ['*'])
|
self.admin_users = self.config.get('admin', {}).get('users', ['*'])
|
||||||
self.current_process = None
|
self.current_process = None
|
||||||
self.should_exit = False
|
self.should_exit = False
|
||||||
@ -634,6 +645,40 @@ class IcecastBot:
|
|||||||
await message.recipient.message("Stream monitoring started.")
|
await message.recipient.message("Stream monitoring started.")
|
||||||
self.command_handlers['start_monitoring'] = start_monitoring
|
self.command_handlers['start_monitoring'] = start_monitoring
|
||||||
|
|
||||||
|
@self.bot.on_message(create_command_pattern('quiet'))
|
||||||
|
@self.admin_required
|
||||||
|
async def quiet_bot(message):
|
||||||
|
"""!quiet: Disable song announcements (admin only)
|
||||||
|
|
||||||
|
Continues monitoring the stream for metadata changes,
|
||||||
|
but stops announcing songs in the channel.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: IRC message object
|
||||||
|
"""
|
||||||
|
self.should_announce = False
|
||||||
|
self.logger.info("Song announcements disabled by admin command")
|
||||||
|
if hasattr(message.recipient, 'name') and message.recipient.name.startswith('#'):
|
||||||
|
await message.recipient.message("Song announcements disabled. Bot will continue monitoring but remain quiet.")
|
||||||
|
self.command_handlers['quiet_bot'] = quiet_bot
|
||||||
|
|
||||||
|
@self.bot.on_message(create_command_pattern('unquiet'))
|
||||||
|
@self.admin_required
|
||||||
|
async def unquiet_bot(message):
|
||||||
|
"""!unquiet: Enable song announcements (admin only)
|
||||||
|
|
||||||
|
Resumes announcing songs in the channel.
|
||||||
|
The bot must already be monitoring the stream.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: IRC message object
|
||||||
|
"""
|
||||||
|
self.should_announce = True
|
||||||
|
self.logger.info("Song announcements enabled by admin command")
|
||||||
|
if hasattr(message.recipient, 'name') and message.recipient.name.startswith('#'):
|
||||||
|
await message.recipient.message("Song announcements enabled. Bot will now announce songs.")
|
||||||
|
self.command_handlers['unquiet_bot'] = unquiet_bot
|
||||||
|
|
||||||
def _build_command_mappings(self):
|
def _build_command_mappings(self):
|
||||||
"""Build bidirectional mappings between command patterns and method names.
|
"""Build bidirectional mappings between command patterns and method names.
|
||||||
|
|
||||||
@ -1018,9 +1063,14 @@ class IcecastBot:
|
|||||||
- The channel object is valid
|
- The channel object is valid
|
||||||
- The song doesn't match any ignore patterns
|
- The song doesn't match any ignore patterns
|
||||||
- The announcement format is configured
|
- The announcement format is configured
|
||||||
|
- The should_announce flag is True
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.logger.info(f"Attempting to announce song: {song}")
|
self.logger.info(f"Attempting to announce song: {song}")
|
||||||
|
if not self.should_announce:
|
||||||
|
self.logger.info(f"Song announcements are disabled, not announcing: {song}")
|
||||||
|
return
|
||||||
|
|
||||||
if self.channel and self.should_announce_song(song):
|
if self.channel and self.should_announce_song(song):
|
||||||
self.logger.debug(f"Song passed filters, preparing to announce")
|
self.logger.debug(f"Song passed filters, preparing to announce")
|
||||||
# Use the stored channel object directly
|
# Use the stored channel object directly
|
||||||
@ -1257,9 +1307,45 @@ async def run_single_bot(bot: IcecastBot):
|
|||||||
bot.logger.info(f"Running single bot with ID: {bot.bot_id}")
|
bot.logger.info(f"Running single bot with ID: {bot.bot_id}")
|
||||||
try:
|
try:
|
||||||
bot.logger.info("Starting bot...")
|
bot.logger.info("Starting bot...")
|
||||||
|
|
||||||
|
# Start a background task to check for quiet/unquiet requests
|
||||||
|
async def check_quiet_unquiet():
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# Check for quiet request
|
||||||
|
if bot.restart_manager.quiet_requested:
|
||||||
|
bot.logger.info("Processing quiet request")
|
||||||
|
bot.should_announce = False
|
||||||
|
bot.restart_manager.quiet_requested = False
|
||||||
|
if bot.channel and hasattr(bot.channel, 'name') and bot.channel.name.startswith('#'):
|
||||||
|
await bot.channel.message("Song announcements disabled via terminal command.")
|
||||||
|
|
||||||
|
# Check for unquiet request
|
||||||
|
if bot.restart_manager.unquiet_requested:
|
||||||
|
bot.logger.info("Processing unquiet request")
|
||||||
|
bot.should_announce = True
|
||||||
|
bot.restart_manager.unquiet_requested = False
|
||||||
|
if bot.channel and hasattr(bot.channel, 'name') and bot.channel.name.startswith('#'):
|
||||||
|
await bot.channel.message("Song announcements enabled via terminal command.")
|
||||||
|
except Exception as e:
|
||||||
|
bot.logger.error(f"Error in quiet/unquiet check: {e}")
|
||||||
|
|
||||||
|
await asyncio.sleep(1) # Check every second
|
||||||
|
|
||||||
|
# Start the background task
|
||||||
|
quiet_check_task = asyncio.create_task(check_quiet_unquiet())
|
||||||
|
|
||||||
|
# Start the bot
|
||||||
await bot.start()
|
await bot.start()
|
||||||
bot.logger.info("Bot start completed")
|
bot.logger.info("Bot start completed")
|
||||||
|
|
||||||
|
# Cancel the quiet check task
|
||||||
|
quiet_check_task.cancel()
|
||||||
|
try:
|
||||||
|
await quiet_check_task
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
|
||||||
# Check if we should restart this bot
|
# Check if we should restart this bot
|
||||||
if bot.restart_manager.should_restart:
|
if bot.restart_manager.should_restart:
|
||||||
bot.logger.info("Bot should restart")
|
bot.logger.info("Bot should restart")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user