]>
jfr.im git - irc/quakenet/newserv.git/blob - tutorbot/tutorbot.c
4 #include "../nick/nick.h"
5 #include "../localuser/localuserchannel.h"
6 #include "../core/schedule.h"
7 #include "../lib/irc_string.h"
8 #include "../lib/version.h"
14 #define STAFFCHAN "#tutorial.staff"
15 #define QUESTIONCHAN "#tutorial.questions"
16 #define PUBLICCHAN "#tutorial"
18 #define QUESTIONINTERVAL 30
20 #define SPAMINTERVAL 10
22 #define NICKMONITORTIME 300
24 #define QUESTIONHASHSIZE 1000
26 #define QUESTION_NEW 0x0
27 #define QUESTION_ANSWERED 0x1
28 #define QUESTION_OFFTOPIC 0x2
29 #define QUESTION_SPAM 0x3
31 #define QUESTION_STATE 0x7
33 typedef struct spammsg
{
38 typedef struct questrec
{
43 unsigned long numeric
;
45 struct questrec
*next
;
48 typedef struct blockeduser
{
49 int type
; /* 0 = account, 1 = hostmask */
50 char content
[NICKLEN
+USERLEN
+HOSTLEN
+3]; /* now includes a mask or an accountname */
51 struct blockeduser
*next
;
54 typedef struct storedanswer
{
57 struct storedanswer
*next
;
63 unsigned int micnumeric
; /* Who has the mic atm */
68 questrec
*questiontable
[QUESTIONHASHSIZE
];
69 blockeduser
*blockedusers
;
70 storedanswer
*storedanswers
;
73 void tutorhandler(nick
*me
, int type
, void **args
);
75 void tutorspam(void *arg
) {
79 if (tutornick
&& (cp
=findchannel(PUBLICCHAN
))) {
80 sendmessagetochannel(tutornick
, cp
, "%s", nextspam
->message
->content
);
84 nextspam
=nextspam
->next
;
86 freesstring(smp
->message
);
92 scheduleoneshot(spamtime
+SPAMINTERVAL
, tutorspam
, NULL
);
100 void spamstored(void) {
104 cp
=findchannel(PUBLICCHAN
);
106 if (!cp
|| !tutornick
)
109 while(storedanswers
) {
111 sendmessagetochannel(tutornick
,cp
,"%s asked: %s",storedanswers
->qptr
->nick
,storedanswers
->qptr
->question
->content
);
112 sendmessagetochannel(tutornick
,cp
,"%s answered: %s",storedanswers
->nick
,storedanswers
->qptr
->answer
->content
);
114 storedanswers
=storedanswers
->next
;
119 void usercountspam(void *arg
) {
122 if (!(cp
=findchannel(PUBLICCHAN
)))
125 if (!(cp2
=findchannel(STAFFCHAN
)))
131 sendmessagetochannel(tutornick
, cp2
, "Currently %d users in %s.",cp
->users
->totalusers
,cp
->index
->name
->content
);
134 void flushspamqueue() {
137 for (smp
=nextspam
;smp
;smp
=nsmp
) {
139 freesstring(smp
->message
);
143 deleteallschedules(tutorspam
);
144 nextspam
=lastspam
=NULL
;
150 if ((tutornext
=registernickext("tutorbot")) > -1) {
152 nextspam
=lastspam
=NULL
;
157 memset(questiontable
, 0, sizeof(questrec
*) * QUESTIONHASHSIZE
);
158 tutornick
=registerlocaluser("Tutor","tutor","quakenet.org","#tutorial bot","tutor",UMODE_ACCOUNT
| UMODE_INV
,
161 if ((cp
=findchannel(PUBLICCHAN
))) {
162 localjoinchannel(tutornick
,cp
);
163 localgetops(tutornick
, cp
);
165 localcreatechannel(tutornick
,PUBLICCHAN
);
168 if ((cp
=findchannel(QUESTIONCHAN
))) {
169 localjoinchannel(tutornick
,cp
);
171 localcreatechannel(tutornick
,QUESTIONCHAN
);
174 if ((cp
=findchannel(STAFFCHAN
))) {
175 localjoinchannel(tutornick
,cp
);
177 localcreatechannel(tutornick
,STAFFCHAN
);
180 schedulerecurring(time(NULL
)+300, 0, 300, usercountspam
, NULL
);
182 Error("tutorbot",ERR_ERROR
,"Unable to get nickext");
188 questrec
*qrp
, *nqrp
;
191 if (tutornext
> -1) {
192 releasenickext(tutornext
);
193 deregisterlocaluser(tutornick
,NULL
);
194 deleteallschedules(usercountspam
);
197 /* remove all blockedusers */
198 while (blockedusers
!=NULL
) {
200 blockedusers
=blockedusers
->next
;
204 /* Blow away all stored questions */
205 for(i
=0;i
<QUESTIONHASHSIZE
;i
++) {
206 for (qrp
=questiontable
[i
];qrp
;qrp
=nqrp
) {
208 freesstring(qrp
->question
);
209 freesstring(qrp
->answer
);
222 if (!(fp
=fopen("tutor-questions","w")))
228 fprintf(fp
,"Untouched questions:\n\n");
231 case QUESTION_ANSWERED
:
232 fprintf(fp
,"\nAnswered questions:\n\n");
235 case QUESTION_OFFTOPIC
:
236 fprintf(fp
,"\nOff-topic questions:\n\n");
240 fprintf(fp
,"\nSpam:\n\n");
244 for (j
=0;j
<=lastquestionID
;j
++) {
245 for (qrp
=questiontable
[j%QUESTIONHASHSIZE
];qrp
;qrp
=qrp
->next
)
252 if ((qrp
->flags
& QUESTION_STATE
) == i
) {
253 fprintf(fp
,"(%s) %s\n",qrp
->nick
,qrp
->question
->content
);
255 if (i
==QUESTION_ANSWERED
)
256 fprintf(fp
,"Answer: %s\n\n",qrp
->answer
->content
);
264 void tutorhandler(nick
*me
, int type
, void **args
) {
271 if (type
==LU_CHANMSG
) {
276 /* Staff channel *only* commands */
277 if (!ircd_strcmp(cp
->index
->name
->content
,STAFFCHAN
)) {
278 if (!ircd_strncmp(text
,"!mic",4)) {
280 if (micnumeric
==np
->numeric
) {
282 sendmessagetochannel(me
,cp
,"Mic deactivated.");
287 micnumeric
=np
->numeric
;
288 sendmessagetochannel(me
,cp
,"%s now has the mic. Anything said by %s will be relayed in %s.",
289 np
->nick
, np
->nick
, PUBLICCHAN
);
292 micnumeric
=np
->numeric
;
293 sendmessagetochannel(me
,cp
,"Mic activated. Anything said by %s will be relayed in %s.",
294 np
->nick
, PUBLICCHAN
);
296 } else if (!ircd_strncmp(text
,"!m00",4)) {
297 /* mandatory m00 command */
298 int i
,maxm00
=rand()%100
;
301 for(i
=0;i
<maxm00
;i
++) {
313 sendmessagetochannel(me
,cp
,"m%s",m00
);
314 } else if (!ircd_strncmp(text
,"!clear",6)) {
316 sendmessagetochannel(me
,cp
,"Cleared message buffer.");
319 sendmessagetochannel(me
,cp
,"Mic deactivated.");
321 } else if (!ircd_strncmp(text
,"!save",5)) {
323 sendnoticetouser(me
,np
,"Save done.");
324 } else if (*text
!= '!' && np
->numeric
==micnumeric
) {
325 /* Message to be relayed */
327 smp
=(spammsg
*)malloc(sizeof(spammsg
));
328 smp
->message
=getsstring(text
, 500);
333 if (spamtime
+ SPAMINTERVAL
< time(NULL
)) {
334 /* Spam it directly */
335 if ((cp
=findchannel(PUBLICCHAN
))) {
336 sendmessagetochannel(me
, cp
, "%s", text
);
340 /* Queue it and start the clock */
341 smp
=(spammsg
*)malloc(sizeof(spammsg
));
342 smp
->message
=getsstring(text
, 500);
344 lastspam
=nextspam
=smp
;
345 scheduleoneshot(spamtime
+SPAMINTERVAL
, tutorspam
, NULL
);
351 /* Commands for staff channel or question channel */
352 if (!ircd_strcmp(cp
->index
->name
->content
,STAFFCHAN
) || !ircd_strcmp(cp
->index
->name
->content
,QUESTIONCHAN
)) {
353 if (!ircd_strncmp(text
, "!spam ", 6)) {
359 while (*(++ch
) == ' '); /* skip spaces */
361 qid
=strtol(ch
, &ch
, 10);
363 if (qid
< 1 || qid
> lastquestionID
) {
364 sendnoticetouser(me
,np
,"Invalid question ID %d.",qid
);
368 for (qrp
=questiontable
[qid%QUESTIONHASHSIZE
];qrp
;qrp
=qrp
->next
)
373 sendnoticetouser(me
,np
,"Can't find question %d.",qid
);
377 qrp
->flags
= ((qrp
->flags
) & ~QUESTION_STATE
) | QUESTION_SPAM
;
378 sendnoticetouser(me
,np
,"Qustion %d has been marked as spam.",qid
);
380 } else if (!ircd_strncmp(text
, "!offtopic ", 10)) {
386 while (*(++ch
) == ' '); /* skip spaces */
388 qid
=strtol(ch
, &ch
, 10);
390 if (qid
< 1 || qid
> lastquestionID
) {
391 sendnoticetouser(me
,np
,"Invalid question ID %d.",qid
);
395 for (qrp
=questiontable
[qid%QUESTIONHASHSIZE
];qrp
;qrp
=qrp
->next
)
400 sendnoticetouser(me
,np
,"Can't find question %d.",qid
);
404 qrp
->flags
= ((qrp
->flags
) & ~QUESTION_STATE
) | QUESTION_OFFTOPIC
;
405 sendnoticetouser(me
,np
,"Qustion %d has been marked as off-topic.",qid
);
407 } else if (!ircd_strncmp(text
, "!reset ",7)) {
408 if (!ircd_strncmp(text
+7,"blocks",6) || !ircd_strncmp(text
+7,"all",3)) {
409 /* reset blocked users */
412 while(blockedusers
) {
414 blockedusers
=blockedusers
->next
;
418 sendnoticetouser(me
,np
,"Reset (blocks): Done.");
420 /* XXX: Grimless alert: only finish here if user asked for "blocks";
421 * "all" case must drop down to below" */
422 if (!ircd_strncmp(text
+7,"blocks",6))
426 if (!ircd_strncmp(text
+7,"questions",9) || !ircd_strncmp(text
+7,"all",3)) {
427 /* reset questions */
431 for(i
=0;i
<QUESTIONHASHSIZE
;i
++) {
432 for(qrp
=questiontable
[i
];qrp
;qrp
=nqrp
) {
435 freesstring(qrp
->question
);
436 freesstring(qrp
->answer
);
439 questiontable
[i
]=NULL
;
443 sendnoticetouser(me
,np
,"Reset (questions): Done.");
448 sendnoticetouser(me
,np
,"Unknown parameter: %s",text
+7);
450 } else if (!ircd_strncmp(text
, "!reset",6)) {
451 sendnoticetouser(me
,np
,"!reset: more parameters needed.");
452 sendnoticetouser(me
,np
," <all|questions|blocks>");
454 } else if (!ircd_strncmp(text
, "!listblocks", 11)) {
458 sendnoticetouser(me
,np
,"There are no blocked users.");
462 sendnoticetouser(me
,np
,"Type Hostmask/Account");
464 for(bu
=blockedusers
;bu
;bu
=bu
->next
) {
466 sendnoticetouser(me
,np
,"A %s",bu
->content
);
468 sendnoticetouser(me
,np
,"H %s",bu
->content
);
472 sendnoticetouser(me
,np
,"End of list.");
474 } else if (!ircd_strncmp(text
, "!closechan", 10)) {
477 if (!(cp
=findchannel(PUBLICCHAN
))) {
478 sendnoticetouser(me
,np
,"Can't find public channel!");
481 localsetmodeinit(&changes
,cp
,me
);
482 localdosetmode_simple(&changes
,CHANMODE_INVITEONLY
,0);
483 localsetmodeflush(&changes
,1);
484 sendnoticetouser(me
,np
,"Public channel has been closed.");
486 } else if (!ircd_strncmp(text
, "!openchan", 9)) {
489 if (!(cp
=findchannel(PUBLICCHAN
))) {
490 sendnoticetouser(me
,np
,"Can't find public channel!");
493 localsetmodeinit(&changes
,cp
,me
);
494 localdosetmode_simple(&changes
,CHANMODE_MODERATE
|CHANMODE_DELJOINS
,CHANMODE_INVITEONLY
);
495 localsetmodeflush(&changes
,1);
496 sendnoticetouser(me
,np
,"Public channel has been opened.");
498 } else if (!ircd_strncmp(text
, "!unblock ", 9)) {
500 if (!ircd_strncmp(text
+9,"-q",2)) {
501 /* remove a blocked accountname */
502 blockeduser
*bu
,*bu2
=NULL
;
505 for(bu
=blockedusers
;bu
;bu
=bu
->next
) {
506 if (!bu
->type
&& !ircd_strncmp(text
+13,bu
->content
,ACCOUNTLEN
)) {
508 blockedusers
= bu
->next
;
510 bu2
->next
= bu
->next
;
513 sendnoticetouser(me
,np
,"Block for users with accountname %s has been removed.",text
+13);
520 bun
=getnickbynick(text
+12);
523 sendnoticetouser(me
,np
,"Unkown user: %s.",text
+12);
527 if (!IsAccount(bun
)) {
528 sendnoticetouser(me
,np
,"%s is not authed.",text
+12);
532 for(bu
=blockedusers
;bu
;bu
=bu
->next
) {
533 if (!bu
->type
&& !ircd_strncmp(text
+12,bu
->content
,ACCOUNTLEN
)) {
535 blockedusers
=bu
->next
;
540 sendnoticetouser(me
,np
,"Block for users with accountname %s has been removed.",text
+12);
547 sendnoticetouser(me
,np
,"No such blocked account %s.",text
+12);
550 /* it's a hostmask, check if the hostmask is valid */
552 blockeduser
*bu
,*bu2
=NULL
;
554 if (!strchr(textc
,'@') || !strchr(textc
,'!')) {
555 /* not a valid hostmask */
556 sendnoticetouser(me
,np
,"%s is not a valid hostmask.",textc
);
560 for(bu
=blockedusers
;bu
;bu
=bu
->next
) {
561 if (bu
->type
&& !ircd_strncmp(textc
,bu
->content
,ACCOUNTLEN
+USERLEN
+NICKLEN
+2)) {
563 blockedusers
= bu
->next
;
565 bu2
->next
= bu
->next
;
568 sendnoticetouser(me
,np
,"Block for users with a hostmask matching %s has been removed.",textc
);
574 sendnoticetouser(me
,np
,"No such blocked hostmask %s.",textc
);
577 } else if (!ircd_strncmp(text
, "!block ", 7)) {
581 if (!ircd_strncmp(text
+7,"-q",2)) {
582 /* block the user by his accountname */
589 /* Account given so we will use it */
592 bun
=getnickbynick(nickp
);
595 sendnoticetouser(me
,np
,"Couldn't find user %s.",nickp
);
599 if (!IsAccount(bun
)) {
600 sendnoticetouser(me
,np
,"%s is not authed.",nickp
);
607 bu
=(blockeduser
*)malloc(sizeof(blockeduser
));
609 strncpy(bu
->content
,bptr
,ACCOUNTLEN
);
611 bu
->next
=blockedusers
;
614 sendnoticetouser(me
,np
,"Now blocking all messages from users with accountname %s.",bptr
);
617 /* block the user by a hostmask */
623 if (!strchr(textc
,'@') || !strchr(textc
,'!')) {
624 /* not a valid hostmask */
625 sendnoticetouser(me
,np
,"%s is not a valid hostmask.",textc
);
629 bu
=(blockeduser
*)malloc(sizeof(blockeduser
));
631 strncpy(bu
->content
,textc
,NICKLEN
+USERLEN
+HOSTLEN
+2);
633 bu
->next
=blockedusers
;
636 sendnoticetouser(me
,np
,"Now blocking all messages from users with a hostmask matching %s.",textc
);
638 } else if (!ircd_strncmp(text
, "!answer ", 8)) {
639 /* Answering a question */
645 /* answers will no be stored and sent later on if mic is enabled */
647 while (*(++ch
) == ' '); /* skip spaces */
649 qid
=strtol(ch
, &ch
, 10);
652 sendnoticetouser(me
,np
,"No answer supplied.");
656 while (*(++ch
) == ' '); /* skip spaces */
659 sendnoticetouser(me
,np
,"No answer supplied.");
663 if (qid
< 1 || qid
> lastquestionID
) {
664 sendnoticetouser(me
,np
,"Invalid question ID %d.",qid
);
668 for (qrp
=questiontable
[qid%QUESTIONHASHSIZE
];qrp
;qrp
=qrp
->next
)
673 sendnoticetouser(me
,np
,"Can't find question %d.",qid
);
677 switch(qrp
->flags
& QUESTION_STATE
) {
678 case QUESTION_ANSWERED
:
679 sendnoticetouser(me
,np
,"Question %d has already been answered.",qid
);
682 case QUESTION_OFFTOPIC
:
683 sendnoticetouser(me
,np
,"Question %d has been marked off-topic.",qid
);
687 sendnoticetouser(me
,np
,"Question %d has been marked as spam.",qid
);
694 qrp
->flags
= ((qrp
->flags
) & ~QUESTION_STATE
) | QUESTION_ANSWERED
;
695 qrp
->answer
=getsstring(ch
, 250);
697 if ((pcp
=findchannel(PUBLICCHAN
)) && (!nextspam
) && (!micnumeric
)) {
698 sendmessagetochannel(me
, pcp
, "%s asked: %s",qrp
->nick
,qrp
->question
->content
);
699 sendmessagetochannel(me
, pcp
, "%s answers: %s",np
->nick
,ch
);
702 ans
=malloc(sizeof(storedanswer
));
704 strncpy(ans
->nick
,np
->nick
,NICKLEN
);
706 ans
->next
=storedanswers
;
709 sendnoticetouser(me
,np
,"Can't send your answer right now. Answer was stored and will be sent later on.");
713 sendnoticetouser(me
,np
,"Answer to question %d has been sent and stored.",qid
);
716 } else if (type
==LU_PRIVMSG
|| type
==LU_SECUREMSG
) {
720 lastmsg
=(time_t)np
->exts
[tutornext
];
722 if (lastmsg
+ QUESTIONINTERVAL
> time(NULL
)) {
723 sendnoticetouser(me
, np
, "You have already sent a question recently - please wait at least 30 seconds between asking questions");
725 char hostbuf
[HOSTLEN
+USERLEN
+NICKLEN
+3];
730 sendnoticetouser(me
, np
, "Your question has been relayed to the #tutorial staff.");
732 cp
=findchannel(PUBLICCHAN
);
734 if (!getnumerichandlefromchanhash(cp
->users
, np
->numeric
))
737 if ((strlen(text
)<5) || (!strchr(text
,' ')) || (*text
==1))
740 sprintf(hostbuf
,"%s!%s@%s",np
->nick
,np
->ident
,np
->host
->name
->content
);
742 for(bu
=blockedusers
;bu
;bu
=bu
->next
) {
744 /* user@host check */
745 if (!match(bu
->content
,hostbuf
))
748 /* account name check */
749 if ((IsAccount(np
)) && !ircd_strncmp(np
->authname
,bu
->content
,ACCOUNTLEN
))
754 np
->exts
[tutornext
] = (void *)time(NULL
);
756 qrp
=(questrec
*)malloc(sizeof(questrec
));
758 qrp
->ID
=++lastquestionID
;
759 qrp
->question
=getsstring(text
,250);
760 qrp
->flags
=QUESTION_NEW
;
761 strcpy(qrp
->nick
, np
->nick
);
762 qrp
->numeric
=np
->numeric
;
764 qrp
->next
=questiontable
[qrp
->ID
% QUESTIONHASHSIZE
];
766 questiontable
[qrp
->ID
% QUESTIONHASHSIZE
] = qrp
;
768 if ((cp
=findchannel(QUESTIONCHAN
))) {
769 sendmessagetochannel(me
, cp
, "ID: %3d <%s> %s", qrp
->ID
, visiblehostmask(np
, hostbuf
), text
);
771 /* Send a seperator every 10 questions */
772 if ((lastquestionID
% 10)==0)
773 sendmessagetochannel(me
,cp
,"-----------------------------------");
776 } else if (type
==LU_KILLED
) {
778 } else if (type
==LU_INVITE
) {
779 /* we were invited, check if someone invited us to PUBLICCHAN */
781 if (!ircd_strcmp(cp
->index
->name
->content
,PUBLICCHAN
)) {
782 localjoinchannel(me
,cp
);