This repository has been archived by the owner on Dec 21, 2018. It is now read-only.
/
standup-irc.js
214 lines (191 loc) · 6.18 KB
/
standup-irc.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
var _ = require('underscore');
var irc = require('irc');
var winston = require('winston');
var nomnom = require('nomnom');
var inireader = require('inireader');
var utils = require('./utils');
var api = require('./api');
var auth = require('./auth');
var options = nomnom.opts({
config: {
string: '-c CONFIG, --config=CONFIG',
'default': 'config.ini',
help: 'What config file to use. Default: config.ini.'
}
}).parseArgs();
var optionsini = inireader.IniReader(options.config);
// Default configs
var DEFAULTS = {
irc: {
ircnick: 'standup'
},
standup: {
port: 80
},
log: {
console: true,
file: null
},
users: {
nicks: []
}
};
optionsini.load();
// Global config.
CONFIG = optionsini.getBlock();
if (CONFIG.users && CONFIG.users.nicks !== undefined) {
CONFIG.users.nicks = CONFIG.users.nicks.split(',');
}
if (CONFIG.irc && CONFIG.irc.channels !== undefined) {
CONFIG.irc.channels = CONFIG.irc.channels.split(',');
}
CONFIG = _.extend({}, DEFAULTS, CONFIG);
var transports = [];
if (CONFIG.log.file) {
transports.push(new (winston.transports.File)({
filename: CONFIG.log.file
}));
}
if (CONFIG.log.console) {
transports.push(new (winston.transports.Console)());
}
// Global logger.
logger = new (winston.Logger)({
transports: transports
});
// Global authentication manager
authman = new auth.AuthManager();
// This regex matches valid IRC nicks.
var NICK_RE = /[a-z_\-\[\]\\{}|`][a-z0-9_\-\[\]\\{}|`]*/;
var TARGET_MSG_RE = new RegExp('^(?:(' + NICK_RE.source + ')[:,]\\s*)?(.*)$');
/********** IRC Client **********/
// Global client
client = new irc.Client(CONFIG.irc.host, CONFIG.irc.nick, {
channels: CONFIG.irc.channels
});
client.on('connect', function() {
logger.info('Connected to irc server.');
});
// Handle errors by dumping them to logging.
client.on('error', function(err) {
// Error 421 comes up a lot on Mozilla servers, but isn't a problem.
if (err.rawCommand !== '421') {
return;
}
logger.error(err);
if (err.hasOwnProperty('stack')) {
logger.error(err.stack);
}
});
/* Receive, parse, and handle messages from IRC.
* - `user`: The nick of the user that send the message.
* - `channel`: The channel the message was received in. Note, this might not be
* a real channel, because it could be a PM. But this function ignores
* those messages anyways.
* - `msg`: The text of the message sent.
*/
client.on('message', function(user, channel, msg) {
var target, match;
match = TARGET_MSG_RE.exec(msg);
// This shouldn't happen, but bail out if it does, just in case.
if (!match) { return; }
target = match[1];
msg = match[2].trim();
// Don't talk to myself, don't list to PMs, and only speak when spoken to.
if (user === CONFIG.irc.nick || channel[0] !== '#' ||
target !== CONFIG.irc.nick) {
return;
}
var cond = msg.charAt(0) === '!';
if (cond) {
// msg = "!cmd arg1 arg2 arg3"
var cmd_name = msg.split(' ')[0].slice(1);
var args = msg.split(' ').slice(1);
var cmd = commands[cmd_name] || commands['default'];
cmd(user, channel, msg, args);
} else {
// Special case for botsnack
if (msg.toLowerCase().trim() === 'botsnack') {
commands.botsnack(user, channel, msg, []);
return;
}
// If they didn't ask for a specific command, post a status.
commands.status(user, channel, msg, [channel, msg]);
}
});
client.on('notice', function(from, to, text) {
if (from === undefined) {
logger.info('Service Notice: ' + text);
from = '';
}
from = from.toLowerCase();
if (from === 'nickserv') {
authman.notice(from, text);
}
});
var commands = {
/* Simple presence check. */
ping: function(user, channel, message, args) {
client.say(channel, "Pong!");
},
/* Create a status. */
status: function(user, channel, message, args) {
utils.ifAuthorized(user, channel, function() {
var project = args[0];
if (project.charAt(0) == '#') {
project = project.slice(1);
}
var ret = api.status.create(user, project, args.slice(1).join(' '));
ret.once('ok', function(data) {
client.say(channel, 'Ok, submitted status #' + data.id);
});
ret.once('error', function(err, data) {
client.say(channel, 'Uh oh, something went wrong.');
});
});
},
/* Delete a status by id number. */
'delete': function(user, channel, message, args) {
utils.ifAuthorized(user, channel, function() {
var ret = api.status.delete_(args[0], user);
ret.once('ok', function(data) {
client.say(channel, 'Ok, status #' + args + ' is no more!');
});
ret.once('error', function(code, data) {
if (code === 403) {
client.say(channel, "You don't have permissiont to do " +
"that. Do you own that status?");
} else {
client.say(channel, "I'm a failure, I couldn't do it.");
}
});
});
},
/* Every bot loves botsnacks. */
'botsnack': function(user, channel, message, args) {
var responses = [
'Yummy!',
'Thanks, ' + user + '!',
'My favorite!',
'Can I have another?',
'Tasty!'
];
var r = Math.floor(Math.random() * responses.length);
client.say(channel, responses[r]);
},
/* Check a user's authorization status. */
'trust': function(user, channel, message, args) {
var a = authman.checkUser(args);
a.once('authorization', function(trust) {
if (trust) {
client.say(channel, 'I trust ' + args);
} else {
client.say(channel, "I don't trust " + args);
}
});
},
/* The default action. Return an error. */
'default': function(user, channel, message) {
client.say(channel, user + ": Wait, what?");
}
};