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