source: protocols/jabber/conference.c @ abf4717

Last change on this file since abf4717 was fb2338d, checked in by dequis <dx@…>, at 2015-10-30T10:28:32Z

jabber: Self message handling (echo removal) in MUCs

XMPP MUCs always echo own messages, and send messages from other
clients. So, we must display everything except the messages we just
sent.

This implementation uses the jabber stanza cache to add an ID to the
message and attach it to a callback which always returns XT_ABORT.
This way, if we do get the echo, the message packet handler can call
jabber_cache_handle_packet(), and if it returns XT_ABORT, it can skip
that particular message.

Every other message that looks like it comes from our own JID and wasn't
handled by the cache will be displayed, with the OPT_SELFMESSAGE flag

Stanza cache entries expire after some time, so it's not a problem if
the server doesn't echo messages for some reason.

I actually wrote this forever ago, for hipchat, but it works the same
way for standard XMPP MUCs.

  • Property mode set to 100644
File size: 13.9 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Jabber module - Conference rooms                                         *
5*                                                                           *
6*  Copyright 2007-2012 Wilmer van der Gaast <wilmer@gaast.net>              *
7*                                                                           *
8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24#include "jabber.h"
25#include "sha1.h"
26
27static xt_status jabber_chat_join_failed(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
28static xt_status jabber_chat_self_message(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
29
30struct groupchat *jabber_chat_join(struct im_connection *ic, const char *room, const char *nick, const char *password)
31{
32        struct jabber_chat *jc;
33        struct xt_node *node;
34        struct groupchat *c;
35        char *roomjid;
36
37        roomjid = g_strdup_printf("%s/%s", room, nick);
38        node = xt_new_node("x", NULL, NULL);
39        xt_add_attr(node, "xmlns", XMLNS_MUC);
40        if (password) {
41                xt_add_child(node, xt_new_node("password", password, NULL));
42        }
43        node = jabber_make_packet("presence", NULL, roomjid, node);
44        jabber_cache_add(ic, node, jabber_chat_join_failed);
45
46        if (!jabber_write_packet(ic, node)) {
47                g_free(roomjid);
48                return NULL;
49        }
50
51        jc = g_new0(struct jabber_chat, 1);
52        jc->name = jabber_normalize(room);
53
54        if ((jc->me = jabber_buddy_add(ic, roomjid)) == NULL) {
55                g_free(roomjid);
56                g_free(jc->name);
57                g_free(jc);
58                return NULL;
59        }
60
61        /* roomjid isn't normalized yet, and we need an original version
62           of the nick to send a proper presence update. */
63        jc->my_full_jid = roomjid;
64
65        c = imcb_chat_new(ic, room);
66        c->data = jc;
67
68        return c;
69}
70
71struct groupchat *jabber_chat_with(struct im_connection *ic, char *who)
72{
73        struct jabber_data *jd = ic->proto_data;
74        struct jabber_chat *jc;
75        struct groupchat *c;
76        sha1_state_t sum;
77        double now = gettime();
78        char *uuid, *rjid, *cserv;
79
80        sha1_init(&sum);
81        sha1_append(&sum, (uint8_t *) ic->acc->user, strlen(ic->acc->user));
82        sha1_append(&sum, (uint8_t *) &now, sizeof(now));
83        sha1_append(&sum, (uint8_t *) who, strlen(who));
84        uuid = sha1_random_uuid(&sum);
85
86        if (jd->flags & JFLAG_GTALK) {
87                cserv = g_strdup("groupchat.google.com");
88        } else {
89                /* Guess... */
90                cserv = g_strdup_printf("conference.%s", jd->server);
91        }
92
93        rjid = g_strdup_printf("private-chat-%s@%s", uuid, cserv);
94        g_free(uuid);
95        g_free(cserv);
96
97        c = jabber_chat_join(ic, rjid, jd->username, NULL);
98        g_free(rjid);
99        if (c == NULL) {
100                return NULL;
101        }
102
103        jc = c->data;
104        jc->invite = g_strdup(who);
105
106        return c;
107}
108
109static xt_status jabber_chat_join_failed(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
110{
111        struct jabber_error *err;
112        struct jabber_buddy *bud;
113        char *room;
114
115        room = xt_find_attr(orig, "to");
116        bud = jabber_buddy_by_jid(ic, room, 0);
117        err = jabber_error_parse(xt_find_node(node->children, "error"), XMLNS_STANZA_ERROR);
118        if (err) {
119                imcb_error(ic, "Error joining groupchat %s: %s%s%s", room, err->code,
120                           err->text ? ": " : "", err->text ? err->text : "");
121                jabber_error_free(err);
122        }
123        if (bud) {
124                jabber_chat_free(jabber_chat_by_jid(ic, bud->bare_jid));
125        }
126
127        return XT_HANDLED;
128}
129
130static xt_status jabber_chat_self_message(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
131{
132        /* This is a self message sent by this bitlbee - just drop it */
133        return XT_ABORT;
134}
135
136struct groupchat *jabber_chat_by_jid(struct im_connection *ic, const char *name)
137{
138        char *normalized = jabber_normalize(name);
139        GSList *l;
140        struct groupchat *ret;
141        struct jabber_chat *jc;
142
143        for (l = ic->groupchats; l; l = l->next) {
144                ret = l->data;
145                jc = ret->data;
146                if (strcmp(normalized, jc->name) == 0) {
147                        break;
148                }
149        }
150        g_free(normalized);
151
152        return l ? ret : NULL;
153}
154
155void jabber_chat_free(struct groupchat *c)
156{
157        struct jabber_chat *jc = c->data;
158
159        jabber_buddy_remove_bare(c->ic, jc->name);
160
161        g_free(jc->my_full_jid);
162        g_free(jc->name);
163        g_free(jc->invite);
164        g_free(jc);
165
166        imcb_chat_free(c);
167}
168
169int jabber_chat_msg(struct groupchat *c, char *message, int flags)
170{
171        struct im_connection *ic = c->ic;
172        struct jabber_chat *jc = c->data;
173        struct xt_node *node;
174
175        jc->flags |= JCFLAG_MESSAGE_SENT;
176
177        node = xt_new_node("body", message, NULL);
178        node = jabber_make_packet("message", "groupchat", jc->name, node);
179
180        jabber_cache_add(ic, node, jabber_chat_self_message);
181
182        return !jabber_write_packet(ic, node);
183}
184
185int jabber_chat_topic(struct groupchat *c, char *topic)
186{
187        struct im_connection *ic = c->ic;
188        struct jabber_chat *jc = c->data;
189        struct xt_node *node;
190
191        node = xt_new_node("subject", topic, NULL);
192        node = jabber_make_packet("message", "groupchat", jc->name, node);
193
194        if (!jabber_write_packet(ic, node)) {
195                xt_free_node(node);
196                return 0;
197        }
198        xt_free_node(node);
199
200        return 1;
201}
202
203int jabber_chat_leave(struct groupchat *c, const char *reason)
204{
205        struct im_connection *ic = c->ic;
206        struct jabber_chat *jc = c->data;
207        struct xt_node *node;
208
209        node = xt_new_node("x", NULL, NULL);
210        xt_add_attr(node, "xmlns", XMLNS_MUC);
211        node = jabber_make_packet("presence", "unavailable", jc->my_full_jid, node);
212
213        if (!jabber_write_packet(ic, node)) {
214                xt_free_node(node);
215                return 0;
216        }
217        xt_free_node(node);
218
219        return 1;
220}
221
222void jabber_chat_invite(struct groupchat *c, char *who, char *message)
223{
224        struct xt_node *node;
225        struct im_connection *ic = c->ic;
226        struct jabber_chat *jc = c->data;
227
228        node = xt_new_node("reason", message, NULL);
229
230        node = xt_new_node("invite", NULL, node);
231        xt_add_attr(node, "to", who);
232
233        node = xt_new_node("x", NULL, node);
234        xt_add_attr(node, "xmlns", XMLNS_MUC_USER);
235
236        node = jabber_make_packet("message", NULL, jc->name, node);
237
238        jabber_write_packet(ic, node);
239
240        xt_free_node(node);
241}
242
243/* Not really the same syntax as the normal pkt_ functions, but this isn't
244   called by the xmltree parser directly and this way I can add some extra
245   parameters so we won't have to repeat too many things done by the caller
246   already. */
247void jabber_chat_pkt_presence(struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node)
248{
249        struct groupchat *chat;
250        struct xt_node *c;
251        char *type = xt_find_attr(node, "type");
252        struct jabber_data *jd = ic->proto_data;
253        struct jabber_chat *jc;
254        char *s;
255
256        if ((chat = jabber_chat_by_jid(ic, bud->bare_jid)) == NULL) {
257                /* How could this happen?? We could do kill( self, 11 )
258                   now or just wait for the OS to do it. :-) */
259                return;
260        }
261
262        jc = chat->data;
263
264        if (type == NULL && !(bud->flags & JBFLAG_IS_CHATROOM)) {
265                bud->flags |= JBFLAG_IS_CHATROOM;
266                /* If this one wasn't set yet, this buddy just joined the chat.
267                   Slightly hackish way of finding out eh? ;-) */
268
269                /* This is pretty messy... Here it sets ext_jid to the real
270                   JID of the participant. Works for non-anonymized channels.
271                   Might break if someone joins a chat twice, though. */
272                for (c = node->children; (c = xt_find_node(c, "x")); c = c->next) {
273                        if ((s = xt_find_attr(c, "xmlns")) &&
274                            (strcmp(s, XMLNS_MUC_USER) == 0)) {
275                                struct xt_node *item;
276
277                                item = xt_find_node(c->children, "item");
278                                if ((s = xt_find_attr(item, "jid"))) {
279                                        /* Yay, found what we need. :-) */
280                                        bud->ext_jid = jabber_normalize(s);
281                                        break;
282                                }
283                        }
284                }
285
286                /* Make up some other handle, if necessary. */
287                if (bud->ext_jid == NULL) {
288                        if (bud == jc->me) {
289                                bud->ext_jid = g_strdup(jd->me);
290                        } else {
291                                int i;
292
293                                /* Don't want the nick to be at the end, so let's
294                                   think of some slightly different notation to use
295                                   for anonymous groupchat participants in BitlBee. */
296                                bud->ext_jid = g_strdup_printf("%s=%s", bud->resource, bud->bare_jid);
297
298                                /* And strip any unwanted characters. */
299                                for (i = 0; bud->resource[i]; i++) {
300                                        if (bud->ext_jid[i] == '=' || bud->ext_jid[i] == '@') {
301                                                bud->ext_jid[i] = '_';
302                                        }
303                                }
304
305                                /* Some program-specific restrictions. */
306                                imcb_clean_handle(ic, bud->ext_jid);
307                        }
308                        bud->flags |= JBFLAG_IS_ANONYMOUS;
309                }
310
311                if (bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS) {
312                        /* If JIDs are anonymized, add them to the local
313                           list for the duration of this chat. */
314                        imcb_add_buddy(ic, bud->ext_jid, NULL);
315                        imcb_buddy_nick_hint(ic, bud->ext_jid, bud->resource);
316                }
317
318                if (bud == jc->me && jc->invite != NULL) {
319                        char *msg = g_strdup_printf("Please join me in room %s", jc->name);
320                        jabber_chat_invite(chat, jc->invite, msg);
321                        g_free(jc->invite);
322                        g_free(msg);
323                        jc->invite = NULL;
324                }
325
326                s = strchr(bud->ext_jid, '/');
327                if (s) {
328                        *s = 0; /* Should NEVER be NULL, but who knows... */
329                }
330                imcb_chat_add_buddy(chat, bud->ext_jid);
331                if (s) {
332                        *s = '/';
333                }
334        } else if (type) { /* type can only be NULL or "unavailable" in this function */
335                if ((bud->flags & JBFLAG_IS_CHATROOM) && bud->ext_jid) {
336                        char *reason = NULL;
337                        char *status = NULL;
338                        char *status_text = NULL;
339                       
340                        if ((c = xt_find_node_by_attr(node->children, "x", "xmlns", XMLNS_MUC_USER))) {
341                                struct xt_node *c2 = c->children;
342
343                                while ((c2 = xt_find_node(c2, "status"))) {
344                                        char *code = xt_find_attr(c2, "code");
345                                        if (g_strcmp0(code, "301") == 0) {
346                                                status = "Banned";
347                                                break;
348                                        } else if (g_strcmp0(code, "303") == 0) {
349                                                /* This could be handled in a cleverer way,
350                                                 * but let's just show a literal part/join for now */
351                                                status = "Changing nicks";
352                                                break;
353                                        } else if (g_strcmp0(code, "307") == 0) {
354                                                status = "Kicked";
355                                                break;
356                                        }
357                                        c2 = c2->next;
358                                }
359
360                                /* Sometimes the status message is in presence/x/item/reason */
361                                if ((c2 = xt_find_path(c, "item/reason")) && c2->text && c2->text_len) {
362                                        status_text = c2->text;
363                                }
364                        }
365
366                        /* Sometimes the status message is right inside <presence> */
367                        if ((c = xt_find_node(node->children, "status")) && c->text && c->text_len) {
368                                status_text = c->text;
369                        }
370
371                        if (status_text && status) {
372                                reason = g_strdup_printf("%s: %s", status, status_text);
373                        } else {
374                                reason = g_strdup(status_text ? : status);
375                        }
376
377                        s = strchr(bud->ext_jid, '/');
378                        if (s) {
379                                *s = 0;
380                        }
381                        imcb_chat_remove_buddy(chat, bud->ext_jid, reason);
382                        if (bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS) {
383                                imcb_remove_buddy(ic, bud->ext_jid, reason);
384                        }
385                        if (s) {
386                                *s = '/';
387                        }
388
389                        g_free(reason);
390                }
391
392                if (bud == jc->me) {
393                        jabber_chat_free(chat);
394                }
395        }
396}
397
398void jabber_chat_pkt_message(struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node)
399{
400        struct xt_node *subject = xt_find_node(node->children, "subject");
401        struct xt_node *body = xt_find_node(node->children, "body");
402        struct groupchat *chat = NULL;
403        struct jabber_chat *jc = NULL;
404        char *from = NULL;
405        char *nick = NULL;
406        char *final_from = NULL;
407        char *bare_jid = NULL;
408        guint32 flags = 0;
409
410        from = (bud) ? bud->full_jid : xt_find_attr(node, "from");
411
412        if (from) {
413                nick = strchr(from, '/');
414                if (nick) {
415                        *nick = 0;
416                }
417                chat = jabber_chat_by_jid(ic, from);
418                if (nick) {
419                        *nick = '/';
420                        nick++;
421                }
422        }
423
424        jc = (chat) ? chat->data : NULL;
425
426        if (!bud) {
427                struct xt_node *c;
428                char *s;
429
430                /* Try some clever stuff to find out the real JID here */
431                c = xt_find_node_by_attr(node->children, "delay", "xmlns", XMLNS_DELAY);
432
433                if (c && ((s = xt_find_attr(c, "from")) ||
434                          (s = xt_find_attr(c, "from_jid")))) {
435                        /* This won't be useful if it's the MUC JID */
436                        if (!(jc && jabber_compare_jid(s, jc->name))) {
437                                /* Hopefully this one makes more sense! */
438                                bud = jabber_buddy_by_jid(ic, s, GET_BUDDY_FIRST | GET_BUDDY_CREAT);
439                        }
440                }
441
442        }
443
444        if (subject && chat) {
445                char *subject_text = subject->text_len > 0 ? subject->text : "";
446                if (g_strcmp0(chat->topic, subject_text) != 0) {
447                        bare_jid = (bud) ? jabber_get_bare_jid(bud->ext_jid) : NULL;
448                        imcb_chat_topic(chat, bare_jid, subject_text,
449                                        jabber_get_timestamp(node));
450                        g_free(bare_jid);
451                        bare_jid = NULL;
452                }
453        }
454
455        if (body == NULL || body->text_len == 0) {
456                /* Meh. Empty messages aren't very interesting, no matter
457                   how much some servers love to send them. */
458                return;
459        }
460
461        if (chat == NULL) {
462                if (nick == NULL) {
463                        imcb_log(ic, "System message from unknown groupchat %s: %s", from, body->text);
464                } else {
465                        imcb_log(ic, "Groupchat message from unknown JID %s: %s", from, body->text);
466                }
467
468                return;
469        } else if (chat != NULL && bud == NULL && nick == NULL) {
470                imcb_chat_log(chat, "From conference server: %s", body->text);
471                return;
472        } else if (jc && jc->flags & JCFLAG_MESSAGE_SENT && bud == jc->me &&
473                   (jabber_cache_handle_packet(ic, node) == XT_ABORT)) {
474                /* Self message marked by this bitlbee, don't show it */
475                return;
476        }
477
478        if (bud) {
479                bare_jid = jabber_get_bare_jid(bud->ext_jid ? bud->ext_jid : bud->full_jid);
480                final_from = bare_jid;
481                flags = (bud == jc->me) ? OPT_SELFMESSAGE : 0;
482        } else {
483                final_from = nick;
484        }
485
486        imcb_chat_msg(chat, final_from, body->text, flags, jabber_get_timestamp(node));
487
488        g_free(bare_jid);
489}
Note: See TracBrowser for help on using the repository browser.