source: irc_channel.c @ ba52ac5

Last change on this file since ba52ac5 was 3fbce97, checked in by Wilmer van der Gaast <wilmer@…>, at 2016-09-24T20:14:34Z

Merge branch 'master' into parson

  • Property mode set to 100644
File size: 23.2 KB
Line 
1/********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2013 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* The IRC-based UI - Representing (virtual) channels.                  */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  USA
24*/
25
26#include "bitlbee.h"
27
28static char *set_eval_channel_type(set_t *set, char *value);
29static gint irc_channel_user_cmp(gconstpointer a_, gconstpointer b_);
30static const struct irc_channel_funcs control_channel_funcs;
31
32extern const struct irc_channel_funcs irc_channel_im_chat_funcs;
33
34irc_channel_t *irc_channel_new(irc_t *irc, const char *name)
35{
36        irc_channel_t *ic;
37        set_t *s;
38
39        if (!irc_channel_name_ok(name) || irc_channel_by_name(irc, name)) {
40                return NULL;
41        }
42
43        ic = g_new0(irc_channel_t, 1);
44        ic->irc = irc;
45        ic->name = g_strdup(name);
46        strcpy(ic->mode, CMODE);
47
48        irc_channel_add_user(ic, irc->root);
49
50        irc->channels = g_slist_append(irc->channels, ic);
51
52        set_add(&ic->set, "auto_join", "false", set_eval_bool, ic);
53
54        s = set_add(&ic->set, "type", "control", set_eval_channel_type, ic);
55        s->flags |= SET_NOSAVE;    /* Layer violation (XML format detail) */
56
57        if (name[0] == '&') {
58                set_setstr(&ic->set, "type", "control");
59        } else { /* if( name[0] == '#' ) */
60                set_setstr(&ic->set, "type", "chat");
61        }
62
63        return ic;
64}
65
66irc_channel_t *irc_channel_by_name(irc_t *irc, const char *name)
67{
68        GSList *l;
69
70        for (l = irc->channels; l; l = l->next) {
71                irc_channel_t *ic = l->data;
72
73                if (irc_channel_name_cmp(name, ic->name) == 0) {
74                        return ic;
75                }
76        }
77
78        return NULL;
79}
80
81irc_channel_t *irc_channel_get(irc_t *irc, char *id)
82{
83        irc_channel_t *ic, *ret = NULL;
84        GSList *l;
85        int nr;
86
87        if (sscanf(id, "%d", &nr) == 1 && nr < 1000) {
88                for (l = irc->channels; l; l = l->next) {
89                        ic = l->data;
90                        if ((nr--) == 0) {
91                                return ic;
92                        }
93                }
94
95                return NULL;
96        }
97
98        /* Exact match first: Partial match only sucks if there's a channel
99           #aa and #aabb */
100        if ((ret = irc_channel_by_name(irc, id))) {
101                return ret;
102        }
103
104        for (l = irc->channels; l; l = l->next) {
105                ic = l->data;
106
107                if (strstr(ic->name, id)) {
108                        /* Make sure it's a unique match. */
109                        if (!ret) {
110                                ret = ic;
111                        } else {
112                                return NULL;
113                        }
114                }
115        }
116
117        return ret;
118}
119
120int irc_channel_free(irc_channel_t *ic)
121{
122        irc_t *irc;
123        GSList *l;
124
125        if (ic == NULL) {
126                return 0;
127        }
128        irc = ic->irc;
129
130        if (ic->flags & IRC_CHANNEL_JOINED) {
131                irc_channel_del_user(ic, irc->user, IRC_CDU_KICK, "Cleaning up channel");
132        }
133
134        if (ic->f->_free) {
135                ic->f->_free(ic);
136        }
137
138        while (ic->set) {
139                set_del(&ic->set, ic->set->key);
140        }
141
142        irc->channels = g_slist_remove(irc->channels, ic);
143        while (ic->users) {
144                g_free(ic->users->data);
145                ic->users = g_slist_remove(ic->users, ic->users->data);
146        }
147
148        for (l = irc->users; l; l = l->next) {
149                irc_user_t *iu = l->data;
150
151                if (iu->last_channel == ic) {
152                        iu->last_channel = irc->default_channel;
153                }
154        }
155
156        if (ic->pastebuf_timer) {
157                b_event_remove(ic->pastebuf_timer);
158        }
159
160        g_free(ic->name);
161        g_free(ic->topic);
162        g_free(ic->topic_who);
163        g_free(ic);
164
165        return 1;
166}
167
168struct irc_channel_free_data {
169        irc_t *irc;
170        irc_channel_t *ic;
171        char *name;
172};
173
174static gboolean irc_channel_free_callback(gpointer data, gint fd, b_input_condition cond)
175{
176        struct irc_channel_free_data *d = data;
177
178        if (g_slist_find(irc_connection_list, d->irc) &&
179            irc_channel_by_name(d->irc, d->name) == d->ic &&
180            !(d->ic->flags & IRC_CHANNEL_JOINED)) {
181                irc_channel_free(d->ic);
182        }
183
184        g_free(d->name);
185        g_free(d);
186        return FALSE;
187}
188
189/* Free the channel, but via the event loop, so after finishing whatever event
190   we're currently handling. */
191void irc_channel_free_soon(irc_channel_t *ic)
192{
193        struct irc_channel_free_data *d = g_new0(struct irc_channel_free_data, 1);
194
195        d->irc = ic->irc;
196        d->ic = ic;
197        d->name = g_strdup(ic->name);
198
199        b_timeout_add(0, irc_channel_free_callback, d);
200}
201
202static char *set_eval_channel_type(set_t *set, char *value)
203{
204        struct irc_channel *ic = set->data;
205        const struct irc_channel_funcs *new;
206
207        if (strcmp(value, "control") == 0) {
208                new = &control_channel_funcs;
209        } else if (ic != ic->irc->default_channel && strcmp(value, "chat") == 0) {
210                new = &irc_channel_im_chat_funcs;
211        } else {
212                return SET_INVALID;
213        }
214
215        /* Skip the free/init if nothing is being changed */
216        if (ic->f == new) {
217                return value;
218        }
219
220        /* TODO: Return values. */
221        if (ic->f && ic->f->_free) {
222                ic->f->_free(ic);
223        }
224
225        ic->f = new;
226
227        if (ic->f && ic->f->_init) {
228                ic->f->_init(ic);
229        }
230
231        return value;
232}
233
234int irc_channel_add_user(irc_channel_t *ic, irc_user_t *iu)
235{
236        irc_channel_user_t *icu;
237
238        if (irc_channel_has_user(ic, iu)) {
239                return 0;
240        }
241
242        icu = g_new0(irc_channel_user_t, 1);
243        icu->iu = iu;
244
245        ic->users = g_slist_insert_sorted(ic->users, icu, irc_channel_user_cmp);
246
247        irc_channel_update_ops(ic, set_getstr(&ic->irc->b->set, "ops"));
248
249        if (iu == ic->irc->user || ic->flags & IRC_CHANNEL_JOINED) {
250                ic->flags |= IRC_CHANNEL_JOINED;
251                irc_send_join(ic, iu);
252        }
253
254        return 1;
255}
256
257int irc_channel_del_user(irc_channel_t *ic, irc_user_t *iu, irc_channel_del_user_type_t type, const char *msg)
258{
259        irc_channel_user_t *icu;
260
261        if (!(icu = irc_channel_has_user(ic, iu))) {
262                if (iu == ic->irc->user && type == IRC_CDU_KICK) {
263                        /* an error happened before joining, inform the client with a numeric */
264                        irc_send_num(ic->irc, 403, "%s :Error joining channel (check control channel?)", ic->name);
265                }
266                return 0;
267        }
268
269        ic->users = g_slist_remove(ic->users, icu);
270        g_free(icu);
271
272        if (!(ic->flags & IRC_CHANNEL_JOINED) || type == IRC_CDU_SILENT) {
273        }
274        /* Do nothing. The caller should promise it won't screw
275           up state of the IRC client. :-) */
276        else if (type == IRC_CDU_PART) {
277                irc_send_part(ic, iu, msg);
278        } else if (type == IRC_CDU_KICK) {
279                irc_send_kick(ic, iu, ic->irc->root, msg);
280        }
281
282        if (iu == ic->irc->user) {
283                ic->flags &= ~IRC_CHANNEL_JOINED;
284
285                if (ic->irc->status & USTATUS_SHUTDOWN) {
286                        /* Don't do anything fancy when we're shutting down anyway. */
287                } else if (ic->flags & IRC_CHANNEL_TEMP) {
288                        irc_channel_free_soon(ic);
289                } else {
290                        /* Flush userlist now. The user won't see it anyway. */
291                        while (ic->users) {
292                                g_free(ic->users->data);
293                                ic->users = g_slist_remove(ic->users, ic->users->data);
294                        }
295                        irc_channel_add_user(ic, ic->irc->root);
296                }
297        }
298
299        return 1;
300}
301
302irc_channel_user_t *irc_channel_has_user(irc_channel_t *ic, irc_user_t *iu)
303{
304        GSList *l;
305
306        for (l = ic->users; l; l = l->next) {
307                irc_channel_user_t *icu = l->data;
308
309                if (icu->iu == iu) {
310                        return icu;
311                }
312        }
313
314        return NULL;
315}
316
317/* Find a channel we're currently in, that currently has iu in it. */
318struct irc_channel *irc_channel_with_user(irc_t *irc, irc_user_t *iu)
319{
320        GSList *l;
321
322        for (l = irc->channels; l; l = l->next) {
323                irc_channel_t *ic = l->data;
324
325                if (strcmp(set_getstr(&ic->set, "type"), "control") != 0) {
326                        continue;
327                }
328
329                if ((ic->flags & IRC_CHANNEL_JOINED) &&
330                    irc_channel_has_user(ic, iu)) {
331                        return ic;
332                }
333        }
334
335        /* If there was no match, try once more but just see if the user
336           *would* be in the channel, i.e. if s/he were online. */
337        if (iu->bu == NULL) {
338                return NULL;
339        }
340
341        for (l = irc->channels; l; l = l->next) {
342                irc_channel_t *ic = l->data;
343
344                if (strcmp(set_getstr(&ic->set, "type"), "control") != 0) {
345                        continue;
346                }
347
348                if ((ic->flags & IRC_CHANNEL_JOINED) &&
349                    irc_channel_wants_user(ic, iu)) {
350                        return ic;
351                }
352        }
353
354        return NULL;
355}
356
357int irc_channel_set_topic(irc_channel_t *ic, const char *topic, const irc_user_t *iu)
358{
359        g_free(ic->topic);
360        ic->topic = g_strdup(topic);
361
362        g_free(ic->topic_who);
363        if (iu) {
364                ic->topic_who = g_strdup_printf("%s!%s@%s", iu->nick, iu->user, iu->host);
365        } else {
366                ic->topic_who = NULL;
367        }
368
369        ic->topic_time = time(NULL);
370
371        if (ic->flags & IRC_CHANNEL_JOINED) {
372                irc_send_topic(ic, TRUE);
373        }
374
375        return 1;
376}
377
378void irc_channel_user_set_mode(irc_channel_t *ic, irc_user_t *iu, irc_channel_user_flags_t flags)
379{
380        irc_channel_user_t *icu = irc_channel_has_user(ic, iu);
381
382        if (!icu || icu->flags == flags) {
383                return;
384        }
385
386        if (ic->flags & IRC_CHANNEL_JOINED) {
387                irc_send_channel_user_mode_diff(ic, iu, icu->flags, flags);
388        }
389
390        icu->flags = flags;
391}
392
393void irc_channel_set_mode(irc_channel_t *ic, const char *s)
394{
395        irc_t *irc = ic->irc;
396        char m[128], st = 1;
397        const char *t;
398        int i;
399        char changes[512], *p, st2 = 2;
400
401        memset(m, 0, sizeof(m));
402
403        for (t = ic->mode; *t; t++) {
404                if (*t < sizeof(m)) {
405                        m[(int) *t] = 1;
406                }
407        }
408
409        p = changes;
410        for (t = s; *t; t++) {
411                if (*t == '+' || *t == '-') {
412                        st = *t == '+';
413                } else if (strchr(CMODES, *t)) {
414                        if (m[(int) *t] != st) {
415                                if (st != st2) {
416                                        st2 = st, *p++ = st ? '+' : '-';
417                                }
418                                *p++ = *t;
419                        }
420                        m[(int) *t] = st;
421                }
422        }
423        *p = '\0';
424
425        memset(ic->mode, 0, sizeof(ic->mode));
426
427        for (i = 'A'; i <= 'z' && strlen(ic->mode) < (sizeof(ic->mode) - 1); i++) {
428                if (m[i]) {
429                        ic->mode[strlen(ic->mode)] = i;
430                }
431        }
432
433        if (*changes && (ic->flags & IRC_CHANNEL_JOINED)) {
434                irc_write(irc, ":%s!%s@%s MODE %s :%s", irc->root->nick,
435                          irc->root->user, irc->root->host, ic->name,
436                          changes);
437        }
438}
439
440char irc_channel_user_get_prefix(irc_channel_user_t *icu)
441{
442        if (icu->flags & IRC_CHANNEL_USER_OP) {
443                return '@';
444        } else if (icu->flags & IRC_CHANNEL_USER_HALFOP) {
445                return '%';
446        } else if (icu->flags & IRC_CHANNEL_USER_VOICE) {
447                return '+';
448        }
449        return 0;
450}
451
452void irc_channel_auto_joins(irc_t *irc, account_t *acc)
453{
454        GSList *l;
455
456        for (l = irc->channels; l; l = l->next) {
457                irc_channel_t *ic = l->data;
458                gboolean aj = set_getbool(&ic->set, "auto_join");
459                char *type;
460
461                if (acc &&
462                    (type = set_getstr(&ic->set, "chat_type")) &&
463                    strcmp(type, "room") == 0) {
464                        /* Bit of an ugly special case: Handle chatrooms here, we
465                           can only auto-join them if their account is online. */
466                        char *acc_s;
467
468                        if (!aj && !(ic->flags & IRC_CHANNEL_JOINED)) {
469                                /* Only proceed if this one's marked as auto_join
470                                   or if we're in it already. (Very likely the IRC
471                                   client auto-(re)joining at reconnect time.) */
472                                continue;
473                        } else if (!(acc_s = set_getstr(&ic->set, "account"))) {
474                                continue;
475                        } else if (account_get(irc->b, acc_s) != acc) {
476                                continue;
477                        } else if (acc->ic == NULL || !(acc->ic->flags & OPT_LOGGED_IN)) {
478                                continue;
479                        } else {
480                                ic->f->join(ic);
481                        }
482                } else if (aj) {
483                        irc_channel_add_user(ic, irc->user);
484                }
485        }
486}
487
488void irc_channel_printf(irc_channel_t *ic, char *format, ...)
489{
490        va_list params;
491        char *text;
492
493        va_start(params, format);
494        text = g_strdup_vprintf(format, params);
495        va_end(params);
496
497        irc_send_msg(ic->irc->root, "PRIVMSG", ic->name, text, NULL);
498        g_free(text);
499}
500
501gboolean irc_channel_name_ok(const char *name_)
502{
503        const unsigned char *name = (unsigned char *) name_;
504        int i;
505
506        if (name_[0] == '\0') {
507                return FALSE;
508        }
509
510        /* Check if the first character is in CTYPES (#&) */
511        if (strchr(CTYPES, name_[0]) == NULL) {
512                return FALSE;
513        }
514
515        /* RFC 1459 keeps amazing me: While only a "few" chars are allowed
516           in nicknames, channel names can be pretty much anything as long
517           as they start with # or &. I'll be a little bit more strict and
518           disallow all non-printable characters. */
519        for (i = 1; name[i]; i++) {
520                if (name[i] <= ' ' || name[i] == ',') {
521                        return FALSE;
522                }
523        }
524
525        return TRUE;
526}
527
528void irc_channel_name_strip(char *name)
529{
530        int i, j;
531
532        for (i = j = 0; name[i]; i++) {
533                if (name[i] > ' ' && name[i] != ',') {
534                        name[j++] = name[i];
535                }
536        }
537
538        name[j] = '\0';
539}
540
541int irc_channel_name_cmp(const char *a_, const char *b_)
542{
543        static unsigned char case_map[256];
544        const unsigned char *a = (unsigned char *) a_, *b = (unsigned char *) b_;
545        int i;
546
547        if (case_map['A'] == '\0') {
548                for (i = 33; i < 256; i++) {
549                        if (i != ',') {
550                                case_map[i] = i;
551                        }
552                }
553
554                for (i = 0; i < 26; i++) {
555                        case_map['A' + i] = 'a' + i;
556                }
557
558                case_map['['] = '{';
559                case_map[']'] = '}';
560                case_map['~'] = '`';
561                case_map['\\'] = '|';
562        }
563
564        if (!irc_channel_name_ok(a_) || !irc_channel_name_ok(b_)) {
565                return -1;
566        }
567
568        for (i = 0; a[i] && b[i] && case_map[a[i]] && case_map[b[i]]; i++) {
569                if (case_map[a[i]] == case_map[b[i]]) {
570                        continue;
571                } else {
572                        return case_map[a[i]] - case_map[b[i]];
573                }
574        }
575
576        return case_map[a[i]] - case_map[b[i]];
577}
578
579gboolean irc_channel_is_unused(irc_t *irc, char *name)
580{
581        char *type, *chat_type;
582        irc_channel_t *oic;
583
584        if (!irc_channel_name_ok(name)) {
585                return FALSE;
586        }
587
588        if (!(oic = irc_channel_by_name(irc, name))) {
589                return TRUE;
590        }
591
592        type = set_getstr(&oic->set, "type");
593        chat_type = set_getstr(&oic->set, "chat_type");
594
595        if (type && chat_type && oic->data == FALSE &&
596            strcmp(type, "chat") == 0 &&
597            strcmp(chat_type, "groupchat") == 0) {
598                /* There's a channel with this name already, but it looks
599                   like it's not in use yet. Most likely the IRC client
600                   rejoined the channel after a reconnect. Remove it so
601                   we can reuse its name. */
602                irc_channel_free(oic);
603                return TRUE;
604        }
605
606        return FALSE;
607}
608
609char *irc_channel_name_gen(irc_t *irc, const char *hint)
610{
611        char name[MAX_NICK_LENGTH + 1] = { 0 };
612        char *translit_name;
613        gsize bytes_written;
614
615        translit_name = g_convert_with_fallback(hint, -1, "ASCII//TRANSLIT", "UTF-8", "", NULL, &bytes_written, NULL);
616
617        if (!translit_name) {
618                /* Same thing as in nick_gen() in nick.c, try again without //TRANSLIT */
619                translit_name = g_convert_with_fallback(hint, -1, "ASCII", "UTF-8", "", NULL, &bytes_written, NULL);
620        }
621
622        if (!translit_name) {
623                return NULL;
624        }
625
626        if (bytes_written > MAX_NICK_LENGTH) {
627                translit_name[MAX_NICK_LENGTH] = '\0';
628        }
629
630        name[0] = '#';
631        strncpy(name + 1, translit_name, MAX_NICK_LENGTH - 1);
632        name[MAX_NICK_LENGTH] = '\0';
633
634        g_free(translit_name);
635
636        irc_channel_name_strip(name);
637
638        if (set_getbool(&irc->b->set, "lcnicks")) {
639                nick_lc(irc, name + 1);
640        }
641
642        while (!irc_channel_is_unused(irc, name)) {
643                underscore_dedupe(name);
644        }
645
646        return g_strdup(name);
647}
648
649gboolean irc_channel_name_hint(irc_channel_t *ic, const char *name)
650{
651        irc_t *irc = ic->irc;
652        char *full_name;
653
654        /* Don't rename a channel if the user's in it already. */
655        if (ic->flags & IRC_CHANNEL_JOINED) {
656                return FALSE;
657        }
658
659        if (!(full_name = irc_channel_name_gen(irc, name))) {
660                return FALSE;
661        }
662
663        g_free(ic->name);
664        ic->name = full_name;
665
666        return TRUE;
667}
668
669static gint irc_channel_user_cmp(gconstpointer a_, gconstpointer b_)
670{
671        const irc_channel_user_t *a = a_, *b = b_;
672
673        return irc_user_cmp(a->iu, b->iu);
674}
675
676void irc_channel_update_ops(irc_channel_t *ic, char *value)
677{
678        irc_channel_user_set_mode(ic, ic->irc->root,
679                                  (strcmp(value, "both") == 0 ||
680                                   strcmp(value, "root") == 0) ? IRC_CHANNEL_USER_OP : 0);
681        irc_channel_user_set_mode(ic, ic->irc->user,
682                                  (strcmp(value, "both") == 0 ||
683                                   strcmp(value, "user") == 0) ? IRC_CHANNEL_USER_OP : 0);
684}
685
686char *set_eval_irc_channel_ops(set_t *set, char *value)
687{
688        irc_t *irc = set->data;
689        GSList *l;
690
691        if (strcmp(value, "both") != 0 && strcmp(value, "none") != 0 &&
692            strcmp(value, "user") != 0 && strcmp(value, "root") != 0) {
693                return SET_INVALID;
694        }
695
696        for (l = irc->channels; l; l = l->next) {
697                irc_channel_update_ops(l->data, value);
698        }
699
700        return value;
701}
702
703/* Channel-type dependent functions, for control channels: */
704static gboolean control_channel_privmsg(irc_channel_t *ic, const char *msg)
705{
706        irc_t *irc = ic->irc;
707        irc_user_t *iu;
708        const char *s;
709
710        /* Scan for non-whitespace chars followed by a colon: */
711        for (s = msg; *s && !g_ascii_isspace(*s) && *s != ':' && *s != ','; s++) {
712        }
713
714        if (*s == ':' || *s == ',') {
715                char to[s - msg + 1];
716
717                memset(to, 0, sizeof(to));
718                strncpy(to, msg, s - msg);
719                while (*(++s) && g_ascii_isspace(*s)) {
720                }
721                msg = s;
722
723                if (!(iu = irc_user_by_name(irc, to))) {
724                        irc_channel_printf(ic, "User does not exist: %s", to);
725                } else {
726                        ic->last_target = iu;
727                }
728        } else if (g_strcasecmp(set_getstr(&irc->b->set, "default_target"), "last") == 0 &&
729                   ic->last_target && g_slist_find(irc->users, ic->last_target)) {
730                iu = ic->last_target;
731        } else {
732                iu = irc->root;
733        }
734
735        if (iu && iu->f->privmsg) {
736                iu->last_channel = ic;
737                iu->f->privmsg(iu, msg);
738        }
739
740        return TRUE;
741}
742
743static gboolean control_channel_invite(irc_channel_t *ic, irc_user_t *iu)
744{
745        struct irc_control_channel *icc = ic->data;
746        bee_user_t *bu = iu->bu;
747
748        if (bu == NULL) {
749                return FALSE;
750        }
751
752        if (icc->type != IRC_CC_TYPE_GROUP) {
753                irc_send_num(ic->irc, 482, "%s :Invitations are only possible to fill_by=group channels", ic->name);
754                return FALSE;
755        }
756
757        if (!bu->ic->acc->prpl->add_buddy) {
758                irc_send_num(ic->irc, 482, "%s :IM protocol does not support contact list modification", ic->name);
759                return FALSE;
760        }
761
762        bu->ic->acc->prpl->add_buddy(bu->ic, bu->handle,
763                                     icc->group ? icc->group->name : NULL);
764
765        return TRUE;
766}
767
768static void control_channel_kick(irc_channel_t *ic, irc_user_t *iu, const char *msg)
769{
770        struct irc_control_channel *icc = ic->data;
771        bee_user_t *bu = iu->bu;
772
773        if (bu == NULL) {
774                return;
775        }
776
777        if (icc->type != IRC_CC_TYPE_GROUP) {
778                irc_send_num(ic->irc, 482, "%s :Kicks are only possible to fill_by=group channels", ic->name);
779                return;
780        }
781
782        if (!bu->ic->acc->prpl->remove_buddy) {
783                irc_send_num(ic->irc, 482, "%s :IM protocol does not support contact list modification", ic->name);
784                return;
785        }
786
787        bu->ic->acc->prpl->remove_buddy(bu->ic, bu->handle,
788                                        icc->group ? icc->group->name : NULL);
789}
790
791static char *set_eval_by_account(set_t *set, char *value);
792static char *set_eval_fill_by(set_t *set, char *value);
793static char *set_eval_by_group(set_t *set, char *value);
794static char *set_eval_by_protocol(set_t *set, char *value);
795static char *set_eval_show_users(set_t *set, char *value);
796
797static gboolean control_channel_init(irc_channel_t *ic)
798{
799        struct irc_control_channel *icc;
800
801        set_add(&ic->set, "account", NULL, set_eval_by_account, ic);
802        set_add(&ic->set, "fill_by", "all", set_eval_fill_by, ic);
803        set_add(&ic->set, "group", NULL, set_eval_by_group, ic);
804        set_add(&ic->set, "protocol", NULL, set_eval_by_protocol, ic);
805
806        /* When changing the default, also change it below. */
807        set_add(&ic->set, "show_users", "online+,special%,away", set_eval_show_users, ic);
808
809        ic->data = icc = g_new0(struct irc_control_channel, 1);
810        icc->type = IRC_CC_TYPE_DEFAULT;
811
812        /* Have to run the evaluator to initialize icc->modes. */
813        set_setstr(&ic->set, "show_users", "online+,special%,away");
814
815        /* For scripts that care. */
816        irc_channel_set_mode(ic, "+C");
817
818        return TRUE;
819}
820
821static gboolean control_channel_join(irc_channel_t *ic)
822{
823        bee_irc_channel_update(ic->irc, ic, NULL);
824
825        return TRUE;
826}
827
828static char *set_eval_by_account(set_t *set, char *value)
829{
830        struct irc_channel *ic = set->data;
831        struct irc_control_channel *icc = ic->data;
832        account_t *acc;
833
834        if (!(acc = account_get(ic->irc->b, value))) {
835                return SET_INVALID;
836        }
837
838        icc->account = acc;
839        if ((icc->type & IRC_CC_TYPE_MASK) == IRC_CC_TYPE_ACCOUNT) {
840                bee_irc_channel_update(ic->irc, ic, NULL);
841        }
842
843        return g_strdup(acc->tag);
844}
845
846static char *set_eval_fill_by(set_t *set, char *value)
847{
848        struct irc_channel *ic = set->data;
849        struct irc_control_channel *icc = ic->data;
850        char *s;
851
852        icc->type &= ~(IRC_CC_TYPE_MASK | IRC_CC_TYPE_INVERT);
853
854        s = value;
855        if (s[0] == '!') {
856                icc->type |= IRC_CC_TYPE_INVERT;
857                s++;
858        }
859
860        if (strcmp(s, "all") == 0) {
861                icc->type |= IRC_CC_TYPE_DEFAULT;
862        } else if (strcmp(s, "rest") == 0) {
863                icc->type |= IRC_CC_TYPE_REST;
864        } else if (strcmp(s, "group") == 0) {
865                icc->type |= IRC_CC_TYPE_GROUP;
866        } else if (strcmp(s, "account") == 0) {
867                icc->type |= IRC_CC_TYPE_ACCOUNT;
868        } else if (strcmp(s, "protocol") == 0) {
869                icc->type |= IRC_CC_TYPE_PROTOCOL;
870        } else {
871                return SET_INVALID;
872        }
873
874        bee_irc_channel_update(ic->irc, ic, NULL);
875        return value;
876}
877
878static char *set_eval_by_group(set_t *set, char *value)
879{
880        struct irc_channel *ic = set->data;
881        struct irc_control_channel *icc = ic->data;
882
883        icc->group = bee_group_by_name(ic->irc->b, value, TRUE);
884        if ((icc->type & IRC_CC_TYPE_MASK) == IRC_CC_TYPE_GROUP) {
885                bee_irc_channel_update(ic->irc, ic, NULL);
886        }
887
888        return g_strdup(icc->group->name);
889}
890
891static char *set_eval_by_protocol(set_t *set, char *value)
892{
893        struct irc_channel *ic = set->data;
894        struct irc_control_channel *icc = ic->data;
895        struct prpl *prpl;
896
897        if (!(prpl = find_protocol(value))) {
898                return SET_INVALID;
899        }
900
901        icc->protocol = prpl;
902        if ((icc->type & IRC_CC_TYPE_MASK) == IRC_CC_TYPE_PROTOCOL) {
903                bee_irc_channel_update(ic->irc, ic, NULL);
904        }
905
906        return value;
907}
908
909static char *set_eval_show_users(set_t *set, char *value)
910{
911        struct irc_channel *ic = set->data;
912        struct irc_control_channel *icc = ic->data;
913        char **parts = g_strsplit(value, ",", 0), **part;
914        char modes[5];
915
916        memset(modes, 0, 5);
917        for (part = parts; *part; part++) {
918                char last, modechar = IRC_CHANNEL_USER_NONE;
919
920                if (**part == '\0') {
921                        goto fail;
922                }
923
924                last = (*part)[strlen(*part + 1)];
925                if (last == '+') {
926                        modechar = IRC_CHANNEL_USER_VOICE;
927                } else if (last == '%') {
928                        modechar = IRC_CHANNEL_USER_HALFOP;
929                } else if (last == '@') {
930                        modechar = IRC_CHANNEL_USER_OP;
931                }
932
933                if (strncmp(*part, "offline", 7) == 0) {
934                        modes[0] = modechar;
935                } else if (strncmp(*part, "away", 4) == 0) {
936                        modes[1] = modechar;
937                } else if (strncmp(*part, "special", 7) == 0) {
938                        modes[2] = modechar;
939                } else if (strncmp(*part, "online", 6) == 0) {
940                        modes[3] = modechar;
941                } else {
942                        goto fail;
943                }
944        }
945        memcpy(icc->modes, modes, 5);
946        bee_irc_channel_update(ic->irc, ic, NULL);
947
948        g_strfreev(parts);
949        return value;
950
951fail:
952        g_strfreev(parts);
953        return SET_INVALID;
954}
955
956/* Figure out if a channel is supposed to have the user, assuming s/he is
957   online or otherwise also selected by the show_users setting. Only works
958   for control channels, but does *not* check if this channel is of that
959   type. Beware! */
960gboolean irc_channel_wants_user(irc_channel_t *ic, irc_user_t *iu)
961{
962        struct irc_control_channel *icc = ic->data;
963        gboolean ret = FALSE;
964
965        if (iu->bu == NULL) {
966                return FALSE;
967        }
968
969        switch (icc->type & IRC_CC_TYPE_MASK) {
970        case IRC_CC_TYPE_GROUP:
971                ret = iu->bu->group == icc->group;
972                break;
973        case IRC_CC_TYPE_ACCOUNT:
974                ret = iu->bu->ic->acc == icc->account;
975                break;
976        case IRC_CC_TYPE_PROTOCOL:
977                ret = iu->bu->ic->acc->prpl == icc->protocol;
978                break;
979        case IRC_CC_TYPE_DEFAULT:
980        default:
981                ret = TRUE;
982                break;
983        }
984
985        if (icc->type & IRC_CC_TYPE_INVERT) {
986                ret = !ret;
987        }
988
989        return ret;
990}
991
992static gboolean control_channel_free(irc_channel_t *ic)
993{
994        struct irc_control_channel *icc = ic->data;
995
996        set_del(&ic->set, "account");
997        set_del(&ic->set, "fill_by");
998        set_del(&ic->set, "group");
999        set_del(&ic->set, "protocol");
1000        set_del(&ic->set, "show_users");
1001
1002        g_free(icc);
1003        ic->data = NULL;
1004
1005        /* For scripts that care. */
1006        irc_channel_set_mode(ic, "-C");
1007
1008        return TRUE;
1009}
1010
1011static const struct irc_channel_funcs control_channel_funcs = {
1012        control_channel_privmsg,
1013        control_channel_join,
1014        NULL,
1015        NULL,
1016        control_channel_invite,
1017        control_channel_kick,
1018
1019        control_channel_init,
1020        control_channel_free,
1021};
Note: See TracBrowser for help on using the repository browser.