mumbullet/bin/mumbullet.dart
2025-06-21 11:47:06 -06:00

268 lines
7.9 KiB
Dart

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<void> main(List<String> 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<String, dynamic>;
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<void>();
// 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;
}
}