<commit_before>require('dotenv').config();
var
	_            = require('lodash'),
	assert       = require('assert'),
	bole         = require('bole'),
	Brain        = require('./lib/brain'),
	fs           = require('fs'),
	Slack        = require('@slack/client'),
	tryrequire   = require('try-require'),
	SLACK_EVENTS = Slack.CLIENT_EVENTS.RTM,
	RTM_EVENTS   = Slack.RTM_EVENTS
	;

var Opsbot = module.exports = function Opsbot(config)
{
	assert(process.env.SLACK_TOKEN, 'you must put a slack bot api key in the SLACK_TOKEN env var');
	this.config = config || {};
	this.adminChannel = config.admin_channel;
	this.logger = bole('shell');
	this.botname = config.botname || 'opsbot';
	this.pattern = new RegExp('^' + this.botname + ':?\\s+');

	if (config.brain)
	{
		this.brain = new Brain(config.brain);
		Brain.setGlobal(this.brain);
	}

	this.createParser();

	this.slack = new Slack.RtmClient(process.env.SLACK_TOKEN, { logLevel: 'warn' });
	this.slack.on(RTM_EVENTS.MESSAGE, this.handleMessage.bind(this));
};

Opsbot.prototype.createParser = function createParser()
{
	const parser = require('yargs')
		.usage(this.botname + ' [command]')
		.commandDir('commands')
		.demand(1)
		.help()
		.epilog('everything is exciting.');

	// now load additional commands
	var requested = Object.keys(this.config.plugins || {});

	// built-ins
	var files = fs.readdirSync('./commands');
	var builtin = _.map(files, function(f)
	{
		if (f.endsWith('.js')) return f.replace(/.js$/, '');
	});
	var external = _.difference(requested, builtin);

	_.each(external, function loadCommand(c)
	{
		var module = tryrequire(c);
		if (module) parser.command(module);
	});

	this.parser = parser;
};

Opsbot.prototype.start = function start()
{
	var self = this;
	this.slack.start();
	this.slack.on(SLACK_EVENTS.RTM_CONNECTION_OPENED, function slackClientOpened()
	{
		self.logger.info('You may fire when ready.');
		if (self.adminChannel)
			self.slack.sendMessage(`${self.botname} coming online.`, self.adminChannel);
	});
};

Opsbot.prototype.handleMessage = function handleMessage(message)
{
	var self = this;

	if (!message.text || message.subtype === 'bot_message') return;
	if (this.pattern.test(message.text))
	{
		self.logger.info('responding', message);

		var context = Object.assign({
			logger: this.logger,
			config: this.config,
			brain: this.brain,
		},  message);
		context.reply = makeReplier(context, self.slack);
		var text = message.text.replace(this.pattern, '');

		self.parser.parse(text, context, function handled(err, argv, output)
		{
			if (err)
				self.logger.error(err.message);
			else if (output)
				self.slack.sendMessage(output, message.channel);
		});
	}
};

function makeReplier(context, slack)
{
	return function replier(payload)
	{
		var textbit = typeof payload === 'string' ? payload : payload.text;
		slack.sendMessage(textbit, context.channel, function(err, msg)
		{
			if (err) context.logger.error(err);
			else
			{
				context.logger.info(msg);
				if (typeof payload === 'object' && payload.attachments)
				{
					msg.opts = { attachments: payload.attachments };
					slack.updateMessage(msg, function(err, msg2)
					{
						if (err) context.logger.error(err);
					});
				}<commit_msg>fix: handle message attachments better.<commit_after>require('dotenv').config();
var
	_            = require('lodash'),
	assert       = require('assert'),
	bole         = require('bole'),
	Brain        = require('./lib/brain'),
	fs           = require('fs'),
	path         = require('path'),
	Slack        = require('@slack/client'),
	tryrequire   = require('try-require'),
	SLACK_EVENTS = Slack.CLIENT_EVENTS.RTM,
	RTM_EVENTS   = Slack.RTM_EVENTS
	;

var Opsbot = module.exports = function Opsbot(config)
{
	assert(process.env.SLACK_TOKEN, 'you must put a slack bot api key in the SLACK_TOKEN env var');
	this.config = config || {};
	this.adminChannel = config.admin_channel;
	this.logger = bole('shell');
	this.botname = config.botname || 'opsbot';
	this.pattern = new RegExp('^' + this.botname + ':?\\s+');

	if (config.brain)
	{
		this.brain = new Brain(config.brain);
		Brain.setGlobal(this.brain);
	}

	this.createParser();

	this.slack = new Slack.RtmClient(process.env.SLACK_TOKEN, { logLevel: 'warn' });
	this.slack.on(RTM_EVENTS.MESSAGE, this.handleMessage.bind(this));
};

Opsbot.prototype.createParser = function createParser()
{
	var self = this;
	var commanddir = path.join(__dirname, 'commands');

	const parser = require('yargs')
		.usage(this.botname + ' [command]')
		.commandDir(commanddir)
		.demand(1)
		.help()
		.epilog('everything is exciting.');

	// now load additional commands
	var requested = Object.keys(this.config.plugins || {});

	// built-ins
	var files = fs.readdirSync(commanddir);
	var builtin = _.map(files, function(f)
	{
		if (f.endsWith('.js')) return f.replace(/.js$/, '');
	});
	var external = _.difference(requested, builtin);

	var hackpath = process.cwd() + '/node_modules/';
	_.each(external, function loadCommand(c)
	{
		var module = tryrequire(c);
		if (!module) module = tryrequire(hackpath + c);
		if (module) parser.command(module);
		else self.logger.info(`could not load plugin ${c}`);
	});

	this.parser = parser;
};

Opsbot.prototype.start = function start()
{
	var self = this;
	this.slack.start();
	this.slack.on(SLACK_EVENTS.RTM_CONNECTION_OPENED, function slackClientOpened()
	{
		self.logger.info('You may fire when ready.');
		if (self.adminChannel)
			self.slack.sendMessage(`${self.botname} coming online.`, self.adminChannel);
	});
};

Opsbot.prototype.handleMessage = function handleMessage(message)
{
	var self = this;

	if (!message.text || message.subtype === 'bot_message') return;
	if (this.pattern.test(message.text))
	{
		self.logger.info('responding', message);

		var context = Object.assign({
			logger: this.logger,
			config: this.config,
			brain: this.brain,
		},  message);
		context.reply = makeReplier(context, self.slack);
		var text = message.text.replace(this.pattern, '');

		self.parser.parse(text, context, function handled(err, argv, output)
		{
			if (err)
				self.logger.error(err.message);
			else if (output)
				self.slack.sendMessage(output, message.channel);
		});
	}
};

function makeReplier(context, slack)
{
	return function replier(payload)
	{
		var textbit = typeof payload === 'string' ? payload : payload.text;
		slack.sendMessage(textbit, context.channel, function(err, msg)
		{
			if (err) context.logger.error(err);
			else
			{
				context.logger.info(msg);
				if (typeof payload === 'object' && payload.attachments)
				{
					msg.opts = Object.assign({}, payload);
					slack.updateMessage(msg, function(err, msg2)
					{
						if (err) context.logger.error(err);
					});
				}
{'fraction_non_alphanumeric': 0.1622185776054868, 'fraction_numerical': 0.0005963918294319368, 'mean_word_length': 3.8258992805755394, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1899929', 'n_tokens_mistral': 2495, 'n_tokens_neox': 2291, 'n_words': 460}