Files
meshcore-bot/modules/channel_manager.py
agessaman cf887938ac Clean up channel information logging
- Suppress raw JSON output from meshcore-cli commands during channel fetching
- Make channel logging more concise - only show channel names instead of full data structures
- Redirect stdout temporarily to /dev/null during next_cmd calls to prevent verbose output
- This eliminates the duplicate channel information that was being printed twice
2025-09-06 11:33:05 -07:00

170 lines
8.1 KiB
Python

#!/usr/bin/env python3
"""
Channel management functionality for the MeshCore Bot
Handles channel fetching, naming, and operations
"""
import asyncio
import sys
import os
from typing import Dict, Any
class ChannelManager:
"""Manages channel operations and information"""
def __init__(self, bot):
self.bot = bot
self.logger = bot.logger
async def fetch_channels(self):
"""Fetch channels from the MeshCore node"""
self.logger.info("Fetching channels from MeshCore node...")
try:
# Wait a moment for the device to be ready
await asyncio.sleep(2)
# Try to fetch channels 0-9 (common channel range)
channels = {}
for channel_num in range(10):
try:
self.logger.debug(f"Fetching channel {channel_num}...")
# Send the get_channel command and wait for the response
# The meshcore library will automatically handle the command and dispatch events
from meshcore_cli.meshcore_cli import next_cmd
# Create a future to capture the channel info event
channel_event = None
async def on_channel_info(event):
nonlocal channel_event
if event.payload.get('channel_idx') == channel_num:
channel_event = event
# Subscribe to channel info events
from meshcore import EventType
subscription = self.bot.meshcore.subscribe(EventType.CHANNEL_INFO, on_channel_info)
# Send the command (suppress raw JSON output)
with open(os.devnull, 'w') as devnull:
old_stdout = sys.stdout
sys.stdout = devnull
try:
await next_cmd(self.bot.meshcore, ["get_channel", str(channel_num)])
finally:
sys.stdout = old_stdout
# Wait a moment for the event to be processed
await asyncio.sleep(0.5)
# Unsubscribe
self.bot.meshcore.unsubscribe(subscription)
# Check if we got the channel info
if channel_event and channel_event.payload:
channels[channel_num] = channel_event.payload
self.logger.debug(f"Found channel {channel_num}: {channel_event.payload}")
# Store channel key for decryption
channel_secret = channel_event.payload.get('channel_secret', b'')
if isinstance(channel_secret, bytes) and len(channel_secret) == 16:
# Convert to hex for easier handling
channels[channel_num]['channel_key_hex'] = channel_secret.hex()
self.logger.debug(f"Channel {channel_num} has key: {channels[channel_num]['channel_key_hex']}")
# Check if this is an empty channel (all-zero channel secret)
if isinstance(channel_secret, bytes) and channel_secret == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00':
self.logger.debug(f"Found empty channel {channel_num}, stopping channel fetch")
break
else:
self.logger.debug(f"No channel {channel_num} found")
# If we can't get channel info, assume no more channels
break
# Add a small delay between requests
await asyncio.sleep(0.5)
except Exception as e:
self.logger.debug(f"Error fetching channel {channel_num}: {e}")
# Don't break on first error, continue trying other channels
continue
if channels:
self.bot.meshcore.channels = channels
self.logger.info(f"Successfully fetched {len(channels)} channels from MeshCore node")
for num, info in channels.items():
channel_name = info.get('channel_name', f'Channel{num}')
if channel_name: # Only log non-empty channel names
self.logger.info(f" Channel {num}: {channel_name}")
else:
self.logger.debug(f" Channel {num}: (empty)")
else:
self.logger.warning("No channels found on MeshCore node")
self.bot.meshcore.channels = {}
except Exception as e:
self.logger.error(f"Failed to fetch channels: {e}")
self.bot.meshcore.channels = {}
def get_channel_name(self, channel_num: int) -> str:
"""Get channel name from channel number"""
if channel_num in self.bot.meshcore.channels:
channel_info = self.bot.meshcore.channels[channel_num]
# Handle different possible data structures
if isinstance(channel_info, dict):
# Check for channel_name (CLI format) or name (fallback)
return channel_info.get('channel_name', channel_info.get('name', f"Channel{channel_num}"))
elif hasattr(channel_info, 'channel_name'):
return channel_info.channel_name
elif hasattr(channel_info, 'name'):
return channel_info.name
elif hasattr(channel_info, 'payload') and isinstance(channel_info.payload, dict):
return channel_info.payload.get('channel_name', channel_info.payload.get('name', f"Channel{channel_num}"))
else:
return f"Channel{channel_num}"
else:
self.logger.warning(f"Channel {channel_num} not found in fetched channels")
return f"Channel{channel_num}"
def get_channel_number(self, channel_name: str) -> int:
"""Get channel number from channel name"""
for num, channel_info in self.bot.meshcore.channels.items():
# Handle different possible data structures
if isinstance(channel_info, dict):
# Check for channel_name (CLI format) or name (fallback)
if (channel_info.get('channel_name', '').lower() == channel_name.lower() or
channel_info.get('name', '').lower() == channel_name.lower()):
return num
elif hasattr(channel_info, 'channel_name'):
if channel_info.channel_name.lower() == channel_name.lower():
return num
elif hasattr(channel_info, 'name'):
if channel_info.name.lower() == channel_name.lower():
return num
self.logger.warning(f"Channel name '{channel_name}' not found in fetched channels")
# Return 0 as fallback, but log a warning
return 0
def get_channel_key(self, channel_num: int) -> str:
"""Get channel encryption key from channel number"""
if channel_num in self.bot.meshcore.channels:
channel_info = self.bot.meshcore.channels[channel_num]
if isinstance(channel_info, dict):
return channel_info.get('channel_key_hex', '')
return ''
def get_channel_info(self, channel_num: int) -> dict:
"""Get complete channel information including name and key"""
if channel_num in self.bot.meshcore.channels:
channel_info = self.bot.meshcore.channels[channel_num]
if isinstance(channel_info, dict):
return {
'name': self.get_channel_name(channel_num),
'key': self.get_channel_key(channel_num),
'info': channel_info
}
return {'name': f"Channel{channel_num}", 'key': '', 'info': {}}