import 'dart:io'; import 'dart:async'; import 'package:args/args.dart'; import 'package:logging/logging.dart'; import 'package:mumbullet/mumbullet.dart'; import 'package:mumbullet/src/dashboard/server.dart'; import 'package:path/path.dart' as path; Future main(List arguments) async { // Parse command line arguments final parser = ArgParser() ..addOption('config', abbr: 'c', defaultsTo: 'config.json', help: 'Path to configuration file') ..addOption( 'log-level', abbr: 'l', defaultsTo: 'info', allowed: ['debug', 'info', 'warning', 'error'], help: 'Log level (debug, info, warning, error)', ) ..addFlag('console-log', abbr: 'o', defaultsTo: true, help: 'Log to console') ..addOption('log-file', abbr: 'f', help: 'Path to log file') ..addFlag('help', abbr: 'h', negatable: false, help: 'Display this help message'); try { final results = parser.parse(arguments); if (results['help'] == true) { print('Mumble Music Bot - A Dart-based music bot for Mumble servers'); print(''); print('Usage:'); print('dart bin/mumbullet.dart [options]'); print(''); print('Options:'); print(parser.usage); exit(0); } // Set up logging final logLevelString = results['log-level'] as String; final Level logLevel = _parseLogLevel(logLevelString); final logManager = LogManager.getInstance(); logManager.initialize( level: logLevel, logToConsole: results['console-log'] as bool, logFilePath: results['log-file'] as String?, ); // Load configuration final configPath = results['config'] as String; final logger = logManager.logger; logger.info('Starting Mumble Music Bot'); logger.info('Loading configuration from $configPath'); final config = AppConfig.fromFile(configPath); try { config.validate(); logger.info('Configuration loaded successfully'); } catch (e) { logger.severe('Configuration validation error: $e'); exit(1); } // Initialize database final dbPath = path.join(config.bot.cacheDirectory, 'mumbullet.db'); logger.info('Initializing database: $dbPath'); final database = DatabaseManager(dbPath); // Initialize permission manager final permissionManager = PermissionManager(database, config.bot); // Create Mumble connection logger.info('Connecting to Mumble server ${config.mumble.server}:${config.mumble.port}'); final mumbleConnection = MumbleConnection(config.mumble); // Create message handler final messageHandler = MumbleMessageHandler(mumbleConnection, config.bot); // Initialize audio services logger.info('Initializing audio services'); final audioConverter = AudioConverter(); final youtubeDownloader = YoutubeDownloader(config.bot, database); final audioStreamer = MumbleAudioStreamer(mumbleConnection); // Create music queue final musicQueue = MusicQueue(config.bot, audioStreamer, audioConverter); // Create command parser with permission callback final commandParser = CommandParser( messageHandler, config.bot, (user) => permissionManager.getPermissionLevel(user), ); // Create command handler with connection for private messaging final commandHandler = CommandHandler( commandParser, musicQueue, youtubeDownloader, config.bot, mumbleConnection, ); final dashboardServer = DashboardServer(config.dashboard, musicQueue, youtubeDownloader, permissionManager); dashboardServer.start(); // Set up event listeners mumbleConnection.connectionState.listen((isConnected) { if (isConnected) { logger.info('Connected to Mumble server'); } else { logger.info('Disconnected from Mumble server'); } }); mumbleConnection.userJoined.listen((user) { logger.info('User joined: ${user.name}'); }); mumbleConnection.userLeft.listen((user) { logger.info('User left: ${user.name}'); }); // Listen to music queue events musicQueue.events.listen((event) { final eventType = event['event'] as String; final data = event['data'] as Map; logger.info('Music queue event: $eventType'); // Log important events switch (eventType) { case 'songStarted': final song = data['song'] as Song; logger.info('Now playing: ${song.title}'); break; case 'songFinished': final song = data['song'] as Song; logger.info('Finished playing: ${song.title}'); break; case 'queueEmpty': logger.info('Music queue is now empty'); break; } }); // Connect to Mumble server try { await mumbleConnection.connect(); // Send a welcome message to the channel if (mumbleConnection.isConnected) { await mumbleConnection.sendChannelMessage( 'MumBullet Music Bot is online! Type ${config.bot.commandPrefix}help for a list of commands.', ); logger.info('Sent welcome message to channel'); } } catch (e) { logger.warning('Failed to connect to Mumble server: $e'); logger.info('The bot will automatically attempt to reconnect...'); } logger.info('MumBullet is now running with the following features:'); logger.info('- Mumble server connection'); logger.info('- Command processing with permissions'); logger.info('- YouTube audio downloading and streaming'); logger.info('- Queue management'); logger.info('- Admin dashboard'); logger.info('Press Ctrl+C to exit'); // Wait for shutdown final completer = Completer(); // Set up signal handlers ProcessSignal.sigint.watch().listen((_) async { logger.info('Shutting down Mumble Music Bot'); // Stop music playback try { await audioStreamer.stopStreaming(); musicQueue.dispose(); } catch (e) { logger.warning('Error stopping audio services: $e'); } // Disconnect from Mumble server if (mumbleConnection.isConnected) { try { await mumbleConnection.sendChannelMessage('MumBullet Music Bot is shutting down...'); await mumbleConnection.disconnect(); } catch (e) { logger.warning('Error during shutdown: $e'); } } // Dispose resources mumbleConnection.dispose(); messageHandler.dispose(); database.close(); await dashboardServer.stop(); completer.complete(); }); ProcessSignal.sigterm.watch().listen((_) async { logger.info('Shutting down Mumble Music Bot'); // Stop music playback try { await audioStreamer.stopStreaming(); musicQueue.dispose(); } catch (e) { logger.warning('Error stopping audio services: $e'); } // Disconnect from Mumble server if (mumbleConnection.isConnected) { try { await mumbleConnection.sendChannelMessage('MumBullet Music Bot is shutting down...'); await mumbleConnection.disconnect(); } catch (e) { logger.warning('Error during shutdown: $e'); } } // Dispose resources mumbleConnection.dispose(); messageHandler.dispose(); database.close(); await dashboardServer.stop(); completer.complete(); }); await completer.future; // Close logger logManager.close(); exit(0); } catch (e, stackTrace) { print('Error: $e'); print('Stack trace: $stackTrace'); print(''); print('Usage:'); print(parser.usage); exit(1); } } Level _parseLogLevel(String level) { switch (level.toLowerCase()) { case 'debug': return Level.FINE; case 'info': return Level.INFO; case 'warning': return Level.WARNING; case 'error': return Level.SEVERE; default: return Level.INFO; } }