Make the bot also respond on message like "botname, [hi|thanks]".
[vomak.git] / irc.c
blobd3dd830d6eb8898438c8033464fdbf5786d27185
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>
33 #include <glib.h>
35 #include "vomak.h"
36 #include "socket.h"
37 #include "irc.h"
40 config_t *config;
44 gboolean irc_query_names(gpointer data)
46 irc_conn_t *irc = data;
47 static gchar msg[1024];
48 guint msg_len;
50 TRACE
52 msg_len = g_snprintf(msg, sizeof msg, "NAMES #%s\r\n", config->channel);
53 send(irc->socket, msg, msg_len, 0);
55 return TRUE;
59 static gchar *get_nickname(const gchar *line, guint len)
61 static gchar result[20];
62 guint i, j = 0;
64 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :df
65 for (i = 0; i < len; i++)
67 if (line[i] == '!' || line[i] == '=' || j >= 19)
68 break;
70 if (line[i] == ':')
71 continue;
73 result[j++] = line[i];
75 result[j] = '\0';
77 return result;
81 static gint get_response(const gchar *line, guint len)
83 static gchar result[4];
84 guint i, j = 0;
85 gboolean in_response = FALSE;
87 // :kornbluth.freenode.net 353 GeanyTestBot @ #eht16 :GeanyTestBot eht16
88 // :kornbluth.freenode.net 366 GeanyTestBot #eht16 :End of /NAMES list.
89 for (i = 0; i < len; i++)
91 // before a response code
92 if (line[i] != ' ' && ! in_response)
93 continue;
95 // after a response code
96 if (line[i] == ' ' && in_response)
98 in_response = FALSE;
99 break;
102 if (line[i] == ' ' )
103 i++; // skip the space
105 result[j++] = line[i];
106 in_response = TRUE;
108 result[j] = '\0';
110 return atoi(result);
114 gchar *irc_get_message(const gchar *line, guint len)
116 static gchar result[1024] = { 0 };
117 guint i, j = 0;
118 gint state = 0; // represents the current part of the whole line, separated by spaces
120 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :df foo: var
121 // -> df foo: var
122 for (i = 0; i < len; i++)
124 if (state < 3)
126 if (line[i] == ' ')
128 state++;
129 i++; // skip the ':'
130 continue;
133 else if (line[i] != '\r' && line[i] != '\n')
135 result[j++] = line[i];
138 result[j] = '\0';
140 return result;
144 static gchar *irc_get_message_with_name(const gchar *line, guint len, const gchar *name)
146 gchar *tmp;
147 gsize name_len;
149 tmp = irc_get_message(line, len);
150 name_len = strlen(name);
152 if (strncmp(tmp, name, name_len) == 0)
153 tmp += name_len;
155 return tmp;
159 // returns a nickname argument given to cmd, it may also be "cmd for nickname", then the "for" is
160 // skipped
161 static gchar *get_argument_target(const gchar *line, guint len, const gchar *cmd)
163 static gchar result[20];
164 gchar *tmp;
165 gchar **parts;
167 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :!beer for me
168 tmp = irc_get_message(line, len);
170 // -> !beer for me
171 parts = g_strsplit(tmp, " ", -1);
172 if (parts == NULL || parts[0] == NULL)
174 g_strfreev(parts);
175 return NULL;
178 // if cmd doesn't match, skip it
179 if (parts[0] != NULL && strcmp(parts[0], cmd) != 0)
181 g_strfreev(parts);
182 return NULL;
185 if (parts[1] == NULL)
187 g_strfreev(parts);
188 return NULL;
191 if (strcmp("for", parts[1]) == 0)
193 if (parts[2] != NULL)
195 if (strcmp(parts[2], "me") == 0)
197 g_strfreev(parts);
198 return NULL; // if we return NULL, the nickname of the caller is used, aka "me"
200 else
201 g_strlcpy(result, parts[2], sizeof(result));
203 else
204 g_strlcpy(result, parts[1], sizeof(result));
206 else if (strcmp(parts[1], "me") == 0)
208 g_strfreev(parts);
209 return NULL; // if we return NULL, the nickname of the caller is used, aka "me"
211 else
213 g_strlcpy(result, parts[1], sizeof(result));
216 g_strfreev(parts);
217 return result;
221 // Parses the line and puts the first argument in arg1, all further arguments are concatenated
222 // in arg2. arg1 and arg2 should be freed when no longer needed.
223 // If arg1 and arg2 were set successfully, TRUE is returned, if any error occurs, FALSE is returned
224 // and arg1 and arg2 are set to NULL.
225 static gboolean get_argument_two(const gchar *line, guint len, const gchar *cmd,
226 gchar **arg1, gchar **arg2)
228 gchar *tmp;
229 gchar **parts;
231 *arg1 = NULL;
232 *arg2 = NULL;
234 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :!learn keyword text to be added
235 tmp = irc_get_message(line, len);
237 // -> !learn keyword text to be added
238 parts = g_strsplit(tmp, " ", 3);
239 if (parts == NULL || parts[0] == NULL)
241 g_strfreev(parts);
242 return FALSE;
245 // if cmd doesn't match, skip it
246 if (parts[0] != NULL && strcmp(parts[0], cmd) != 0)
248 g_strfreev(parts);
249 return FALSE;
252 if (parts[1] == NULL || parts[2] == NULL)
254 g_strfreev(parts);
255 return FALSE;
258 *arg1 = g_strdup(parts[1]);
259 *arg2 = g_strdup(parts[2]);
261 g_strfreev(parts);
263 return TRUE;
267 static void process_line(irc_conn_t *irc_conn, const gchar *line, guint len)
269 static gchar msg[1024];
270 guint msg_len;
271 gint response = get_response(line, len);
272 static gchar tmp_userlist[1024];
274 if (! irc_conn->connected)
276 // don't do anything else until we got finished connecting (to skip MOTD messages)
278 // PING-PONG
279 else if (strncmp("PING :", line, 6) == 0)
281 msg_len = g_snprintf(msg, sizeof msg, "PONG %s\r\n", line + 6); // 7 = "PING :"
282 debug("PONG: -%s-\n", msg);
283 send(irc_conn->socket, msg, msg_len, 0);
285 // retrieve user name list
286 else if (response == 353)
288 gchar *names = irc_get_message(line, len);
289 if (tmp_userlist[0] == '\0')
290 g_strlcpy(tmp_userlist, strchr(names, ':') + 1, sizeof(tmp_userlist));
291 else
292 g_strlcat(tmp_userlist, strchr(names, ':') + 1, sizeof(tmp_userlist));
294 // retrieve end of user name list
295 else if (response == 366)
297 if (tmp_userlist[0] != '\0')
299 set_user_list(tmp_userlist);
300 tmp_userlist[0] = '\0';
303 // !test
304 else if (strstr(line, ":!test") != NULL)
306 irc_send_message(irc_conn, get_nickname(line, len), "I don't like tests!");
308 // !roulette
309 else if (strstr(line, ":!roulette") != NULL)
311 gint32 rand = g_random_int();
312 if (rand % 2 == 0)
313 irc_send_message(irc_conn, NULL, "*click*");
314 else
316 irc_send_message(irc_conn, NULL, "*bang*");
317 irc_send_message(irc_conn, get_nickname(line, len), "You are dead.");
320 // !coffee
321 else if (strstr(line, ":!coffee") != NULL)
323 gchar *arg = get_argument_target(line, len, "!coffee");
325 if (arg == NULL)
326 arg = get_nickname(line, len);
328 irc_send_message(irc_conn, NULL,
329 "A nice sexy waitress brings %s a big cup of coffee!", arg);
331 // !beer
332 else if (strstr(line, ":!beer") != NULL)
334 gchar *arg = get_argument_target(line, len, "!beer");
336 if (arg == NULL)
337 arg = get_nickname(line, len);
339 irc_send_message(irc_conn, NULL,
340 "A nice sexy waitress brings %s a nice bottle of beer!", arg);
342 // !help
343 else if (strstr(line, ":!help") != NULL)
345 irc_send_message(irc_conn, get_nickname(line, len), "please use ?? help");
347 // !learn
348 /// TODO require op privileges for !learn
349 else if (strstr(line, ":!learn") != NULL)
351 gchar *arg1, *arg2;
353 if (get_argument_two(line, len, "!learn", &arg1, &arg2))
355 gint result = help_system_learn(arg1, arg2);
356 gchar *text;
358 switch (result)
360 case 0:
362 text = g_strdup_printf("new keyword \"%s\" was added.", arg1);
363 break;
365 case 1:
367 text = g_strdup_printf("existing keyword \"%s\" was updated.", arg1);
368 break;
370 default:
372 text = g_strdup("an error occurred. Database not updated.");
373 break;
376 irc_send_message(irc_conn, get_nickname(line, len), "%s", text);
378 g_free(text);
379 g_free(arg1);
380 g_free(arg2);
382 else
383 irc_send_message(irc_conn, get_nickname(line, len),
384 "wrong usage of !learn. Use \"?? learn\" for usage information.");
386 // ?? ...
387 else if (strstr(line, ":?? ") != NULL)
389 help_system_query(line, len);
391 // Hi /me, acts on "hello $nickname" and "hi $nickname", hi and hello are case-insensitive
392 // Thanks /me
393 else if (strstr(line, config->nickname) != NULL)
395 gchar *tmp_msg = irc_get_message(line, len);
396 gchar *tmp_msg2 = irc_get_message_with_name(line, len, config->nickname);
398 if (strncasecmp("hi", tmp_msg, 2) == 0 || strncasecmp("hello", tmp_msg, 5) == 0 ||
399 strcasecmp(", hi", tmp_msg2) == 0 || strcasecmp(", hello", tmp_msg2) == 0)
401 irc_send_message(irc_conn, NULL,
402 "Hi %s. My name is %s and I'm here to offer additional services to you! "
403 "Try \"?? help\" for general information and \"?? vomak\" for information about me.",
404 get_nickname(line, len), config->nickname);
406 else if (strncasecmp("thanks", tmp_msg, 6) == 0 || strncasecmp("thx", tmp_msg, 3) == 0 ||
407 strcasecmp(", thanks", tmp_msg2) == 0 || strcasecmp(", thx", tmp_msg2) == 0)
409 irc_send_message(irc_conn, get_nickname(line, len),
410 "no problem. It was a pleasure to serve you.");
416 static gboolean input_cb(GIOChannel *source, GIOCondition cond, gpointer data)
418 #if 1
419 gchar buf[1024];
420 guint buf_len;
421 irc_conn_t *irc = data;
423 if ((buf_len = socket_fd_gets(irc->socket, buf, sizeof(buf))) != -1)
425 process_line(irc, buf, buf_len);
427 #else
428 gsize buf_len;
429 irc_conn_t *irc = data;
431 if (cond & (G_IO_IN | G_IO_PRI))
433 gchar *buf = NULL;
434 GIOStatus rv;
435 GError *err = NULL;
439 rv = g_io_channel_read_line(source, &buf, &buf_len, NULL, &err);
440 if (buf != NULL)
442 buf_len -= 2;
443 buf[buf_len] = '\0'; // skip trailing \r\n
445 process_line(irc, buf, buf_len);
446 g_free(buf);
448 if (err != NULL)
450 debug("%s: error: %s", __func__, err->message);
451 g_error_free(err);
452 err = NULL;
455 while (rv == G_IO_STATUS_NORMAL || rv == G_IO_STATUS_AGAIN);
456 debug("%s: status %d\n", __func__, rv);
458 #endif
459 return TRUE;
463 void irc_send_message(irc_conn_t *irc_conn, const gchar *target, const gchar *format, ...)
465 static gchar tmp_msg[1024];
466 static gchar msg[1024];
467 guint msg_len;
468 va_list ap;
470 va_start(ap, format);
471 g_vsnprintf(tmp_msg, sizeof tmp_msg, format, ap);
472 va_end(ap);
474 if (target)
475 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG #%s :%s, %s\r\n", config->channel, target, tmp_msg);
476 else
477 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG #%s :%s\r\n", config->channel, tmp_msg);
479 send(irc_conn->socket, msg, msg_len, 0);
483 void irc_goodbye(irc_conn_t *irc)
485 guint len;
486 gchar msg[256];
488 len = g_strlcpy(msg, "QUIT :Good bye. It was a pleasure to serve you\r\n", sizeof msg);
489 send(irc->socket, msg, len, 0);
493 gint irc_finalize(irc_conn_t *irc_conn)
495 if (irc_conn->socket < 0)
496 return -1;
498 if (irc_conn->lock_tag > 0)
499 g_source_remove(irc_conn->lock_tag);
501 if (irc_conn->read_ioc)
503 g_io_channel_shutdown(irc_conn->read_ioc, TRUE, NULL);
504 g_io_channel_unref(irc_conn->read_ioc);
505 irc_conn->read_ioc = NULL;
507 socket_fd_close(irc_conn->socket);
508 irc_conn->socket = -1;
510 return 0;
514 void irc_connect(irc_conn_t *irc_conn)
516 struct hostent *he;
517 struct sockaddr_in their_addr;
518 gchar msg[256];
519 guint msg_len;
521 TRACE
523 // Connect the socket to the server
524 if ((he = gethostbyname(config->server)) == NULL)
526 perror("gethostbyname");
527 exit(1);
530 if ((irc_conn->socket = socket(PF_INET, SOCK_STREAM, 0)) == -1)
532 perror("socket");
533 exit(1);
536 their_addr.sin_family = PF_INET;
537 their_addr.sin_port = htons(6667);
538 their_addr.sin_addr = *((struct in_addr *)he->h_addr);
539 memset(&(their_addr.sin_zero), '\0', 8);
541 if (connect(irc_conn->socket, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1)
543 perror("connect");
544 exit(1);
546 // say who we are
547 msg_len = g_snprintf(msg, sizeof(msg), "USER %s %s %s :%s\r\nNICK %s\r\n",
548 config->username, config->servername, config->servername, config->realname, config->nickname);
549 if (send(irc_conn->socket, msg, msg_len, 0) == -1)
551 perror("send USER");
553 // identify our nick
554 if (NZV(config->nickserv_password))
556 msg_len = g_snprintf(msg, sizeof(msg), "PRIVMSG nickserv :identify %s\r\n", config->nickserv_password);
557 if (send(irc_conn->socket, msg, msg_len, 0) == -1)
559 perror("send NICKSERV");
562 // join the channel
563 g_snprintf(msg, sizeof msg, "JOIN #%s\r\n ", config->channel);
564 if (send(irc_conn->socket, msg, strlen(msg), 0) == -1)
566 perror("send");
569 // input callback, attached to the main loop
570 irc_conn->read_ioc = g_io_channel_unix_new(irc_conn->socket);
571 //~ g_io_channel_set_flags(irc_conn.read_ioc, G_IO_FLAG_NONBLOCK, NULL);
572 g_io_channel_set_encoding(irc_conn->read_ioc, NULL, NULL);
573 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);