source: irc_channel.c @ 5c90890

Last change on this file since 5c90890 was 5c90890, checked in by dequis <dx@…>, at 2018-07-12T08:54:12Z

Stop using the irc->users linked list, use the hash table instead

irc_user_new() mentions that the reason this list is kept is for easy
iteration. Luckily, this is the future, and GHashTableIter exists now.

The main point of this is to get rid of the g_slist_insert_sorted() in
irc_user_set_nick() which is a particularly slow part of loading large
user lists, and scales poorly

In a test with discord, the GUILD_SYNC event is now 4 times faster, on
top of the improvements of the other bee_user hash tables patch.
Combining both patches it went from 136 to 6 seconds for 50k members.

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