Add support for a 'history' command
[loudgame] / loudgame.c
1 /*
2  * Copyright (C) 2003-2004 Imendio AB
3  * Copyright (C) 2008 Carl Worth
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
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.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see http://www.gnu.org/licenses/ .
17  *
18  * Authors: Imendio AB
19  *          Carl Worth <cworth@cworth.org>
20  */
21
22 #include "loudgame.h"
23  
24 #include <glib.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28  
29 void
30 loudgame_quit (loudgame_t *lg, int return_value)
31 {
32     GError *error = NULL;
33
34     lg->return_value = return_value;
35
36     if (lg->connection)
37     {
38         if (! lm_connection_close (lg->connection, &error))
39         {
40             g_print ("An error occurred during lm_connection_close: %s\n",
41                      error->message);
42         }
43         lm_connection_unref (lg->connection);
44         lg->connection = NULL;
45     }
46
47     g_main_loop_quit (lg->main_loop);
48 }
49  
50 static void
51 authentication_cb (LmConnection *connection, gboolean result, gpointer closure)
52 {
53     LmMessage *m;
54     loudgame_t *lg = closure;
55
56     if (! result) {
57         g_print ("Authentication for %s failed\n", lg->name);
58         loudgame_quit (lg, 1);
59         return;
60     }
61                  
62     m = lm_message_new_with_sub_type (NULL,
63                                       LM_MESSAGE_TYPE_PRESENCE,
64                                       LM_MESSAGE_SUB_TYPE_AVAILABLE);
65
66     lm_connection_send (connection, m, NULL);
67     lm_message_unref (m);
68 }
69  
70 static void
71 connection_open_cb (LmConnection *connection, gboolean result, loudgame_t *lg)
72 {
73     lm_connection_authenticate (connection,
74                                 lg->name, lg->passwd, "loudgame",
75                                 authentication_cb, lg, FALSE,  NULL);
76 }
77
78 void
79 loudgame_send (loudgame_t       *lg,
80                const char       *peer,
81                const char       *message)
82 {
83     LmMessage *reply;
84     gboolean result;
85     GError *error = NULL;
86
87     reply = lm_message_new (peer, LM_MESSAGE_TYPE_MESSAGE);
88
89     lm_message_node_add_child (reply->node, "body", message);
90
91     result = lm_connection_send (lg->connection, reply, &error);
92     lm_message_unref (reply);
93
94     if (! result) {
95         g_error ("lm_connection_send failed: %s\n",
96                  error->message);
97         loudgame_quit (lg, 1);
98     }
99 }
100
101 void
102 loudgame_vsendf (loudgame_t     *lg,
103                  const char     *peer,
104                  const char     *format,
105                  va_list         va)
106 {
107     char *str;
108
109     str = g_strdup_vprintf (format, va);
110
111     loudgame_send (lg, peer, str);
112
113     free (str);
114 }
115
116 void
117 loudgame_sendf (loudgame_t      *lg,
118                 const char      *peer,
119                 const char      *format,
120                 ...)
121 {
122     va_list va;
123
124     va_start (va, format);
125
126     loudgame_vsendf (lg, peer, format, va);
127
128     va_end (va);
129 }
130
131 typedef struct _loudgame_broadcast_data
132 {
133     loudgame_t *lg;
134     const char *message;
135 } loudgame_broadcast_data_t;
136
137 static void
138 loudgame_broadcast_cb (void *key,
139                        void *data,
140                        void *closure)
141 {
142     loudgame_broadcast_data_t *lbd = closure;
143     char *peer = key;
144
145     loudgame_send (lbd->lg, peer, lbd->message);
146 }
147
148 void
149 loudgame_broadcast (loudgame_t *lg,
150                     const char *message)
151 {
152     loudgame_broadcast_data_t lbd;
153
154     lbd.lg = lg;
155     lbd.message = message;
156
157     g_hash_table_foreach (lg->players,
158                           loudgame_broadcast_cb, &lbd);
159 }
160
161 void
162 loudgame_vbroadcastf (loudgame_t *lg,
163                       const char *format,
164                       va_list     va)
165 {
166     char *str;
167
168     str = g_strdup_vprintf (format, va);
169
170     loudgame_broadcast (lg, str);
171
172     free (str);
173 }
174
175 void
176 loudgame_broadcastf (loudgame_t *lg,
177                      const char *format,
178                      ...)
179 {
180     va_list va;
181
182     va_start (va,format);
183
184     loudgame_vbroadcastf (lg, format, va);
185
186     va_end (va);
187 }
188
189 static void
190 handle_command (loudgame_t      *lg,
191                 const char      *peer,
192                 const char      *command)
193 {
194     if (strcmp (command, "quit") == 0) {
195         loudgame_quit (lg, 0);
196         return;
197     }
198
199     loudgame_sendf (lg, "Unknown command: '%s'", command);
200 }
201
202 static void
203 handle_say (loudgame_t  *lg,
204             const char  *peer,
205             const char  *message)
206 {
207     loudgame_broadcastf (lg, "%s: %s", peer, message);
208 }
209
210 static void
211 handle_whisper (loudgame_t  *lg,
212                 const char  *peer,
213                 const char  *body)
214 {
215     const char *recipient, *space, *message;
216
217     space = strchr (body, ' ');
218     if (space == NULL) {
219         loudgame_sendf (lg, peer,
220                         "Error: whisper should be of the form 'user message'");
221         return;
222     }
223
224     recipient = g_strndup (body, space - body);
225
226     message = space + 1;
227
228     loudgame_sendf (lg, recipient, "*%s*: %s", peer, message);
229 }
230  
231 static LmHandlerResult
232 handle_messages (LmMessageHandler *handler,
233                  LmConnection     *connection,
234                  LmMessage        *m,
235                  gpointer          closure)
236 {
237     loudgame_t *lg = closure;
238     LmMessageNode *body;
239     const char *peer;
240     const char *body_str;
241
242     peer = lm_message_node_get_attribute (m->node, "from");
243
244     body = lm_message_node_get_child (m->node, "body");
245     if (! body)
246         return LM_HANDLER_RESULT_REMOVE_MESSAGE;
247
248     if (! g_hash_table_lookup_extended (lg->players, peer,
249                                         NULL, NULL))
250     {
251         char *peer_copy = g_strdup (peer);
252         g_hash_table_insert (lg->players, peer_copy, NULL);
253         loudgame_broadcastf (lg, "%s has joined the game", peer);
254     }
255
256     body_str = lm_message_node_get_value (body);
257
258     if (body_str) {
259         if (body_str[0] == '%')
260             handle_command (lg, peer, body_str + 1);
261         else if (strncmp (body_str, "say ", 4) == 0)
262             handle_say (lg, peer, body_str + 4);
263         else if (strncmp (body_str, "whisper ", 8) == 0)
264             handle_whisper (lg, peer, body_str + 8);
265         else if (lg->handle_message)
266         (lg->handle_message) (lg, peer, body_str);
267     }
268         
269     return LM_HANDLER_RESULT_REMOVE_MESSAGE;
270 }
271
272 static gboolean
273 make_connection (gpointer closure)
274 {
275     loudgame_t       *lg;
276     LmMessageHandler *handler;
277     gchar            *jid;
278     GError           *error = NULL;
279
280     lg = closure;
281                                                                                 
282     lg->connection = lm_connection_new (lg->server);
283
284     jid = g_strdup_printf ("%s@%s", lg->name, lg->server);
285     lm_connection_set_jid (lg->connection, jid);
286     g_free (jid);
287
288     handler = lm_message_handler_new (handle_messages, lg, NULL);
289     lm_connection_register_message_handler (lg->connection,
290                                             handler,
291                                             LM_MESSAGE_TYPE_MESSAGE,
292                                             LM_HANDLER_PRIORITY_NORMAL);
293                                                                                 
294     lm_message_handler_unref (handler);
295                                                                                 
296     if (! lm_connection_open (lg->connection,
297                               (LmResultFunction) connection_open_cb,
298                               lg, NULL, &error))
299     {
300         g_print ("Failed to open connection to %s: %s.\n",
301                  lg->server, error->message);
302         loudgame_quit (lg, 1);
303     }
304
305     /* It seems to be necessary to explicitly tell the server we're
306      * still here. Let's see if one keep-alive every 60 seconds is
307      * sufficient. */
308     lm_connection_set_keep_alive_rate (lg->connection, 60);
309
310     /* Return false to not schedule another call. */
311     return 0;
312 }
313
314 int
315 loudgame_init (loudgame_t *lg, int argc, char **argv)
316 {
317     if (argc != 4) {
318         g_print ("Usage: %s <server> <username> <password>\n", argv[0]);
319         return 1;
320     }
321
322     lg->server = argv[1];
323     lg->name = argv[2];
324     lg->passwd = argv[3];
325
326     lg->connection = NULL;
327
328     lg->main_loop = g_main_loop_new (NULL, FALSE);
329     lg->players = g_hash_table_new_full (g_str_hash, g_str_equal,
330                                          g_free, /* key_destroy */
331                                          NULL    /*value_destroy */);
332
333     lg->return_value = 0;
334
335     lg->handle_message = NULL;
336
337     return 0;
338 }
339
340 static void
341 loudgame_fini (loudgame_t *lg)
342 {
343     g_main_loop_unref (lg->main_loop);
344     g_hash_table_destroy (lg->players);
345 }
346
347 int
348 loudgame_run (loudgame_t *lg)
349 {
350     g_idle_add (make_connection, lg);
351
352     g_main_loop_run (lg->main_loop);
353
354     loudgame_fini (lg);
355                                                                                 
356     return lg->return_value;
357 }