]> git.xonotic.org Git - xonotic/darkplaces.git/blob - irc.c
fixed irc_hilights
[xonotic/darkplaces.git] / irc.c
1 //      irc.c
2 //      
3 //      Copyright 2011 Akari <Akari` @ irc.quakenet.org>
4 //      
5 //      This program is free software; you can redistribute it and/or modify
6 //      it under the terms of the GNU General Public License as published by
7 //      the Free Software Foundation; either version 2 of the License, or
8 //      (at your option) any later version.
9 //      
10 //      This program is distributed in the hope that it will be useful,
11 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //      GNU General Public License for more details.
14
15 #include <pthread.h>
16 #include <string.h>
17
18 #include "quakedef.h"
19 #include "irc.h"
20 #include "cvar.h"
21
22 cvar_t irc_server = {CVAR_SAVE, "irc_server", "", "IRC server to connect to"};
23 cvar_t irc_port = {CVAR_SAVE, "irc_port", "6667", "Port of the IRC server"};
24 cvar_t irc_password = {CVAR_SAVE, "irc_password", "", "IRC server password"};
25 cvar_t irc_nick = {CVAR_SAVE, "irc_nick", "", "Your nickname to use on IRC. Note: this cvar only defines your prefered nick, do NOT use this to change your nickname while connected, use irc_chnick instead"};
26 cvar_t irc_connected = {CVAR_READONLY, "irc_connected", "0", "IRC connection state (0 = not connected, 1 = connecting, 2 = connected)"};
27 cvar_t irc_msgprefix = {CVAR_SAVE, "irc_msgprefix", "^5IRC^0|^7", "What all IRC events will be prefixed with when printed to the console"};
28 cvar_t irc_chatwindow = {CVAR_SAVE, "irc_chatwindow", "2", "0 = IRC messages will be printed in the console only, 1 = IRC messages will go to the chat window, 2 = Only hilights and private messages will appear in the chat window"};
29 cvar_t irc_numeric_errorsonly = {CVAR_SAVE, "irc_numeric_errorsonly", "0", "If 1, any numeric event below 400 won't be printed'"};
30 cvar_t irc_save_target = {CVAR_SAVE, "irc_save_target", "1", "Whether to save target for irc_messagemode or not"};
31 cvar_t irc_current_nick = {CVAR_READONLY, "irc_current_nick", "", "Holds your current IRC nick"};
32 cvar_t irc_hilights = {CVAR_SAVE, "irc_hilights", "", "Space separated list of words to hilight"};
33 cvar_t irc_autochangetarget = {CVAR_SAVE, "irc_autochangetarget", "0", "if 1, will automatically change target for irc_messagemode when a hilight or a private message is recieved, so you can reply instantly. Requires irc_save_target"};
34 cvar_t irc_watched_channels = {CVAR_SAVE, "irc_watched_channels", "", "Space separated list of watched channels. PRIVMSGs from those channels will be always printed to the chat area, regardless of irc_chatwindow setting"};
35
36 irc_session_t *irc_session_global = NULL;
37 int irc_msgmode;
38 char last_channel[MAX_INPUTLINE];
39
40 //
41 //  Initialization
42 //
43
44 void CL_Irc_Init(void)
45 {
46     Cvar_RegisterVariable(&irc_server);
47     Cvar_RegisterVariable(&irc_port);
48     Cvar_RegisterVariable(&irc_password);
49     Cvar_RegisterVariable(&irc_nick);
50     Cvar_RegisterVariable(&irc_connected);
51     Cvar_RegisterVariable(&irc_msgprefix);
52     Cvar_RegisterVariable(&irc_chatwindow);
53     Cvar_RegisterVariable(&irc_numeric_errorsonly);
54     Cvar_RegisterVariable(&irc_save_target);
55     Cvar_RegisterVariable(&irc_current_nick);
56     Cvar_RegisterVariable(&irc_hilights);
57     Cvar_RegisterVariable(&irc_autochangetarget);
58     Cvar_RegisterVariable(&irc_watched_channels);
59     
60     Cmd_AddCommand ("irc_connect", CL_Irc_Connect_f, "Connects you to the IRC server");
61     Cmd_AddCommand ("irc_disconnect", CL_Irc_Disconnect_f, "Disconnects you from the IRC server");
62     Cmd_AddCommand ("irc_say", CL_Irc_Say_f, "Sends a privmsg to a channel or nick");
63     Cmd_AddCommand ("irc_notice", CL_Irc_Notice_f, "Sends a notice to a channel or nick");
64     Cmd_AddCommand ("irc_me", CL_Irc_Me_f, "Sends a CTCP ACTION (/me) to a channel or nick");
65     Cmd_AddCommand ("irc_join", CL_Irc_Join_f, "Joins an IRC channel");
66     Cmd_AddCommand ("irc_part", CL_Irc_Part_f, "Parts an IRC channel");
67     Cmd_AddCommand ("irc_names", CL_Irc_Names_f, "Lists users of an IRC channel");
68     Cmd_AddCommand ("irc_messagemode", CL_Irc_MessageMode_f, "Interactive prompt for sending IRC messages. Meant to be bound to a key");
69     Cmd_AddCommand ("irc_chnick", CL_Irc_ChNick_f, "Changes your nick");
70     Cmd_AddCommand ("irc_raw", CL_Irc_Raw_f, "Sends a raw string to the IRC server");
71 }
72
73 //
74 //  Function that starts IRC main loop in a thread
75 //
76
77 static void IRC_Thread(void *p)
78 {
79     if(irc_run(irc_session_global))
80     {
81         Con_Printf("%s^1Error: ^7%s\n",
82             irc_msgprefix.string,
83             irc_strerror(irc_errno(irc_session_global))
84         );
85         
86         CL_Irc_Disconnect_f();
87         return;
88     }
89 }
90
91 //
92 //  irc_ console commands callbacks
93 //
94
95 static void CL_Irc_Connect_f(void)
96 {
97     irc_callbacks_t cb;
98     pthread_t irc_thread;
99     
100     switch(irc_connected.integer)
101     {
102         case 1:
103             Con_Printf("%sAlready connecting to %s:%i\n",
104                 irc_msgprefix.string, 
105                 irc_server.string,
106                 irc_port.integer
107             );
108             return;
109         
110         case 2:
111             Con_Printf("%sAlready connected to %s:%i.\n",
112                 irc_msgprefix.string,
113                 irc_server.string,
114                 irc_port.integer
115             );
116             return;
117     }
118     
119     if(NOTSET(irc_server))
120     {
121         Con_Printf("Please set the irc_server variable\n");
122         return;
123     }
124     
125     if(NOTSET(irc_nick))
126     {
127         Con_Printf("Please set the irc_nick variable\n");
128         return;
129     }
130     
131     if(NOTSET(irc_port))
132     {
133         Con_Printf("Please set the irc_port variable\n");
134         return;
135     }
136     
137     Cvar_SetQuick(&irc_connected, "1");
138     
139     memset(&cb, 0, sizeof(cb));
140     cb.event_connect = event_connect;
141     cb.event_numeric = event_numeric;
142     cb.event_privmsg = event_privmsg;
143     cb.event_channel = event_channel;
144     cb.event_nick    = event_nick;
145     cb.event_quit    = event_quit;
146     cb.event_join    = event_join;
147     cb.event_part    = event_part;
148     cb.event_mode    = event_mode;
149     cb.event_umode   = event_umode;
150     cb.event_topic   = event_topic;
151     cb.event_kick    = event_kick;
152     cb.event_notice  = event_notice;
153     cb.event_invite  = event_invite;
154
155     Cvar_SetQuick(&irc_current_nick, irc_nick.string);
156
157     irc_session_global = irc_create_session(&cb);
158     irc_option_set(irc_session_global, LIBIRC_OPTION_STRIPNICKS);
159     if(irc_connect(
160         irc_session_global,
161         irc_server.string,
162         irc_port.integer,
163         NOTSET(irc_password)? NULL : irc_password.string,
164         irc_nick.string,
165         "dpirc", "DPIRC user"
166     ))
167     {   //Connection failed
168         Con_Printf("%s^1Connection failed: ^7%s\n",
169             irc_msgprefix.string,
170             irc_strerror(irc_errno(irc_session_global))
171         );
172         
173         CL_Irc_Disconnect_f();
174         return;
175     }
176     
177     pthread_create (&irc_thread, NULL, (void *) &IRC_Thread, NULL);
178 }
179
180 static void CL_Irc_Disconnect_f(void)
181 {
182     if(!irc_connected.integer)
183     {
184         Con_Printf("%sNot connected\n",
185             irc_msgprefix.string
186         );
187         
188         return;
189     }
190     
191     Con_Printf("^1Disconnected from the IRC server\n");
192     irc_cmd_quit(irc_session_global, "Disconnected");
193     
194     irc_destroy_session(irc_session_global);
195
196     Cvar_SetQuick(&irc_connected, "0");
197 }
198
199 static void CL_Irc_Say_Universal_f(void)
200 {
201     int cmdlen, i, j, space;
202     const char *cmd, *dest;
203     char message[MAX_INPUTLINE];
204     qboolean watched;
205
206     if(Cmd_Argc() < 3) switch(irc_msgmode)
207     {
208         case MSGMODE_PRIVMSG:
209             Con_Printf("Usage: irc_say channel_or_nick message\n");
210             return;
211         case MSGMODE_NOTICE:
212             Con_Printf("Usage: irc_notice channel_or_nick message\n");
213             return;
214         case MSGMODE_ACTION:
215             Con_Printf("Usage: irc_me channel_or_nick message\n");
216             return;
217     }
218     
219     cmd = Cmd_Args();
220     cmdlen = strlen(cmd);
221     
222     space = 0;
223     for(i = 0, j = 0; i < cmdlen; ++i)
224     {
225         if(space)
226         {
227             message[j++] = cmd[i];
228         }
229         else if(cmd[i] == ' ') ++space;
230     }
231     
232     message[j] = '\0';
233     
234     dest = Cmd_Argv(1);
235     
236     switch(irc_msgmode)
237     {
238         case MSGMODE_PRIVMSG:
239             irc_cmd_msg(irc_session_global, dest, message);
240             watched = Irc_IsWatchedChannel(dest);
241             
242             if(ISCHANNEL(dest)) Con_Printf("%s%s^3%s^0|^7<^2%s^7> %s\n",
243                 watched? "\001" : CHATWINDOW,
244                 irc_msgprefix.string,
245                 dest,
246                 irc_current_nick.string,
247                 message
248             ); else Con_Printf("%s%s^1Privmsg to ^2%s^7: ^3%s\n",
249                 CHATWINDOW_URGENT,
250                 irc_msgprefix.string,
251                 dest,
252                 message
253             );
254             
255             break;
256         
257         case MSGMODE_NOTICE:
258             irc_cmd_notice(irc_session_global, dest, message);
259             
260             if(!ISCHANNEL(dest)) Con_Printf("%s%sNotice to ^2%s^7: ^9%s\n",
261                 CHATWINDOW,
262                 irc_msgprefix.string,
263                 dest,
264                 message
265             ); else Con_Printf("%s%s^3%s^0|^9-^2%s^9- %s\n",
266                 CHATWINDOW,
267                 irc_msgprefix.string,
268                 dest,
269                 irc_current_nick.string,
270                 message
271             );
272             
273             break;
274         
275         case MSGMODE_ACTION: //to-do
276             irc_cmd_me(irc_session_global, dest, message);
277             break;
278     }
279 }
280
281 static void CL_Irc_Raw_f(void)
282 {
283     if(Cmd_Argc() < 2)
284     {
285         Con_Printf("Usage: irc_raw command\n");
286         return;
287     }
288     
289     irc_send_raw(irc_session_global, "%s", Cmd_Args());
290 }
291
292 static void CL_Irc_Say_f(void)
293 {
294     MUSTCONNECT
295     irc_msgmode = MSGMODE_PRIVMSG;
296     CL_Irc_Say_Universal_f();
297 }
298
299 static void CL_Irc_Notice_f(void)
300 {
301     MUSTCONNECT
302     irc_msgmode = MSGMODE_NOTICE;
303     CL_Irc_Say_Universal_f();
304 }
305
306 static void CL_Irc_Me_f(void)
307 {
308     MUSTCONNECT
309     irc_msgmode = MSGMODE_ACTION;
310     CL_Irc_Say_Universal_f();
311 }
312
313 static void CL_Irc_Join_f(void)
314 {
315     int argc = Cmd_Argc();
316     const char *channel;
317     
318     MUSTCONNECT
319     
320     if(argc < 2)
321     {
322         Con_Printf("Usage: irc_join channel [key]\n");
323         return;
324     }
325     
326     channel = Cmd_Argv(1);
327     
328     if(argc > 2)
329         irc_cmd_join(irc_session_global, channel, Cmd_Argv(2));
330     else
331         irc_cmd_join(irc_session_global, channel, NULL);
332 }
333
334 static void CL_Irc_Part_f(void)
335 {
336     MUSTCONNECT
337     
338     if(Cmd_Argc() < 2)
339     {
340         Con_Printf("Usage: irc_part channel\n");
341         return;
342     }
343     
344     irc_cmd_part(irc_session_global, Cmd_Argv(1));
345 }
346
347 static void CL_Irc_Names_f(void)
348 {
349     MUSTCONNECT
350     
351     if(Cmd_Argc() < 2)
352     {
353         Con_Printf("Usage: irc_names channel\n");
354         return;
355     }
356     
357     irc_cmd_names(irc_session_global, Cmd_Argv(1));
358 }
359
360 void CL_Irc_MessageMode_f(void)
361 {
362     const char *tmp;
363     
364     key_dest = key_message;
365     chat_mode = 2;
366     chat_bufferlen = 0;
367     chat_buffer[0] = 0;
368     
369     if(irc_save_target.integer)
370     {
371         tmp = Irc_GetLastChannel();
372         strlcpy(chat_buffer, tmp, sizeof(chat_buffer));
373         chat_bufferlen = strlen(chat_buffer);
374     }
375 }
376
377 void CL_Irc_ChNick_f(void)
378 {
379     MUSTCONNECT
380     
381     if(Cmd_Argc() < 2)
382     {
383         Con_Printf("Usage: irc_chnick nick\n");
384         return;
385     }
386     
387     irc_cmd_nick(irc_session_global, Cmd_Argv(1));
388 }
389
390
391 void Irc_SetLastChannel(const char *value)
392 {
393     strlcpy(last_channel, value, sizeof(last_channel));
394 }
395
396 const char* Irc_GetLastChannel(void)
397 {
398     return last_channel;
399 }
400
401
402 void Irc_SendMessage(const char *message)
403 {
404     const char* dest = Irc_GetLastChannel();
405     qboolean watched = Irc_IsWatchedChannel(dest);
406     
407     MUSTCONNECT
408     
409     irc_cmd_msg(irc_session_global, dest, message);
410             
411     if(ISCHANNEL(dest)) Con_Printf("%s%s^3%s^0|^7<^2%s^7> %s\n",
412         watched? "\001" : CHATWINDOW,
413         irc_msgprefix.string,
414         dest,
415         irc_current_nick.string,
416         message
417     ); else Con_Printf("%s%s^1Privmsg to ^2%s^7: ^3%s\n",
418         CHATWINDOW_URGENT,
419         irc_msgprefix.string,
420         dest,
421         message
422     );
423 }
424
425 //
426 //  IRC events
427 //
428
429 IRCEVENT(event_connect)
430 {
431     Cvar_SetQuick(&irc_connected, "2");
432     Con_Printf("%sConnected to %s:%i\n",
433         irc_msgprefix.string, 
434         irc_server.string,
435         irc_port.integer
436     );
437 }
438
439 IRCNUMEVENT(event_numeric)
440 {
441     //Get our initial nick from the welcome message
442     if(event == 001)
443         Cvar_SetQuick(&irc_current_nick, params[0]);
444     
445     if (!irc_numeric_errorsonly.integer || event > 400)
446     {
447         Con_Printf("%s^3%d ^2%s ^1%s^7 %s %s %s\n",
448             irc_msgprefix.string,
449             event,
450             origin ? origin : "^8(unknown)",
451             params[0],
452             count > 1 ? params[1] : "",
453             count > 2 ? params[2] : "",
454             count > 3 ? params[3] : ""
455         );
456     }
457 }
458
459 IRCEVENT(event_privmsg)
460 {
461     char* msgstr = "";
462     
463     if(count > 1)
464         msgstr = irc_color_strip_from_mirc(params[1]);
465     
466     UPDATETARGET(origin)
467     
468     Con_Printf("%s%s^1Privmsg from ^2%s^7: ^3%s\n",
469         CHATWINDOW_URGENT,
470         irc_msgprefix.string,
471         origin,
472         msgstr
473     );
474     
475     if(count > 1) free(msgstr);
476 }
477
478
479 IRCEVENT(event_channel)
480 {
481     char* msgstr = "";
482     qboolean watched;
483     
484     //weird shit
485     if(!ISCHANNEL(params[0]))
486         return event_privmsg(session, event, origin, params, count);
487     
488     if(count > 1)
489         msgstr = irc_color_strip_from_mirc(params[1]);
490     
491     watched = Irc_IsWatchedChannel(params[0]);
492     
493     if(Irc_CheckHilight(msgstr))
494     {   
495         UPDATETARGET(params[0])
496         
497         Con_Printf("%s%s^3%s^0|^7<^2%s^7> ^1%s\n",
498             watched? "\001" : CHATWINDOW_URGENT,
499             irc_msgprefix.string,
500             params[0],
501             origin,
502             msgstr
503         );
504     }
505     else Con_Printf("%s%s^3%s^0|^7<^2%s^7> %s\n",
506         watched? "\001" : CHATWINDOW,
507         irc_msgprefix.string,
508         params[0],
509         origin,
510         msgstr
511     );
512     
513     if(count > 1) free(msgstr);
514 }
515
516 IRCEVENT(event_nick)
517 {
518     //printf("%sorigin -> %s (%s, %s)\n", origin, params[0], irc_current_nick.string, irc_nick.string);
519     //crash!
520     //cvar_t dumb;
521     //Cvar_SetQuick(&dumb, NULL);
522     
523     if(!strncmp(origin, irc_current_nick.string, 512)) //Our nick is changed
524     {
525         Cvar_SetQuick(&irc_current_nick, params[0]);
526         
527         Con_Printf("%s%s^7Your nick is now ^2%s\n",
528             CHATWINDOW,
529             irc_msgprefix.string,
530             params[0]
531         );
532         
533         return;
534     }
535     
536     Con_Printf("%s%s^2%s^7 is now known as ^2%s\n",
537         CHATWINDOW,
538         irc_msgprefix.string,
539         origin,
540         params[0]
541     );
542 }
543
544 IRCEVENT(event_quit)
545 {
546     Con_Printf("%s%s^2%s^7 has quit IRC: ^2%s\n",
547         CHATWINDOW,
548         irc_msgprefix.string,
549         origin,
550         count > 1? params[0] : "^8(quit message missing)"
551     );
552 }
553
554 IRCEVENT(event_join)
555 {
556     Con_Printf("%s%s^2%s^7 has joined ^3%s\n",
557         CHATWINDOW,
558         irc_msgprefix.string,
559         origin,
560         params[0]
561     );
562 }
563
564 IRCEVENT(event_part)
565 {
566     Con_Printf("%s%s^2%s^7 has left ^3%s^7: %s\n",
567         CHATWINDOW,
568         irc_msgprefix.string,
569         origin,
570         params[0],
571         count > 1? params[1] : "^8(part message missing)"
572     );
573 }
574
575 IRCEVENT(event_mode)
576 {
577     char paramstring[MAX_INPUTLINE] = "";
578     unsigned int i;
579     
580     for(i = 2; i < count; i++)
581     {
582         strlcat(paramstring, params[i], sizeof(paramstring));
583         strlcat(paramstring, " ", sizeof(paramstring));
584     }
585     
586     Con_Printf("%s%s^2%s^7 set mode on ^3%s^7: %s %s\n",
587         CHATWINDOW,
588         irc_msgprefix.string,
589         origin,
590         params[0],
591         params[1],
592         paramstring
593     );
594 }
595
596 IRCEVENT(event_umode)
597 {
598     Con_Printf("%sUsermode changed for ^2%s^7: %s\n",
599         irc_msgprefix.string,
600         origin,
601         params[0]
602     );
603 }
604
605 IRCEVENT(event_topic)
606 {
607     if(count > 1) Con_Printf("%s%s^2%s^7 changed the topic of ^3%s^7: %s\n",
608         CHATWINDOW,
609         irc_msgprefix.string,
610         origin,
611         params[0],
612         params[1]
613     );
614 }
615
616 IRCEVENT(event_kick)
617 {
618     Con_Printf("%s%s^2%s^7 has kicked ^2%s^7 out of ^3%s^7: %s\n",
619         CHATWINDOW,
620         irc_msgprefix.string,
621         origin,
622         count > 1? params[1] : "^8(nick missing)", //libircclient documentation says params[1] is optinal.
623         params[0],
624         count > 2? params[2] : "^8(random kick victim! yay!)"
625     );
626 }
627
628 IRCEVENT(event_notice)
629 {
630     char* msgstr = "";
631     
632     if(count > 1)
633         msgstr = irc_color_strip_from_mirc(params[1]);
634     
635     if(!ISCHANNEL(params[0])) Con_Printf("%s%sNotice from ^2%s^7: ^9%s\n",
636         CHATWINDOW,
637         irc_msgprefix.string,
638         origin,
639         msgstr
640     ); else Con_Printf("%s%s^3%s^0|^9-^2%s^9- %s\n",
641         CHATWINDOW,
642         irc_msgprefix.string,
643         params[0],
644         origin,
645         msgstr
646     );
647     
648     if(count > 1) free(msgstr);
649 }
650
651 IRCEVENT(event_invite)
652 {
653     Con_Printf("%s%s^2%s^7 invites you to ^3%s\n",
654         CHATWINDOW,
655         irc_msgprefix.string,
656         origin,
657         params[1]
658     );
659 }
660
661 //
662 //  Functions that checks if a message contains hilights
663 //
664
665 qboolean Irc_CheckHilight(const char *msg)
666 {
667     int start, idx, len;
668     char buffer[512];
669     
670     if(strcasestr(msg, irc_current_nick.string))
671         return TRUE; //Contains our nick
672     
673     if(NOTSET(irc_hilights))
674         return FALSE;
675     
676     len = strlen(irc_hilights.string);
677     start = 0;
678     
679     for(idx = 0; idx < len; ++idx)
680     {
681         if(irc_hilights.string[idx] == ' ')
682         {
683             strlcpy(buffer, irc_hilights.string+start, idx+1);
684             if(strcasestr(msg, buffer))
685                 return TRUE; //Contains a word from hilight list
686                 
687             start = idx+1;
688         }
689     }
690     
691     //Catch the final word
692     strlcpy(buffer, irc_hilights.string+start, idx+1);
693     if(strcasestr(msg, buffer))
694         return TRUE; //Contains a word from hilight list
695     
696     return FALSE;
697 }
698
699 //
700 //  Checks if channel is watched
701 //
702
703 qboolean Irc_IsWatchedChannel(const char* chan)
704 {
705     int start, idx, len;
706     char buffer[512];
707     
708     if(NOTSET(irc_watched_channels))
709         return FALSE;
710     
711     len = strlen(irc_watched_channels.string);
712     start = 0;
713     
714     for(idx = 0; idx < len; ++idx)
715     {
716         if(irc_watched_channels.string[idx] == ' ')
717         {
718             strlcpy(buffer, irc_watched_channels.string+start, idx+1);
719             if(strcasestr(chan, buffer))
720                 return TRUE;
721                 
722             start = idx+1;
723         }
724     }
725     
726     //Catch the final channel
727     strlcpy(buffer, irc_watched_channels.string+start, idx+1);
728     if(strcasestr(chan, buffer))
729         return TRUE;
730     
731     return FALSE;
732 }
733
734 //
735 //  Functions that should have been in libircclient
736 //
737
738 int irc_cmd_nick(irc_session_t * session, const char *nick)
739 {
740     if(!nick)
741     {
742         //session->lasterror = LIBIRC_ERR_STATE; Won't compile
743         return 1;
744     }
745
746     return irc_send_raw(session, "NICK %s", nick);
747 }