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.
27 #include <sys/types.h>
29 #include <netinet/in.h>
30 #include <sys/socket.h>
50 gboolean
irc_query_names(gpointer data
)
52 irc_conn_t
*irc
= data
;
53 static gchar msg
[1024];
58 msg_len
= g_snprintf(msg
, sizeof msg
, "NAMES #%s\r\n", config
->channel
);
59 send(irc
->socket
, msg
, msg_len
, 0);
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];
76 g_vsnprintf(tmp_msg
, sizeof tmp_msg
, format
, 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];
89 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :df
90 for (i
= 0; i
< len
; i
++)
92 if (line
[i
] == '!' || line
[i
] == '=' || j
>= 19)
98 result
[j
++] = line
[i
];
106 static gchar
*get_private_message_sender(const gchar
*line
, guint len
)
110 // :eht16!n=enrico@uvena.de PRIVMSG GeanyTestBot :hi
111 for (i
= 0; i
< len
; i
++)
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
);
133 static gint
get_response(const gchar
*line
, guint len
)
135 static gchar result
[4];
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
)
147 // after a response code
148 if (line
[i
] == ' ' && in_response
)
155 i
++; // skip the space
157 result
[j
++] = line
[i
];
166 gchar
*irc_get_message(const gchar
*line
, guint len
)
168 static gchar result
[1024] = { 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
174 for (i
= 0; i
< len
; i
++)
185 else if (line
[i
] != '\r' && line
[i
] != '\n')
187 result
[j
++] = line
[i
];
196 static gchar
*irc_get_message_with_name(const gchar
*line
, guint len
, const gchar
*name
)
201 tmp
= irc_get_message(line
, len
);
202 name_len
= strlen(name
);
204 if (strncmp(tmp
, name
, name_len
) == 0)
211 // returns a nickname argument given to cmd, it may also be "cmd for nickname", then the "for" is
213 static gchar
*get_argument_target(const gchar
*line
, guint len
, const gchar
*cmd
)
215 static gchar result
[20];
219 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :!beer for me
220 tmp
= irc_get_message(line
, len
);
223 parts
= g_strsplit(tmp
, " ", -1);
224 if (parts
== NULL
|| parts
[0] == NULL
)
230 // if cmd doesn't match, skip it
231 if (parts
[0] != NULL
&& strcmp(parts
[0], cmd
) != 0)
237 if (parts
[1] == NULL
)
243 if (strcmp("for", parts
[1]) == 0)
245 if (parts
[2] != NULL
)
247 if (strcmp(parts
[2], "me") == 0)
250 return NULL
; // if we return NULL, the nickname of the caller is used, aka "me"
253 g_strlcpy(result
, parts
[2], sizeof(result
));
256 g_strlcpy(result
, parts
[1], sizeof(result
));
258 else if (strcmp(parts
[1], "me") == 0)
261 return NULL
; // if we return NULL, the nickname of the caller is used, aka "me"
265 g_strlcpy(result
, parts
[1], sizeof(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
)
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
)
297 // if cmd doesn't match, skip it
298 if (parts
[0] != NULL
&& strcmp(parts
[0], cmd
) != 0)
304 if (parts
[1] == NULL
|| parts
[2] == NULL
)
310 *arg1
= g_strdup(parts
[1]);
311 *arg2
= g_strdup(parts
[2]);
319 static gboolean
process_line(irc_conn_t
*irc_conn
, const gchar
*line
, guint len
)
321 static gchar msg
[1024];
323 gint response
= get_response(line
, len
);
324 static gchar tmp_userlist
[1024];
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
);
336 syslog(LOG_WARNING
, "received error: %d (%s)", response
, g_strstrip((gchar
*) line
));
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
));
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)
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!");
379 else if (strstr(line
, ":!test") != NULL
)
381 irc_send_message(irc_conn
, get_nickname(line
, len
), "I don't like tests!");
384 else if (strstr(line
, ":!roulette") != NULL
)
386 gint32 rand
= g_random_int();
389 irc_send_message(irc_conn
, NULL
, "*bang*");
390 irc_send_message(irc_conn
, get_nickname(line
, len
), "You are dead.");
393 irc_send_message(irc_conn
, NULL
, "*click*");
396 else if (strstr(line
, ":!coffee") != NULL
)
398 gchar
*arg
= get_argument_target(line
, len
, "!coffee");
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
);
407 else if (strstr(line
, ":!coke") != NULL
)
409 gchar
*arg
= get_argument_target(line
, len
, "!coke");
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
);
418 else if (strstr(line
, ":!beer") != NULL
)
420 gchar
*arg
= get_argument_target(line
, len
, "!beer");
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
);
429 else if (strstr(line
, ":!help") != NULL
)
431 irc_send_message(irc_conn
, get_nickname(line
, len
), "please use ?? help");
434 /// TODO require op privileges for !learn
435 else if (strstr(line
, ":!learn") != NULL
)
439 if (get_argument_two(line
, len
, "!learn", &arg1
, &arg2
))
441 gint result
= help_system_learn(arg1
, arg2
);
448 text
= g_strdup_printf("new keyword \"%s\" was added.", arg1
);
453 text
= g_strdup_printf("existing keyword \"%s\" was updated.", arg1
);
458 text
= g_strdup("an error occurred. Database not updated.");
462 irc_send_message(irc_conn
, get_nickname(line
, len
), "%s", text
);
469 irc_send_message(irc_conn
, get_nickname(line
, len
),
470 "wrong usage of !learn. Use \"?? learn\" for usage information.");
474 else if (strstr(line
, ":!alias") != NULL
)
478 if (get_argument_two(line
, len
, "!alias", &arg1
, &arg2
))
483 // detect if arg2 has more than one word by scanning for spaces in
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
);
493 gchar
*alias
= g_strconcat("@", arg2
, NULL
);
494 result
= help_system_learn(arg1
, alias
);
501 text
= g_strdup_printf("new alias \"%s\" was added.", arg1
);
506 text
= g_strdup_printf("existing alias \"%s\" was updated.", arg1
);
511 text
= g_strdup("An error occurred. Database not updated.");
516 irc_send_message(irc_conn
, get_nickname(line
, len
), "%s", text
);
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
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.");
558 static gboolean
input_cb(GIOChannel
*source
, GIOCondition cond
, gpointer data
)
563 irc_conn_t
*irc
= data
;
566 if ((buf_len
= socket_fd_gets(irc
->socket
, buf
, sizeof(buf
))) != -1)
568 ret
= process_line(irc
, buf
, buf_len
);
572 irc_conn_t
*irc
= data
;
574 if (cond
& (G_IO_IN
| G_IO_PRI
))
582 rv
= g_io_channel_read_line(source
, &buf
, &buf_len
, NULL
, &err
);
586 buf
[buf_len
] = '\0'; // skip trailing \r\n
588 process_line(irc
, buf
, buf_len
);
593 debug("%s: error: %s", __func__
, err
->message
);
598 while (rv
== G_IO_STATUS_NORMAL
|| rv
== G_IO_STATUS_AGAIN
);
599 debug("%s: status %d\n", __func__
, rv
);
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];
613 va_start(ap
, format
);
614 g_vsnprintf(tmp_msg
, sizeof tmp_msg
, format
, ap
);
618 msg_len
= g_snprintf(msg
, sizeof msg
, "PRIVMSG #%s :%s, %s\r\n", config
->channel
, target
, tmp_msg
);
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
)
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)
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;
657 void irc_connect(irc_conn_t
*irc_conn
)
660 struct sockaddr_in their_addr
;
666 // Connect the socket to the server
667 if ((he
= gethostbyname(config
->server
)) == NULL
)
669 perror("gethostbyname");
673 if ((irc_conn
->socket
= socket(PF_INET
, SOCK_STREAM
, 0)) == -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)
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)
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");
706 g_snprintf(msg
, sizeof msg
, "JOIN #%s\r\n ", config
->channel
);
707 if (send(irc_conn
->socket
, msg
, strlen(msg
), 0) == -1)
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
);