Add configure command to the Makefile.
[vomak.git] / irc.c
blob907bb8fa59d801b19cab3d825f094f1573d7e659
1 /*
2 * irc.c - this file is part of vomak - a very simple IRC bot
4 * Copyright 2008 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 of the License.
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.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #define _GNU_SOURCE
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <errno.h>
25 #include <netdb.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <netinet/in.h>
29 #include <sys/socket.h>
30 #include <unistd.h>
31 #include <string.h>
32 #include <strings.h>
33 #include <signal.h>
34 #ifndef DEBUG
35 # include <syslog.h>
36 #endif
38 #include <glib.h>
40 #include "vomak.h"
41 #include "socket.h"
42 #include "irc.h"
45 config_t *config;
49 gboolean irc_query_names(gpointer data)
51 irc_conn_t *irc = data;
52 static gchar msg[1024];
53 guint msg_len;
55 TRACE
57 msg_len = g_snprintf(msg, sizeof msg, "NAMES #%s\r\n", config->channel);
58 send(irc->socket, msg, msg_len, 0);
60 return TRUE;
64 static void irc_send_private_message(irc_conn_t *irc_conn, const gchar *target, const gchar *format, ...)
66 static gchar tmp_msg[1024];
67 static gchar msg[1024];
68 guint msg_len;
69 va_list ap;
71 if (target == NULL)
72 return;
74 va_start(ap, format);
75 g_vsnprintf(tmp_msg, sizeof tmp_msg, format, ap);
76 va_end(ap);
78 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG %s :%s, %s\r\n", target, target, tmp_msg);
79 send(irc_conn->socket, msg, msg_len, 0);
83 static gchar *get_nickname(const gchar *line, guint len)
85 static gchar result[20];
86 guint i, j = 0;
88 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :df
89 for (i = 0; i < len; i++)
91 if (line[i] == '!' || line[i] == '=' || j >= 19)
92 break;
94 if (line[i] == ':')
95 continue;
97 result[j++] = line[i];
99 result[j] = '\0';
101 return result;
105 static gchar *get_private_message_sender(const gchar *line, guint len)
107 guint i;
109 // :eht16!n=enrico@uvena.de PRIVMSG GeanyTestBot :hi
110 for (i = 0; i < len; i++)
112 if (line[i] != ' ')
113 continue;
115 if (strncmp("PRIVMSG", line + i + 1, 7) == 0)
117 static gchar name[20];
118 g_snprintf(name, sizeof(name), "%s :", config->nickname);
119 // if the receiver of the message is me, then it's a private message and we return the
120 // sender's nickname, otherwise NULL
121 if (strncmp(name, line + i + 9, strlen(config->nickname) + 2) == 0)
122 return get_nickname(line, len);
123 else
124 return NULL;
128 return NULL;
132 static gint get_response(const gchar *line, guint len)
134 static gchar result[4];
135 guint i, j = 0;
136 gboolean in_response = FALSE;
138 // :kornbluth.freenode.net 353 GeanyTestBot @ #eht16 :GeanyTestBot eht16
139 // :kornbluth.freenode.net 366 GeanyTestBot #eht16 :End of /NAMES list.
140 for (i = 0; i < len; i++)
142 // before a response code
143 if (line[i] != ' ' && ! in_response)
144 continue;
146 // after a response code
147 if (line[i] == ' ' && in_response)
149 in_response = FALSE;
150 break;
153 if (line[i] == ' ' )
154 i++; // skip the space
156 result[j++] = line[i];
157 in_response = TRUE;
159 result[j] = '\0';
161 return atoi(result);
165 gchar *irc_get_message(const gchar *line, guint len)
167 static gchar result[1024] = { 0 };
168 guint i, j = 0;
169 gint state = 0; // represents the current part of the whole line, separated by spaces
171 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :df foo: var
172 // -> df foo: var
173 for (i = 0; i < len; i++)
175 if (state < 3)
177 if (line[i] == ' ')
179 state++;
180 i++; // skip the ':'
181 continue;
184 else if (line[i] != '\r' && line[i] != '\n')
186 result[j++] = line[i];
189 result[j] = '\0';
191 return result;
195 static gchar *irc_get_message_with_name(const gchar *line, guint len, const gchar *name)
197 gchar *tmp;
198 gsize name_len;
200 tmp = irc_get_message(line, len);
201 name_len = strlen(name);
203 if (strncmp(tmp, name, name_len) == 0)
204 tmp += name_len;
206 return tmp;
210 // returns a nickname argument given to cmd, it may also be "cmd for nickname", then the "for" is
211 // skipped
212 static gchar *get_argument_target(const gchar *line, guint len, const gchar *cmd)
214 static gchar result[20];
215 gchar *tmp;
216 gchar **parts;
218 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :!beer for me
219 tmp = irc_get_message(line, len);
221 // -> !beer for me
222 parts = g_strsplit(tmp, " ", -1);
223 if (parts == NULL || parts[0] == NULL)
225 g_strfreev(parts);
226 return NULL;
229 // if cmd doesn't match, skip it
230 if (parts[0] != NULL && strcmp(parts[0], cmd) != 0)
232 g_strfreev(parts);
233 return NULL;
236 if (parts[1] == NULL)
238 g_strfreev(parts);
239 return NULL;
242 if (strcmp("for", parts[1]) == 0)
244 if (parts[2] != NULL)
246 if (strcmp(parts[2], "me") == 0)
248 g_strfreev(parts);
249 return NULL; // if we return NULL, the nickname of the caller is used, aka "me"
251 else
252 g_strlcpy(result, parts[2], sizeof(result));
254 else
255 g_strlcpy(result, parts[1], sizeof(result));
257 else if (strcmp(parts[1], "me") == 0)
259 g_strfreev(parts);
260 return NULL; // if we return NULL, the nickname of the caller is used, aka "me"
262 else
264 g_strlcpy(result, parts[1], sizeof(result));
267 g_strfreev(parts);
268 return result;
272 // Parses the line and puts the first argument in arg1, all further arguments are concatenated
273 // in arg2. arg1 and arg2 should be freed when no longer needed.
274 // If arg1 and arg2 were set successfully, TRUE is returned, if any error occurs, FALSE is returned
275 // and arg1 and arg2 are set to NULL.
276 static gboolean get_argument_two(const gchar *line, guint len, const gchar *cmd,
277 gchar **arg1, gchar **arg2)
279 gchar *tmp;
280 gchar **parts;
282 *arg1 = NULL;
283 *arg2 = NULL;
285 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :!learn keyword text to be added
286 tmp = irc_get_message(line, len);
288 // -> !learn keyword text to be added
289 parts = g_strsplit(tmp, " ", 3);
290 if (parts == NULL || parts[0] == NULL)
292 g_strfreev(parts);
293 return FALSE;
296 // if cmd doesn't match, skip it
297 if (parts[0] != NULL && strcmp(parts[0], cmd) != 0)
299 g_strfreev(parts);
300 return FALSE;
303 if (parts[1] == NULL || parts[2] == NULL)
305 g_strfreev(parts);
306 return FALSE;
309 *arg1 = g_strdup(parts[1]);
310 *arg2 = g_strdup(parts[2]);
312 g_strfreev(parts);
314 return TRUE;
318 static gboolean process_line(irc_conn_t *irc_conn, const gchar *line, guint len)
320 static gchar msg[1024];
321 guint msg_len;
322 gint response = get_response(line, len);
323 static gchar tmp_userlist[1024];
324 gchar *priv_sender;
326 // An error occurred, try to quit cleanly and print the error
327 if (response > 400 && response < 503)
329 // ignore Freenode's info messages sent with error code 477
330 // (see http://freenode.net/faq.shtml#freenode-info)
331 if (response != 477 || strstr(line, "[freenode-info]") == NULL)
333 g_print("Error: %s", line);
334 #ifndef DEBUG
335 syslog(LOG_WARNING, "received error: %d (%s)", response, g_strstrip((gchar*) line));
336 #endif
337 main_quit();
338 return FALSE;
341 // retrieve user name list
342 else if (response == 353)
344 gchar *names = irc_get_message(line, len);
345 if (tmp_userlist[0] == '\0')
346 g_strlcpy(tmp_userlist, strchr(names, ':') + 1, sizeof(tmp_userlist));
347 else
348 g_strlcat(tmp_userlist, strchr(names, ':') + 1, sizeof(tmp_userlist));
350 // retrieve end of user name list
351 else if (response == 366)
353 if (tmp_userlist[0] != '\0')
355 set_user_list(tmp_userlist);
356 tmp_userlist[0] = '\0';
359 else if (! irc_conn->connected)
361 // don't do anything else until we got finished connecting (to skip MOTD messages)
363 // PING-PONG
364 else if (strncmp("PING :", line, 6) == 0)
366 msg_len = g_snprintf(msg, sizeof msg, "PONG %s\r\n", line + 6); // 7 = "PING :"
367 debug("PONG: -%s-\n", msg);
368 send(irc_conn->socket, msg, msg_len, 0);
370 // handle private message
371 else if ((priv_sender = get_private_message_sender(line, len)) != NULL)
373 // to be able to send private messages to users, you need to register your bot's
374 // nickname with Nickserv (at least on Freenode)
375 irc_send_private_message(irc_conn, priv_sender, "I don't like private messages!");
377 // !test
378 else if (strstr(line, ":!test") != NULL)
380 irc_send_message(irc_conn, get_nickname(line, len), "I don't like tests!");
382 // !roulette
383 else if (strstr(line, ":!roulette") != NULL)
385 gint32 rand = g_random_int();
386 if (rand % 6 == 0)
388 irc_send_message(irc_conn, NULL, "*bang*");
389 irc_send_message(irc_conn, get_nickname(line, len), "You are dead.");
391 else
392 irc_send_message(irc_conn, NULL, "*click*");
394 // !coffee
395 else if (strstr(line, ":!coffee") != NULL)
397 gchar *arg = get_argument_target(line, len, "!coffee");
399 if (arg == NULL)
400 arg = get_nickname(line, len);
402 irc_send_message(irc_conn, NULL,
403 "A nice sexy waitress brings %s a big cup of coffee!", arg);
405 // !coke
406 else if (strstr(line, ":!coke") != NULL)
408 gchar *arg = get_argument_target(line, len, "!coke");
410 if (arg == NULL)
411 arg = get_nickname(line, len);
413 irc_send_message(irc_conn, NULL,
414 "A nice sexy waitress brings %s a cool bottle of coke!", arg);
416 // !beer
417 else if (strstr(line, ":!beer") != NULL)
419 gchar *arg = get_argument_target(line, len, "!beer");
421 if (arg == NULL)
422 arg = get_nickname(line, len);
424 irc_send_message(irc_conn, NULL,
425 "A nice sexy waitress brings %s a nice bottle of beer!", arg);
427 // !help
428 else if (strstr(line, ":!help") != NULL)
430 irc_send_message(irc_conn, get_nickname(line, len), "please use ?? help");
432 // !learn
433 /// TODO require op privileges for !learn
434 else if (strstr(line, ":!learn") != NULL)
436 gchar *arg1, *arg2;
438 if (get_argument_two(line, len, "!learn", &arg1, &arg2))
440 gint result = help_system_learn(arg1, arg2);
441 gchar *text;
443 switch (result)
445 case 0:
447 text = g_strdup_printf("new keyword \"%s\" was added.", arg1);
448 break;
450 case 1:
452 text = g_strdup_printf("existing keyword \"%s\" was updated.", arg1);
453 break;
455 default:
457 text = g_strdup("an error occurred. Database not updated.");
458 break;
461 irc_send_message(irc_conn, get_nickname(line, len), "%s", text);
463 g_free(text);
464 g_free(arg1);
465 g_free(arg2);
467 else
468 irc_send_message(irc_conn, get_nickname(line, len),
469 "wrong usage of !learn. Use \"?? learn\" for usage information.");
472 // !alias-command
473 else if (strstr(line, ":!alias") != NULL)
475 gchar *arg1, *arg2;
477 if (get_argument_two(line, len, "!alias", &arg1, &arg2))
479 gchar *text;
480 gint result;
482 // detect if arg2 has more than one word by scanning for spaces in
483 // the string
484 if (strchr(arg2, ' '))
486 text = "You gave me more than two arguments for !alias. I can not handle this.";
487 irc_send_message(irc_conn, get_nickname(line, len), "%s", text);
490 else
492 gchar *alias = g_strconcat("@", arg2, NULL);
493 result = help_system_learn(arg1, alias);
494 g_free(alias);
496 switch (result)
498 case 0:
500 text = g_strdup_printf("new alias \"%s\" was added.", arg1);
501 break;
503 case 1:
505 text = g_strdup_printf("existing alias \"%s\" was updated.", arg1);
506 break;
508 default:
510 text = g_strdup("An error occurred. Database not updated.");
511 break;
515 irc_send_message(irc_conn, get_nickname(line, len), "%s", text);
518 g_free(text);
519 g_free(arg1);
520 g_free(arg2);
525 // ?? ...
526 else if (strstr(line, ":?? ") != NULL)
528 help_system_query(line, len);
530 // Hi /me, acts on "hello $nickname" and "hi $nickname", hi and hello are case-insensitive
531 // Thanks /me
532 else if (strstr(line, config->nickname) != NULL)
534 gchar *tmp_msg = irc_get_message(line, len);
535 gchar *tmp_msg2 = irc_get_message_with_name(line, len, config->nickname);
537 if (strncasecmp("hi", tmp_msg, 2) == 0 || strncasecmp("hello", tmp_msg, 5) == 0 ||
538 strcasecmp(", hi", tmp_msg2) == 0 || strcasecmp(", hello", tmp_msg2) == 0)
540 irc_send_message(irc_conn, NULL,
541 "Hi %s. My name is %s and I'm here to offer additional services to you! "
542 "Try \"?? help\" for general information and \"?? vomak\" for information about me.",
543 get_nickname(line, len), config->nickname);
545 else if (strncasecmp("thanks", tmp_msg, 6) == 0 || strncasecmp("thx", tmp_msg, 3) == 0 ||
546 strcasecmp(", thanks", tmp_msg2) == 0 || strcasecmp(", thx", tmp_msg2) == 0)
548 irc_send_message(irc_conn, get_nickname(line, len),
549 "no problem. It was a pleasure to serve you.");
553 return TRUE;
557 static gboolean input_cb(GIOChannel *source, GIOCondition cond, gpointer data)
559 #if 1
560 gchar buf[1024];
561 guint buf_len;
562 irc_conn_t *irc = data;
563 gboolean ret = TRUE;
565 if ((buf_len = socket_fd_gets(irc->socket, buf, sizeof(buf))) != -1)
567 ret = process_line(irc, buf, buf_len);
569 #else
570 gsize buf_len;
571 irc_conn_t *irc = data;
573 if (cond & (G_IO_IN | G_IO_PRI))
575 gchar *buf = NULL;
576 GIOStatus rv;
577 GError *err = NULL;
581 rv = g_io_channel_read_line(source, &buf, &buf_len, NULL, &err);
582 if (buf != NULL)
584 buf_len -= 2;
585 buf[buf_len] = '\0'; // skip trailing \r\n
587 process_line(irc, buf, buf_len);
588 g_free(buf);
590 if (err != NULL)
592 debug("%s: error: %s", __func__, err->message);
593 g_error_free(err);
594 err = NULL;
597 while (rv == G_IO_STATUS_NORMAL || rv == G_IO_STATUS_AGAIN);
598 debug("%s: status %d\n", __func__, rv);
600 #endif
601 return ret;
605 void irc_send_message(irc_conn_t *irc_conn, const gchar *target, const gchar *format, ...)
607 static gchar tmp_msg[1024];
608 static gchar msg[1024];
609 guint msg_len;
610 va_list ap;
612 va_start(ap, format);
613 g_vsnprintf(tmp_msg, sizeof tmp_msg, format, ap);
614 va_end(ap);
616 if (target)
617 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG #%s :%s, %s\r\n", config->channel, target, tmp_msg);
618 else
619 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG #%s :%s\r\n", config->channel, tmp_msg);
621 send(irc_conn->socket, msg, msg_len, 0);
625 void irc_goodbye(irc_conn_t *irc)
627 guint len;
628 gchar msg[256];
630 len = g_strlcpy(msg, "QUIT :Good bye. It was a pleasure to serve you\r\n", sizeof msg);
631 send(irc->socket, msg, len, 0);
635 gint irc_finalize(irc_conn_t *irc_conn)
637 if (irc_conn->socket < 0)
638 return -1;
640 if (irc_conn->lock_tag > 0)
641 g_source_remove(irc_conn->lock_tag);
643 if (irc_conn->read_ioc)
645 g_io_channel_shutdown(irc_conn->read_ioc, TRUE, NULL);
646 g_io_channel_unref(irc_conn->read_ioc);
647 irc_conn->read_ioc = NULL;
649 socket_fd_close(irc_conn->socket);
650 irc_conn->socket = -1;
652 return 0;
656 void irc_connect(irc_conn_t *irc_conn)
658 struct hostent *he;
659 struct sockaddr_in their_addr;
660 gchar msg[256];
661 guint msg_len;
663 TRACE
665 // Connect the socket to the server
666 if ((he = gethostbyname(config->server)) == NULL)
668 perror("gethostbyname");
669 exit(1);
672 if ((irc_conn->socket = socket(PF_INET, SOCK_STREAM, 0)) == -1)
674 perror("socket");
675 exit(1);
678 their_addr.sin_family = PF_INET;
679 their_addr.sin_port = htons(6667);
680 their_addr.sin_addr = *((struct in_addr *)he->h_addr_list[0]);
681 memset(&(their_addr.sin_zero), '\0', 8);
683 if (connect(irc_conn->socket, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1)
685 perror("connect");
686 exit(1);
688 // say who we are
689 msg_len = g_snprintf(msg, sizeof(msg), "USER %s %s %s :%s\r\nNICK %s\r\n",
690 config->username, config->servername, config->servername, config->realname, config->nickname);
691 if (send(irc_conn->socket, msg, msg_len, 0) == -1)
693 perror("send USER");
695 // identify our nick
696 if (NZV(config->nickserv_password))
698 msg_len = g_snprintf(msg, sizeof(msg), "PRIVMSG nickserv :identify %s\r\n", config->nickserv_password);
699 if (send(irc_conn->socket, msg, msg_len, 0) == -1)
701 perror("send NICKSERV");
704 // join the channel
705 g_snprintf(msg, sizeof msg, "JOIN #%s\r\n ", config->channel);
706 if (send(irc_conn->socket, msg, strlen(msg), 0) == -1)
708 perror("send");
711 // input callback, attached to the main loop
712 irc_conn->read_ioc = g_io_channel_unix_new(irc_conn->socket);
713 //~ g_io_channel_set_flags(irc_conn.read_ioc, G_IO_FLAG_NONBLOCK, NULL);
714 g_io_channel_set_encoding(irc_conn->read_ioc, NULL, NULL);
715 irc_conn->lock_tag = g_io_add_watch(irc_conn->read_ioc, G_IO_IN|G_IO_PRI|G_IO_ERR, input_cb, irc_conn);