]>
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"
11 #define STAFFCHAN "#tutorial.staff"
12 #define QUESTIONCHAN "#tutorial.questions"
13 #define PUBLICCHAN "#tutorial"
15 #define QUESTIONINTERVAL 30
17 #define SPAMINTERVAL 10
19 #define NICKMONITORTIME 300
21 #define QUESTIONHASHSIZE 1000
23 #define QUESTION_NEW 0x0
24 #define QUESTION_ANSWERED 0x1
25 #define QUESTION_OFFTOPIC 0x2
26 #define QUESTION_SPAM 0x3
28 #define QUESTION_STATE 0x7
30 typedef struct spammsg
{
35 typedef struct questrec
{
40 unsigned long numeric
;
42 struct questrec
*next
;
45 typedef struct blockeduser
{
46 int type
; /* 0 = account, 1 = hostmask */
47 char content
[NICKLEN
+USERLEN
+HOSTLEN
+3]; /* now includes a mask or an accountname */
48 struct blockeduser
*next
;
51 typedef struct storedanswer
{
54 struct storedanswer
*next
;
60 unsigned int micnumeric
; /* Who has the mic atm */
65 questrec
*questiontable
[QUESTIONHASHSIZE
];
66 blockeduser
*blockedusers
;
67 storedanswer
*storedanswers
;
70 void tutorhandler(nick
*me
, int type
, void **args
);
72 void tutorspam(void *arg
) {
76 if (tutornick
&& (cp
=findchannel(PUBLICCHAN
))) {
77 sendmessagetochannel(tutornick
, cp
, nextspam
->message
->content
);
81 nextspam
=nextspam
->next
;
83 freesstring(smp
->message
);
89 scheduleoneshot(spamtime
+SPAMINTERVAL
, tutorspam
, NULL
);
97 void spamstored(void) {
101 cp
=findchannel(PUBLICCHAN
);
103 if (!cp
|| !tutornick
)
106 while(storedanswers
) {
108 sendmessagetochannel(tutornick
,cp
,"%s asked: %s",storedanswers
->qptr
->nick
,storedanswers
->qptr
->question
->content
);
109 sendmessagetochannel(tutornick
,cp
,"%s answered: %s",storedanswers
->nick
,storedanswers
->qptr
->answer
->content
);
111 storedanswers
=storedanswers
->next
;
116 void usercountspam(void *arg
) {
119 if (!(cp
=findchannel(PUBLICCHAN
)))
122 if (!(cp2
=findchannel(STAFFCHAN
)))
128 sendmessagetochannel(tutornick
, cp2
, "Currently %d users in %s.",cp
->users
->totalusers
,cp
->index
->name
->content
);
131 void flushspamqueue() {
134 for (smp
=nextspam
;smp
;smp
=nsmp
) {
136 freesstring(smp
->message
);
140 deleteallschedules(tutorspam
);
141 nextspam
=lastspam
=NULL
;
147 if ((tutornext
=registernickext("tutorbot")) > -1) {
149 nextspam
=lastspam
=NULL
;
154 memset(questiontable
, 0, sizeof(questrec
*) * QUESTIONHASHSIZE
);
155 tutornick
=registerlocaluser("Tutor","tutor","quakenet.org","#tutorial bot","tutor",UMODE_ACCOUNT
| UMODE_INV
,
158 if ((cp
=findchannel(PUBLICCHAN
))) {
159 localjoinchannel(tutornick
,cp
);
160 localgetops(tutornick
, cp
);
162 localcreatechannel(tutornick
,PUBLICCHAN
);
165 if ((cp
=findchannel(QUESTIONCHAN
))) {
166 localjoinchannel(tutornick
,cp
);
168 localcreatechannel(tutornick
,QUESTIONCHAN
);
171 if ((cp
=findchannel(STAFFCHAN
))) {
172 localjoinchannel(tutornick
,cp
);
174 localcreatechannel(tutornick
,STAFFCHAN
);
177 schedulerecurring(time(NULL
)+300, 0, 300, usercountspam
, NULL
);
179 Error("tutorbot",ERR_ERROR
,"Unable to get nickext");
185 questrec
*qrp
, *nqrp
;
188 if (tutornext
> -1) {
189 releasenickext(tutornext
);
190 deregisterlocaluser(tutornick
,NULL
);
191 deleteallschedules(usercountspam
);
194 /* remove all blockedusers */
195 while (blockedusers
!=NULL
) {
197 blockedusers
=blockedusers
->next
;
201 /* Blow away all stored questions */
202 for(i
=0;i
<QUESTIONHASHSIZE
;i
++) {
203 for (qrp
=questiontable
[i
];qrp
;qrp
=nqrp
) {
205 freesstring(qrp
->question
);
206 freesstring(qrp
->answer
);
219 if (!(fp
=fopen("tutor-questions","w")))
225 fprintf(fp
,"Untouched questions:\n\n");
228 case QUESTION_ANSWERED
:
229 fprintf(fp
,"\nAnswered questions:\n\n");
232 case QUESTION_OFFTOPIC
:
233 fprintf(fp
,"\nOff-topic questions:\n\n");
237 fprintf(fp
,"\nSpam:\n\n");
241 for (j
=0;j
<=lastquestionID
;j
++) {
242 for (qrp
=questiontable
[j%QUESTIONHASHSIZE
];qrp
;qrp
=qrp
->next
)
249 if ((qrp
->flags
& QUESTION_STATE
) == i
) {
250 fprintf(fp
,"(%s) %s\n",qrp
->nick
,qrp
->question
->content
);
252 if (i
==QUESTION_ANSWERED
)
253 fprintf(fp
,"Answer: %s\n\n",qrp
->answer
->content
);
261 void tutorhandler(nick
*me
, int type
, void **args
) {
268 if (type
==LU_CHANMSG
) {
273 /* Staff channel *only* commands */
274 if (!ircd_strcmp(cp
->index
->name
->content
,STAFFCHAN
)) {
275 if (!ircd_strncmp(text
,"!mic",4)) {
277 if (micnumeric
==np
->numeric
) {
279 sendmessagetochannel(me
,cp
,"Mic deactivated.");
284 micnumeric
=np
->numeric
;
285 sendmessagetochannel(me
,cp
,"%s now has the mic. Anything said by %s will be relayed in %s.",
286 np
->nick
, np
->nick
, PUBLICCHAN
);
289 micnumeric
=np
->numeric
;
290 sendmessagetochannel(me
,cp
,"Mic activated. Anything said by %s will be relayed in %s.",
291 np
->nick
, PUBLICCHAN
);
293 } else if (!ircd_strncmp(text
,"!m00",4)) {
294 /* mandatory m00 command */
295 int i
,maxm00
=rand()%100
;
298 for(i
=0;i
<maxm00
;i
++) {
310 sendmessagetochannel(me
,cp
,"m%s",m00
);
311 } else if (!ircd_strncmp(text
,"!clear",6)) {
313 sendmessagetochannel(me
,cp
,"Cleared message buffer.");
316 sendmessagetochannel(me
,cp
,"Mic deactivated.");
318 } else if (!ircd_strncmp(text
,"!save",5)) {
320 sendnoticetouser(me
,np
,"Save done.");
321 } else if (*text
!= '!' && np
->numeric
==micnumeric
) {
322 /* Message to be relayed */
324 smp
=(spammsg
*)malloc(sizeof(spammsg
));
325 smp
->message
=getsstring(text
, 500);
330 if (spamtime
+ SPAMINTERVAL
< time(NULL
)) {
331 /* Spam it directly */
332 if ((cp
=findchannel(PUBLICCHAN
))) {
333 sendmessagetochannel(me
, cp
, text
);
337 /* Queue it and start the clock */
338 smp
=(spammsg
*)malloc(sizeof(spammsg
));
339 smp
->message
=getsstring(text
, 500);
341 lastspam
=nextspam
=smp
;
342 scheduleoneshot(spamtime
+SPAMINTERVAL
, tutorspam
, NULL
);
348 /* Commands for staff channel or question channel */
349 if (!ircd_strcmp(cp
->index
->name
->content
,STAFFCHAN
) || !ircd_strcmp(cp
->index
->name
->content
,QUESTIONCHAN
)) {
350 if (!ircd_strncmp(text
, "!spam ", 6)) {
356 while (*(++ch
) == ' '); /* skip spaces */
358 qid
=strtol(ch
, &ch
, 10);
360 if (qid
< 1 || qid
> lastquestionID
) {
361 sendnoticetouser(me
,np
,"Invalid question ID %d.",qid
);
365 for (qrp
=questiontable
[qid%QUESTIONHASHSIZE
];qrp
;qrp
=qrp
->next
)
370 sendnoticetouser(me
,np
,"Can't find question %d.",qid
);
374 qrp
->flags
= ((qrp
->flags
) & ~QUESTION_STATE
) | QUESTION_SPAM
;
375 sendnoticetouser(me
,np
,"Qustion %d has been marked as spam.",qid
);
377 } else if (!ircd_strncmp(text
, "!offtopic ", 10)) {
383 while (*(++ch
) == ' '); /* skip spaces */
385 qid
=strtol(ch
, &ch
, 10);
387 if (qid
< 1 || qid
> lastquestionID
) {
388 sendnoticetouser(me
,np
,"Invalid question ID %d.",qid
);
392 for (qrp
=questiontable
[qid%QUESTIONHASHSIZE
];qrp
;qrp
=qrp
->next
)
397 sendnoticetouser(me
,np
,"Can't find question %d.",qid
);
401 qrp
->flags
= ((qrp
->flags
) & ~QUESTION_STATE
) | QUESTION_OFFTOPIC
;
402 sendnoticetouser(me
,np
,"Qustion %d has been marked as off-topic.",qid
);
404 } else if (!ircd_strncmp(text
, "!reset ",7)) {
405 if (!ircd_strncmp(text
+7,"blocks",6) || !ircd_strncmp(text
+7,"all",3)) {
406 /* reset blocked users */
409 while(blockedusers
) {
411 blockedusers
=blockedusers
->next
;
415 sendnoticetouser(me
,np
,"Reset (blocks): Done.");
417 /* XXX: Grimless alert: only finish here if user asked for "blocks";
418 * "all" case must drop down to below" */
419 if (!ircd_strncmp(text
+7,"blocks",6))
423 if (!ircd_strncmp(text
+7,"questions",9) || !ircd_strncmp(text
+7,"all",3)) {
424 /* reset questions */
428 for(i
=0;i
<QUESTIONHASHSIZE
;i
++) {
429 for(qrp
=questiontable
[i
];qrp
;qrp
=nqrp
) {
432 freesstring(qrp
->question
);
433 freesstring(qrp
->answer
);
436 questiontable
[i
]=NULL
;
440 sendnoticetouser(me
,np
,"Reset (questions): Done.");
445 sendnoticetouser(me
,np
,"Unknown parameter: %s",text
+7);
447 } else if (!ircd_strncmp(text
, "!reset",6)) {
448 sendnoticetouser(me
,np
,"!reset: more parameters needed.");
449 sendnoticetouser(me
,np
," <all|questions|blocks>");
451 } else if (!ircd_strncmp(text
, "!listblocks", 11)) {
455 sendnoticetouser(me
,np
,"There are no blocked users.");
459 sendnoticetouser(me
,np
,"Type Hostmask/Account");
461 for(bu
=blockedusers
;bu
;bu
=bu
->next
) {
463 sendnoticetouser(me
,np
,"A %s",bu
->content
);
465 sendnoticetouser(me
,np
,"H %s",bu
->content
);
469 sendnoticetouser(me
,np
,"End of list.");
471 } else if (!ircd_strncmp(text
, "!closechan", 10)) {
474 if (!(cp
=findchannel(PUBLICCHAN
))) {
475 sendnoticetouser(me
,np
,"Can't find public channel!");
478 localsetmodeinit(&changes
,cp
,me
);
479 localdosetmode_simple(&changes
,CHANMODE_INVITEONLY
,0);
480 localsetmodeflush(&changes
,1);
481 sendnoticetouser(me
,np
,"Public channel has been closed.");
483 } else if (!ircd_strncmp(text
, "!openchan", 9)) {
486 if (!(cp
=findchannel(PUBLICCHAN
))) {
487 sendnoticetouser(me
,np
,"Can't find public channel!");
490 localsetmodeinit(&changes
,cp
,me
);
491 localdosetmode_simple(&changes
,CHANMODE_MODERATE
|CHANMODE_DELJOINS
,CHANMODE_INVITEONLY
);
492 localsetmodeflush(&changes
,1);
493 sendnoticetouser(me
,np
,"Public channel has been opened.");
495 } else if (!ircd_strncmp(text
, "!unblock ", 9)) {
497 if (!ircd_strncmp(text
+9,"-q",2)) {
498 /* remove a blocked accountname */
499 blockeduser
*bu
,*bu2
=NULL
;
502 for(bu
=blockedusers
;bu
;bu
=bu
->next
) {
503 if (!bu
->type
&& !ircd_strncmp(text
+13,bu
->content
,ACCOUNTLEN
)) {
505 blockedusers
= bu
->next
;
507 bu2
->next
= bu
->next
;
510 sendnoticetouser(me
,np
,"Block for users with accountname %s has been removed.",text
+13);
517 bun
=getnickbynick(text
+12);
520 sendnoticetouser(me
,np
,"Unkown user: %s.",text
+12);
524 if (!IsAccount(bun
)) {
525 sendnoticetouser(me
,np
,"%s is not authed.",text
+12);
529 for(bu
=blockedusers
;bu
;bu
=bu
->next
) {
530 if (!bu
->type
&& !ircd_strncmp(text
+12,bu
->content
,ACCOUNTLEN
)) {
532 blockedusers
=bu
->next
;
537 sendnoticetouser(me
,np
,"Block for users with accountname %s has been removed.",text
+12);
544 sendnoticetouser(me
,np
,"No such blocked account %s.",text
+12);
547 /* it's a hostmask, check if the hostmask is valid */
549 blockeduser
*bu
,*bu2
=NULL
;
551 if (!strchr(textc
,'@') || !strchr(textc
,'!')) {
552 /* not a valid hostmask */
553 sendnoticetouser(me
,np
,"%s is not a valid hostmask.",textc
);
557 for(bu
=blockedusers
;bu
;bu
=bu
->next
) {
558 if (bu
->type
&& !ircd_strncmp(textc
,bu
->content
,ACCOUNTLEN
+USERLEN
+NICKLEN
+2)) {
560 blockedusers
= bu
->next
;
562 bu2
->next
= bu
->next
;
565 sendnoticetouser(me
,np
,"Block for users with a hostmask matching %s has been removed.",textc
);
571 sendnoticetouser(me
,np
,"No such blocked hostmask %s.",textc
);
574 } else if (!ircd_strncmp(text
, "!block ", 7)) {
578 if (!ircd_strncmp(text
+7,"-q",2)) {
579 /* block the user by his accountname */
586 /* Account given so we will use it */
589 bun
=getnickbynick(nickp
);
592 sendnoticetouser(me
,np
,"Couldn't find user %s.",nickp
);
596 if (!IsAccount(bun
)) {
597 sendnoticetouser(me
,np
,"%s is not authed.",nickp
);
604 bu
=(blockeduser
*)malloc(sizeof(blockeduser
));
606 strncpy(bu
->content
,bptr
,ACCOUNTLEN
);
608 bu
->next
=blockedusers
;
611 sendnoticetouser(me
,np
,"Now blocking all messages from users with accountname %s.",bptr
);
614 /* block the user by a hostmask */
620 if (!strchr(textc
,'@') || !strchr(textc
,'!')) {
621 /* not a valid hostmask */
622 sendnoticetouser(me
,np
,"%s is not a valid hostmask.",textc
);
626 bu
=(blockeduser
*)malloc(sizeof(blockeduser
));
628 strncpy(bu
->content
,textc
,NICKLEN
+USERLEN
+HOSTLEN
+2);
630 bu
->next
=blockedusers
;
633 sendnoticetouser(me
,np
,"Now blocking all messages from users with a hostmask matching %s.",textc
);
635 } else if (!ircd_strncmp(text
, "!answer ", 8)) {
636 /* Answering a question */
642 /* answers will no be stored and sent later on if mic is enabled */
644 while (*(++ch
) == ' '); /* skip spaces */
646 qid
=strtol(ch
, &ch
, 10);
649 sendnoticetouser(me
,np
,"No answer supplied.");
653 while (*(++ch
) == ' '); /* skip spaces */
656 sendnoticetouser(me
,np
,"No answer supplied.");
660 if (qid
< 1 || qid
> lastquestionID
) {
661 sendnoticetouser(me
,np
,"Invalid question ID %d.",qid
);
665 for (qrp
=questiontable
[qid%QUESTIONHASHSIZE
];qrp
;qrp
=qrp
->next
)
670 sendnoticetouser(me
,np
,"Can't find question %d.",qid
);
674 switch(qrp
->flags
& QUESTION_STATE
) {
675 case QUESTION_ANSWERED
:
676 sendnoticetouser(me
,np
,"Question %d has already been answered.",qid
);
679 case QUESTION_OFFTOPIC
:
680 sendnoticetouser(me
,np
,"Question %d has been marked off-topic.",qid
);
684 sendnoticetouser(me
,np
,"Question %d has been marked as spam.",qid
);
691 qrp
->flags
= ((qrp
->flags
) & ~QUESTION_STATE
) | QUESTION_ANSWERED
;
692 qrp
->answer
=getsstring(ch
, 250);
694 if ((pcp
=findchannel(PUBLICCHAN
)) && (!nextspam
) && (!micnumeric
)) {
695 sendmessagetochannel(me
, pcp
, "%s asked: %s",qrp
->nick
,qrp
->question
->content
);
696 sendmessagetochannel(me
, pcp
, "%s answers: %s",np
->nick
,ch
);
699 ans
=malloc(sizeof(storedanswer
));
701 strncpy(ans
->nick
,np
->nick
,NICKLEN
);
703 ans
->next
=storedanswers
;
706 sendnoticetouser(me
,np
,"Can't send your answer right now. Answer was stored and will be sent later on.");
710 sendnoticetouser(me
,np
,"Answer to question %d has been sent and stored.",qid
);
713 } else if (type
==LU_PRIVMSG
|| type
==LU_SECUREMSG
) {
717 lastmsg
=(time_t)np
->exts
[tutornext
];
719 if (lastmsg
+ QUESTIONINTERVAL
> time(NULL
)) {
720 sendnoticetouser(me
, np
, "You have already sent a question recently - please wait at least 30 seconds between asking questions");
722 char hostbuf
[HOSTLEN
+USERLEN
+NICKLEN
+3];
727 sendnoticetouser(me
, np
, "Your question has been relayed to the #tutorial staff.");
729 cp
=findchannel(PUBLICCHAN
);
731 if (!getnumerichandlefromchanhash(cp
->users
, np
->numeric
))
734 if ((strlen(text
)<5) || (!strchr(text
,' ')) || (*text
==1))
737 sprintf(hostbuf
,"%s!%s@%s",np
->nick
,np
->ident
,np
->host
->name
->content
);
739 for(bu
=blockedusers
;bu
;bu
=bu
->next
) {
741 /* user@host check */
742 if (!match(bu
->content
,hostbuf
))
745 /* account name check */
746 if ((IsAccount(np
)) && !ircd_strncmp(np
->authname
,bu
->content
,ACCOUNTLEN
))
751 np
->exts
[tutornext
] = (void *)time(NULL
);
753 qrp
=(questrec
*)malloc(sizeof(questrec
));
755 qrp
->ID
=++lastquestionID
;
756 qrp
->question
=getsstring(text
,250);
757 qrp
->flags
=QUESTION_NEW
;
758 strcpy(qrp
->nick
, np
->nick
);
759 qrp
->numeric
=np
->numeric
;
761 qrp
->next
=questiontable
[qrp
->ID
% QUESTIONHASHSIZE
];
763 questiontable
[qrp
->ID
% QUESTIONHASHSIZE
] = qrp
;
765 if ((cp
=findchannel(QUESTIONCHAN
))) {
766 sendmessagetochannel(me
, cp
, "ID: %3d <%s> %s", qrp
->ID
, visiblehostmask(np
, hostbuf
), text
);
768 /* Send a seperator every 10 questions */
769 if ((lastquestionID
% 10)==0)
770 sendmessagetochannel(me
,cp
,"-----------------------------------");
773 } else if (type
==LU_KILLED
) {
775 } else if (type
==LU_INVITE
) {
776 /* we were invited, check if someone invited us to PUBLICCHAN */
778 if (!ircd_strcmp(cp
->index
->name
->content
,PUBLICCHAN
)) {
779 localjoinchannel(me
,cp
);