]> jfr.im git - irc/quakenet/newserv.git/blame - qabot/qabot.c
r562@blue (orig r476): slug | 2006-05-01 20:48:42 +0100
[irc/quakenet/newserv.git] / qabot / qabot.c
CommitLineData
0253ea49 1#include <stdio.h>
2#include <stdlib.h>
3#include <stdarg.h>
4#include <string.h>
5#include <time.h>
6
7#include "../nick/nick.h"
8#include "../localuser/localuserchannel.h"
9#include "../core/hooks.h"
10#include "../core/schedule.h"
11#include "../lib/array.h"
12#include "../lib/base64.h"
13#include "../lib/irc_string.h"
14#include "../lib/splitline.h"
15
16#include "qabot.h"
17
18time_t qab_startime;
19int qabot_nickext;
20int qabot_spam_nickext;
21int qabot_chanext;
22nick* qabot_nick = 0;
23CommandTree* qabot_commands;
24CommandTree* qabot_chancommands;
25qab_bot* qab_bots = 0;
26unsigned long qab_lastq_crc = 0;
27int qab_lastq_count = 0;
28qab_bot* qabot_currentbot = 0;
29channel* qabot_currentchan = 0;
30
31void _init() {
32 qab_startime = time(0);
33
34 registerhook(HOOK_NICK_LOSTNICK, qabot_lostnick);
35 registerhook(HOOK_CHANNEL_PART, qabot_channel_part);
36
37 qabot_commands = newcommandtree();
38 addcommandtotree(qabot_commands, "showcommands", 0, 0, qabot_doshowcommands);
39 addcommandtotree(qabot_commands, "help", QAUFLAG_STAFF, 1, qabot_dohelp);
40 addcommandtotree(qabot_commands, "hello", 0, 0, qabot_dohello);
41 addcommandtotree(qabot_commands, "save", QAUFLAG_ADMIN, 0, qabot_dosave);
42 addcommandtotree(qabot_commands, "listbots", QAUFLAG_STAFF, 0, qabot_dolistbots);
43 addcommandtotree(qabot_commands, "listusers", QAUFLAG_STAFF, 0, qabot_dolistusers);
44 addcommandtotree(qabot_commands, "showbot", QAUFLAG_STAFF, 1, qabot_doshowbot);
45 addcommandtotree(qabot_commands, "addbot", QAUFLAG_STAFF, 6, qabot_doaddbot);
46 addcommandtotree(qabot_commands, "delbot", QAUFLAG_STAFF, 1, qabot_dodelbot);
47 addcommandtotree(qabot_commands, "adduser", QAUFLAG_ADMIN, 2, qabot_doadduser);
48 addcommandtotree(qabot_commands, "changelev", QAUFLAG_ADMIN, 2, qabot_dochangelev);
49 addcommandtotree(qabot_commands, "deluser", QAUFLAG_ADMIN, 1, qabot_dodeluser);
50 addcommandtotree(qabot_commands, "whois", QAUFLAG_STAFF, 1, qabot_dowhois);
51 addcommandtotree(qabot_commands, "status", QAUFLAG_DEVELOPER, 0, qabot_dostatus);
52
53 qabot_chancommands = newcommandtree();
54 addcommandtotree(qabot_chancommands, "answer", QAC_QUESTIONCHAN|QAC_STAFFCHAN, 2, qabot_dochananswer);
55 addcommandtotree(qabot_chancommands, "block", QAC_QUESTIONCHAN|QAC_STAFFCHAN, 2, qabot_dochanblock);
56 addcommandtotree(qabot_chancommands, "clear", QAC_STAFFCHAN, 0, qabot_dochanclear);
57 addcommandtotree(qabot_chancommands, "closechan", QAC_QUESTIONCHAN|QAC_STAFFCHAN, 0, qabot_dochanclosechan);
58 addcommandtotree(qabot_chancommands, "config", QAC_QUESTIONCHAN|QAC_STAFFCHAN, 2, qabot_dochanconfig);
59 addcommandtotree(qabot_chancommands, "help", QAC_QUESTIONCHAN|QAC_STAFFCHAN, 1, qabot_dochanhelp);
60 addcommandtotree(qabot_chancommands, "listblocks", QAC_QUESTIONCHAN|QAC_STAFFCHAN, 0, qabot_dochanlistblocks);
61 addcommandtotree(qabot_chancommands, "mic", QAC_STAFFCHAN, 0, qabot_dochanmic);
62 addcommandtotree(qabot_chancommands, "moo", QAC_QUESTIONCHAN|QAC_STAFFCHAN, 0, qabot_dochanmoo);
63 addcommandtotree(qabot_chancommands, "offtopic", QAC_QUESTIONCHAN|QAC_STAFFCHAN, 30, qabot_dochanofftopic);
64 addcommandtotree(qabot_chancommands, "openchan", QAC_QUESTIONCHAN|QAC_STAFFCHAN, 0, qabot_dochanopenchan);
65 addcommandtotree(qabot_chancommands, "ping", QAC_QUESTIONCHAN|QAC_STAFFCHAN, 0, qabot_dochanping);
66 addcommandtotree(qabot_chancommands, "reset", QAC_QUESTIONCHAN|QAC_STAFFCHAN, 1, qabot_dochanreset);
67 addcommandtotree(qabot_chancommands, "spam", QAC_QUESTIONCHAN|QAC_STAFFCHAN, 30, qabot_dochanspam);
68 addcommandtotree(qabot_chancommands, "status", QAC_QUESTIONCHAN|QAC_STAFFCHAN, 0, qabot_dochanstatus);
69 addcommandtotree(qabot_chancommands, "unblock", QAC_QUESTIONCHAN|QAC_STAFFCHAN, 2, qabot_dochanunblock);
70
71 if ((qabot_nickext = registernickext("QABOT")) == -1) {
72 return;
73 }
74
75 if ((qabot_spam_nickext = registernickext("QABOT_SPAM")) == -1) {
76 return;
77 }
78
79 qabot_loaddb();
80
81 scheduleoneshot(time(NULL) + 1, &qabot_createfakeuser, NULL);
82 scheduleoneshot(time(NULL) + QABOT_SAVEWAIT, &qabot_savetimer, NULL);
83}
84
85void _fini() {
86 deregisterhook(HOOK_NICK_LOSTNICK, qabot_lostnick);
87 deregisterhook(HOOK_CHANNEL_PART, qabot_channel_part);
88
89 deleteschedule(0, qabot_savetimer, NULL);
90
91 qabot_savedb();
92
93 while (qab_bots)
94 qabot_delbot(qab_bots);
95
96 if (qabot_nickext != -1)
97 releasenickext(qabot_nickext);
98
99 if (qabot_spam_nickext != -1)
100 releasenickext(qabot_spam_nickext);
101
102 if (qabot_nick) {
103 deregisterlocaluser(qabot_nick, "Module unloaded.");
104 qabot_nick = NULL;
105 }
106
107 while (qabot_users)
108 qabot_squelchuser(qabot_users);
109
110 deletecommandfromtree(qabot_commands, "showcommands", qabot_doshowcommands);
111 deletecommandfromtree(qabot_commands, "help", qabot_dohelp);
112 deletecommandfromtree(qabot_commands, "hello", qabot_dohello);
113 deletecommandfromtree(qabot_commands, "save", qabot_dosave);
114 deletecommandfromtree(qabot_commands, "listbots", qabot_dolistbots);
115 deletecommandfromtree(qabot_commands, "listusers", qabot_dolistusers);
116 deletecommandfromtree(qabot_commands, "showbot", qabot_doshowbot);
117 deletecommandfromtree(qabot_commands, "addbot", qabot_doaddbot);
118 deletecommandfromtree(qabot_commands, "delbot", qabot_dodelbot);
119 deletecommandfromtree(qabot_commands, "adduser", qabot_doadduser);
120 deletecommandfromtree(qabot_commands, "changelev", qabot_dochangelev);
121 deletecommandfromtree(qabot_commands, "deluser", qabot_dodeluser);
122 deletecommandfromtree(qabot_commands, "whois", qabot_dowhois);
123 deletecommandfromtree(qabot_commands, "status", qabot_dostatus);
124 destroycommandtree(qabot_commands);
125
126 deletecommandfromtree(qabot_chancommands, "answer", qabot_dochananswer);
127 deletecommandfromtree(qabot_chancommands, "block", qabot_dochanblock);
128 deletecommandfromtree(qabot_chancommands, "clear", qabot_dochanclear);
129 deletecommandfromtree(qabot_chancommands, "closechan", qabot_dochanclosechan);
130 deletecommandfromtree(qabot_chancommands, "config", qabot_dochanconfig);
131 deletecommandfromtree(qabot_chancommands, "help", qabot_dochanhelp);
132 deletecommandfromtree(qabot_chancommands, "listblocks", qabot_dochanlistblocks);
133 deletecommandfromtree(qabot_chancommands, "mic", qabot_dochanmic);
134 deletecommandfromtree(qabot_chancommands, "moo", qabot_dochanmoo);
135 deletecommandfromtree(qabot_chancommands, "offtopic", qabot_dochanofftopic);
136 deletecommandfromtree(qabot_chancommands, "openchan", qabot_dochanopenchan);
137 deletecommandfromtree(qabot_chancommands, "ping", qabot_dochanping);
138 deletecommandfromtree(qabot_chancommands, "reset", qabot_dochanreset);
139 deletecommandfromtree(qabot_chancommands, "spam", qabot_dochanspam);
140 deletecommandfromtree(qabot_chancommands, "status", qabot_dochanstatus);
141 deletecommandfromtree(qabot_chancommands, "unblock", qabot_dochanunblock);
142 destroycommandtree(qabot_chancommands);
143}
144
145void qabot_lostnick(int hooknum, void* arg) {
146 nick* np = (nick*)arg;
147 qab_bot* b;
148
149 if (!qab_bots)
150 return;
151
152 if (!IsAccount(np))
153 return;
154
155 for (b = qab_bots; b; b = b->next) {
156 if (b->micnumeric == np->numeric) {
157 b->micnumeric = 0;
158 sendmessagetochannel(b->np, b->staff_chan->channel, "Mic deactivated.");
159 if (!b->lastspam)
160 qabot_spamstored((void*)b);
161 }
162 }
163}
164
165void qabot_channel_part(int hooknum, void* arg) {
166 channel* cp = (channel*)((void**)arg)[0];
167 nick* np = (nick*)((void**)arg)[1];
168 qab_bot* b;
169
170 if (!qab_bots)
171 return;
172
173 if (!IsAccount(np))
174 return;
175
176 for (b = qab_bots; b; b = b->next) {
177 if ((b->micnumeric == np->numeric) && (b->staff_chan->channel == cp)) {
178 b->micnumeric = 0;
179 sendmessagetochannel(b->np, b->staff_chan->channel, "Mic deactivated.");
180 if (!b->lastspam)
181 qabot_spamstored((void*)b);
182 }
183 }
184}
185
186void qabot_createfakeuser(void* arg) {
187 channel* cp;
188
189 if ((qabot_nick = registerlocaluser(QABOT_NICK, QABOT_USER, QABOT_HOST,
190 QABOT_NAME, QABOT_ACCT, QABOT_UMDE, &qabot_handler))) {
191 if ((cp = findchannel(QABOT_HOMECHAN))) {
192 localjoinchannel(qabot_nick, cp);
193 localgetops(qabot_nick, cp);
194 }
195 else
196 localcreatechannel(qabot_nick, QABOT_HOMECHAN);
197 }
198}
199
200void qabot_handler(nick* me, int type, void** args) {
201 nick* sender;
202 Command* cmd;
203 channel* cp;
204 char* cargv[50];
205 int cargc;
206 qab_user* u;
207
208 switch (type) {
209 case LU_PRIVMSG:
210 case LU_SECUREMSG:
211 sender = (nick*)args[0];
212
213 if (!strncmp("\001VERSION", args[1], 8)) {
214 sendnoticetouser(qabot_nick, sender, "\001VERSION %s\001", QABOT_NAME);
215 return;
216 }
217
218 cargc = splitline((char*)args[1], cargv, 50, 0);
219 cmd = findcommandintree(qabot_commands, cargv[0], 1);
220 if (!cmd) {
221 sendnoticetouser(qabot_nick, sender, "Unknown command.");
222 return;
223 }
224
225 if (!IsAccount(sender)) {
226 sendnoticetouser(qabot_nick, sender, "You must be authed to use this command.");
227 return;
228 }
229
230 u = qabot_getuser(sender->authname);
231 if (cmd->level) {
232 if (!u) {
233 sendnoticetouser(qabot_nick, sender, "You need an account to use this command.");
234 return;
235 }
236
237 if ((cmd->level & QAUFLAG_STAFF) && !QAIsStaff(u)) {
238 sendnoticetouser(qabot_nick, sender, "You do not have access to this command.");
239 return;
240 }
241
242 if ((cmd->level & QAUFLAG_ADMIN) && !QAIsAdmin(u)) {
243 sendnoticetouser(qabot_nick, sender, "You do not have access to this command.");
244 return;
245 }
246
247 if ((cmd->level & QAUFLAG_DEVELOPER) && !QAIsDeveloper(u)) {
248 sendnoticetouser(qabot_nick, sender, "You do not have access to this command.");
249 return;
250 }
251 }
252
253 if (cmd->maxparams < (cargc-1)) {
254 rejoinline(cargv[cmd->maxparams], cargc - (cmd->maxparams));
255 cargc=(cmd->maxparams) + 1;
256 }
257
258 (cmd->handler)((void*)sender, cargc - 1, &(cargv[1]));
259
260 break;
261
262 case LU_KILLED:
263 qabot_nick = NULL;
264 scheduleoneshot(time(NULL) + 1, &qabot_createfakeuser, NULL);
265 break;
266
267 case LU_KICKED:
268 cp = (channel*)args[1];
269 if (cp) {
270 localjoinchannel(qabot_nick, cp);
271 localgetops(qabot_nick, cp);
272 }
273 break;
274 }
275}
276
277void qabot_child_handler(nick* me, int type, void** args) {
278 nick* sender;
279 channel* cp;
280 qab_bot* bot = me->exts[qabot_nickext];
281 char* text;
282 Command* cmd;
283 char* cargv[50];
284 int cargc;
285
286 if (!bot) {
287 return;
288 }
289
290 switch (type) {
291 case LU_CHANMSG:
292 sender = (nick*)args[0];
293 cp = (channel*)args[1];
294 text = (char*)args[2];
295
296 if (*text == '!') {
297 if (*(++text) == '\0') {
298 sendnoticetouser(me, sender, "No command specified.");
299 return;
300 }
301 cargc = splitline((char*)text, cargv, 50, 0);
302 cmd = findcommandintree(qabot_chancommands, cargv[0], 1);
303 if (!cmd) {
304 sendnoticetouser(me, sender, "Unknown command.");
305 return;
306 }
307
308 if ((cp->index == bot->staff_chan) && !(cmd->level & QAC_STAFFCHAN)) {
309 sendnoticetouser(me, sender, "This command cannot be used in the staff channel.");
310 return;
311 }
312
313 if ((cp->index == bot->question_chan) && !(cmd->level & QAC_QUESTIONCHAN)) {
314 sendnoticetouser(me, sender, "This command cannot be used in the question channel.");
315 return;
316 }
317
318 if (cmd->maxparams < (cargc-1)) {
319 rejoinline(cargv[cmd->maxparams], cargc - (cmd->maxparams));
320 cargc=(cmd->maxparams) + 1;
321 }
322
323 qabot_currentbot = bot;
324 qabot_currentchan = cp;
325
326 (cmd->handler)((void*)sender, cargc - 1, &(cargv[1]));
327 }
328 else if ((*text != '!') && (bot->micnumeric == sender->numeric) && (cp->index == bot->staff_chan)) {
329 bot->lastmic = time(NULL);
330 if (bot->lastspam) {
331 qab_spam* s;
332
333 s = (qab_spam*)malloc(sizeof(qab_spam));
334 s->message = strdup(text);
335 s->next = 0;
336 bot->lastspam->next = s;
337 bot->lastspam = s;
338 }
339 else {
340 if ((bot->spamtime + bot->spam_interval) < time(0)) {
341 sendmessagetochannel(me, bot->public_chan->channel, "%s", text);
342 bot->spammed++;
343 bot->spamtime = time(0);
344 }
345 else {
346 qab_spam* s;
347
348 s = (qab_spam*)malloc(sizeof(qab_spam));
349 s->message = strdup(text);
350 s->next = 0;
351 bot->nextspam = bot->lastspam = s;
352 scheduleoneshot(bot->spamtime + bot->spam_interval, qabot_spam, (void*)bot);
353 }
354 }
355 }
356 break;
357
358 case LU_PRIVMSG:
359 case LU_SECUREMSG:
360 sender = (nick*)args[0];
361 text = (char*)args[1];
362 time_t lastmsg;
363
364 lastmsg = (time_t)sender->exts[qabot_spam_nickext];
365
366 if ((lastmsg + bot->ask_wait) > time(0)) {
367 sendnoticetouser(me, sender, "You have already sent a question recently, please wait at least %d seconds between asking questions.", bot->ask_wait);
368 return;
369 }
370 else {
371 char hostbuf[NICKLEN + USERLEN + HOSTLEN + 3];
372 qab_block* b;
373 qab_question* q;
374 unsigned long crc;
375 int len;
376
377 sendnoticetouser(me, sender, "Your question has been relayed to the %s staff.", bot->public_chan->name->content);
378
379 if (!getnumerichandlefromchanhash(bot->public_chan->channel->users, sender->numeric))
380 return;
381
382 if (*text == '<')
383 text++;
384
385 len = strlen(text);
386 if ((len > 0) && (text[len - 1] == '>')) {
387 text[len - 1] = '\0';
388 len--;
389 }
390
391 if ((len < 5) || !strchr(text, ' ') || (*text == 1))
392 return;
393
394 if ((bot->flags & QAB_AUTHEDONLY) && !IsAccount(sender))
395 return;
396
397 if (bot->flags & QAB_CONTROLCHAR) {
398 char* ch;
399
400 for (ch = text; *ch; ch++)
401 if ((*ch == 2) || (*ch == 3) || (*ch == 22) || (*ch == 27) || (*ch == 31))
402 return;
403 }
404 else if (bot->flags & QAB_COLOUR) {
405 char* ch;
406
407 for (ch = text; *ch; ch++)
408 if (*ch == 3)
409 return;
410 }
411
412 if (bot->flags & QAB_FLOODDETECT) {
413 crc = crc32i(text);
414 if (crc == qab_lastq_crc) {
415 qab_lastq_count++;
416 if (qab_lastq_count >= 3) {
417 if ((qab_lastq_count == 3) || ((qab_lastq_count % 10) == 0))
418 sendmessagetochannel(me, bot->question_chan->channel, "WARNING: Possible question flood detected%s", (bot->flags & QAB_FLOODSTOP) ? " - AUTO IGNORED." : ".");
419 if (bot->flags & QAB_FLOODSTOP)
420 return;
421 }
422 }
423 else {
424 qab_lastq_crc = crc;
425 qab_lastq_count = 1;
426 }
427 }
428
429 sprintf(hostbuf,"%s!%s@%s", sender->nick, sender->ident, sender->host->name->content);
430
431 for (b = bot->blocks; b; b = b->next) {
432 switch (b->type) {
433 case QABBLOCK_ACCOUNT:
434 if (IsAccount(sender) && !ircd_strncmp(sender->authname, b->blockstr, ACCOUNTLEN))
435 return;
436 break;
437
438 case QABBLOCK_HOST:
439 if (!match(b->blockstr, hostbuf))
440 return;
441 break;
442
443 case QABBLOCK_TEXT:
444 if (!match(b->blockstr, text))
445 return;
446 break;
447 }
448 }
449
450 sender->exts[qabot_spam_nickext] = (void*)time(0);
451
452 q = (qab_question*)malloc(sizeof(qab_question));
453 q->id = ++bot->lastquestionID;
454 q->question = strdup(text);
455 q->flags = QAQ_NEW;
456 strncpy(q->nick, sender->nick, NICKLEN);
457 q->nick[NICKLEN] = '\0';
458 q->numeric = sender->numeric;
459 q->crc = (bot->flags & QAB_FLOODDETECT) ? crc : 0;
460 q->answer = 0;
461 q->next = bot->questions[q->id % QUESTIONHASHSIZE];
462
463 bot->questions[q->id % QUESTIONHASHSIZE] = q;
464
465 sendmessagetochannel(me, bot->question_chan->channel, "ID: %3d <%s> %s", q->id, visiblehostmask(sender, hostbuf), text);
466
467 if ((bot->flags & QAB_LINEBREAK) && ((bot->lastquestionID % 10) == 0))
468 sendmessagetochannel(me, bot->question_chan->channel, "-----------------------------------");
469 }
470 break;
471
472 case LU_KILLED:
473 bot->np = NULL;
474 scheduleoneshot(time(NULL) + 1, &qabot_createbotfakeuser, (void*)bot);
475 break;
476
477 case LU_KICKED:
478 cp = (channel*)args[1];
479 if (cp) {
480 localjoinchannel(bot->np, cp);
481 localgetops(bot->np, cp);
482 }
483 break;
484
485 default:
486 break;
487 }
488}
489
490char* qabot_getvictim(nick* np, char* target) {
491 if (*target != '#') {
492 nick* victim;
493
494 if (!(victim = getnickbynick(target))) {
495 sendnoticetouser(qabot_nick, np, "No such nickname.");
496 return 0;
497 }
498
499 if (!IsAccount(victim)) {
500 sendnoticetouser(qabot_nick, np, "%s is not authed.", victim->nick);
501 return 0;
502 }
503
504 return victim->authname;
505 }
506
507 return target + 1;
508}
509
510const char* qabot_uflagtostr(flag_t flags) {
511 static char buf[20];
512 int i = 0;
513
514 buf[i++] = '+';
515
516 if (flags & QAUFLAG_ADMIN) { buf[i++] = 'a'; }
517 if (flags & QAUFLAG_DEVELOPER) { buf[i++] = 'd'; }
518 if (flags & QAUFLAG_STAFF) { buf[i++] = 's'; }
519
520 buf[i] = '\0';
521
522 return buf;
523}
524
525const char* qabot_formattime(time_t tme) {
526 static char buf[50];
527 struct tm* tmp;
528
529 tmp = gmtime(&tme);
530 strftime(buf, 15, "%d/%m/%y %H:%M", tmp);
531
532 return buf;
533}
534
535qab_bot* qabot_getcurrentbot() {
536 return qabot_currentbot;
537}
538
539channel* qabot_getcurrentchannel() {
540 return qabot_currentchan;
541}