add small explanation for the !alias-command to the database.sample
[vomak.git] / irc.c
blob391e2af2f4136deb8995f97f30dd016dcfa0b1d8
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>
5 * Copyright 2008 Dominic Hopf
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; version 2 of the License.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #define _GNU_SOURCE
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <errno.h>
26 #include <netdb.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <netinet/in.h>
30 #include <sys/socket.h>
31 #include <unistd.h>
32 #include <string.h>
33 #include <strings.h>
34 #include <signal.h>
35 #ifndef DEBUG
36 # include <syslog.h>
37 #endif
39 #include <glib.h>
41 #include "vomak.h"
42 #include "socket.h"
43 #include "irc.h"
46 config_t *config;
50 gboolean irc_query_names(gpointer data)
52 irc_conn_t *irc = data;
53 static gchar msg[1024];
54 guint msg_len;
56 TRACE
58 msg_len = g_snprintf(msg, sizeof msg, "NAMES #%s\r\n", config->channel);
59 send(irc->socket, msg, msg_len, 0);
61 return TRUE;
65 static void irc_send_private_message(irc_conn_t *irc_conn, const gchar *target, const gchar *format, ...)
67 static gchar tmp_msg[1024];
68 static gchar msg[1024];
69 guint msg_len;
70 va_list ap;
72 if (target == NULL)
73 return;
75 va_start(ap, format);
76 g_vsnprintf(tmp_msg, sizeof tmp_msg, format, ap);
77 va_end(ap);
79 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG %s :%s, %s\r\n", target, target, tmp_msg);
80 send(irc_conn->socket, msg, msg_len, 0);
84 static gchar *get_nickname(const gchar *line, guint len)
86 static gchar result[20];
87 guint i, j = 0;
89 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :df
90 for (i = 0; i < len; i++)
92 if (line[i] == '!' || line[i] == '=' || j >= 19)
93 break;
95 if (line[i] == ':')
96 continue;
98 result[j++] = line[i];
100 result[j] = '\0';
102 return result;
106 static gchar *get_private_message_sender(const gchar *line, guint len)
108 guint i;
110 // :eht16!n=enrico@uvena.de PRIVMSG GeanyTestBot :hi
111 for (i = 0; i < len; i++)
113 if (line[i] != ' ')
114 continue;
116 if (strncmp("PRIVMSG", line + i + 1, 7) == 0)
118 static gchar name[20];
119 g_snprintf(name, sizeof(name), "%s :", config->nickname);
120 // if the receiver of the message is me, then it's a private message and we return the
121 // sender's nickname, otherwise NULL
122 if (strncmp(name, line + i + 9, strlen(config->nickname) + 2) == 0)
123 return get_nickname(line, len);
124 else
125 return NULL;
129 return NULL;
133 static gint get_response(const gchar *line, guint len)
135 static gchar result[4];
136 guint i, j = 0;
137 gboolean in_response = FALSE;
139 // :kornbluth.freenode.net 353 GeanyTestBot @ #eht16 :GeanyTestBot eht16
140 // :kornbluth.freenode.net 366 GeanyTestBot #eht16 :End of /NAMES list.
141 for (i = 0; i < len; i++)
143 // before a response code
144 if (line[i] != ' ' && ! in_response)
145 continue;
147 // after a response code
148 if (line[i] == ' ' && in_response)
150 in_response = FALSE;
151 break;
154 if (line[i] == ' ' )
155 i++; // skip the space
157 result[j++] = line[i];
158 in_response = TRUE;
160 result[j] = '\0';
162 return atoi(result);
166 gchar *irc_get_message(const gchar *line, guint len)
168 static gchar result[1024] = { 0 };
169 guint i, j = 0;
170 gint state = 0; // represents the current part of the whole line, separated by spaces
172 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :df foo: var
173 // -> df foo: var
174 for (i = 0; i < len; i++)
176 if (state < 3)
178 if (line[i] == ' ')
180 state++;
181 i++; // skip the ':'
182 continue;
185 else if (line[i] != '\r' && line[i] != '\n')
187 result[j++] = line[i];
190 result[j] = '\0';
192 return result;
196 static gchar *irc_get_message_with_name(const gchar *line, guint len, const gchar *name)
198 gchar *tmp;
199 gsize name_len;
201 tmp = irc_get_message(line, len);
202 name_len = strlen(name);
204 if (strncmp(tmp, name, name_len) == 0)
205 tmp += name_len;
207 return tmp;
211 // returns a nickname argument given to cmd, it may also be "cmd for nickname", then the "for" is
212 // skipped
213 static gchar *get_argument_target(const gchar *line, guint len, const gchar *cmd)
215 static gchar result[20];
216 gchar *tmp;
217 gchar **parts;
219 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :!beer for me
220 tmp = irc_get_message(line, len);
222 // -> !beer for me
223 parts = g_strsplit(tmp, " ", -1);
224 if (parts == NULL || parts[0] == NULL)
226 g_strfreev(parts);
227 return NULL;
230 // if cmd doesn't match, skip it
231 if (parts[0] != NULL && strcmp(parts[0], cmd) != 0)
233 g_strfreev(parts);
234 return NULL;
237 if (parts[1] == NULL)
239 g_strfreev(parts);
240 return NULL;
243 if (strcmp("for", parts[1]) == 0)
245 if (parts[2] != NULL)
247 if (strcmp(parts[2], "me") == 0)
249 g_strfreev(parts);
250 return NULL; // if we return NULL, the nickname of the caller is used, aka "me"
252 else
253 g_strlcpy(result, parts[2], sizeof(result));
255 else
256 g_strlcpy(result, parts[1], sizeof(result));
258 else if (strcmp(parts[1], "me") == 0)
260 g_strfreev(parts);
261 return NULL; // if we return NULL, the nickname of the caller is used, aka "me"
263 else
265 g_strlcpy(result, parts[1], sizeof(result));
268 g_strfreev(parts);
269 return result;
273 // Parses the line and puts the first argument in arg1, all further arguments are concatenated
274 // in arg2. arg1 and arg2 should be freed when no longer needed.
275 // If arg1 and arg2 were set successfully, TRUE is returned, if any error occurs, FALSE is returned
276 // and arg1 and arg2 are set to NULL.
277 static gboolean get_argument_two(const gchar *line, guint len, const gchar *cmd,
278 gchar **arg1, gchar **arg2)
280 gchar *tmp;
281 gchar **parts;
283 *arg1 = NULL;
284 *arg2 = NULL;
286 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :!learn keyword text to be added
287 tmp = irc_get_message(line, len);
289 // -> !learn keyword text to be added
290 parts = g_strsplit(tmp, " ", 3);
291 if (parts == NULL || parts[0] == NULL)
293 g_strfreev(parts);
294 return FALSE;
297 // if cmd doesn't match, skip it
298 if (parts[0] != NULL && strcmp(parts[0], cmd) != 0)
300 g_strfreev(parts);
301 return FALSE;
304 if (parts[1] == NULL || parts[2] == NULL)
306 g_strfreev(parts);
307 return FALSE;
310 *arg1 = g_strdup(parts[1]);
311 *arg2 = g_strdup(parts[2]);
313 g_strfreev(parts);
315 return TRUE;
319 static gboolean process_line(irc_conn_t *irc_conn, const gchar *line, guint len)
321 static gchar msg[1024];
322 guint msg_len;
323 gint response = get_response(line, len);
324 static gchar tmp_userlist[1024];
325 gchar *priv_sender;
327 // An error occurred, try to quit cleanly and print the error
328 if (response > 400 && response < 503)
330 // ignore Freenode's info messages sent with error code 477
331 // (see http://freenode.net/faq.shtml#freenode-info)
332 if (response != 477 || strstr(line, "[freenode-info]") == NULL)
334 g_print("Error: %s", line);
335 #ifndef DEBUG
336 syslog(LOG_WARNING, "received error: %d (%s)", response, g_strstrip((gchar*) line));
337 #endif
338 main_quit();
339 return FALSE;
342 // retrieve user name list
343 else if (response == 353)
345 gchar *names = irc_get_message(line, len);
346 if (tmp_userlist[0] == '\0')
347 g_strlcpy(tmp_userlist, strchr(names, ':') + 1, sizeof(tmp_userlist));
348 else
349 g_strlcat(tmp_userlist, strchr(names, ':') + 1, sizeof(tmp_userlist));
351 // retrieve end of user name list
352 else if (response == 366)
354 if (tmp_userlist[0] != '\0')
356 set_user_list(tmp_userlist);
357 tmp_userlist[0] = '\0';
360 else if (! irc_conn->connected)
362 // don't do anything else until we got finished connecting (to skip MOTD messages)
364 // PING-PONG
365 else if (strncmp("PING :", line, 6) == 0)
367 msg_len = g_snprintf(msg, sizeof msg, "PONG %s\r\n", line + 6); // 7 = "PING :"
368 debug("PONG: -%s-\n", msg);
369 send(irc_conn->socket, msg, msg_len, 0);
371 // handle private message
372 else if ((priv_sender = get_private_message_sender(line, len)) != NULL)
374 // to be able to send private messages to users, you need to register your bot's
375 // nickname with Nickserv (at least on Freenode)
376 irc_send_private_message(irc_conn, priv_sender, "I don't like private messages!");
378 // !test
379 else if (strstr(line, ":!test") != NULL)
381 irc_send_message(irc_conn, get_nickname(line, len), "I don't like tests!");
383 // !roulette
384 else if (strstr(line, ":!roulette") != NULL)
386 gint32 rand = g_random_int();
387 if (rand % 6 == 0)
389 irc_send_message(irc_conn, NULL, "*bang*");
390 irc_send_message(irc_conn, get_nickname(line, len), "You are dead.");
392 else
393 irc_send_message(irc_conn, NULL, "*click*");
395 // !coffee
396 else if (strstr(line, ":!coffee") != NULL)
398 gchar *arg = get_argument_target(line, len, "!coffee");
400 if (arg == NULL)
401 arg = get_nickname(line, len);
403 irc_send_message(irc_conn, NULL,
404 "A nice sexy waitress brings %s a big cup of coffee!", arg);
406 // !coke
407 else if (strstr(line, ":!coke") != NULL)
409 gchar *arg = get_argument_target(line, len, "!coke");
411 if (arg == NULL)
412 arg = get_nickname(line, len);
414 irc_send_message(irc_conn, NULL,
415 "A nice sexy waitress brings %s a cool bottle of coke!", arg);
417 // !beer
418 else if (strstr(line, ":!beer") != NULL)
420 gchar *arg = get_argument_target(line, len, "!beer");
422 if (arg == NULL)
423 arg = get_nickname(line, len);
425 irc_send_message(irc_conn, NULL,
426 "A nice sexy waitress brings %s a nice bottle of beer!", arg);
428 // !help
429 else if (strstr(line, ":!help") != NULL)
431 irc_send_message(irc_conn, get_nickname(line, len), "please use ?? help");
433 // !learn
434 /// TODO require op privileges for !learn
435 else if (strstr(line, ":!learn") != NULL)
437 gchar *arg1, *arg2;
439 if (get_argument_two(line, len, "!learn", &arg1, &arg2))
441 gint result = help_system_learn(arg1, arg2);
442 gchar *text;
444 switch (result)
446 case 0:
448 text = g_strdup_printf("new keyword \"%s\" was added.", arg1);
449 break;
451 case 1:
453 text = g_strdup_printf("existing keyword \"%s\" was updated.", arg1);
454 break;
456 default:
458 text = g_strdup("an error occurred. Database not updated.");
459 break;
462 irc_send_message(irc_conn, get_nickname(line, len), "%s", text);
464 g_free(text);
465 g_free(arg1);
466 g_free(arg2);
468 else
469 irc_send_message(irc_conn, get_nickname(line, len),
470 "wrong usage of !learn. Use \"?? learn\" for usage information.");
473 // !alias-command
474 else if (strstr(line, ":!alias") != NULL)
476 gchar *arg1, *arg2;
478 if (get_argument_two(line, len, "!alias", &arg1, &arg2))
480 gchar *text;
481 gint result;
483 // detect if arg2 has more than one word by scanning for spaces in
484 // the string
485 if (strchr(arg2, ' '))
487 text = "You gave me more than two arguments for !alias. I can not handle this.";
488 irc_send_message(irc_conn, get_nickname(line, len), "%s", text);
491 else
493 gchar *alias = g_strconcat("@", arg2, NULL);
494 result = help_system_learn(arg1, alias);
495 g_free(alias);
497 switch (result)
499 case 0:
501 text = g_strdup_printf("new alias \"%s\" was added.", arg1);
502 break;
504 case 1:
506 text = g_strdup_printf("existing alias \"%s\" was updated.", arg1);
507 break;
509 default:
511 text = g_strdup("An error occurred. Database not updated.");
512 break;
516 irc_send_message(irc_conn, get_nickname(line, len), "%s", text);
519 g_free(text);
520 g_free(arg1);
521 g_free(arg2);
526 // ?? ...
527 else if (strstr(line, ":?? ") != NULL)
529 help_system_query(line, len);
531 // Hi /me, acts on "hello $nickname" and "hi $nickname", hi and hello are case-insensitive
532 // Thanks /me
533 else if (strstr(line, config->nickname) != NULL)
535 gchar *tmp_msg = irc_get_message(line, len);
536 gchar *tmp_msg2 = irc_get_message_with_name(line, len, config->nickname);
538 if (strncasecmp("hi", tmp_msg, 2) == 0 || strncasecmp("hello", tmp_msg, 5) == 0 ||
539 strcasecmp(", hi", tmp_msg2) == 0 || strcasecmp(", hello", tmp_msg2) == 0)
541 irc_send_message(irc_conn, NULL,
542 "Hi %s. My name is %s and I'm here to offer additional services to you! "
543 "Try \"?? help\" for general information and \"?? vomak\" for information about me.",
544 get_nickname(line, len), config->nickname);
546 else if (strncasecmp("thanks", tmp_msg, 6) == 0 || strncasecmp("thx", tmp_msg, 3) == 0 ||
547 strcasecmp(", thanks", tmp_msg2) == 0 || strcasecmp(", thx", tmp_msg2) == 0)
549 irc_send_message(irc_conn, get_nickname(line, len),
550 "no problem. It was a pleasure to serve you.");
554 return TRUE;
558 static gboolean input_cb(GIOChannel *source, GIOCondition cond, gpointer data)
560 #if 1
561 gchar buf[1024];
562 guint buf_len;
563 irc_conn_t *irc = data;
564 gboolean ret = TRUE;
566 if ((buf_len = socket_fd_gets(irc->socket, buf, sizeof(buf))) != -1)
568 ret = process_line(irc, buf, buf_len);
570 #else
571 gsize buf_len;
572 irc_conn_t *irc = data;
574 if (cond & (G_IO_IN | G_IO_PRI))
576 gchar *buf = NULL;
577 GIOStatus rv;
578 GError *err = NULL;
582 rv = g_io_channel_read_line(source, &buf, &buf_len, NULL, &err);
583 if (buf != NULL)
585 buf_len -= 2;
586 buf[buf_len] = '\0'; // skip trailing \r\n
588 process_line(irc, buf, buf_len);
589 g_free(buf);
591 if (err != NULL)
593 debug("%s: error: %s", __func__, err->message);
594 g_error_free(err);
595 err = NULL;
598 while (rv == G_IO_STATUS_NORMAL || rv == G_IO_STATUS_AGAIN);
599 debug("%s: status %d\n", __func__, rv);
601 #endif
602 return ret;
606 void irc_send_message(irc_conn_t *irc_conn, const gchar *target, const gchar *format, ...)
608 static gchar tmp_msg[1024];
609 static gchar msg[1024];
610 guint msg_len;
611 va_list ap;
613 va_start(ap, format);
614 g_vsnprintf(tmp_msg, sizeof tmp_msg, format, ap);
615 va_end(ap);
617 if (target)
618 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG #%s :%s, %s\r\n", config->channel, target, tmp_msg);
619 else
620 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG #%s :%s\r\n", config->channel, tmp_msg);
622 send(irc_conn->socket, msg, msg_len, 0);
626 void irc_goodbye(irc_conn_t *irc)
628 guint len;
629 gchar msg[256];
631 len = g_strlcpy(msg, "QUIT :Good bye. It was a pleasure to serve you\r\n", sizeof msg);
632 send(irc->socket, msg, len, 0);
636 gint irc_finalize(irc_conn_t *irc_conn)
638 if (irc_conn->socket < 0)
639 return -1;
641 if (irc_conn->lock_tag > 0)
642 g_source_remove(irc_conn->lock_tag);
644 if (irc_conn->read_ioc)
646 g_io_channel_shutdown(irc_conn->read_ioc, TRUE, NULL);
647 g_io_channel_unref(irc_conn->read_ioc);
648 irc_conn->read_ioc = NULL;
650 socket_fd_close(irc_conn->socket);
651 irc_conn->socket = -1;
653 return 0;
657 void irc_connect(irc_conn_t *irc_conn)
659 struct hostent *he;
660 struct sockaddr_in their_addr;
661 gchar msg[256];
662 guint msg_len;
664 TRACE
666 // Connect the socket to the server
667 if ((he = gethostbyname(config->server)) == NULL)
669 perror("gethostbyname");
670 exit(1);
673 if ((irc_conn->socket = socket(PF_INET, SOCK_STREAM, 0)) == -1)
675 perror("socket");
676 exit(1);
679 their_addr.sin_family = PF_INET;
680 their_addr.sin_port = htons(6667);
681 their_addr.sin_addr = *((struct in_addr *)he->h_addr_list[0]);
682 memset(&(their_addr.sin_zero), '\0', 8);
684 if (connect(irc_conn->socket, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1)
686 perror("connect");
687 exit(1);
689 // say who we are
690 msg_len = g_snprintf(msg, sizeof(msg), "USER %s %s %s :%s\r\nNICK %s\r\n",
691 config->username, config->servername, config->servername, config->realname, config->nickname);
692 if (send(irc_conn->socket, msg, msg_len, 0) == -1)
694 perror("send USER");
696 // identify our nick
697 if (NZV(config->nickserv_password))
699 msg_len = g_snprintf(msg, sizeof(msg), "PRIVMSG nickserv :identify %s\r\n", config->nickserv_password);
700 if (send(irc_conn->socket, msg, msg_len, 0) == -1)
702 perror("send NICKSERV");
705 // join the channel
706 g_snprintf(msg, sizeof msg, "JOIN #%s\r\n ", config->channel);
707 if (send(irc_conn->socket, msg, strlen(msg), 0) == -1)
709 perror("send");
712 // input callback, attached to the main loop
713 irc_conn->read_ioc = g_io_channel_unix_new(irc_conn->socket);
714 //~ g_io_channel_set_flags(irc_conn.read_ioc, G_IO_FLAG_NONBLOCK, NULL);
715 g_io_channel_set_encoding(irc_conn->read_ioc, NULL, NULL);
716 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);