Add "You are dead" message when one lost when playing with the roulette command.
[vomak.git] / irc.c
blob45bbad0e2ce56123af060f7b02a339d241ac98cd
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 // returns a nickname argument given to cmd, it may also be "cmd for nickname", then the "for" is
145 // skipped
146 static gchar *get_argument_target(const gchar *line, guint len, const gchar *cmd)
148 static gchar result[20];
149 gchar *tmp;
150 gchar **parts;
152 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :!beer for me
153 tmp = irc_get_message(line, len);
155 // -> !beer for me
156 parts = g_strsplit(tmp, " ", -1);
157 if (parts == NULL || parts[0] == NULL)
159 g_strfreev(parts);
160 return NULL;
163 // if cmd doesn't match, skip it
164 if (parts[0] != NULL && strcmp(parts[0], cmd) != 0)
166 g_strfreev(parts);
167 return NULL;
170 if (parts[1] == NULL)
172 g_strfreev(parts);
173 return NULL;
176 if (strcmp("for", parts[1]) == 0)
178 if (parts[2] != NULL)
180 if (strcmp(parts[2], "me") == 0)
182 g_strfreev(parts);
183 return NULL; // if we return NULL, the nickname of the caller is used, aka "me"
185 else
186 g_strlcpy(result, parts[2], sizeof(result));
188 else
189 g_strlcpy(result, parts[1], sizeof(result));
191 else if (strcmp(parts[1], "me") == 0)
193 g_strfreev(parts);
194 return NULL; // if we return NULL, the nickname of the caller is used, aka "me"
196 else
198 g_strlcpy(result, parts[1], sizeof(result));
201 g_strfreev(parts);
202 return result;
206 // Parses the line and puts the first argument in arg1, all further arguments are concatenated
207 // in arg2. arg1 and arg2 should be freed when no longer needed.
208 // If arg1 and arg2 were set successfully, TRUE is returned, if any error occurs, FALSE is returned
209 // and arg1 and arg2 are set to NULL.
210 static gboolean get_argument_two(const gchar *line, guint len, const gchar *cmd,
211 gchar **arg1, gchar **arg2)
213 gchar *tmp;
214 gchar **parts;
216 *arg1 = NULL;
217 *arg2 = NULL;
219 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :!learn keyword text to be added
220 tmp = irc_get_message(line, len);
222 // -> !learn keyword text to be added
223 parts = g_strsplit(tmp, " ", 3);
224 if (parts == NULL || parts[0] == NULL)
226 g_strfreev(parts);
227 return FALSE;
230 // if cmd doesn't match, skip it
231 if (parts[0] != NULL && strcmp(parts[0], cmd) != 0)
233 g_strfreev(parts);
234 return FALSE;
237 if (parts[1] == NULL || parts[2] == NULL)
239 g_strfreev(parts);
240 return FALSE;
243 *arg1 = g_strdup(parts[1]);
244 *arg2 = g_strdup(parts[2]);
246 g_strfreev(parts);
248 return TRUE;
252 static void process_line(irc_conn_t *irc_conn, const gchar *line, guint len)
254 static gchar msg[1024];
255 guint msg_len;
256 gint response = get_response(line, len);
257 static gchar tmp_userlist[1024];
259 if (! irc_conn->connected)
261 // don't do anything else until we got finished connecting (to skip MOTD messages)
263 // PING-PONG
264 else if (strncmp("PING :", line, 6) == 0)
266 msg_len = g_snprintf(msg, sizeof msg, "PONG %s\r\n", line + 6); // 7 = "PING :"
267 debug("PONG: -%s-\n", msg);
268 send(irc_conn->socket, msg, msg_len, 0);
270 // retrieve user name list
271 else if (response == 353)
273 gchar *names = irc_get_message(line, len);
274 if (tmp_userlist[0] == '\0')
275 g_strlcpy(tmp_userlist, strchr(names, ':') + 1, sizeof(tmp_userlist));
276 else
277 g_strlcat(tmp_userlist, strchr(names, ':') + 1, sizeof(tmp_userlist));
279 // retrieve end of user name list
280 else if (response == 366)
282 if (tmp_userlist[0] != '\0')
284 set_user_list(tmp_userlist);
285 tmp_userlist[0] = '\0';
288 // !test
289 else if (strstr(line, ":!test") != NULL)
291 irc_send_message(irc_conn, get_nickname(line, len), "I don't like tests!");
293 // !roulette
294 else if (strstr(line, ":!roulette") != NULL)
296 gint32 rand = g_random_int();
297 if (rand % 2 == 0)
298 irc_send_message(irc_conn, NULL, "*click*");
299 else
301 irc_send_message(irc_conn, NULL, "*bang*");
302 irc_send_message(irc_conn, get_nickname(line, len), "You are dead.");
305 // !coffee
306 else if (strstr(line, ":!coffee") != NULL)
308 gchar *arg = get_argument_target(line, len, "!coffee");
310 if (arg == NULL)
311 arg = get_nickname(line, len);
313 irc_send_message(irc_conn, NULL,
314 "A nice sexy waitress brings %s a big cup of coffee!", arg);
316 // !beer
317 else if (strstr(line, ":!beer") != NULL)
319 gchar *arg = get_argument_target(line, len, "!beer");
321 if (arg == NULL)
322 arg = get_nickname(line, len);
324 irc_send_message(irc_conn, NULL,
325 "A nice sexy waitress brings %s a nice bottle of beer!", arg);
327 // !help
328 else if (strstr(line, ":!help") != NULL)
330 irc_send_message(irc_conn, get_nickname(line, len), "please use ?? help");
332 // !learn
333 /// TODO require op privileges for !learn
334 else if (strstr(line, ":!learn") != NULL)
336 gchar *arg1, *arg2;
338 if (get_argument_two(line, len, "!learn", &arg1, &arg2))
340 gint result = help_system_learn(arg1, arg2);
341 gchar *text;
343 switch (result)
345 case 0:
347 text = g_strdup_printf("new keyword \"%s\" was added.", arg1);
348 break;
350 case 1:
352 text = g_strdup_printf("existing keyword \"%s\" was updated.", arg1);
353 break;
355 default:
357 text = g_strdup("an error occurred. Database not updated.");
358 break;
361 irc_send_message(irc_conn, get_nickname(line, len), "%s", text);
363 g_free(text);
364 g_free(arg1);
365 g_free(arg2);
367 else
368 irc_send_message(irc_conn, get_nickname(line, len),
369 "wrong usage of !learn. Use \"?? learn\" for usage information.");
371 // ?? ...
372 else if (strstr(line, ":?? ") != NULL)
374 help_system_query(line, len);
376 // Hi /me, acts on "hello $nickname" and "hi $nickname", hi and hello are case-insensitive
377 else if (strstr(line, config->nickname) != NULL)
379 gchar *tmp_msg = irc_get_message(line, len);
381 if (strncasecmp("hi", tmp_msg, 2) == 0 || strncasecmp("hello", tmp_msg, 5) == 0)
383 irc_send_message(irc_conn, NULL,
384 "Hi %s. My name is %s and I'm here to offer additional services to you! "
385 "Try \"?? help\" for general information and \"?? vomak\" for information about me.",
386 get_nickname(line, len), config->nickname);
392 static gboolean input_cb(GIOChannel *source, GIOCondition cond, gpointer data)
394 #if 1
395 gchar buf[1024];
396 guint buf_len;
397 irc_conn_t *irc = data;
399 if ((buf_len = socket_fd_gets(irc->socket, buf, sizeof(buf))) != -1)
401 process_line(irc, buf, buf_len);
403 #else
404 gsize buf_len;
405 irc_conn_t *irc = data;
407 if (cond & (G_IO_IN | G_IO_PRI))
409 gchar *buf = NULL;
410 GIOStatus rv;
411 GError *err = NULL;
415 rv = g_io_channel_read_line(source, &buf, &buf_len, NULL, &err);
416 if (buf != NULL)
418 buf_len -= 2;
419 buf[buf_len] = '\0'; // skip trailing \r\n
421 process_line(irc, buf, buf_len);
422 g_free(buf);
424 if (err != NULL)
426 debug("%s: error: %s", __func__, err->message);
427 g_error_free(err);
428 err = NULL;
431 while (rv == G_IO_STATUS_NORMAL || rv == G_IO_STATUS_AGAIN);
432 debug("%s: status %d\n", __func__, rv);
434 #endif
435 return TRUE;
439 void irc_send_message(irc_conn_t *irc_conn, const gchar *target, const gchar *format, ...)
441 static gchar tmp_msg[1024];
442 static gchar msg[1024];
443 guint msg_len;
444 va_list ap;
446 va_start(ap, format);
447 g_vsnprintf(tmp_msg, sizeof tmp_msg, format, ap);
448 va_end(ap);
450 if (target)
451 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG #%s :%s, %s\r\n", config->channel, target, tmp_msg);
452 else
453 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG #%s :%s\r\n", config->channel, tmp_msg);
455 send(irc_conn->socket, msg, msg_len, 0);
459 void irc_goodbye(irc_conn_t *irc)
461 guint len;
462 gchar msg[256];
464 len = g_strlcpy(msg, "QUIT :Good bye. It was a pleasure to serve you\r\n", sizeof msg);
465 send(irc->socket, msg, len, 0);
469 gint irc_finalize(irc_conn_t *irc_conn)
471 if (irc_conn->socket < 0)
472 return -1;
474 if (irc_conn->lock_tag > 0)
475 g_source_remove(irc_conn->lock_tag);
477 if (irc_conn->read_ioc)
479 g_io_channel_shutdown(irc_conn->read_ioc, TRUE, NULL);
480 g_io_channel_unref(irc_conn->read_ioc);
481 irc_conn->read_ioc = NULL;
483 socket_fd_close(irc_conn->socket);
484 irc_conn->socket = -1;
486 return 0;
490 void irc_connect(irc_conn_t *irc_conn)
492 struct hostent *he;
493 struct sockaddr_in their_addr;
494 gchar msg[256];
495 guint msg_len;
497 TRACE
499 // Connect the socket to the server
500 if ((he = gethostbyname(config->server)) == NULL)
502 perror("gethostbyname");
503 exit(1);
506 if ((irc_conn->socket = socket(PF_INET, SOCK_STREAM, 0)) == -1)
508 perror("socket");
509 exit(1);
512 their_addr.sin_family = PF_INET;
513 their_addr.sin_port = htons(6667);
514 their_addr.sin_addr = *((struct in_addr *)he->h_addr);
515 memset(&(their_addr.sin_zero), '\0', 8);
517 if (connect(irc_conn->socket, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1)
519 perror("connect");
520 exit(1);
522 // say who we are
523 msg_len = g_snprintf(msg, sizeof(msg), "USER %s %s %s :%s\r\nNICK %s\r\n",
524 config->username, config->servername, config->servername, config->realname, config->nickname);
525 if (send(irc_conn->socket, msg, msg_len, 0) == -1)
527 perror("send USER");
529 // identify our nick
530 if (NZV(config->nickserv_password))
532 msg_len = g_snprintf(msg, sizeof(msg), "PRIVMSG nickserv :identify %s\r\n", config->nickserv_password);
533 if (send(irc_conn->socket, msg, msg_len, 0) == -1)
535 perror("send NICKSERV");
538 // join the channel
539 g_snprintf(msg, sizeof msg, "JOIN #%s\r\n ", config->channel);
540 if (send(irc_conn->socket, msg, strlen(msg), 0) == -1)
542 perror("send");
545 // input callback, attached to the main loop
546 irc_conn->read_ioc = g_io_channel_unix_new(irc_conn->socket);
547 //~ g_io_channel_set_flags(irc_conn.read_ioc, G_IO_FLAG_NONBLOCK, NULL);
548 g_io_channel_set_encoding(irc_conn->read_ioc, NULL, NULL);
549 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);