Make the bot also respond on message like "botname, [hi|thanks]".
[vomak.git] / vomak.c
blobf6f29e26e10ea58d3a456a159b4f55492580ffb0
1 /*
2 * vomak.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 #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 <signal.h>
35 #include <glib.h>
36 #include <glib/gstdio.h>
38 #include "vomak.h"
39 #include "socket.h"
40 #include "irc.h"
42 #define CONFIG_SECTION_GENERAL "general"
43 #define CONFIG_SECTION_DATABASE "general"
46 static irc_conn_t irc_conn;
47 static socket_info_t socket_info;
48 static GMainLoop *main_loop = NULL;
49 static gchar *userlist = NULL;
50 config_t *config;
54 static void load_database_real(GKeyFile *keyfile)
56 gsize j, len_keys = 0;
57 gchar **keys;
59 TRACE
61 // unload previously loaded data, aka reload
62 if (config->data != NULL)
64 g_hash_table_destroy(config->data);
67 config->data = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
69 keys = g_key_file_get_keys(keyfile, CONFIG_SECTION_DATABASE, &len_keys, NULL);
70 for (j = 0; j < len_keys; j++)
72 g_hash_table_insert(config->data, g_strdup(keys[j]),
73 g_key_file_get_string(keyfile, CONFIG_SECTION_DATABASE, keys[j], NULL));
75 g_strfreev(keys);
79 static void load_database()
81 gchar *config_name = g_build_filename(getenv("HOME"), ".vomak", "database", NULL);
82 GKeyFile *keyfile = g_key_file_new();
84 TRACE
86 g_key_file_load_from_file(keyfile, config_name, G_KEY_FILE_NONE, NULL);
88 load_database_real(keyfile);
90 g_key_file_free(keyfile);
91 g_free(config_name);
95 static void signal_cb(gint sig)
97 if (sig == SIGTERM || sig == SIGINT)
99 gchar *signame;
101 switch (sig)
103 case SIGTERM: signame = "SIGTERM"; break;
104 case SIGINT: signame = "SIGINT"; break;
105 default: signame = "unknown"; break;
107 debug("Received %s, cleaning up", signame);
109 irc_goodbye(&irc_conn);
111 irc_finalize(&irc_conn);
112 socket_finalize(&socket_info);
114 g_main_loop_quit(main_loop);
120 static gboolean socket_input_cb(GIOChannel *ioc, GIOCondition cond, gpointer data)
122 gint fd, sock;
123 static gchar buf[1024];
124 static gchar msg[1024];
125 struct sockaddr_in caddr;
126 guint caddr_len, len, msg_len;
128 caddr_len = sizeof(caddr);
130 fd = g_io_channel_unix_get_fd(ioc);
131 sock = accept(fd, (struct sockaddr *)&caddr, &caddr_len);
133 TRACE
134 while ((len = socket_fd_gets(sock, buf, sizeof(buf))) != -1)
136 if (strncmp("quit", buf, 4) == 0)
138 signal_cb(SIGTERM);
140 else if (strncmp("reload", buf, 6) == 0)
142 load_database();
144 else if (strncmp("userlist", buf, 8) == 0)
146 if (userlist)
148 send(sock, userlist, strlen(userlist), 0);
149 send(sock, "\n", 1, 0);
151 else
152 send(sock, "error\n", 6, 0);
154 else
156 msg_len = g_snprintf(msg, sizeof msg, "%s\r\n", buf);
157 send(irc_conn.socket, msg, msg_len, 0);
158 vdebug(msg);
161 socket_fd_close(sock);
163 return TRUE;
167 static void hash_add_to_string(gpointer key, gpointer value, gpointer user_data)
169 if (key != NULL)
171 g_string_append(user_data, key);
172 g_string_append_c(user_data, ' ');
177 static gint write_file(const gchar *filename, const gchar *text)
179 FILE *fp;
180 gint bytes_written, len;
182 if (filename == NULL)
184 return ENOENT;
187 len = strlen(text);
189 fp = g_fopen(filename, "w");
190 if (fp != NULL)
192 bytes_written = fwrite(text, sizeof (gchar), len, fp);
193 fclose(fp);
195 if (len != bytes_written)
197 debug("written only %d bytes, had to write %d bytes to %s", bytes_written, len, filename);
198 return EIO;
201 else
203 debug("could not write to file %s (%s)", filename, g_strerror(errno));
204 return errno;
206 return 0;
210 void help_system_query(const gchar *line, guint len)
212 gchar *msg = irc_get_message(line, len);
213 gchar *result;
215 if (strncmp("?? ", msg, 3) != 0)
216 return;
218 msg += 3;
220 if (strncmp("keywords", msg, 8) == 0)
222 GString *str = g_string_sized_new(256);
223 g_hash_table_foreach(config->data, hash_add_to_string, str);
225 irc_send_message(&irc_conn, NULL, "%s: %s", msg, str->str);
226 g_string_free(str, TRUE);
228 else
230 result = g_hash_table_lookup(config->data, msg);
231 if (result)
232 irc_send_message(&irc_conn, NULL, "%s: %s", msg, result);
237 // return value:
238 // 0 - added
239 // 1 - updated
240 // -1 - error
241 gint help_system_learn(const gchar *keyword, const gchar *text)
243 gchar *config_name;
244 gchar *data;
245 gchar *key;
246 gint ret = 0;
247 GKeyFile *keyfile;
249 TRACE
251 if (keyword == NULL || text == NULL)
252 return -1;
254 config_name = g_build_filename(getenv("HOME"), ".vomak", "database", NULL);
255 keyfile = g_key_file_new();
257 g_key_file_load_from_file(keyfile, config_name, G_KEY_FILE_NONE, NULL);
259 key = g_hash_table_lookup(config->data, keyword);
260 if (key != NULL)
261 ret = 1; // if key is non-NULL it is already available and gets updated
263 g_key_file_set_string(keyfile, CONFIG_SECTION_DATABASE, keyword, text);
265 data = g_key_file_to_data(keyfile, NULL, NULL);
266 write_file(config_name, data);
267 g_free(data);
269 load_database_real(keyfile);
271 g_key_file_free(keyfile);
272 g_free(config_name);
274 return ret;
278 static void config_free()
280 TRACE
281 g_free(config->socketname);
282 g_free(config->socketperm);
283 g_free(config->server);
284 g_free(config->channel);
285 g_free(config->servername);
286 g_free(config->nickname);
287 g_free(config->username);
288 g_free(config->realname);
289 g_free(config->nickserv_password);
290 //~ g_hash_table_destroy(config->data);
291 g_free(config);
292 config = NULL;
296 static config_t *config_read()
298 GKeyFile *keyfile = g_key_file_new();
299 gchar *config_name = g_build_filename(getenv("HOME"), ".vomak", "config", NULL);
300 config_t *config = g_new0(config_t, 1);
302 TRACE
304 g_key_file_load_from_file(keyfile, config_name, G_KEY_FILE_NONE, NULL);
306 config->socketname = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "socketname", NULL);
307 config->socketperm = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "socketperm", NULL);
308 config->server = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "server", NULL);
309 config->port = g_key_file_get_integer(keyfile, CONFIG_SECTION_GENERAL, "port", NULL);
310 config->channel = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "channel", NULL);
311 config->servername = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "servername", NULL);
312 config->nickname = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "nickname", NULL);
313 config->username = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "username", NULL);
314 config->realname = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "realname", NULL);
315 config->nickserv_password = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "nickserv_password", NULL);
317 g_key_file_free(keyfile);
318 g_free(config_name);
320 // if anything could not be read, abort. We don't use default values
321 if (config->socketname == NULL ||
322 config->socketperm == NULL ||
323 config->server == NULL ||
324 config->channel == NULL ||
325 config->servername == NULL ||
326 config->nickname == NULL ||
327 config->username == NULL ||
328 config->realname == NULL)
330 fprintf(stderr, "Config file does not exist or is not complete.\n");
331 exit(1);
334 return config;
338 static void init_socket()
340 TRACE
342 /* create unix domain socket for remote operation */
343 socket_info.lock_socket = -1;
344 socket_info.lock_socket_tag = 0;
345 socket_init(&socket_info, config->socketname);
347 if (socket_info.lock_socket >= 0)
349 mode_t mode;
350 sscanf(config->socketperm, "%o", &mode);
351 /// FIXME for some reason the sticky bit is set when socketperm is "0640"
352 chmod(config->socketname, mode);
353 socket_info.read_ioc = g_io_channel_unix_new(socket_info.lock_socket);
354 g_io_channel_set_flags(socket_info.read_ioc, G_IO_FLAG_NONBLOCK, NULL);
355 socket_info.lock_socket_tag = g_io_add_watch(socket_info.read_ioc,
356 G_IO_IN|G_IO_PRI|G_IO_ERR, socket_input_cb, NULL);
361 static gboolean mark_connect(gpointer data)
363 TRACE
365 // delay the internal connected status to skip parsing of MOTD messages
366 irc_conn.connected = TRUE;
368 return FALSE;
372 void set_user_list(const gchar *list)
374 g_free(userlist);
375 userlist = g_strdup(list);
379 gint main(gint argc, gchar **argv)
381 irc_conn.connected = FALSE;
383 // init stuff
384 main_loop = g_main_loop_new(NULL, FALSE);
385 config = config_read();
386 load_database();
387 init_socket();
388 irc_connect(&irc_conn);
389 signal(SIGTERM, signal_cb);
390 signal(SIGINT, signal_cb);
392 g_timeout_add(10000, mark_connect, NULL);
393 g_timeout_add(180000, irc_query_names, &irc_conn);
395 // if not a debug build, deattach from console and go into background
396 #ifndef DEBUG
397 signal(SIGTTOU, SIG_IGN);
398 signal(SIGTTIN, SIG_IGN);
399 signal(SIGTSTP, SIG_IGN);
401 if (daemon(1, 0) < 0)
403 g_printerr("Unable to daemonize\n");
404 return -1;
406 #endif
408 // main loop
409 g_main_loop_run(main_loop);
411 config_free();
413 return 0;