dbot/run.js

339 lines
12 KiB
JavaScript

var fs = require('fs'),
_ = require('underscore')._,
jsbot = require('./jsbot/jsbot'),
DatabaseDriver = require('./database').DatabaseDriver,
async = require('async');
require('./snippets');
var DBot = function() {
/*** Load the DB ***/
if(fs.existsSync('db.json')) {
try {
this.db = JSON.parse(fs.readFileSync('db.json', 'utf-8'));
} catch(err) {
console.log('Error loading db.json. Stopping: ' + err);
process.exit();
}
} else {
this.db = {};
}
this.reloadConfig();
/*** Load the fancy DB ***/
this.ddb = new DatabaseDriver(this.config);
/*** Load main strings ***/
try {
this.strings = JSON.parse(fs.readFileSync('strings.json', 'utf-8'));
} catch(err) {
console.log('Probably a syntax error in strings.json: ' + err);
this.strings = {};
}
// Initialise run-time resources
this.usage = {};
this.status = {};
this.sessionData = {};
// Populate bot properties with config data
// Create JSBot and connect to each server
this.instance = jsbot.createJSBot(this.config.name);
_.each(this.config.servers, function(server, name) {
this.instance.addConnection(name, server.server, server.port,
this.config.admin, function(event) {
var server = this.config.servers[event.server];
_.each(server.channels, function(channel) {
this.instance.join(event, channel);
}, this);
}.bind(this), server.nickserv, server.password);
}, this);
// Load the modules and connect to the server
this.reloadModules();
this.instance.connectAll();
};
DBot.prototype.reloadConfig = function() {
this.config = {};
if(!fs.existsSync('config.json')) {
console.log('Error: config.json file does not exist. Stopping');
process.exit();
}
try {
var configFile = fs.readFileSync('config.json', 'utf-8');
this.config = JSON.parse(configFile);
this.customConfig = JSON.parse(configFile);
} catch(err) {
console.log('Config file is invalid. Stopping: ' + err);
process.exit();
}
try {
var defaultConfig = JSON.parse(fs.readFileSync('config.json.sample', 'utf-8'));
} catch(err) {
console.log('Error loading sample config. Bugger off this should not even be edited. Stopping.');
process.exit();
}
// Load missing config directives from sample file
if(!_.has(this.customConfig, 'modules')) {
this.customConfig.modules = {};
this.config.modules = {};
}
_.defaults(this.config, defaultConfig);
};
// Say something in a channel
DBot.prototype.say = function(server, channel, message) {
this.instance.say(server, channel, message);
};
// Format given stored string in config language
DBot.prototype.t = function(string, formatData) {
var formattedString = 'String not found. Something has gone screwy. Maybe.';
if(_.has(this.strings, string)) {
var lang = this.config.language;
if(!_.has(this.strings[string], lang)) {
lang = "en";
}
if(_.has(this.strings[string], lang)) {
var module = this.stringMap[string];
formattedString = this.strings[string][lang].format(formatData);
if(this.config.modules[module] && this.config.modules[module].outputPrefix) {
formattedString = '[' + this.config.modules[module].outputPrefix + '] ' +
formattedString;
}
}
}
return formattedString;
};
/*DBot.prototype.act = function(channel, data) {
this.instance.send('PRIVMSG', channel, ':\001ACTION ' + data + '\001');
}*/
// Save the database file
DBot.prototype.save = function(cb) {
fs.writeFile('db.json', JSON.stringify(this.db, null, ' '), cb);
};
// Hot-reload module files.
DBot.prototype.reloadModules = function() {
if(this.modules) { // Run 'onDestroy' code for each module if it exists.
_.each(this.modules, function(module) {
if(module.onDestroy) {
module.onDestroy();
}
});
}
this.rawModules = [];
this.pages = {};
this.status = {};
this.modules = {};
this.commands = {};
this.api = {};
this.stringMap = {};
this.usage = {};
this.reloadConfig();
this.ddb.createDB(name, this.config.dbType, {}, function(db) {});
try {
this.strings = JSON.parse(fs.readFileSync('strings.json', 'utf-8'));
} catch(err) {
this.strings = {};
}
var moduleNames = this.config.moduleNames;
// Enforce having command. it can still be reloaded, but dbot _will not_
// function without it, so not having it should be impossible
if(!_.include(moduleNames, 'command')) {
moduleNames.push("command");
}
// Reload Javascript snippets
var path = require.resolve('./snippets');
delete require.cache[path];
require('./snippets');
// Clear JSBot listeners and hooks
this.instance.removeListeners();
this.instance.clearHooks();
var name, moduleDir, config;
for(i=0;i<moduleNames.length;i++) {
name = moduleNames[i];
this.status[name] = true;
moduleDir = './modules/' + name + '/';
try {
var cacheKey = require.resolve(moduleDir + name);
delete require.cache[cacheKey];
} catch(err) {
this.status[name] = 'Error loading module: ' + err + ' ' + err.stack.split('\n')[2].trim();
continue;
}
if(!_.has(this.config.modules, name)) this.config.modules[name] = {};
if(fs.existsSync(moduleDir + 'config.json')) {
try {
var defaultConfig = JSON.parse(fs.readFileSync(moduleDir + 'config.json', 'utf-8'));
} catch(err) { // Syntax error
this.status[name] = 'Error parsing config: ' + err + ' ' + err.stack.split('\n')[2].trim();
continue;
}
this.config.modules[name] = _.defaults(this.config.modules[name], defaultConfig);
}
var config = this.config.modules[name];
// Don't shit out if dependencies not met
if(_.has(config, 'dependencies')) {
_.each(config.dependencies, function(dependency) {
if(!_.include(moduleNames, dependency)) {
console.log('Warning: Automatically loading ' + dependency);
moduleNames.push(dependency);
}
}, this);
}
// Groovy funky database shit
if(!_.has(config, 'dbType') || config.dbType == 'olde') {
// Generate missing DB keys
_.each(config.dbKeys, function(dbKey) {
if(!_.has(this.db, dbKey)) {
this.db[dbKey] = {};
}
}, this);
}
}
_.each(moduleNames, function(name) {
if(this.status[name] === true) {
try {
var moduleDir = './modules/' + name + '/';
var rawModule = require(moduleDir + name);
var module = rawModule.fetch(this);
this.rawModules.push(rawModule);
} catch(err) {
var stack = err.stack.split('\n')[2].trim();
this.status[name] = 'Error loading module: ' + err + ' ' + stack;
console.log('Error loading module: ' + err + ' ' + stack);
return;
}
module.name = name;
module.db = this.ddb.databank;
module.config = this.config.modules[name];
// Load the module data
_.each([ 'commands', 'pages', 'api' ], function(property) {
var propertyObj = {};
if(fs.existsSync(moduleDir + property + '.js')) {
try {
var propertyKey = require.resolve(moduleDir + property);
if(propertyKey) delete require.cache[propertyKey];
propertyObj = require(moduleDir + property).fetch(this);
} catch(err) {
console.log('Module error (' + module.name + ') in ' +
property + ': ' + err);
}
}
if(!_.has(module, property)) module[property] = {};
_.extend(module[property], propertyObj);
_.each(module[property], function(item, itemName) {
item.module = name;
if(_.has(module.config, property) && _.has(module.config[property], itemName)) {
_.extend(item, module.config[property][itemName]);
}
module[property][itemName] = _.bind(item, module);
_.extend(module[property][itemName], item);
}, this);
if(property == 'api') {
this[property][name] = module[property];
} else {
_.extend(this[property], module[property]);
}
}, this);
// Load the module listener
if(module.listener) {
if(!_.isArray(module.on)) {
module.on = [ module.on ];
}
_.each(module.on, function(on) {
this.instance.addListener(on, module.name, module.listener);
}, this);
}
// Load string data for the module
_.each([ 'usage', 'strings' ], function(property) {
var propertyData = {};
try {
propertyData = JSON.parse(fs.readFileSync(moduleDir + property + '.json', 'utf-8'));
} catch(err) {
console.log('Data error (' + module.name + ') in ' +
property + ': ' + err);
};
_.extend(this[property], propertyData);
if(property == 'strings') {
_.each(_.keys(propertyData), function(string) {
this.stringMap[string] = name;
}.bind(this));
}
}, this);
// Provide toString for module name
module.toString = function() {
return this.name;
}
this.modules[module.name] = module;
}
}.bind(this));
process.nextTick(function() {
_.each(this.modules, function(module, name) {
if(module.onLoad) {
try {
module.onLoad();
} catch(err) {
this.status[name] = 'Error in onLoad: ' + err + ' ' + err.stack.split('\n')[1].trim();
console.log('MODULE ONLOAD ERROR (' + name + '): ' + err );
}
}
}, this);
// Legacy fix for ~ command prefix
_.each(this.commands, function(command, cName) {
if(cName.charAt(0) == '~') {
delete this.commands[cName];
this.commands[cName.substring(1)] = command;
}
}, this);
_.each(this.usage, function(command, cName) {
if(cName.charAt(0) == '~') {
delete this.usage[cName];
this.usage[cName.substring(1)] = command;
}
}, this);
}.bind(this));
this.save(function() {});
};
new DBot();