Implement logging error messages from the server to syslog.
[vomak.git] / irc.c
blob9af92ca076e9983e806f3debb51848eefbd75c3f
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.
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <errno.h>
24 #include <netdb.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <netinet/in.h>
28 #include <sys/socket.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <signal.h>
32 #ifndef DEBUG
33 # include <syslog.h>
34 #endif
36 #include <glib.h>
38 #include "vomak.h"
39 #include "socket.h"
40 #include "irc.h"
43 config_t *config;
47 gboolean irc_query_names(gpointer data)
49 irc_conn_t *irc = data;
50 static gchar msg[1024];
51 guint msg_len;
53 TRACE
55 msg_len = g_snprintf(msg, sizeof msg, "NAMES #%s\r\n", config->channel);
56 send(irc->socket, msg, msg_len, 0);
58 return TRUE;
62 static void irc_send_private_message(irc_conn_t *irc_conn, const gchar *target, const gchar *format, ...)
64 static gchar tmp_msg[1024];
65 static gchar msg[1024];
66 guint msg_len;
67 va_list ap;
69 if (target == NULL)
70 return;
72 va_start(ap, format);
73 g_vsnprintf(tmp_msg, sizeof tmp_msg, format, ap);
74 va_end(ap);
76 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG %s :%s, %s\r\n", target, target, tmp_msg);
77 send(irc_conn->socket, msg, msg_len, 0);
81 static gchar *get_nickname(const gchar *line, guint len)
83 static gchar result[20];
84 guint i, j = 0;
86 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :df
87 for (i = 0; i < len; i++)
89 if (line[i] == '!' || line[i] == '=' || j >= 19)
90 break;
92 if (line[i] == ':')
93 continue;
95 result[j++] = line[i];
97 result[j] = '\0';
99 return result;
103 static gchar *get_private_message_sender(const gchar *line, guint len)
105 guint i;
107 // :eht16!n=enrico@uvena.de PRIVMSG GeanyTestBot :hi
108 for (i = 0; i < len; i++)
110 if (line[i] != ' ')
111 continue;
113 if (strncmp("PRIVMSG", line + i + 1, 7) == 0)
115 static gchar name[20];
116 g_snprintf(name, sizeof(name), "%s :", config->nickname);
117 // if the receiver of the message is me, then it's a private message and we return the
118 // sender's nickname, otherwise NULL
119 if (strncmp(name, line + i + 9, strlen(config->nickname) + 2) == 0)
120 return get_nickname(line, len);
121 else
122 return NULL;
126 return NULL;
130 static gint get_response(const gchar *line, guint len)
132 static gchar result[4];
133 guint i, j = 0;
134 gboolean in_response = FALSE;
136 // :kornbluth.freenode.net 353 GeanyTestBot @ #eht16 :GeanyTestBot eht16
137 // :kornbluth.freenode.net 366 GeanyTestBot #eht16 :End of /NAMES list.
138 for (i = 0; i < len; i++)
140 // before a response code
141 if (line[i] != ' ' && ! in_response)
142 continue;
144 // after a response code
145 if (line[i] == ' ' && in_response)
147 in_response = FALSE;
148 break;
151 if (line[i] == ' ' )
152 i++; // skip the space
154 result[j++] = line[i];
155 in_response = TRUE;
157 result[j] = '\0';
159 return atoi(result);
163 gchar *irc_get_message(const gchar *line, guint len)
165 static gchar result[1024] = { 0 };
166 guint i, j = 0;
167 gint state = 0; // represents the current part of the whole line, separated by spaces
169 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :df foo: var
170 // -> df foo: var
171 for (i = 0; i < len; i++)
173 if (state < 3)
175 if (line[i] == ' ')
177 state++;
178 i++; // skip the ':'
179 continue;
182 else if (line[i] != '\r' && line[i] != '\n')
184 result[j++] = line[i];
187 result[j] = '\0';
189 return result;
193 static gchar *irc_get_message_with_name(const gchar *line, guint len, const gchar *name)
195 gchar *tmp;
196 gsize name_len;
198 tmp = irc_get_message(line, len);
199 name_len = strlen(name);
201 if (strncmp(tmp, name, name_len) == 0)
202 tmp += name_len;
204 return tmp;
208 // returns a nickname argument given to cmd, it may also be "cmd for nickname", then the "for" is
209 // skipped
210 static gchar *get_argument_target(const gchar *line, guint len, const gchar *cmd)
212 static gchar result[20];
213 gchar *tmp;
214 gchar **parts;
216 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :!beer for me
217 tmp = irc_get_message(line, len);
219 // -> !beer for me
220 parts = g_strsplit(tmp, " ", -1);
221 if (parts == NULL || parts[0] == NULL)
223 g_strfreev(parts);
224 return NULL;
227 // if cmd doesn't match, skip it
228 if (parts[0] != NULL && strcmp(parts[0], cmd) != 0)
230 g_strfreev(parts);
231 return NULL;
234 if (parts[1] == NULL)
236 g_strfreev(parts);
237 return NULL;
240 if (strcmp("for", parts[1]) == 0)
242 if (parts[2] != NULL)
244 if (strcmp(parts[2], "me") == 0)
246 g_strfreev(parts);
247 return NULL; // if we return NULL, the nickname of the caller is used, aka "me"
249 else
250 g_strlcpy(result, parts[2], sizeof(result));
252 else
253 g_strlcpy(result, parts[1], sizeof(result));
255 else if (strcmp(parts[1], "me") == 0)
257 g_strfreev(parts);
258 return NULL; // if we return NULL, the nickname of the caller is used, aka "me"
260 else
262 g_strlcpy(result, parts[1], sizeof(result));
265 g_strfreev(parts);
266 return result;
270 // Parses the line and puts the first argument in arg1, all further arguments are concatenated
271 // in arg2. arg1 and arg2 should be freed when no longer needed.
272 // If arg1 and arg2 were set successfully, TRUE is returned, if any error occurs, FALSE is returned
273 // and arg1 and arg2 are set to NULL.
274 static gboolean get_argument_two(const gchar *line, guint len, const gchar *cmd,
275 gchar **arg1, gchar **arg2)
277 gchar *tmp;
278 gchar **parts;
280 *arg1 = NULL;
281 *arg2 = NULL;
283 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :!learn keyword text to be added
284 tmp = irc_get_message(line, len);
286 // -> !learn keyword text to be added
287 parts = g_strsplit(tmp, " ", 3);
288 if (parts == NULL || parts[0] == NULL)
290 g_strfreev(parts);
291 return FALSE;
294 // if cmd doesn't match, skip it
295 if (parts[0] != NULL && strcmp(parts[0], cmd) != 0)
297 g_strfreev(parts);
298 return FALSE;
301 if (parts[1] == NULL || parts[2] == NULL)
303 g_strfreev(parts);
304 return FALSE;
307 *arg1 = g_strdup(parts[1]);
308 *arg2 = g_strdup(parts[2]);
310 g_strfreev(parts);
312 return TRUE;
316 static gboolean process_line(irc_conn_t *irc_conn, const gchar *line, guint len)
318 static gchar msg[1024];
319 guint msg_len;
320 gint response = get_response(line, len);
321 static gchar tmp_userlist[1024];
322 gchar *priv_sender;
324 // An error occurred, try to quit cleanly and print the error
325 if (response > 400 && response < 503)
327 // ignore Freenode's info messages sent with error code 477
328 // (see http://freenode.net/faq.shtml#freenode-info)
329 if (response != 477 || strstr(line, "[freenode-info]") == NULL)
331 g_print("Error: %s", line);
332 #ifndef DEBUG
333 syslog(LOG_WARNING, "received error: %d (%s)", response, g_strstrip((gchar*) line));
334 #endif
335 main_quit();
336 return FALSE;
339 // retrieve user name list
340 else if (response == 353)
342 gchar *names = irc_get_message(line, len);
343 if (tmp_userlist[0] == '\0')
344 g_strlcpy(tmp_userlist, strchr(names, ':') + 1, sizeof(tmp_userlist));
345 else
346 g_strlcat(tmp_userlist, strchr(names, ':') + 1, sizeof(tmp_userlist));
348 // retrieve end of user name list
349 else if (response == 366)
351 if (tmp_userlist[0] != '\0')
353 set_user_list(tmp_userlist);
354 tmp_userlist[0] = '\0';
357 else if (! irc_conn->connected)
359 // don't do anything else until we got finished connecting (to skip MOTD messages)
361 // PING-PONG
362 else if (strncmp("PING :", line, 6) == 0)
364 msg_len = g_snprintf(msg, sizeof msg, "PONG %s\r\n", line + 6); // 7 = "PING :"
365 debug("PONG: -%s-\n", msg);
366 send(irc_conn->socket, msg, msg_len, 0);
368 // handle private message
369 else if ((priv_sender = get_private_message_sender(line, len)) != NULL)
371 // to be able to send private messages to users, you need to register your bot's
372 // nickname with Nickserv (at least on Freenode)
373 irc_send_private_message(irc_conn, priv_sender, "I don't like private messages!");
375 // !test
376 else if (strstr(line, ":!test") != NULL)
378 irc_send_message(irc_conn, get_nickname(line, len), "I don't like tests!");
380 // !roulette
381 else if (strstr(line, ":!roulette") != NULL)
383 gint32 rand = g_random_int();
384 if (rand % 6 == 0)
386 irc_send_message(irc_conn, NULL, "*bang*");
387 irc_send_message(irc_conn, get_nickname(line, len), "You are dead.");
389 else
390 irc_send_message(irc_conn, NULL, "*click*");
392 // !coffee
393 else if (strstr(line, ":!coffee") != NULL)
395 gchar *arg = get_argument_target(line, len, "!coffee");
397 if (arg == NULL)
398 arg = get_nickname(line, len);
400 irc_send_message(irc_conn, NULL,
401 "A nice sexy waitress brings %s a big cup of coffee!", arg);
403 // !beer
404 else if (strstr(line, ":!beer") != NULL)
406 gchar *arg = get_argument_target(line, len, "!beer");
408 if (arg == NULL)
409 arg = get_nickname(line, len);
411 irc_send_message(irc_conn, NULL,
412 "A nice sexy waitress brings %s a nice bottle of beer!", arg);
414 // !help
415 else if (strstr(line, ":!help") != NULL)
417 irc_send_message(irc_conn, get_nickname(line, len), "please use ?? help");
419 // !learn
420 /// TODO require op privileges for !learn
421 else if (strstr(line, ":!learn") != NULL)
423 gchar *arg1, *arg2;
425 if (get_argument_two(line, len, "!learn", &arg1, &arg2))
427 gint result = help_system_learn(arg1, arg2);
428 gchar *text;
430 switch (result)
432 case 0:
434 text = g_strdup_printf("new keyword \"%s\" was added.", arg1);
435 break;
437 case 1:
439 text = g_strdup_printf("existing keyword \"%s\" was updated.", arg1);
440 break;
442 default:
444 text = g_strdup("an error occurred. Database not updated.");
445 break;
448 irc_send_message(irc_conn, get_nickname(line, len), "%s", text);
450 g_free(text);
451 g_free(arg1);
452 g_free(arg2);
454 else
455 irc_send_message(irc_conn, get_nickname(line, len),
456 "wrong usage of !learn. Use \"?? learn\" for usage information.");
458 // ?? ...
459 else if (strstr(line, ":?? ") != NULL)
461 help_system_query(line, len);
463 // Hi /me, acts on "hello $nickname" and "hi $nickname", hi and hello are case-insensitive
464 // Thanks /me
465 else if (strstr(line, config->nickname) != NULL)
467 gchar *tmp_msg = irc_get_message(line, len);
468 gchar *tmp_msg2 = irc_get_message_with_name(line, len, config->nickname);
470 if (strncasecmp("hi", tmp_msg, 2) == 0 || strncasecmp("hello", tmp_msg, 5) == 0 ||
471 strcasecmp(", hi", tmp_msg2) == 0 || strcasecmp(", hello", tmp_msg2) == 0)
473 irc_send_message(irc_conn, NULL,
474 "Hi %s. My name is %s and I'm here to offer additional services to you! "
475 "Try \"?? help\" for general information and \"?? vomak\" for information about me.",
476 get_nickname(line, len), config->nickname);
478 else if (strncasecmp("thanks", tmp_msg, 6) == 0 || strncasecmp("thx", tmp_msg, 3) == 0 ||
479 strcasecmp(", thanks", tmp_msg2) == 0 || strcasecmp(", thx", tmp_msg2) == 0)
481 irc_send_message(irc_conn, get_nickname(line, len),
482 "no problem. It was a pleasure to serve you.");
486 return TRUE;
490 static gboolean input_cb(GIOChannel *source, GIOCondition cond, gpointer data)
492 #if 1
493 gchar buf[1024];
494 guint buf_len;
495 irc_conn_t *irc = data;
496 gboolean ret = TRUE;
498 if ((buf_len = socket_fd_gets(irc->socket, buf, sizeof(buf))) != -1)
500 ret = process_line(irc, buf, buf_len);
502 #else
503 gsize buf_len;
504 irc_conn_t *irc = data;
506 if (cond & (G_IO_IN | G_IO_PRI))
508 gchar *buf = NULL;
509 GIOStatus rv;
510 GError *err = NULL;
514 rv = g_io_channel_read_line(source, &buf, &buf_len, NULL, &err);
515 if (buf != NULL)
517 buf_len -= 2;
518 buf[buf_len] = '\0'; // skip trailing \r\n
520 process_line(irc, buf, buf_len);
521 g_free(buf);
523 if (err != NULL)
525 debug("%s: error: %s", __func__, err->message);
526 g_error_free(err);
527 err = NULL;
530 while (rv == G_IO_STATUS_NORMAL || rv == G_IO_STATUS_AGAIN);
531 debug("%s: status %d\n", __func__, rv);
533 #endif
534 return ret;
538 void irc_send_message(irc_conn_t *irc_conn, const gchar *target, const gchar *format, ...)
540 static gchar tmp_msg[1024];
541 static gchar msg[1024];
542 guint msg_len;
543 va_list ap;
545 va_start(ap, format);
546 g_vsnprintf(tmp_msg, sizeof tmp_msg, format, ap);
547 va_end(ap);
549 if (target)
550 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG #%s :%s, %s\r\n", config->channel, target, tmp_msg);
551 else
552 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG #%s :%s\r\n", config->channel, tmp_msg);
554 send(irc_conn->socket, msg, msg_len, 0);
558 void irc_goodbye(irc_conn_t *irc)
560 guint len;
561 gchar msg[256];
563 len = g_strlcpy(msg, "QUIT :Good bye. It was a pleasure to serve you\r\n", sizeof msg);
564 send(irc->socket, msg, len, 0);
568 gint irc_finalize(irc_conn_t *irc_conn)
570 if (irc_conn->socket < 0)
571 return -1;
573 if (irc_conn->lock_tag > 0)
574 g_source_remove(irc_conn->lock_tag);
576 if (irc_conn->read_ioc)
578 g_io_channel_shutdown(irc_conn->read_ioc, TRUE, NULL);
579 g_io_channel_unref(irc_conn->read_ioc);
580 irc_conn->read_ioc = NULL;
582 socket_fd_close(irc_conn->socket);
583 irc_conn->socket = -1;
585 return 0;
589 void irc_connect(irc_conn_t *irc_conn)
591 struct hostent *he;
592 struct sockaddr_in their_addr;
593 gchar msg[256];
594 guint msg_len;
596 TRACE
598 // Connect the socket to the server
599 if ((he = gethostbyname(config->server)) == NULL)
601 perror("gethostbyname");
602 exit(1);
605 if ((irc_conn->socket = socket(PF_INET, SOCK_STREAM, 0)) == -1)
607 perror("socket");
608 exit(1);
611 their_addr.sin_family = PF_INET;
612 their_addr.sin_port = htons(6667);
613 their_addr.sin_addr = *((struct in_addr *)he->h_addr);
614 memset(&(their_addr.sin_zero), '\0', 8);
616 if (connect(irc_conn->socket, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1)
618 perror("connect");
619 exit(1);
621 // say who we are
622 msg_len = g_snprintf(msg, sizeof(msg), "USER %s %s %s :%s\r\nNICK %s\r\n",
623 config->username, config->servername, config->servername, config->realname, config->nickname);
624 if (send(irc_conn->socket, msg, msg_len, 0) == -1)
626 perror("send USER");
628 // identify our nick
629 if (NZV(config->nickserv_password))
631 msg_len = g_snprintf(msg, sizeof(msg), "PRIVMSG nickserv :identify %s\r\n", config->nickserv_password);
632 if (send(irc_conn->socket, msg, msg_len, 0) == -1)
634 perror("send NICKSERV");
637 // join the channel
638 g_snprintf(msg, sizeof msg, "JOIN #%s\r\n ", config->channel);
639 if (send(irc_conn->socket, msg, strlen(msg), 0) == -1)
641 perror("send");
644 // input callback, attached to the main loop
645 irc_conn->read_ioc = g_io_channel_unix_new(irc_conn->socket);
646 //~ g_io_channel_set_flags(irc_conn.read_ioc, G_IO_FLAG_NONBLOCK, NULL);
647 g_io_channel_set_encoding(irc_conn->read_ioc, NULL, NULL);
648 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);