Merged with Irssi CVS.
[silc.git] / apps / irssi / src / core / chat-commands.c
1 /*
2  chat-commands.c : irssi
3
4     Copyright (C) 2000 Timo Sirainen
5
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; either version 2 of the License, or
9     (at your option) any later version.
10
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.
15
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "network.h"
23 #include "signals.h"
24 #include "commands.h"
25 #include "special-vars.h"
26 #include "settings.h"
27
28 #include "chat-protocols.h"
29 #include "servers.h"
30 #include "servers-setup.h"
31 #include "servers-reconnect.h"
32 #include "channels.h"
33 #include "queries.h"
34 #include "window-item-def.h"
35 #include "rawlog.h"
36
37 static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr,
38                                               char **rawlog_file)
39 {
40         CHAT_PROTOCOL_REC *proto;
41         SERVER_CONNECT_REC *conn;
42         GHashTable *optlist;
43         char *addr, *portstr, *password, *nick, *chatnet, *host;
44         void *free_arg;
45
46         g_return_val_if_fail(data != NULL, NULL);
47
48         if (!cmd_get_params(data, &free_arg, 4 | PARAM_FLAG_OPTIONS,
49                             "connect", &optlist, &addr, &portstr,
50                             &password, &nick))
51                 return NULL;
52         if (plus_addr != NULL) *plus_addr = *addr == '+';
53         if (*addr == '+') addr++;
54         if (*addr == '\0') {
55                 signal_emit("error command", 1,
56                             GINT_TO_POINTER(CMDERR_NOT_ENOUGH_PARAMS));
57                 cmd_params_free(free_arg);
58                 return NULL;
59         }
60
61         if (strcmp(password, "-") == 0)
62                 *password = '\0';
63
64         /* check if -<chatnet> option is used to specify chat protocol */
65         proto = chat_protocol_find_net(optlist);
66
67         /* connect to server */
68         chatnet = proto == NULL ? NULL :
69                 g_hash_table_lookup(optlist, proto->chatnet);
70         conn = server_create_conn(proto != NULL ? proto->id : -1, addr,
71                                   atoi(portstr), chatnet, password, nick);
72         if (proto == NULL)
73                 proto = chat_protocol_find_id(conn->chat_type);
74
75         if (proto->not_initialized) {
76                 /* trying to use protocol that isn't yet initialized */
77                 signal_emit("chat protocol unknown", 1, proto->name);
78                 server_connect_unref(conn);
79                 cmd_params_free(free_arg);
80                 return NULL;
81         }
82
83         if (strchr(addr, '/') != NULL)
84                 conn->unix_socket = TRUE;
85
86         if (g_hash_table_lookup(optlist, "6") != NULL)
87                 conn->family = AF_INET6;
88         else if (g_hash_table_lookup(optlist, "4") != NULL)
89                 conn->family = AF_INET;
90
91         if(g_hash_table_lookup(optlist, "ssl") != NULL)
92                 conn->use_ssl = TRUE;
93
94         if (g_hash_table_lookup(optlist, "!") != NULL)
95                 conn->no_autojoin_channels = TRUE;
96
97         if (g_hash_table_lookup(optlist, "noproxy") != NULL)
98                 g_free_and_null(conn->proxy);
99
100
101         *rawlog_file = g_strdup(g_hash_table_lookup(optlist, "rawlog"));
102
103         host = g_hash_table_lookup(optlist, "host");
104         if (host != NULL && *host != '\0') {
105                 IPADDR ip4, ip6;
106
107                 if (net_gethostbyname(host, &ip4, &ip6) == 0)
108                         server_connect_own_ip_save(conn, &ip4, &ip6);
109         }
110
111         cmd_params_free(free_arg);
112         return conn;
113 }
114
115 /* SYNTAX: CONNECT [-4 | -6] [-ssl] [-ircnet <ircnet>] [-host <hostname>]
116                    <address>|<chatnet> [<port> [<password> [<nick>]]] */
117 static void cmd_connect(const char *data)
118 {
119         SERVER_CONNECT_REC *conn;
120         SERVER_REC *server;
121         char *rawlog_file;
122
123         conn = get_server_connect(data, NULL, &rawlog_file);
124         if (conn != NULL) {
125                 server = server_connect(conn);
126                 server_connect_unref(conn);
127
128                 if (server != NULL && rawlog_file != NULL)
129                         rawlog_open(server->rawlog, rawlog_file);
130
131                 g_free(rawlog_file);
132         }
133 }
134
135 static RECONNECT_REC *find_reconnect_server(int chat_type,
136                                             const char *addr, int port)
137 {
138         RECONNECT_REC *match, *last_proto_match;
139         GSList *tmp;
140         int count;
141
142         g_return_val_if_fail(addr != NULL, NULL);
143
144         /* check if there's a reconnection to the same host and maybe even
145            the same port */
146         match = last_proto_match = NULL; count = 0;
147         for (tmp = reconnects; tmp != NULL; tmp = tmp->next) {
148                 RECONNECT_REC *rec = tmp->data;
149
150                 if (rec->conn->chat_type == chat_type) {
151                         count++; last_proto_match = rec;
152                         if (g_strcasecmp(rec->conn->address, addr) == 0) {
153                                 if (rec->conn->port == port)
154                                         return rec;
155                                 match = rec;
156                         }
157                 }
158         }
159
160         if (count == 1) {
161                 /* only one reconnection with wanted protocol,
162                    we probably want to use it */
163                 return last_proto_match;
164         }
165
166         return match;
167 }
168
169 static void update_reconnection(SERVER_CONNECT_REC *conn, SERVER_REC *server)
170 {
171         SERVER_CONNECT_REC *oldconn;
172         RECONNECT_REC *recon;
173
174         if (server != NULL) {
175                 oldconn = server->connrec;
176                 server_connect_ref(oldconn);
177                 reconnect_save_status(conn, server);
178         } else {
179                 /* maybe we can reconnect some server from
180                    reconnection queue */
181                 recon = find_reconnect_server(conn->chat_type,
182                                               conn->address, conn->port);
183                 if (recon == NULL) return;
184
185                 oldconn = recon->conn;
186                 server_connect_ref(oldconn);
187                 server_reconnect_destroy(recon);
188
189                 conn->away_reason = g_strdup(oldconn->away_reason);
190                 conn->channels = g_strdup(oldconn->channels);
191         }
192
193         conn->reconnection = TRUE;
194
195         if (conn->chatnet == NULL && oldconn->chatnet != NULL)
196                 conn->chatnet = g_strdup(oldconn->chatnet);
197
198         server_connect_unref(oldconn);
199         if (server != NULL) {
200                 signal_emit("command disconnect", 2,
201                             "* Changing server", server);
202         }
203 }
204
205 static void cmd_server(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
206 {
207         command_runsub("server", data, server, item);
208 }
209
210 static void sig_default_command_server(const char *data, SERVER_REC *server,
211                                        WI_ITEM_REC *item)
212 {
213         signal_emit("command server connect", 3, data, server, item);
214 }
215
216 /* SYNTAX: SERVER [-4 | -6] [-ssl] [-ircnet <ircnet>] [-host <hostname>]
217                   [+]<address>|<chatnet> [<port> [<password> [<nick>]]] */
218 static void cmd_server_connect(const char *data, SERVER_REC *server)
219 {
220         SERVER_CONNECT_REC *conn;
221         char *rawlog_file;
222         int plus_addr;
223
224         g_return_if_fail(data != NULL);
225
226         /* create connection record */
227         conn = get_server_connect(data, &plus_addr, &rawlog_file);
228         if (conn != NULL) {
229                 if (!plus_addr)
230                         update_reconnection(conn, server);
231                 server = server_connect(conn);
232                 server_connect_unref(conn);
233
234                 if (server != NULL && rawlog_file != NULL)
235                         rawlog_open(server->rawlog, rawlog_file);
236
237                 g_free(rawlog_file);
238         }
239 }
240
241 /* SYNTAX: DISCONNECT *|<tag> [<message>] */
242 static void cmd_disconnect(const char *data, SERVER_REC *server)
243 {
244         char *tag, *msg;
245         void *free_arg;
246
247         g_return_if_fail(data != NULL);
248
249         if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &tag, &msg))
250                 return;
251
252         if (*tag != '\0' && strcmp(tag, "*") != 0) {
253                 server = server_find_tag(tag);
254                 if (server == NULL)
255                         server = server_find_lookup_tag(tag);
256         }
257         if (server == NULL) cmd_param_error(CMDERR_NOT_CONNECTED);
258
259         if (*msg == '\0') msg = (char *) settings_get_str("quit_message");
260         signal_emit("server quit", 2, server, msg);
261
262         cmd_params_free(free_arg);
263         server_disconnect(server);
264 }
265
266 /* SYNTAX: QUIT [<message>] */
267 static void cmd_quit(const char *data)
268 {
269         GSList *tmp, *next;
270         const char *quitmsg;
271         char *str;
272
273         g_return_if_fail(data != NULL);
274
275         quitmsg = *data != '\0' ? data :
276                 settings_get_str("quit_message");
277
278         /* disconnect from every server */
279         for (tmp = servers; tmp != NULL; tmp = next) {
280                 next = tmp->next;
281
282                 str = g_strdup_printf("* %s", quitmsg);
283                 cmd_disconnect(str, tmp->data);
284                 g_free(str);
285         }
286
287         signal_emit("gui exit", 0);
288 }
289
290 /* SYNTAX: JOIN [-invite] [-<server tag>] <channels> [<keys>] */
291 static void cmd_join(const char *data, SERVER_REC *server)
292 {
293         GHashTable *optlist;
294         char *channels;
295         void *free_arg;
296
297         g_return_if_fail(data != NULL);
298
299         if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
300                             PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
301                             "join", &optlist, &channels))
302                 return;
303
304         /* -<server tag> */
305         server = cmd_options_get_server("join", optlist, server);
306         if (server == NULL || !server->connected)
307                 cmd_param_error(CMDERR_NOT_CONNECTED);
308
309         if (g_hash_table_lookup(optlist, "invite"))
310                 channels = server->last_invite;
311         else {
312                 if (*channels == '\0')
313                         cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
314         }
315
316         if (channels != NULL)
317                 server->channels_join(server, channels, FALSE);
318         cmd_params_free(free_arg);
319 }
320
321 /* SYNTAX: MSG [-<server tag>] [-channel | -nick] <targets> <message> */
322 static void cmd_msg(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
323 {
324         GHashTable *optlist;
325         char *target, *origtarget, *msg;
326         void *free_arg;
327         int free_ret, target_type;
328
329         g_return_if_fail(data != NULL);
330
331         if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS |
332                             PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
333                             "msg", &optlist, &target, &msg))
334                 return;
335         if (*target == '\0' || *msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
336
337         server = cmd_options_get_server("msg", optlist, server);
338         if (server == NULL || !server->connected)
339                 cmd_param_error(CMDERR_NOT_CONNECTED);
340
341         origtarget = target;
342         free_ret = FALSE;
343         if (strcmp(target, ",") == 0 || strcmp(target, ".") == 0) {
344                 target = parse_special(&target, server, item,
345                                        NULL, &free_ret, NULL, 0);
346                 if (target != NULL && *target == '\0')
347                         target = NULL;
348         }
349
350         if (strcmp(target, "*") == 0) {
351                 /* send to active channel/query */
352                 if (item == NULL)
353                         cmd_param_error(CMDERR_NOT_JOINED);
354
355                 target_type = IS_CHANNEL(item) ?
356                         SEND_TARGET_CHANNEL : SEND_TARGET_NICK;
357                 target = (char *) window_item_get_target(item);
358         } else if (g_hash_table_lookup(optlist, "channel") != NULL)
359                 target_type = SEND_TARGET_CHANNEL;
360         else if (g_hash_table_lookup(optlist, "nick") != NULL)
361                 target_type = SEND_TARGET_NICK;
362         else {
363                 /* Need to rely on server_ischannel(). If the protocol
364                    doesn't really know if it's channel or nick based on the
365                    name, it should just assume it's nick, because when typing
366                    text to channels it's always sent with /MSG -channel. */
367                 target_type = server_ischannel(server, target) ?
368                         SEND_TARGET_CHANNEL : SEND_TARGET_NICK;
369         }
370
371         if (target != NULL)
372                 server->send_message(server, target, msg, target_type);
373
374         signal_emit(target != NULL && target_type == SEND_TARGET_CHANNEL ?
375                     "message own_public" : "message own_private", 4,
376                     server, msg, target, origtarget);
377
378         if (free_ret && target != NULL) g_free(target);
379         cmd_params_free(free_arg);
380 }
381
382 static void cmd_foreach(const char *data, SERVER_REC *server,
383                         WI_ITEM_REC *item)
384 {
385         command_runsub("foreach", data, server, item);
386 }
387
388 /* SYNTAX: FOREACH SERVER <command> */
389 static void cmd_foreach_server(const char *data, SERVER_REC *server)
390 {
391         GSList *list;
392
393         list = g_slist_copy(servers);
394         while (list != NULL) {
395                 signal_emit("send command", 3, data, list->data, NULL);
396                 list = g_slist_remove(list, list->data);
397         }
398 }
399
400 /* SYNTAX: FOREACH CHANNEL <command> */
401 static void cmd_foreach_channel(const char *data)
402 {
403         GSList *list;
404
405         list = g_slist_copy(channels);
406         while (list != NULL) {
407                 CHANNEL_REC *rec = list->data;
408
409                 signal_emit("send command", 3, data, rec->server, rec);
410                 list = g_slist_remove(list, list->data);
411         }
412 }
413
414 /* SYNTAX: FOREACH QUERY <command> */
415 static void cmd_foreach_query(const char *data)
416 {
417         GSList *list;
418
419         list = g_slist_copy(queries);
420         while (list != NULL) {
421                 QUERY_REC *rec = list->data;
422
423                 signal_emit("send command", 3, data, rec->server, rec);
424                 list = g_slist_remove(list, list->data);
425         }
426 }
427
428 void chat_commands_init(void)
429 {
430         settings_add_str("misc", "quit_message", "leaving");
431
432         command_bind("server", NULL, (SIGNAL_FUNC) cmd_server);
433         command_bind("server connect", NULL, (SIGNAL_FUNC) cmd_server_connect);
434         command_bind("connect", NULL, (SIGNAL_FUNC) cmd_connect);
435         command_bind("disconnect", NULL, (SIGNAL_FUNC) cmd_disconnect);
436         command_bind("quit", NULL, (SIGNAL_FUNC) cmd_quit);
437         command_bind("join", NULL, (SIGNAL_FUNC) cmd_join);
438         command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg);
439         command_bind("foreach", NULL, (SIGNAL_FUNC) cmd_foreach);
440         command_bind("foreach server", NULL, (SIGNAL_FUNC) cmd_foreach_server);
441         command_bind("foreach channel", NULL, (SIGNAL_FUNC) cmd_foreach_channel);
442         command_bind("foreach query", NULL, (SIGNAL_FUNC) cmd_foreach_query);
443
444         signal_add("default command server", (SIGNAL_FUNC) sig_default_command_server);
445
446         command_set_options("connect", "4 6 !! ssl +host noproxy -rawlog");
447         command_set_options("join", "invite");
448         command_set_options("msg", "channel nick");
449 }
450
451 void chat_commands_deinit(void)
452 {
453         command_unbind("server", (SIGNAL_FUNC) cmd_server);
454         command_unbind("server connect", (SIGNAL_FUNC) cmd_server_connect);
455         command_unbind("connect", (SIGNAL_FUNC) cmd_connect);
456         command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect);
457         command_unbind("quit", (SIGNAL_FUNC) cmd_quit);
458         command_unbind("join", (SIGNAL_FUNC) cmd_join);
459         command_unbind("msg", (SIGNAL_FUNC) cmd_msg);
460         command_unbind("foreach", (SIGNAL_FUNC) cmd_foreach);
461         command_unbind("foreach server", (SIGNAL_FUNC) cmd_foreach_server);
462         command_unbind("foreach channel", (SIGNAL_FUNC) cmd_foreach_channel);
463         command_unbind("foreach query", (SIGNAL_FUNC) cmd_foreach_query);
464
465         signal_remove("default command server", (SIGNAL_FUNC) sig_default_command_server);
466 }