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