Merged from silc_1_0_branch.
[silc.git] / apps / irssi / src / fe-common / core / fe-messages.c
1 /*
2  fe-messages.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 "module-formats.h"
23 #include "signals.h"
24 #include "commands.h"
25 #include "levels.h"
26 #include "misc.h"
27 #include "special-vars.h"
28 #include "settings.h"
29
30 #include "servers.h"
31 #include "channels.h"
32 #include "nicklist.h"
33 #include "ignore.h"
34
35 #include "window-items.h"
36 #include "fe-queries.h"
37 #include "hilight-text.h"
38 #include "printtext.h"
39
40 #define ishighalnum(c) ((unsigned char) (c) >= 128 || i_isalnum(c))
41
42 GHashTable *printnicks;
43
44 /* convert _underlined_ and *bold* words (and phrases) to use real
45    underlining or bolding */
46 char *expand_emphasis(WI_ITEM_REC *item, const char *text)
47 {
48         GString *str;
49         char *ret;
50         int pos;
51
52         g_return_val_if_fail(text != NULL, NULL);
53
54         str = g_string_new(text);
55
56         for (pos = 0; pos < str->len; pos++) {
57                 char type, *bgn, *end;
58
59                 bgn = str->str + pos;
60
61                 if (*bgn == '*') 
62                         type = 2; /* bold */
63                 else if (*bgn == '_') 
64                         type = 31; /* underlined */
65                 else
66                         continue;
67
68                 /* check that the beginning marker starts a word, and
69                    that the matching end marker ends a word */
70                 if ((pos > 0 && !i_isspace(bgn[-1])) || !ishighalnum(bgn[1]))
71                         continue;
72                 if ((end = strchr(bgn+1, *bgn)) == NULL)
73                         continue;
74                 if (!ishighalnum(end[-1]) || ishighalnum(end[1]) ||
75                     end[1] == type || end[1] == '*' || end[1] == '_')
76                         continue;
77
78                 if (IS_CHANNEL(item)) {
79                         /* check that this isn't a _nick_, we don't want to
80                            use emphasis on them. */
81                         int found;
82                         char c;
83
84                         c = end[1];
85                         end[1] = '\0';
86                         found = nicklist_find(CHANNEL(item), bgn) != NULL;
87                         end[1] = c;
88                         if (found) continue;
89                 }
90
91                 /* allow only *word* emphasis, not *multiple words* */
92                 if (!settings_get_bool("emphasis_multiword")) {
93                         char *c;
94                         for (c = bgn+1; c != end; c++) {
95                                 if (!ishighalnum(*c))
96                                         break;
97                         }
98                         if (c != end) continue;
99                 }
100
101                 if (settings_get_bool("emphasis_replace")) {
102                         *bgn = *end = type;
103                         pos += (end-bgn);
104                 } else {
105                         g_string_insert_c(str, pos, type);
106                         pos += (end - bgn) + 2;
107                         g_string_insert_c(str, pos++, type);
108                 }
109         }
110
111         ret = str->str;
112         g_string_free(str, FALSE);
113         return ret;
114 }
115
116 static char *channel_get_nickmode_rec(NICK_REC *nickrec)
117 {
118         char *emptystr;
119
120         if (!settings_get_bool("show_nickmode"))
121                 return "";
122
123         emptystr = settings_get_bool("show_nickmode_empty") ? " " : "";
124
125         return nickrec == NULL ? emptystr :
126                 nickrec->op ? "@" :
127                 nickrec->halfop ? "%" :
128                 nickrec->voice ? "+" :
129                 emptystr;
130 }
131
132 char *channel_get_nickmode(CHANNEL_REC *channel, const char *nick)
133 {
134         g_return_val_if_fail(nick != NULL, NULL);
135
136         return channel_get_nickmode_rec(channel == NULL ? NULL :
137                                         nicklist_find(channel, nick));
138 }
139
140 static void sig_message_public(SERVER_REC *server, const char *msg,
141                                const char *nick, const char *address,
142                                const char *target, NICK_REC *nickrec)
143 {
144         CHANNEL_REC *chanrec;
145         const char *nickmode, *printnick;
146         int for_me, print_channel, level;
147         char *color, *freemsg = NULL;
148
149         /* NOTE: this may return NULL if some channel is just closed with
150            /WINDOW CLOSE and server still sends the few last messages */
151         chanrec = channel_find(server, target);
152         if (nickrec == NULL && chanrec != NULL)
153                 nickrec = nicklist_find(chanrec, nick);
154
155         for_me = !settings_get_bool("hilight_nick_matches") ? FALSE :
156                 nick_match_msg(chanrec, msg, server->nick);
157         color = for_me ? NULL :
158                 hilight_match_nick(server, target, nick, address, MSGLEVEL_PUBLIC, msg);
159
160         print_channel = chanrec == NULL ||
161                 !window_item_is_active((WI_ITEM_REC *) chanrec);
162         if (!print_channel && settings_get_bool("print_active_channel") &&
163             window_item_window((WI_ITEM_REC *) chanrec)->items->next != NULL)
164                 print_channel = TRUE;
165
166         level = MSGLEVEL_PUBLIC;
167         if (for_me || color != NULL)
168                 level |= MSGLEVEL_HILIGHT;
169
170         if (settings_get_bool("emphasis"))
171                 msg = freemsg = expand_emphasis((WI_ITEM_REC *) chanrec, msg);
172
173         /* get nick mode & nick what to print the msg with
174            (in case there's multiple identical nicks) */
175         nickmode = channel_get_nickmode_rec(nickrec);
176         printnick = nickrec == NULL ? nick :
177                 g_hash_table_lookup(printnicks, nickrec);
178         if (printnick == NULL)
179                 printnick = nick;
180
181         if (!print_channel) {
182                 /* message to active channel in window */
183                 if (color != NULL) {
184                         /* highlighted nick */
185                         printformat(server, target, level,
186                                     TXT_PUBMSG_HILIGHT,
187                                     color, printnick, msg, nickmode);
188                 } else {
189                         printformat(server, target, level,
190                                     for_me ? TXT_PUBMSG_ME : TXT_PUBMSG,
191                                     printnick, msg, nickmode);
192                 }
193         } else {
194                 /* message to not existing/active channel */
195                 if (color != NULL) {
196                         /* highlighted nick */
197                         printformat(server, target, level,
198                                     TXT_PUBMSG_HILIGHT_CHANNEL,
199                                     color, printnick, target, msg, nickmode);
200                 } else {
201                         printformat(server, target, level,
202                                     for_me ? TXT_PUBMSG_ME_CHANNEL :
203                                     TXT_PUBMSG_CHANNEL,
204                                     printnick, target, msg, nickmode);
205                 }
206         }
207
208         g_free_not_null(freemsg);
209         g_free_not_null(color);
210 }
211
212 static void sig_message_private(SERVER_REC *server, const char *msg,
213                                 const char *nick, const char *address)
214 {
215         QUERY_REC *query;
216         char *freemsg = NULL;
217
218         query = query_find(server, nick);
219
220         if (settings_get_bool("emphasis"))
221                 msg = freemsg = expand_emphasis((WI_ITEM_REC *) query, msg);
222
223         printformat(server, nick, MSGLEVEL_MSGS,
224                     query == NULL ? TXT_MSG_PRIVATE :
225                     TXT_MSG_PRIVATE_QUERY, nick, address, msg);
226
227         g_free_not_null(freemsg);
228 }
229
230 static void sig_message_own_public(SERVER_REC *server, const char *msg,
231                                    const char *target)
232 {
233         WINDOW_REC *window;
234         CHANNEL_REC *channel;
235         const char *nickmode;
236         char *freemsg = NULL;
237         int print_channel;
238
239         channel = channel_find(server, target);
240         if (channel != NULL)
241                 target = channel->visible_name;
242
243         nickmode = channel_get_nickmode(channel, server->nick);
244
245         window = channel == NULL ? NULL :
246                 window_item_window((WI_ITEM_REC *) channel);
247
248         print_channel = window == NULL ||
249                 window->active != (WI_ITEM_REC *) channel;
250
251         if (!print_channel && settings_get_bool("print_active_channel") &&
252             window != NULL && g_slist_length(window->items) > 1)
253                 print_channel = TRUE;
254
255         if (settings_get_bool("emphasis"))
256                 msg = freemsg = expand_emphasis((WI_ITEM_REC *) channel, msg);
257
258         if (!print_channel) {
259                 printformat(server, target, MSGLEVEL_PUBLIC | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT,
260                             TXT_OWN_MSG, server->nick, msg, nickmode);
261         } else {
262                 printformat(server, target, MSGLEVEL_PUBLIC | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT,
263                             TXT_OWN_MSG_CHANNEL, server->nick, target, msg, nickmode);
264         }
265
266         g_free_not_null(freemsg);
267 }
268
269 static void sig_message_own_private(SERVER_REC *server, const char *msg,
270                                     const char *target, const char *origtarget)
271 {
272         QUERY_REC *query;
273         char *freemsg = NULL;
274
275         g_return_if_fail(server != NULL);
276         g_return_if_fail(msg != NULL);
277
278         if (target == NULL) {
279                 /* this should only happen if some special target failed and
280                    we should display some error message. currently the special
281                    targets are only ',' and '.'. */
282                 g_return_if_fail(strcmp(origtarget, ",") == 0 ||
283                                  strcmp(origtarget, ".") == 0);
284
285                 printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
286                             *origtarget == ',' ? TXT_NO_MSGS_GOT :
287                             TXT_NO_MSGS_SENT);
288                 signal_stop();
289                 return;
290         }
291
292         query = privmsg_get_query(server, target, TRUE, MSGLEVEL_MSGS);
293
294         if (settings_get_bool("emphasis"))
295                 msg = freemsg = expand_emphasis((WI_ITEM_REC *) query, msg);
296
297         printformat(server, target,
298                     MSGLEVEL_MSGS | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT,
299                     query == NULL ? TXT_OWN_MSG_PRIVATE :
300                     TXT_OWN_MSG_PRIVATE_QUERY, target, msg, server->nick);
301
302         g_free_not_null(freemsg);
303 }
304
305 static void sig_message_join(SERVER_REC *server, const char *channel,
306                              const char *nick, const char *address)
307 {
308         printformat(server, channel, MSGLEVEL_JOINS,
309                     TXT_JOIN, nick, address, channel);
310 }
311
312 static void sig_message_part(SERVER_REC *server, const char *channel,
313                              const char *nick, const char *address,
314                              const char *reason)
315 {
316         printformat(server, channel, MSGLEVEL_PARTS,
317                     TXT_PART, nick, address, channel, reason);
318 }
319
320 static void sig_message_quit(SERVER_REC *server, const char *nick,
321                              const char *address, const char *reason)
322 {
323         WINDOW_REC *window;
324         GString *chans;
325         GSList *tmp, *windows;
326         char *print_channel;
327         int once, count;
328
329         if (ignore_check(server, nick, address, NULL, reason, MSGLEVEL_QUITS))
330                 return;
331
332         print_channel = NULL;
333         once = settings_get_bool("show_quit_once");
334
335         count = 0; windows = NULL;
336         chans = g_string_new(NULL);
337         for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
338                 CHANNEL_REC *rec = tmp->data;
339
340                 if (!nicklist_find(rec, nick))
341                         continue;
342
343                 if (ignore_check(server, nick, address, rec->visible_name,
344                                  reason, MSGLEVEL_QUITS)) {
345                         count++;
346                         continue;
347                 }
348
349                 if (print_channel == NULL ||
350                     active_win->active == (WI_ITEM_REC *) rec)
351                         print_channel = rec->visible_name;
352
353                 if (once)
354                         g_string_sprintfa(chans, "%s,", rec->visible_name);
355                 else {
356                         window = window_item_window((WI_ITEM_REC *) rec);
357                         if (g_slist_find(windows, window) == NULL) {
358                                 windows = g_slist_append(windows, window);
359                                 printformat(server, rec->visible_name,
360                                             MSGLEVEL_QUITS,
361                                             TXT_QUIT, nick, address, reason,
362                                             rec->visible_name);
363                         }
364                 }
365                 count++;
366         }
367         g_slist_free(windows);
368
369         if (!once) {
370                 /* check if you had query with the nick and
371                    display the quit there too */
372                 QUERY_REC *query = query_find(server, nick);
373                 if (query != NULL) {
374                         printformat(server, nick, MSGLEVEL_QUITS,
375                                     TXT_QUIT, nick, address, reason, "");
376                 }
377         }
378
379         if (once || count == 0) {
380                 if (chans->len > 0)
381                         g_string_truncate(chans, chans->len-1);
382                 printformat(server, print_channel, MSGLEVEL_QUITS,
383                             count <= 1 ? TXT_QUIT : TXT_QUIT_ONCE,
384                             nick, address, reason, chans->str);
385         }
386         g_string_free(chans, TRUE);
387 }
388
389 static void sig_message_kick(SERVER_REC *server, const char *channel,
390                              const char *nick, const char *kicker,
391                              const char *address, const char *reason)
392 {
393         printformat(server, channel, MSGLEVEL_KICKS,
394                     TXT_KICK, nick, channel, kicker, reason, address);
395 }
396
397 static void print_nick_change_channel(SERVER_REC *server, const char *channel,
398                                       const char *newnick, const char *oldnick,
399                                       const char *address,
400                                       int ownnick)
401 {
402         int level;
403
404         if (ignore_check(server, oldnick, address,
405                          channel, newnick, MSGLEVEL_NICKS))
406                 return;
407
408         level = MSGLEVEL_NICKS;
409         if (ownnick) level |= MSGLEVEL_NO_ACT;
410
411         printformat(server, channel, level,
412                     ownnick ? TXT_YOUR_NICK_CHANGED : TXT_NICK_CHANGED,
413                     oldnick, newnick, channel, address);
414 }
415
416 static void print_nick_change(SERVER_REC *server, const char *newnick,
417                               const char *oldnick, const char *address,
418                               int ownnick)
419 {
420         GSList *tmp, *windows;
421         int msgprint;
422
423         msgprint = FALSE;
424
425         /* Print to each channel/query where the nick is.
426            Don't print more than once to the same window. */
427         windows = NULL;
428         for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
429                 CHANNEL_REC *channel = tmp->data;
430                 WINDOW_REC *window =
431                         window_item_window((WI_ITEM_REC *) channel);
432
433                 if (nicklist_find(channel, newnick) == NULL ||
434                     g_slist_find(windows, window) != NULL)
435                         continue;
436
437                 windows = g_slist_append(windows, window);
438                 print_nick_change_channel(server, channel->visible_name,
439                                           newnick, oldnick, address, ownnick);
440                 msgprint = TRUE;
441         }
442
443         g_slist_free(windows);
444
445         if (!msgprint && ownnick) {
446                 printformat(server, NULL, MSGLEVEL_NICKS,
447                             TXT_YOUR_NICK_CHANGED, oldnick, newnick, "",
448                             address);
449         }
450 }
451
452 static void sig_message_nick(SERVER_REC *server, const char *newnick,
453                              const char *oldnick, const char *address)
454 {
455         print_nick_change(server, newnick, oldnick, address, FALSE);
456 }
457
458 static void sig_message_own_nick(SERVER_REC *server, const char *newnick,
459                                  const char *oldnick, const char *address)
460 {
461         if (!settings_get_bool("show_own_nickchange_once"))
462                 print_nick_change(server, newnick, oldnick, address, TRUE);
463         else {
464                 printformat(server, NULL, MSGLEVEL_NICKS,
465                             TXT_YOUR_NICK_CHANGED, oldnick, newnick, "",
466                             address);
467         }
468 }
469
470 static void sig_message_invite(SERVER_REC *server, const char *channel,
471                                const char *nick, const char *address)
472 {
473         char *str;
474
475         str = show_lowascii(channel);
476         printformat(server, NULL, MSGLEVEL_INVITES,
477                     TXT_INVITE, nick, str, address);
478         g_free(str);
479 }
480
481 static void sig_message_topic(SERVER_REC *server, const char *channel,
482                               const char *topic,
483                               const char *nick, const char *address)
484 {
485         printformat(server, channel, MSGLEVEL_TOPICS,
486                     *topic != '\0' ? TXT_NEW_TOPIC : TXT_TOPIC_UNSET,
487                     nick, channel, topic, address);
488 }
489
490 static int printnick_exists(NICK_REC *first, NICK_REC *ignore,
491                             const char *nick)
492 {
493         char *printnick;
494
495         while (first != NULL) {
496                 if (first != ignore) {
497                         printnick = g_hash_table_lookup(printnicks, first);
498                         if (printnick != NULL && strcmp(printnick, nick) == 0)
499                                 return TRUE;
500                 }
501
502                 first = first->next;
503         }
504
505         return FALSE;
506 }
507
508 static NICK_REC *printnick_find_original(NICK_REC *nick)
509 {
510         while (nick != NULL) {
511                 if (g_hash_table_lookup(printnicks, nick) == NULL)
512                         return nick;
513
514                 nick = nick->next;
515         }
516
517         return NULL;
518 }
519
520 static void sig_nicklist_new(CHANNEL_REC *channel, NICK_REC *nick)
521 {
522         NICK_REC *firstnick;
523         GString *newnick;
524         char *nickhost, *p;
525         int n;
526
527         if (nick->host == NULL)
528                 return;
529
530         firstnick = g_hash_table_lookup(channel->nicks, nick->nick);
531         if (firstnick->next == NULL)
532                 return;
533
534         if (nick == channel->ownnick) {
535                 /* own nick is being added, might be a nick change and
536                    someone else having the original nick already in use.. */
537                 nick = printnick_find_original(firstnick->next);
538                 if (nick == NULL)
539                         return; /* nope, we have it */
540         }
541
542         /* identical nick already exists, have to change it somehow.. */
543         p = strchr(nick->host, '@');
544         if (p == NULL) p = nick->host; else p++;
545
546         nickhost = g_strdup_printf("%s@%s", nick->nick, p);
547         p = strchr(nickhost+strlen(nick->nick), '.');
548         if (p != NULL) *p = '\0';
549
550         if (!printnick_exists(firstnick, nick, nickhost)) {
551                 /* use nick@host */
552                 g_hash_table_insert(printnicks, nick, nickhost);
553                 return;
554         }
555
556         newnick = g_string_new(NULL);
557         n = 2;
558         do {
559                 g_string_sprintf(newnick, "%s%d", nickhost, n);
560                 n++;
561         } while (printnick_exists(firstnick, nick, newnick->str));
562
563         g_hash_table_insert(printnicks, nick, newnick->str);
564         g_string_free(newnick, FALSE);
565         g_free(nickhost);
566 }
567
568 static void sig_nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick)
569 {
570         char *nickname;
571
572         nickname = g_hash_table_lookup(printnicks, nick);
573         if (nickname != NULL) {
574                 g_free(nickname);
575                 g_hash_table_remove(printnicks, nick);
576         }
577 }
578
579 static void sig_nicklist_changed(CHANNEL_REC *channel, NICK_REC *nick)
580 {
581         sig_nicklist_remove(channel, nick);
582         sig_nicklist_new(channel, nick);
583 }
584
585 static void sig_channel_joined(CHANNEL_REC *channel)
586 {
587         NICK_REC *nick;
588         char *nickname;
589
590         /* channel->ownnick is set at this point - check if our own nick
591            has been changed, if it was set it back to the original nick and
592            change the previous original to something else */
593
594         nickname = g_hash_table_lookup(printnicks, channel->ownnick);
595         if (nickname == NULL)
596                 return;
597
598         g_free(nickname);
599         g_hash_table_remove(printnicks, channel->ownnick);
600
601         /* our own nick is guaranteed to be the first in list */
602         nick = channel->ownnick->next;
603         while (nick != NULL) {
604                 if (g_hash_table_lookup(printnicks, nick) == NULL) {
605                         sig_nicklist_new(channel, nick);
606                         break;
607                 }
608                 nick = nick->next;
609         }
610 }
611
612 static void g_hash_free_value(void *key, void *value)
613 {
614         g_free(value);
615 }
616
617 void fe_messages_init(void)
618 {
619         printnicks = g_hash_table_new((GHashFunc) g_direct_hash,
620                                       (GCompareFunc) g_direct_equal);
621
622         settings_add_bool("lookandfeel", "hilight_nick_matches", TRUE);
623         settings_add_bool("lookandfeel", "emphasis", TRUE);
624         settings_add_bool("lookandfeel", "emphasis_replace", FALSE);
625         settings_add_bool("lookandfeel", "emphasis_multiword", FALSE);
626         settings_add_bool("lookandfeel", "show_nickmode", TRUE);
627         settings_add_bool("lookandfeel", "show_nickmode_empty", TRUE);
628         settings_add_bool("lookandfeel", "print_active_channel", FALSE);
629         settings_add_bool("lookandfeel", "show_quit_once", FALSE);
630         settings_add_bool("lookandfeel", "show_own_nickchange_once", FALSE);
631
632         signal_add_last("message public", (SIGNAL_FUNC) sig_message_public);
633         signal_add_last("message private", (SIGNAL_FUNC) sig_message_private);
634         signal_add_last("message own_public", (SIGNAL_FUNC) sig_message_own_public);
635         signal_add_last("message own_private", (SIGNAL_FUNC) sig_message_own_private);
636         signal_add_last("message join", (SIGNAL_FUNC) sig_message_join);
637         signal_add_last("message part", (SIGNAL_FUNC) sig_message_part);
638         signal_add_last("message quit", (SIGNAL_FUNC) sig_message_quit);
639         signal_add_last("message kick", (SIGNAL_FUNC) sig_message_kick);
640         signal_add_last("message nick", (SIGNAL_FUNC) sig_message_nick);
641         signal_add_last("message own_nick", (SIGNAL_FUNC) sig_message_own_nick);
642         signal_add_last("message invite", (SIGNAL_FUNC) sig_message_invite);
643         signal_add_last("message topic", (SIGNAL_FUNC) sig_message_topic);
644
645         signal_add("nicklist new", (SIGNAL_FUNC) sig_nicklist_new);
646         signal_add("nicklist remove", (SIGNAL_FUNC) sig_nicklist_remove);
647         signal_add("nicklist changed", (SIGNAL_FUNC) sig_nicklist_changed);
648         signal_add("nicklist host changed", (SIGNAL_FUNC) sig_nicklist_new);
649         signal_add("channel joined", (SIGNAL_FUNC) sig_channel_joined);
650 }
651
652 void fe_messages_deinit(void)
653 {
654         g_hash_table_foreach(printnicks, (GHFunc) g_hash_free_value, NULL);
655         g_hash_table_destroy(printnicks);
656
657         signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
658         signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
659         signal_remove("message own_public", (SIGNAL_FUNC) sig_message_own_public);
660         signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private);
661         signal_remove("message join", (SIGNAL_FUNC) sig_message_join);
662         signal_remove("message part", (SIGNAL_FUNC) sig_message_part);
663         signal_remove("message quit", (SIGNAL_FUNC) sig_message_quit);
664         signal_remove("message kick", (SIGNAL_FUNC) sig_message_kick);
665         signal_remove("message nick", (SIGNAL_FUNC) sig_message_nick);
666         signal_remove("message own_nick", (SIGNAL_FUNC) sig_message_own_nick);
667         signal_remove("message invite", (SIGNAL_FUNC) sig_message_invite);
668         signal_remove("message topic", (SIGNAL_FUNC) sig_message_topic);
669
670         signal_remove("nicklist new", (SIGNAL_FUNC) sig_nicklist_new);
671         signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nicklist_remove);
672         signal_remove("nicklist changed", (SIGNAL_FUNC) sig_nicklist_changed);
673         signal_remove("nicklist host changed", (SIGNAL_FUNC) sig_nicklist_new);
674         signal_remove("channel joined", (SIGNAL_FUNC) sig_channel_joined);
675 }