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