source: irc_channel.c @ 830864d

Last change on this file since 830864d was 830864d, checked in by dequis <dx@…>, at 2015-03-15T09:31:18Z

WIP placeholder channels with hipchat implementation

i was going to clean this up and split in two commits but uhhh...
maybe some other day, i'm tired now

not very tested and i'm not 100% happy about the design, but sucks way
less than what i had in the hip-cat branch

feedback still appreciated.

this adds channels to the channel list without creating groupchats for
them, allowing users to /join them. what the hip-cat branch did before
but with proper api and hopefully less dumb behavior

it still 'leaks' them intentionally, just like it did before, but now it
prevents saving them to the xml so yay

also slightly improved channel name generation, refactored
bee_irc_chat_name_hint into three or four functions, and so on

  • Property mode set to 100644
File size: 21.9 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        /* TODO: Return values. */
216        if (ic->f && ic->f->_free) {
217                ic->f->_free(ic);
218        }
219
220        ic->f = new;
221
222        if (ic->f && ic->f->_init) {
223                ic->f->_init(ic);
224        }
225
226        return value;
227}
228
229int irc_channel_add_user(irc_channel_t *ic, irc_user_t *iu)
230{
231        irc_channel_user_t *icu;
232
233        if (irc_channel_has_user(ic, iu)) {
234                return 0;
235        }
236
237        icu = g_new0(irc_channel_user_t, 1);
238        icu->iu = iu;
239
240        ic->users = g_slist_insert_sorted(ic->users, icu, irc_channel_user_cmp);
241
242        irc_channel_update_ops(ic, set_getstr(&ic->irc->b->set, "ops"));
243
244        if (iu == ic->irc->user || ic->flags & IRC_CHANNEL_JOINED) {
245                ic->flags |= IRC_CHANNEL_JOINED;
246                irc_send_join(ic, iu);
247        }
248
249        return 1;
250}
251
252int irc_channel_del_user(irc_channel_t *ic, irc_user_t *iu, irc_channel_del_user_type_t type, const char *msg)
253{
254        irc_channel_user_t *icu;
255
256        if (!(icu = irc_channel_has_user(ic, iu))) {
257                return 0;
258        }
259
260        ic->users = g_slist_remove(ic->users, icu);
261        g_free(icu);
262
263        if (!(ic->flags & IRC_CHANNEL_JOINED) || type == IRC_CDU_SILENT) {
264        }
265        /* Do nothing. The caller should promise it won't screw
266           up state of the IRC client. :-) */
267        else if (type == IRC_CDU_PART) {
268                irc_send_part(ic, iu, msg);
269        } else if (type == IRC_CDU_KICK) {
270                irc_send_kick(ic, iu, ic->irc->root, msg);
271        }
272
273        if (iu == ic->irc->user) {
274                ic->flags &= ~IRC_CHANNEL_JOINED;
275
276                if (ic->irc->status & USTATUS_SHUTDOWN) {
277                        /* Don't do anything fancy when we're shutting down anyway. */
278                } else if (ic->flags & IRC_CHANNEL_TEMP && !(ic->flags & IRC_CHANNEL_KEEP_PLACEHOLDER)) {
279                        irc_channel_free_soon(ic);
280                } else {
281                        /* Flush userlist now. The user won't see it anyway. */
282                        while (ic->users) {
283                                g_free(ic->users->data);
284                                ic->users = g_slist_remove(ic->users, ic->users->data);
285                        }
286                        irc_channel_add_user(ic, ic->irc->root);
287                }
288        }
289
290        return 1;
291}
292
293irc_channel_user_t *irc_channel_has_user(irc_channel_t *ic, irc_user_t *iu)
294{
295        GSList *l;
296
297        for (l = ic->users; l; l = l->next) {
298                irc_channel_user_t *icu = l->data;
299
300                if (icu->iu == iu) {
301                        return icu;
302                }
303        }
304
305        return NULL;
306}
307
308/* Find a channel we're currently in, that currently has iu in it. */
309struct irc_channel *irc_channel_with_user(irc_t *irc, irc_user_t *iu)
310{
311        GSList *l;
312
313        for (l = irc->channels; l; l = l->next) {
314                irc_channel_t *ic = l->data;
315
316                if (strcmp(set_getstr(&ic->set, "type"), "control") != 0) {
317                        continue;
318                }
319
320                if ((ic->flags & IRC_CHANNEL_JOINED) &&
321                    irc_channel_has_user(ic, iu)) {
322                        return ic;
323                }
324        }
325
326        /* If there was no match, try once more but just see if the user
327           *would* be in the channel, i.e. if s/he were online. */
328        if (iu->bu == NULL) {
329                return NULL;
330        }
331
332        for (l = irc->channels; l; l = l->next) {
333                irc_channel_t *ic = l->data;
334
335                if (strcmp(set_getstr(&ic->set, "type"), "control") != 0) {
336                        continue;
337                }
338
339                if ((ic->flags & IRC_CHANNEL_JOINED) &&
340                    irc_channel_wants_user(ic, iu)) {
341                        return ic;
342                }
343        }
344
345        return NULL;
346}
347
348int irc_channel_set_topic(irc_channel_t *ic, const char *topic, const irc_user_t *iu)
349{
350        g_free(ic->topic);
351        ic->topic = g_strdup(topic);
352
353        g_free(ic->topic_who);
354        if (iu) {
355                ic->topic_who = g_strdup_printf("%s!%s@%s", iu->nick, iu->user, iu->host);
356        } else {
357                ic->topic_who = NULL;
358        }
359
360        ic->topic_time = time(NULL);
361
362        if (ic->flags & IRC_CHANNEL_JOINED) {
363                irc_send_topic(ic, TRUE);
364        }
365
366        return 1;
367}
368
369void irc_channel_user_set_mode(irc_channel_t *ic, irc_user_t *iu, irc_channel_user_flags_t flags)
370{
371        irc_channel_user_t *icu = irc_channel_has_user(ic, iu);
372
373        if (!icu || icu->flags == flags) {
374                return;
375        }
376
377        if (ic->flags & IRC_CHANNEL_JOINED) {
378                irc_send_channel_user_mode_diff(ic, iu, icu->flags, flags);
379        }
380
381        icu->flags = flags;
382}
383
384void irc_channel_set_mode(irc_channel_t *ic, const char *s)
385{
386        irc_t *irc = ic->irc;
387        char m[128], st = 1;
388        const char *t;
389        int i;
390        char changes[512], *p, st2 = 2;
391
392        memset(m, 0, sizeof(m));
393
394        for (t = ic->mode; *t; t++) {
395                if (*t < sizeof(m)) {
396                        m[(int) *t] = 1;
397                }
398        }
399
400        p = changes;
401        for (t = s; *t; t++) {
402                if (*t == '+' || *t == '-') {
403                        st = *t == '+';
404                } else if (strchr(CMODES, *t)) {
405                        if (m[(int) *t] != st) {
406                                if (st != st2) {
407                                        st2 = st, *p++ = st ? '+' : '-';
408                                }
409                                *p++ = *t;
410                        }
411                        m[(int) *t] = st;
412                }
413        }
414        *p = '\0';
415
416        memset(ic->mode, 0, sizeof(ic->mode));
417
418        for (i = 'A'; i <= 'z' && strlen(ic->mode) < (sizeof(ic->mode) - 1); i++) {
419                if (m[i]) {
420                        ic->mode[strlen(ic->mode)] = i;
421                }
422        }
423
424        if (*changes && (ic->flags & IRC_CHANNEL_JOINED)) {
425                irc_write(irc, ":%s!%s@%s MODE %s :%s", irc->root->nick,
426                          irc->root->user, irc->root->host, ic->name,
427                          changes);
428        }
429}
430
431void irc_channel_auto_joins(irc_t *irc, account_t *acc)
432{
433        GSList *l;
434
435        for (l = irc->channels; l; l = l->next) {
436                irc_channel_t *ic = l->data;
437                gboolean aj = set_getbool(&ic->set, "auto_join");
438                char *type;
439
440                if (acc &&
441                    (type = set_getstr(&ic->set, "chat_type")) &&
442                    strcmp(type, "room") == 0) {
443                        /* Bit of an ugly special case: Handle chatrooms here, we
444                           can only auto-join them if their account is online. */
445                        char *acc_s;
446
447                        if (!aj || (ic->flags & IRC_CHANNEL_JOINED)) {
448                                /* Only continue if this one's marked as auto_join
449                                   or if we're in it already. (Possible if the
450                                   client auto-rejoined it before identyfing.) */
451                                continue;
452                        } else if (!(acc_s = set_getstr(&ic->set, "account"))) {
453                                continue;
454                        } else if (account_get(irc->b, acc_s) != acc) {
455                                continue;
456                        } else if (acc->ic == NULL || !(acc->ic->flags & OPT_LOGGED_IN)) {
457                                continue;
458                        } else {
459                                ic->f->join(ic);
460                        }
461                } else if (aj) {
462                        irc_channel_add_user(ic, irc->user);
463                }
464        }
465}
466
467void irc_channel_printf(irc_channel_t *ic, char *format, ...)
468{
469        va_list params;
470        char *text;
471
472        va_start(params, format);
473        text = g_strdup_vprintf(format, params);
474        va_end(params);
475
476        irc_send_msg(ic->irc->root, "PRIVMSG", ic->name, text, NULL);
477        g_free(text);
478}
479
480gboolean irc_channel_name_ok(const char *name_)
481{
482        const unsigned char *name = (unsigned char *) name_;
483        int i;
484
485        if (name_[0] == '\0') {
486                return FALSE;
487        }
488
489        /* Check if the first character is in CTYPES (#&) */
490        if (strchr(CTYPES, name_[0]) == NULL) {
491                return FALSE;
492        }
493
494        /* RFC 1459 keeps amazing me: While only a "few" chars are allowed
495           in nicknames, channel names can be pretty much anything as long
496           as they start with # or &. I'll be a little bit more strict and
497           disallow all non-printable characters. */
498        for (i = 1; name[i]; i++) {
499                if (name[i] <= ' ' || name[i] == ',') {
500                        return FALSE;
501                }
502        }
503
504        return TRUE;
505}
506
507void irc_channel_name_strip(char *name)
508{
509        int i, j;
510
511        for (i = j = 0; name[i]; i++) {
512                if (name[i] > ' ' && name[i] != ',') {
513                        name[j++] = name[i];
514                }
515        }
516
517        name[j] = '\0';
518}
519
520int irc_channel_name_cmp(const char *a_, const char *b_)
521{
522        static unsigned char case_map[256];
523        const unsigned char *a = (unsigned char *) a_, *b = (unsigned char *) b_;
524        int i;
525
526        if (case_map['A'] == '\0') {
527                for (i = 33; i < 256; i++) {
528                        if (i != ',') {
529                                case_map[i] = i;
530                        }
531                }
532
533                for (i = 0; i < 26; i++) {
534                        case_map['A' + i] = 'a' + i;
535                }
536
537                case_map['['] = '{';
538                case_map[']'] = '}';
539                case_map['~'] = '`';
540                case_map['\\'] = '|';
541        }
542
543        if (!irc_channel_name_ok(a_) || !irc_channel_name_ok(b_)) {
544                return -1;
545        }
546
547        for (i = 0; a[i] && b[i] && case_map[a[i]] && case_map[b[i]]; i++) {
548                if (case_map[a[i]] == case_map[b[i]]) {
549                        continue;
550                } else {
551                        return case_map[a[i]] - case_map[b[i]];
552                }
553        }
554
555        return case_map[a[i]] - case_map[b[i]];
556}
557
558gboolean irc_channel_is_unused(bee_t *bee, char *name)
559{
560        char *type, *chat_type;
561        irc_channel_t *oic;
562
563        if (!irc_channel_name_ok(name)) {
564                return FALSE;
565        }
566
567        if (!(oic = irc_channel_by_name(bee->ui_data, name))) {
568                return TRUE;
569        }
570
571        type = set_getstr(&oic->set, "type");
572        chat_type = set_getstr(&oic->set, "chat_type");
573
574        if (type && chat_type && oic->data == FALSE &&
575            strcmp(type, "chat") == 0 &&
576            strcmp(chat_type, "groupchat") == 0) {
577                /* There's a channel with this name already, but it looks
578                   like it's not in use yet. Most likely the IRC client
579                   rejoined the channel after a reconnect. Remove it so
580                   we can reuse its name. */
581                irc_channel_free(oic);
582                return TRUE;
583        }
584
585        return FALSE;
586}
587
588char *irc_channel_name_gen(bee_t *bee, const char *hint)
589{
590        char name[MAX_NICK_LENGTH + 1] = { 0 };
591
592        name[0] = '#';
593        strncpy(name + 1, hint, MAX_NICK_LENGTH - 1);
594        name[MAX_NICK_LENGTH] = '\0';
595
596        irc_channel_name_strip(name);
597
598        if (set_getbool(&bee->set, "lcnicks")) {
599                nick_lc(bee->ui_data, name + 1);
600        }
601
602        while (!irc_channel_is_unused(bee, name)) {
603                underscore_dedupe(name);
604        }
605
606        return g_strdup(name);
607}
608
609gboolean irc_channel_name_hint(irc_channel_t *ic, const char *name)
610{
611        irc_t *irc = ic->irc;
612        char *full_name;
613
614        /* Don't rename a channel if the user's in it already. */
615        if (ic->flags & IRC_CHANNEL_JOINED) {
616                return FALSE;
617        }
618
619        if (!(full_name = irc_channel_name_gen(irc->b, name))) {
620                return FALSE;
621        }
622
623        g_free(ic->name);
624        ic->name = full_name;
625
626        return TRUE;
627}
628
629static gint irc_channel_user_cmp(gconstpointer a_, gconstpointer b_)
630{
631        const irc_channel_user_t *a = a_, *b = b_;
632
633        return irc_user_cmp(a->iu, b->iu);
634}
635
636void irc_channel_update_ops(irc_channel_t *ic, char *value)
637{
638        irc_channel_user_set_mode(ic, ic->irc->root,
639                                  (strcmp(value, "both") == 0 ||
640                                   strcmp(value, "root") == 0) ? IRC_CHANNEL_USER_OP : 0);
641        irc_channel_user_set_mode(ic, ic->irc->user,
642                                  (strcmp(value, "both") == 0 ||
643                                   strcmp(value, "user") == 0) ? IRC_CHANNEL_USER_OP : 0);
644}
645
646char *set_eval_irc_channel_ops(set_t *set, char *value)
647{
648        irc_t *irc = set->data;
649        GSList *l;
650
651        if (strcmp(value, "both") != 0 && strcmp(value, "none") != 0 &&
652            strcmp(value, "user") != 0 && strcmp(value, "root") != 0) {
653                return SET_INVALID;
654        }
655
656        for (l = irc->channels; l; l = l->next) {
657                irc_channel_update_ops(l->data, value);
658        }
659
660        return value;
661}
662
663/* Channel-type dependent functions, for control channels: */
664static gboolean control_channel_privmsg(irc_channel_t *ic, const char *msg)
665{
666        irc_t *irc = ic->irc;
667        irc_user_t *iu;
668        const char *s;
669
670        /* Scan for non-whitespace chars followed by a colon: */
671        for (s = msg; *s && !g_ascii_isspace(*s) && *s != ':' && *s != ','; s++) {
672        }
673
674        if (*s == ':' || *s == ',') {
675                char to[s - msg + 1];
676
677                memset(to, 0, sizeof(to));
678                strncpy(to, msg, s - msg);
679                while (*(++s) && g_ascii_isspace(*s)) {
680                }
681                msg = s;
682
683                if (!(iu = irc_user_by_name(irc, to))) {
684                        irc_channel_printf(ic, "User does not exist: %s", to);
685                } else {
686                        ic->last_target = iu;
687                }
688        } else if (g_strcasecmp(set_getstr(&irc->b->set, "default_target"), "last") == 0 &&
689                   ic->last_target && g_slist_find(irc->users, ic->last_target)) {
690                iu = ic->last_target;
691        } else {
692                iu = irc->root;
693        }
694
695        if (iu && iu->f->privmsg) {
696                iu->last_channel = ic;
697                iu->f->privmsg(iu, msg);
698        }
699
700        return TRUE;
701}
702
703static gboolean control_channel_invite(irc_channel_t *ic, irc_user_t *iu)
704{
705        struct irc_control_channel *icc = ic->data;
706        bee_user_t *bu = iu->bu;
707
708        if (bu == NULL) {
709                return FALSE;
710        }
711
712        if (icc->type != IRC_CC_TYPE_GROUP) {
713                irc_send_num(ic->irc, 482, "%s :Invitations are only possible to fill_by=group channels", ic->name);
714                return FALSE;
715        }
716
717        bu->ic->acc->prpl->add_buddy(bu->ic, bu->handle,
718                                     icc->group ? icc->group->name : NULL);
719
720        return TRUE;
721}
722
723static void control_channel_kick(irc_channel_t *ic, irc_user_t *iu, const char *msg)
724{
725        struct irc_control_channel *icc = ic->data;
726        bee_user_t *bu = iu->bu;
727
728        if (bu == NULL) {
729                return;
730        }
731
732        if (icc->type != IRC_CC_TYPE_GROUP) {
733                irc_send_num(ic->irc, 482, "%s :Kicks are only possible to fill_by=group channels", ic->name);
734                return;
735        }
736
737        bu->ic->acc->prpl->remove_buddy(bu->ic, bu->handle,
738                                        icc->group ? icc->group->name : NULL);
739}
740
741static char *set_eval_by_account(set_t *set, char *value);
742static char *set_eval_fill_by(set_t *set, char *value);
743static char *set_eval_by_group(set_t *set, char *value);
744static char *set_eval_by_protocol(set_t *set, char *value);
745static char *set_eval_show_users(set_t *set, char *value);
746
747static gboolean control_channel_init(irc_channel_t *ic)
748{
749        struct irc_control_channel *icc;
750
751        set_add(&ic->set, "account", NULL, set_eval_by_account, ic);
752        set_add(&ic->set, "fill_by", "all", set_eval_fill_by, ic);
753        set_add(&ic->set, "group", NULL, set_eval_by_group, ic);
754        set_add(&ic->set, "protocol", NULL, set_eval_by_protocol, ic);
755
756        /* When changing the default, also change it below. */
757        set_add(&ic->set, "show_users", "online+,special%,away", set_eval_show_users, ic);
758
759        ic->data = icc = g_new0(struct irc_control_channel, 1);
760        icc->type = IRC_CC_TYPE_DEFAULT;
761
762        /* Have to run the evaluator to initialize icc->modes. */
763        set_setstr(&ic->set, "show_users", "online+,special%,away");
764
765        /* For scripts that care. */
766        irc_channel_set_mode(ic, "+C");
767
768        return TRUE;
769}
770
771static gboolean control_channel_join(irc_channel_t *ic)
772{
773        bee_irc_channel_update(ic->irc, ic, NULL);
774
775        return TRUE;
776}
777
778static char *set_eval_by_account(set_t *set, char *value)
779{
780        struct irc_channel *ic = set->data;
781        struct irc_control_channel *icc = ic->data;
782        account_t *acc;
783
784        if (!(acc = account_get(ic->irc->b, value))) {
785                return SET_INVALID;
786        }
787
788        icc->account = acc;
789        if ((icc->type & IRC_CC_TYPE_MASK) == IRC_CC_TYPE_ACCOUNT) {
790                bee_irc_channel_update(ic->irc, ic, NULL);
791        }
792
793        return g_strdup(acc->tag);
794}
795
796static char *set_eval_fill_by(set_t *set, char *value)
797{
798        struct irc_channel *ic = set->data;
799        struct irc_control_channel *icc = ic->data;
800        char *s;
801
802        icc->type &= ~(IRC_CC_TYPE_MASK | IRC_CC_TYPE_INVERT);
803
804        s = value;
805        if (s[0] == '!') {
806                icc->type |= IRC_CC_TYPE_INVERT;
807                s++;
808        }
809
810        if (strcmp(s, "all") == 0) {
811                icc->type |= IRC_CC_TYPE_DEFAULT;
812        } else if (strcmp(s, "rest") == 0) {
813                icc->type |= IRC_CC_TYPE_REST;
814        } else if (strcmp(s, "group") == 0) {
815                icc->type |= IRC_CC_TYPE_GROUP;
816        } else if (strcmp(s, "account") == 0) {
817                icc->type |= IRC_CC_TYPE_ACCOUNT;
818        } else if (strcmp(s, "protocol") == 0) {
819                icc->type |= IRC_CC_TYPE_PROTOCOL;
820        } else {
821                return SET_INVALID;
822        }
823
824        bee_irc_channel_update(ic->irc, ic, NULL);
825        return value;
826}
827
828static char *set_eval_by_group(set_t *set, char *value)
829{
830        struct irc_channel *ic = set->data;
831        struct irc_control_channel *icc = ic->data;
832
833        icc->group = bee_group_by_name(ic->irc->b, value, TRUE);
834        if ((icc->type & IRC_CC_TYPE_MASK) == IRC_CC_TYPE_GROUP) {
835                bee_irc_channel_update(ic->irc, ic, NULL);
836        }
837
838        return g_strdup(icc->group->name);
839}
840
841static char *set_eval_by_protocol(set_t *set, char *value)
842{
843        struct irc_channel *ic = set->data;
844        struct irc_control_channel *icc = ic->data;
845        struct prpl *prpl;
846
847        if (!(prpl = find_protocol(value))) {
848                return SET_INVALID;
849        }
850
851        icc->protocol = prpl;
852        if ((icc->type & IRC_CC_TYPE_MASK) == IRC_CC_TYPE_PROTOCOL) {
853                bee_irc_channel_update(ic->irc, ic, NULL);
854        }
855
856        return value;
857}
858
859static char *set_eval_show_users(set_t *set, char *value)
860{
861        struct irc_channel *ic = set->data;
862        struct irc_control_channel *icc = ic->data;
863        char **parts = g_strsplit(value, ",", 0), **part;
864        char modes[5];
865
866        memset(modes, 0, 5);
867        for (part = parts; *part; part++) {
868                char last, modechar = IRC_CHANNEL_USER_NONE;
869
870                if (**part == '\0') {
871                        goto fail;
872                }
873
874                last = (*part)[strlen(*part + 1)];
875                if (last == '+') {
876                        modechar = IRC_CHANNEL_USER_VOICE;
877                } else if (last == '%') {
878                        modechar = IRC_CHANNEL_USER_HALFOP;
879                } else if (last == '@') {
880                        modechar = IRC_CHANNEL_USER_OP;
881                }
882
883                if (strncmp(*part, "offline", 7) == 0) {
884                        modes[0] = modechar;
885                } else if (strncmp(*part, "away", 4) == 0) {
886                        modes[1] = modechar;
887                } else if (strncmp(*part, "special", 7) == 0) {
888                        modes[2] = modechar;
889                } else if (strncmp(*part, "online", 6) == 0) {
890                        modes[3] = modechar;
891                } else {
892                        goto fail;
893                }
894        }
895        memcpy(icc->modes, modes, 5);
896        bee_irc_channel_update(ic->irc, ic, NULL);
897
898        g_strfreev(parts);
899        return value;
900
901fail:
902        g_strfreev(parts);
903        return SET_INVALID;
904}
905
906/* Figure out if a channel is supposed to have the user, assuming s/he is
907   online or otherwise also selected by the show_users setting. Only works
908   for control channels, but does *not* check if this channel is of that
909   type. Beware! */
910gboolean irc_channel_wants_user(irc_channel_t *ic, irc_user_t *iu)
911{
912        struct irc_control_channel *icc = ic->data;
913        gboolean ret = FALSE;
914
915        if (iu->bu == NULL) {
916                return FALSE;
917        }
918
919        switch (icc->type & IRC_CC_TYPE_MASK) {
920        case IRC_CC_TYPE_GROUP:
921                ret = iu->bu->group == icc->group;
922                break;
923        case IRC_CC_TYPE_ACCOUNT:
924                ret = iu->bu->ic->acc == icc->account;
925                break;
926        case IRC_CC_TYPE_PROTOCOL:
927                ret = iu->bu->ic->acc->prpl == icc->protocol;
928                break;
929        case IRC_CC_TYPE_DEFAULT:
930        default:
931                ret = TRUE;
932                break;
933        }
934
935        if (icc->type & IRC_CC_TYPE_INVERT) {
936                ret = !ret;
937        }
938
939        return ret;
940}
941
942static gboolean control_channel_free(irc_channel_t *ic)
943{
944        struct irc_control_channel *icc = ic->data;
945
946        set_del(&ic->set, "account");
947        set_del(&ic->set, "fill_by");
948        set_del(&ic->set, "group");
949        set_del(&ic->set, "protocol");
950        set_del(&ic->set, "show_users");
951
952        g_free(icc);
953        ic->data = NULL;
954
955        /* For scripts that care. */
956        irc_channel_set_mode(ic, "-C");
957
958        return TRUE;
959}
960
961static const struct irc_channel_funcs control_channel_funcs = {
962        control_channel_privmsg,
963        control_channel_join,
964        NULL,
965        NULL,
966        control_channel_invite,
967        control_channel_kick,
968
969        control_channel_init,
970        control_channel_free,
971};
Note: See TracBrowser for help on using the repository browser.