1
2 /*
3 Meanwhile Protocol Plugin for Purple
4 Adds Lotus Sametime support to Purple using the Meanwhile library
5
6 Copyright (C) 2004 Christopher (siege) O'Brien <siege@preoccupied.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 (at
11 your option) any later version.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301,
21 USA.
22 */
23
24 #include "internal.h"
25
26 /* system includes */
27 #include <stdlib.h>
28 #include <time.h>
29
30 /* glib includes */
31 #include <glib.h>
32
33 /* purple includes */
34 #include "account.h"
35 #include "accountopt.h"
36 #include "circbuffer.h"
37 #include "conversation.h"
38 #include "debug.h"
39 #include "ft.h"
40 #include "imgstore.h"
41 #include "mime.h"
42 #include "notify.h"
43 #include "plugin.h"
44 #include "privacy.h"
45 #include "prpl.h"
46 #include "request.h"
47 #include "util.h"
48 #include "version.h"
49
50 /* meanwhile includes */
51 #include <mw_cipher.h>
52 #include <mw_common.h>
53 #include <mw_error.h>
54 #include <mw_service.h>
55 #include <mw_session.h>
56 #include <mw_srvc_aware.h>
57 #include <mw_srvc_conf.h>
58 #include <mw_srvc_ft.h>
59 #include <mw_srvc_im.h>
60 #include <mw_srvc_place.h>
61 #include <mw_srvc_resolve.h>
62 #include <mw_srvc_store.h>
63 #include <mw_st_list.h>
64
65 /* plugin includes */
66 #include "sametime.h"
67
68
69 /* considering that there's no display of this information for prpls,
70 I don't know why I even bother providing these. Oh valiant reader,
71 I do it all for you. */
72 /* scratch that, I just added it to the prpl options panel */
73 #define PLUGIN_ID "prpl-meanwhile"
74 #define PLUGIN_NAME "Sametime"
75 #define PLUGIN_SUMMARY "Sametime Protocol Plugin"
76 #define PLUGIN_DESC "Open implementation of a Lotus Sametime client"
77 #define PLUGIN_AUTHOR "Christopher (siege) O'Brien <siege@preoccupied.net>"
78 #define PLUGIN_HOMEPAGE "http://meanwhile.sourceforge.net/"
79
80
81 /* plugin preference names */
82 #define MW_PRPL_OPT_BASE "/plugins/prpl/meanwhile"
83 #define MW_PRPL_OPT_BLIST_ACTION MW_PRPL_OPT_BASE "/blist_action"
84 #define MW_PRPL_OPT_PSYCHIC MW_PRPL_OPT_BASE "/psychic"
85 #define MW_PRPL_OPT_FORCE_LOGIN MW_PRPL_OPT_BASE "/force_login"
86 #define MW_PRPL_OPT_SAVE_DYNAMIC MW_PRPL_OPT_BASE "/save_dynamic"
87
88
89 /* stages of connecting-ness */
90 #define MW_CONNECT_STEPS 11
91
92
93 /* stages of conciousness */
94 #define MW_STATE_OFFLINE "offline"
95 #define MW_STATE_ACTIVE "active"
96 #define MW_STATE_AWAY "away"
97 #define MW_STATE_BUSY "dnd"
98 #define MW_STATE_MESSAGE "message"
99 #define MW_STATE_ENLIGHTENED "buddha"
100
101
102 /* keys to get/set chat information */
103 #define CHAT_KEY_CREATOR "chat.creator"
104 #define CHAT_KEY_NAME "chat.name"
105 #define CHAT_KEY_TOPIC "chat.topic"
106 #define CHAT_KEY_INVITE "chat.invite"
107 #define CHAT_KEY_IS_PLACE "chat.is_place"
108
109
110 /* key for associating a mwLoginType with a buddy */
111 #define BUDDY_KEY_CLIENT "meanwhile.client"
112
113 /* store the remote alias so that we can re-create it easily */
114 #define BUDDY_KEY_NAME "meanwhile.shortname"
115
116 /* enum mwSametimeUserType */
117 #define BUDDY_KEY_TYPE "meanwhile.type"
118
119
120 /* key for the real group name for a meanwhile group */
121 #define GROUP_KEY_NAME "meanwhile.group"
122
123 /* enum mwSametimeGroupType */
124 #define GROUP_KEY_TYPE "meanwhile.type"
125
126 /* NAB group owning account */
127 #define GROUP_KEY_OWNER "meanwhile.account"
128
129 /* key gtk blist uses to indicate a collapsed group */
130 #define GROUP_KEY_COLLAPSED "collapsed"
131
132
133 /* verification replacement */
134 #define mwSession_NO_SECRET "meanwhile.no_secret"
135
136
137 /* keys to get/set purple plugin information */
138 #define MW_KEY_HOST "server"
139 #define MW_KEY_PORT "port"
140 #define MW_KEY_FORCE "force_login"
141 #define MW_KEY_FAKE_IT "fake_client_id"
142 #define MW_KEY_CLIENT "client_id_val"
143 #define MW_KEY_MAJOR "client_major"
144 #define MW_KEY_MINOR "client_minor"
145
146
147 /** number of seconds from the first blist change before a save to the
148 storage service occurs. */
149 #define BLIST_SAVE_SECONDS 15
150
151
152 /** the possible buddy list storage settings */
153 enum blist_choice {
154 blist_choice_LOCAL = 1, /**< local only */
155 blist_choice_MERGE = 2, /**< merge from server */
156 blist_choice_STORE = 3, /**< merge from and save to server */
157 blist_choice_SYNCH = 4 /**< sync with server */
158 };
159
160
161 /** the default blist storage option */
162 #define BLIST_CHOICE_DEFAULT blist_choice_SYNCH
163
164
165 /* testing for the above */
166 #define BLIST_PREF_IS(n) (purple_prefs_get_int(MW_PRPL_OPT_BLIST_ACTION)==(n))
167 #define BLIST_PREF_IS_LOCAL() BLIST_PREF_IS(blist_choice_LOCAL)
168 #define BLIST_PREF_IS_MERGE() BLIST_PREF_IS(blist_choice_MERGE)
169 #define BLIST_PREF_IS_STORE() BLIST_PREF_IS(blist_choice_STORE)
170 #define BLIST_PREF_IS_SYNCH() BLIST_PREF_IS(blist_choice_SYNCH)
171
172
173 /* debugging output */
174 #define DEBUG_ERROR(...) purple_debug_error(G_LOG_DOMAIN, __VA_ARGS__)
175 #define DEBUG_INFO(...) purple_debug_info(G_LOG_DOMAIN, __VA_ARGS__)
176 #define DEBUG_MISC(...) purple_debug_misc(G_LOG_DOMAIN, __VA_ARGS__)
177 #define DEBUG_WARN(...) purple_debug_warning(G_LOG_DOMAIN, __VA_ARGS__)
178
179
180 /** ensure non-null strings */
181 #ifndef NSTR
182 # define NSTR(str) ((str)? (str): "(null)")
183 #endif
184
185
186 /** calibrates distinct secure channel nomenclature */
187 static const unsigned char no_secret[] = {
188 0x2d, 0x2d, 0x20, 0x73, 0x69, 0x65, 0x67, 0x65,
189 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x6a,
190 0x65, 0x6e, 0x6e, 0x69, 0x20, 0x61, 0x6e, 0x64,
191 0x20, 0x7a, 0x6f, 0x65, 0x20, 0x2d, 0x2d, 0x00,
192 };
193
194
195 /** handler IDs from g_log_set_handler in mw_plugin_init */
196 static guint log_handler[2] = { 0, 0 };
197
198
199 /** the purple plugin data.
200 available as gc->proto_data and mwSession_getClientData */
201 struct mwPurplePluginData {
202 struct mwSession *session;
203
204 struct mwServiceAware *srvc_aware;
205 struct mwServiceConference *srvc_conf;
206 struct mwServiceFileTransfer *srvc_ft;
207 struct mwServiceIm *srvc_im;
208 struct mwServicePlace *srvc_place;
209 struct mwServiceResolve *srvc_resolve;
210 struct mwServiceStorage *srvc_store;
211
212 /** map of PurpleGroup:mwAwareList and mwAwareList:PurpleGroup */
213 GHashTable *group_list_map;
214
215 /** event id for the buddy list save callback */
216 guint save_event;
217
218 /** socket fd */
219 int socket;
220 gint outpa; /* like inpa, but the other way */
221
222 /** circular buffer for outgoing data */
223 PurpleCircBuffer *sock_buf;
224
225 PurpleConnection *gc;
226 };
227
228
229 typedef struct {
230 PurpleBuddy *buddy;
231 PurpleGroup *group;
232 } BuddyAddData;
233
234
235 /* blist and aware functions */
236
237 static void blist_export(PurpleConnection *gc, struct mwSametimeList *stlist);
238
239 static void blist_store(struct mwPurplePluginData *pd);
240
241 static void blist_schedule(struct mwPurplePluginData *pd);
242
243 static void blist_merge(PurpleConnection *gc, struct mwSametimeList *stlist);
244
245 static void blist_sync(PurpleConnection *gc, struct mwSametimeList *stlist);
246
247 static gboolean buddy_is_external(PurpleBuddy *b);
248
249 static void buddy_add(struct mwPurplePluginData *pd, PurpleBuddy *buddy);
250
251 static PurpleBuddy *
252 buddy_ensure(PurpleConnection *gc, PurpleGroup *group,
253 struct mwSametimeUser *stuser);
254
255 static void group_add(struct mwPurplePluginData *pd, PurpleGroup *group);
256
257 static PurpleGroup *
258 group_ensure(PurpleConnection *gc, struct mwSametimeGroup *stgroup);
259
260 static struct mwAwareList *
261 list_ensure(struct mwPurplePluginData *pd, PurpleGroup *group);
262
263
264 /* session functions */
265
266 static struct mwSession *
267 gc_to_session(PurpleConnection *gc);
268
269 static PurpleConnection *session_to_gc(struct mwSession *session);
270
271
272 /* conference functions */
273
274 static struct mwConference *
275 conf_find_by_id(struct mwPurplePluginData *pd, int id);
276
277
278 /* conversation functions */
279
280 struct convo_msg {
281 enum mwImSendType type;
282 gpointer data;
283 GDestroyNotify clear;
284 };
285
286
287 struct convo_data {
288 struct mwConversation *conv;
289 GList *queue; /**< outgoing message queue, list of convo_msg */
290 };
291
292 static void convo_data_new(struct mwConversation *conv);
293
294 static void convo_data_free(struct convo_data *conv);
295
296 static void convo_features(struct mwConversation *conv);
297
298 static PurpleConversation *convo_get_gconv(struct mwConversation *conv);
299
300
301 /* name and id */
302
303 struct named_id {
304 char *id;
305 char *name;
306 };
307
308
309 /* connection functions */
310
311 static void connect_cb(gpointer data, gint source, const gchar *error_message);
312
313
314 /* ----- session ------ */
315
316
317 /** resolves a mwSession from a PurpleConnection */
gc_to_session(PurpleConnection * gc)318 static struct mwSession *gc_to_session(PurpleConnection *gc) {
319 struct mwPurplePluginData *pd;
320
321 g_return_val_if_fail(gc != NULL, NULL);
322
323 pd = gc->proto_data;
324 g_return_val_if_fail(pd != NULL, NULL);
325
326 return pd->session;
327 }
328
329
330 /** resolves a PurpleConnection from a mwSession */
session_to_gc(struct mwSession * session)331 static PurpleConnection *session_to_gc(struct mwSession *session) {
332 struct mwPurplePluginData *pd;
333
334 g_return_val_if_fail(session != NULL, NULL);
335
336 pd = mwSession_getClientData(session);
337 g_return_val_if_fail(pd != NULL, NULL);
338
339 return pd->gc;
340 }
341
342
write_cb(gpointer data,gint source,PurpleInputCondition cond)343 static void write_cb(gpointer data, gint source, PurpleInputCondition cond) {
344 struct mwPurplePluginData *pd = data;
345 PurpleCircBuffer *circ = pd->sock_buf;
346 gsize avail;
347 int ret;
348
349 DEBUG_INFO("write_cb\n");
350
351 g_return_if_fail(circ != NULL);
352
353 avail = purple_circ_buffer_get_max_read(circ);
354 if(BUF_LONG < avail) avail = BUF_LONG;
355
356 while(avail) {
357 ret = write(pd->socket, circ->outptr, avail);
358
359 if(ret <= 0)
360 break;
361
362 purple_circ_buffer_mark_read(circ, ret);
363 avail = purple_circ_buffer_get_max_read(circ);
364 if(BUF_LONG < avail) avail = BUF_LONG;
365 }
366
367 if(! avail) {
368 purple_input_remove(pd->outpa);
369 pd->outpa = 0;
370 }
371 }
372
373
mw_session_io_write(struct mwSession * session,const guchar * buf,gsize len)374 static int mw_session_io_write(struct mwSession *session,
375 const guchar *buf, gsize len) {
376 struct mwPurplePluginData *pd;
377 gssize ret = 0;
378 int err = 0;
379
380 pd = mwSession_getClientData(session);
381
382 /* socket was already closed. */
383 if(pd->socket == 0)
384 return 1;
385
386 if(pd->outpa) {
387 DEBUG_INFO("already pending INPUT_WRITE, buffering\n");
388 purple_circ_buffer_append(pd->sock_buf, buf, len);
389 return 0;
390 }
391
392 while(len) {
393 ret = write(pd->socket, buf, (len > BUF_LEN)? BUF_LEN: len);
394
395 if(ret <= 0)
396 break;
397
398 len -= ret;
399 buf += ret;
400 }
401
402 if(ret <= 0)
403 err = errno;
404
405 if(err == EAGAIN) {
406 /* append remainder to circular buffer */
407 DEBUG_INFO("EAGAIN\n");
408 purple_circ_buffer_append(pd->sock_buf, buf, len);
409 pd->outpa = purple_input_add(pd->socket, PURPLE_INPUT_WRITE, write_cb, pd);
410
411 } else if(len > 0) {
412 gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
413 g_strerror(errno));
414 DEBUG_ERROR("write returned %" G_GSSIZE_FORMAT ", %" G_GSIZE_FORMAT
415 " bytes left unwritten\n", ret, len);
416 purple_connection_error_reason(pd->gc,
417 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
418 tmp);
419 g_free(tmp);
420
421 #if 0
422 close(pd->socket);
423 pd->socket = 0;
424 #endif
425
426 return -1;
427 }
428
429 return 0;
430 }
431
432
mw_session_io_close(struct mwSession * session)433 static void mw_session_io_close(struct mwSession *session) {
434 struct mwPurplePluginData *pd;
435 PurpleConnection *gc;
436
437 pd = mwSession_getClientData(session);
438 g_return_if_fail(pd != NULL);
439
440 gc = pd->gc;
441
442 if(pd->outpa) {
443 purple_input_remove(pd->outpa);
444 pd->outpa = 0;
445 }
446
447 if(pd->socket) {
448 close(pd->socket);
449 pd->socket = 0;
450 }
451
452 if(gc->inpa) {
453 purple_input_remove(gc->inpa);
454 gc->inpa = 0;
455 }
456 }
457
458
mw_session_clear(struct mwSession * session)459 static void mw_session_clear(struct mwSession *session) {
460 ; /* nothing for now */
461 }
462
463
464 /* ----- aware list ----- */
465
466
blist_resolve_alias_cb(struct mwServiceResolve * srvc,guint32 id,guint32 code,GList * results,gpointer data)467 static void blist_resolve_alias_cb(struct mwServiceResolve *srvc,
468 guint32 id, guint32 code, GList *results,
469 gpointer data) {
470 struct mwResolveResult *result;
471 struct mwResolveMatch *match;
472
473 g_return_if_fail(results != NULL);
474
475 result = results->data;
476 g_return_if_fail(result != NULL);
477 g_return_if_fail(result->matches != NULL);
478
479 match = result->matches->data;
480 g_return_if_fail(match != NULL);
481
482 purple_blist_server_alias_buddy(data, match->name);
483 purple_blist_node_set_string(data, BUDDY_KEY_NAME, match->name);
484 }
485
486
mw_aware_list_on_aware(struct mwAwareList * list,struct mwAwareSnapshot * aware)487 static void mw_aware_list_on_aware(struct mwAwareList *list,
488 struct mwAwareSnapshot *aware) {
489
490 PurpleConnection *gc;
491 PurpleAccount *acct;
492
493 struct mwPurplePluginData *pd;
494 guint32 idle;
495 guint stat;
496 const char *id;
497 const char *status = MW_STATE_ACTIVE;
498
499 gc = mwAwareList_getClientData(list);
500 acct = purple_connection_get_account(gc);
501
502 pd = gc->proto_data;
503 idle = aware->status.time;
504 stat = aware->status.status;
505 id = aware->id.user;
506
507 if(idle) {
508 guint32 idle_len; /*< how long a client has been idle */
509 guint32 ugly_idle_len; /*< how long a broken client has been idle */
510
511 DEBUG_INFO("%s has idle value 0x%x\n", NSTR(id), idle);
512
513 idle_len = time(NULL) - idle;
514 ugly_idle_len = ((time(NULL) * 1000) - idle) / 1000;
515
516 if(idle > ugly_idle_len)
517 ugly_idle_len = 0;
518 else
519 ugly_idle_len = (ugly_idle_len - idle) / 1000;
520
521 /*
522 what's the deal here? Well, good clients are smart enough to
523 publish their idle time by using an attribute to indicate that
524 they went idle at some time UTC, in seconds since epoch. Bad
525 clients use milliseconds since epoch. So we're going to compute
526 the idle time for either method, then figure out the lower of
527 the two and use that. Blame the ST 7.5 development team for
528 this.
529 */
530
531 DEBUG_INFO("idle time: %u, ugly idle time: %u\n", idle_len, ugly_idle_len);
532
533 #if 1
534 if(idle_len <= ugly_idle_len) {
535 ; /* DEBUG_INFO("sane idle value, let's use it\n"); */
536 } else {
537 idle = time(NULL) - ugly_idle_len;
538 }
539
540 #else
541 if(idle < 0 || idle > time(NULL)) {
542 DEBUG_INFO("hiding a messy idle value 0x%x\n", NSTR(id), idle);
543 idle = -1;
544 }
545 #endif
546 }
547
548 switch(stat) {
549 case mwStatus_ACTIVE:
550 status = MW_STATE_ACTIVE;
551 idle = 0;
552 break;
553
554 case mwStatus_IDLE:
555 if(! idle) idle = -1;
556 break;
557
558 case mwStatus_AWAY:
559 status = MW_STATE_AWAY;
560 break;
561
562 case mwStatus_BUSY:
563 status = MW_STATE_BUSY;
564 break;
565 }
566
567 /* NAB group members */
568 if(aware->group) {
569 PurpleGroup *group;
570 PurpleBuddy *buddy;
571 PurpleBlistNode *bnode;
572
573 group = g_hash_table_lookup(pd->group_list_map, list);
574 buddy = purple_find_buddy_in_group(acct, id, group);
575 bnode = (PurpleBlistNode *) buddy;
576
577 if(! buddy) {
578 struct mwServiceResolve *srvc;
579 GList *query;
580
581 buddy = purple_buddy_new(acct, id, NULL);
582 purple_blist_add_buddy(buddy, NULL, group, NULL);
583
584 bnode = (PurpleBlistNode *) buddy;
585
586 srvc = pd->srvc_resolve;
587 query = g_list_append(NULL, (char *) id);
588
589 mwServiceResolve_resolve(srvc, query, mwResolveFlag_USERS,
590 blist_resolve_alias_cb, buddy, NULL);
591 g_list_free(query);
592 }
593
594 purple_blist_node_set_int(bnode, BUDDY_KEY_TYPE, mwSametimeUser_NORMAL);
595 }
596
597 if(aware->online) {
598 purple_prpl_got_user_status(acct, id, status, NULL);
599 purple_prpl_got_user_idle(acct, id, !!idle, (time_t) idle);
600
601 } else {
602 purple_prpl_got_user_status(acct, id, MW_STATE_OFFLINE, NULL);
603 }
604 }
605
606
mw_aware_list_on_attrib(struct mwAwareList * list,struct mwAwareIdBlock * id,struct mwAwareAttribute * attrib)607 static void mw_aware_list_on_attrib(struct mwAwareList *list,
608 struct mwAwareIdBlock *id,
609 struct mwAwareAttribute *attrib) {
610
611 ; /* nothing. We'll get attribute data as we need it */
612 }
613
614
mw_aware_list_clear(struct mwAwareList * list)615 static void mw_aware_list_clear(struct mwAwareList *list) {
616 ; /* nothing for now */
617 }
618
619
620 static struct mwAwareListHandler mw_aware_list_handler = {
621 mw_aware_list_on_aware,
622 mw_aware_list_on_attrib,
623 mw_aware_list_clear,
624 };
625
626
627 /** Ensures that an Aware List is associated with the given group, and
628 returns that list. */
629 static struct mwAwareList *
list_ensure(struct mwPurplePluginData * pd,PurpleGroup * group)630 list_ensure(struct mwPurplePluginData *pd, PurpleGroup *group) {
631
632 struct mwAwareList *list;
633
634 g_return_val_if_fail(pd != NULL, NULL);
635 g_return_val_if_fail(group != NULL, NULL);
636
637 list = g_hash_table_lookup(pd->group_list_map, group);
638 if(! list) {
639 list = mwAwareList_new(pd->srvc_aware, &mw_aware_list_handler);
640 mwAwareList_setClientData(list, pd->gc, NULL);
641
642 mwAwareList_watchAttributes(list,
643 mwAttribute_AV_PREFS_SET,
644 mwAttribute_MICROPHONE,
645 mwAttribute_SPEAKERS,
646 mwAttribute_VIDEO_CAMERA,
647 mwAttribute_FILE_TRANSFER,
648 NULL);
649
650 g_hash_table_replace(pd->group_list_map, group, list);
651 g_hash_table_insert(pd->group_list_map, list, group);
652 }
653
654 return list;
655 }
656
657
blist_export(PurpleConnection * gc,struct mwSametimeList * stlist)658 static void blist_export(PurpleConnection *gc, struct mwSametimeList *stlist) {
659 /* - find the account for this connection
660 - iterate through the buddy list
661 - add each buddy matching this account to the stlist
662 */
663
664 PurpleAccount *acct;
665 PurpleBlistNode *gn, *cn, *bn;
666 PurpleGroup *grp;
667 PurpleBuddy *bdy;
668
669 struct mwSametimeGroup *stg = NULL;
670 struct mwIdBlock idb = { NULL, NULL };
671
672 acct = purple_connection_get_account(gc);
673 g_return_if_fail(acct != NULL);
674
675 for(gn = purple_blist_get_root(); gn;
676 gn = purple_blist_node_get_sibling_next(gn)) {
677 const char *owner;
678 const char *gname;
679 enum mwSametimeGroupType gtype;
680 gboolean gopen;
681
682 if(! PURPLE_BLIST_NODE_IS_GROUP(gn)) continue;
683 grp = (PurpleGroup *) gn;
684
685 /* the group's type (normal or dynamic) */
686 gtype = purple_blist_node_get_int(gn, GROUP_KEY_TYPE);
687 if(! gtype) gtype = mwSametimeGroup_NORMAL;
688
689 /* if it's a normal group with none of our people in it, skip it */
690 if(gtype == mwSametimeGroup_NORMAL && !purple_group_on_account(grp, acct))
691 continue;
692
693 /* if the group has an owner and we're not it, skip it */
694 owner = purple_blist_node_get_string(gn, GROUP_KEY_OWNER);
695 if(owner && !purple_strequal(owner, purple_account_get_username(acct)))
696 continue;
697
698 /* the group's actual name may be different from the purple group's
699 name. Find whichever is there */
700 gname = purple_blist_node_get_string(gn, GROUP_KEY_NAME);
701 if(! gname) gname = purple_group_get_name(grp);
702
703 /* we save this, but never actually honor it */
704 gopen = ! purple_blist_node_get_bool(gn, GROUP_KEY_COLLAPSED);
705
706 stg = mwSametimeGroup_new(stlist, gtype, gname);
707 mwSametimeGroup_setAlias(stg, purple_group_get_name(grp));
708 mwSametimeGroup_setOpen(stg, gopen);
709
710 /* don't attempt to put buddies in a dynamic group, it breaks
711 other clients */
712 if(gtype == mwSametimeGroup_DYNAMIC)
713 continue;
714
715 for(cn = purple_blist_node_get_first_child(gn);
716 cn;
717 cn = purple_blist_node_get_sibling_next(cn)) {
718 if(! PURPLE_BLIST_NODE_IS_CONTACT(cn)) continue;
719
720 for(bn = purple_blist_node_get_first_child(cn);
721 bn;
722 bn = purple_blist_node_get_sibling_next(bn)) {
723 if(! PURPLE_BLIST_NODE_IS_BUDDY(bn)) continue;
724 if(! PURPLE_BLIST_NODE_SHOULD_SAVE(bn)) continue;
725
726 bdy = (PurpleBuddy *) bn;
727
728 if(purple_buddy_get_account(bdy) == acct) {
729 struct mwSametimeUser *stu;
730 enum mwSametimeUserType utype;
731
732 idb.user = (char *)purple_buddy_get_name(bdy);
733
734 utype = purple_blist_node_get_int(bn, BUDDY_KEY_TYPE);
735 if(! utype) utype = mwSametimeUser_NORMAL;
736
737 stu = mwSametimeUser_new(stg, utype, &idb);
738 mwSametimeUser_setShortName(stu, purple_buddy_get_server_alias(bdy));
739 mwSametimeUser_setAlias(stu, purple_buddy_get_local_buddy_alias(bdy));
740 }
741 }
742 }
743 }
744 }
745
746
blist_store(struct mwPurplePluginData * pd)747 static void blist_store(struct mwPurplePluginData *pd) {
748
749 struct mwSametimeList *stlist;
750 struct mwServiceStorage *srvc;
751 struct mwStorageUnit *unit;
752
753 PurpleConnection *gc;
754
755 struct mwPutBuffer *b;
756 struct mwOpaque *o;
757
758 g_return_if_fail(pd != NULL);
759
760 srvc = pd->srvc_store;
761 g_return_if_fail(srvc != NULL);
762
763 gc = pd->gc;
764
765 if(BLIST_PREF_IS_LOCAL() || BLIST_PREF_IS_MERGE()) {
766 DEBUG_INFO("preferences indicate not to save remote blist\n");
767 return;
768
769 } else if(MW_SERVICE_IS_DEAD(srvc)) {
770 DEBUG_INFO("aborting save of blist: storage service is not alive\n");
771 return;
772
773 } else if(BLIST_PREF_IS_STORE() || BLIST_PREF_IS_SYNCH()) {
774 DEBUG_INFO("saving remote blist\n");
775
776 } else {
777 g_return_if_reached();
778 }
779
780 /* create and export to a list object */
781 stlist = mwSametimeList_new();
782 blist_export(gc, stlist);
783
784 /* write it to a buffer */
785 b = mwPutBuffer_new();
786 mwSametimeList_put(b, stlist);
787 mwSametimeList_free(stlist);
788
789 /* put the buffer contents into a storage unit */
790 unit = mwStorageUnit_new(mwStore_AWARE_LIST);
791 o = mwStorageUnit_asOpaque(unit);
792 mwPutBuffer_finalize(o, b);
793
794 /* save the storage unit to the service */
795 mwServiceStorage_save(srvc, unit, NULL, NULL, NULL);
796 }
797
798
blist_save_cb(gpointer data)799 static gboolean blist_save_cb(gpointer data) {
800 struct mwPurplePluginData *pd = data;
801
802 blist_store(pd);
803 pd->save_event = 0;
804 return FALSE;
805 }
806
807
808 /** schedules the buddy list to be saved to the server */
blist_schedule(struct mwPurplePluginData * pd)809 static void blist_schedule(struct mwPurplePluginData *pd) {
810 if(pd->save_event) return;
811
812 pd->save_event = purple_timeout_add_seconds(BLIST_SAVE_SECONDS,
813 blist_save_cb, pd);
814 }
815
816
buddy_is_external(PurpleBuddy * b)817 static gboolean buddy_is_external(PurpleBuddy *b) {
818 g_return_val_if_fail(b != NULL, FALSE);
819 return purple_str_has_prefix(purple_buddy_get_name(b), "@E ");
820 }
821
822
823 /** Actually add a buddy to the aware service, and schedule the buddy
824 list to be saved to the server */
buddy_add(struct mwPurplePluginData * pd,PurpleBuddy * buddy)825 static void buddy_add(struct mwPurplePluginData *pd,
826 PurpleBuddy *buddy) {
827
828 struct mwAwareIdBlock idb = { mwAware_USER, (char *) purple_buddy_get_name(buddy), NULL };
829 struct mwAwareList *list;
830
831 PurpleGroup *group;
832 GList *add;
833
834 add = g_list_prepend(NULL, &idb);
835
836 group = purple_buddy_get_group(buddy);
837 list = list_ensure(pd, group);
838
839 if(mwAwareList_addAware(list, add)) {
840 purple_blist_remove_buddy(buddy);
841 }
842
843 blist_schedule(pd);
844
845 g_list_free(add);
846 }
847
848
849 /** ensure that a PurpleBuddy exists in the group with data
850 appropriately matching the st user entry from the st list */
buddy_ensure(PurpleConnection * gc,PurpleGroup * group,struct mwSametimeUser * stuser)851 static PurpleBuddy *buddy_ensure(PurpleConnection *gc, PurpleGroup *group,
852 struct mwSametimeUser *stuser) {
853
854 struct mwPurplePluginData *pd = gc->proto_data;
855 PurpleBuddy *buddy;
856 PurpleAccount *acct = purple_connection_get_account(gc);
857
858 const char *id = mwSametimeUser_getUser(stuser);
859 const char *name = mwSametimeUser_getShortName(stuser);
860 const char *alias = mwSametimeUser_getAlias(stuser);
861 enum mwSametimeUserType type = mwSametimeUser_getType(stuser);
862
863 g_return_val_if_fail(id != NULL, NULL);
864 g_return_val_if_fail(strlen(id) > 0, NULL);
865
866 buddy = purple_find_buddy_in_group(acct, id, group);
867 if(! buddy) {
868 buddy = purple_buddy_new(acct, id, alias);
869
870 purple_blist_add_buddy(buddy, NULL, group, NULL);
871 buddy_add(pd, buddy);
872 }
873
874 purple_blist_alias_buddy(buddy, alias);
875 purple_blist_server_alias_buddy(buddy, name);
876 purple_blist_node_set_string((PurpleBlistNode *) buddy, BUDDY_KEY_NAME, name);
877 purple_blist_node_set_int((PurpleBlistNode *) buddy, BUDDY_KEY_TYPE, type);
878
879 return buddy;
880 }
881
882
883 /** add aware watch for a dynamic group */
group_add(struct mwPurplePluginData * pd,PurpleGroup * group)884 static void group_add(struct mwPurplePluginData *pd,
885 PurpleGroup *group) {
886
887 struct mwAwareIdBlock idb = { mwAware_GROUP, NULL, NULL };
888 struct mwAwareList *list;
889 const char *n;
890 GList *add;
891
892 n = purple_blist_node_get_string((PurpleBlistNode *) group, GROUP_KEY_NAME);
893 if(! n) n = purple_group_get_name(group);
894
895 idb.user = (char *) n;
896 add = g_list_prepend(NULL, &idb);
897
898 list = list_ensure(pd, group);
899 mwAwareList_addAware(list, add);
900 g_list_free(add);
901 }
902
903
904 /** ensure that a PurpleGroup exists in the blist with data
905 appropriately matching the st group entry from the st list */
group_ensure(PurpleConnection * gc,struct mwSametimeGroup * stgroup)906 static PurpleGroup *group_ensure(PurpleConnection *gc,
907 struct mwSametimeGroup *stgroup) {
908 PurpleAccount *acct;
909 PurpleGroup *group = NULL;
910 PurpleBuddyList *blist;
911 PurpleBlistNode *gn;
912 const char *name, *alias, *owner;
913 enum mwSametimeGroupType type;
914
915 acct = purple_connection_get_account(gc);
916 owner = purple_account_get_username(acct);
917
918 blist = purple_get_blist();
919 g_return_val_if_fail(blist != NULL, NULL);
920
921 name = mwSametimeGroup_getName(stgroup);
922 alias = mwSametimeGroup_getAlias(stgroup);
923 type = mwSametimeGroup_getType(stgroup);
924
925 if (!name) {
926 DEBUG_WARN("Can't ensure a null group\n");
927 return NULL;
928 }
929
930 if (!name) {
931 DEBUG_WARN("Can't ensure a null group\n");
932 return NULL;
933 }
934
935 DEBUG_INFO("attempting to ensure group %s, called %s\n",
936 NSTR(name), NSTR(alias));
937
938 /* first attempt at finding the group, by the name key */
939 for(gn = purple_blist_get_root(); gn;
940 gn = purple_blist_node_get_sibling_next(gn)) {
941 const char *n, *o;
942 if(! PURPLE_BLIST_NODE_IS_GROUP(gn)) continue;
943 n = purple_blist_node_get_string(gn, GROUP_KEY_NAME);
944 o = purple_blist_node_get_string(gn, GROUP_KEY_OWNER);
945
946 DEBUG_INFO("found group named %s, owned by %s\n", NSTR(n), NSTR(o));
947
948 if(n && purple_strequal(n, name)) {
949 if(!o || purple_strequal(o, owner)) {
950 DEBUG_INFO("that'll work\n");
951 group = (PurpleGroup *) gn;
952 break;
953 }
954 }
955 }
956
957 /* try again, by alias */
958 if(! group) {
959 DEBUG_INFO("searching for group by alias %s\n", NSTR(alias));
960 group = purple_find_group(alias);
961 }
962
963 /* oh well, no such group. Let's create it! */
964 if(! group) {
965 DEBUG_INFO("creating group\n");
966 group = purple_group_new(alias);
967 purple_blist_add_group(group, NULL);
968 }
969
970 gn = (PurpleBlistNode *) group;
971 purple_blist_node_set_string(gn, GROUP_KEY_NAME, name);
972 purple_blist_node_set_int(gn, GROUP_KEY_TYPE, type);
973
974 if(type == mwSametimeGroup_DYNAMIC) {
975 purple_blist_node_set_string(gn, GROUP_KEY_OWNER, owner);
976 group_add(gc->proto_data, group);
977 }
978
979 return group;
980 }
981
982
983 /** merge the entries from a st list into the purple blist */
blist_merge(PurpleConnection * gc,struct mwSametimeList * stlist)984 static void blist_merge(PurpleConnection *gc, struct mwSametimeList *stlist) {
985 struct mwSametimeGroup *stgroup;
986 struct mwSametimeUser *stuser;
987
988 PurpleGroup *group;
989
990 GList *gl, *gtl, *ul, *utl;
991
992 gl = gtl = mwSametimeList_getGroups(stlist);
993 for(; gl; gl = gl->next) {
994
995 stgroup = (struct mwSametimeGroup *) gl->data;
996 group = group_ensure(gc, stgroup);
997
998 ul = utl = mwSametimeGroup_getUsers(stgroup);
999 for(; ul; ul = ul->next) {
1000
1001 stuser = (struct mwSametimeUser *) ul->data;
1002 buddy_ensure(gc, group, stuser);
1003 }
1004 g_list_free(utl);
1005 }
1006 g_list_free(gtl);
1007 }
1008
1009
1010 /** remove all buddies on account from group. If del is TRUE and group
1011 is left empty, remove group as well */
group_clear(PurpleGroup * group,PurpleAccount * acct,gboolean del)1012 static void group_clear(PurpleGroup *group, PurpleAccount *acct, gboolean del) {
1013 PurpleConnection *gc;
1014 GList *prune = NULL;
1015 PurpleBlistNode *gn, *cn, *bn;
1016
1017 g_return_if_fail(group != NULL);
1018
1019 DEBUG_INFO("clearing members from pruned group %s\n", NSTR(purple_group_get_name(group)));
1020
1021 gc = purple_account_get_connection(acct);
1022 g_return_if_fail(gc != NULL);
1023
1024 gn = (PurpleBlistNode *) group;
1025
1026 for(cn = purple_blist_node_get_first_child(gn);
1027 cn;
1028 cn = purple_blist_node_get_sibling_next(cn)) {
1029 if(! PURPLE_BLIST_NODE_IS_CONTACT(cn)) continue;
1030
1031 for(bn = purple_blist_node_get_first_child(cn);
1032 bn;
1033 bn = purple_blist_node_get_sibling_next(bn)) {
1034 PurpleBuddy *gb = (PurpleBuddy *) bn;
1035
1036 if(! PURPLE_BLIST_NODE_IS_BUDDY(bn)) continue;
1037
1038 if(purple_buddy_get_account(gb) == acct) {
1039 DEBUG_INFO("clearing %s from group\n", NSTR(purple_buddy_get_name(gb)));
1040 prune = g_list_prepend(prune, gb);
1041 }
1042 }
1043 }
1044
1045 /* quickly unsubscribe from presence for the entire group */
1046 purple_account_remove_group(acct, group);
1047
1048 /* remove blist entries that need to go */
1049 while(prune) {
1050 purple_blist_remove_buddy(prune->data);
1051 prune = g_list_delete_link(prune, prune);
1052 }
1053 DEBUG_INFO("cleared buddies\n");
1054
1055 /* optionally remove group from blist */
1056 if(del && !purple_blist_get_group_size(group, TRUE)) {
1057 DEBUG_INFO("removing empty group\n");
1058 purple_blist_remove_group(group);
1059 }
1060 }
1061
1062
1063 /** prune out group members that shouldn't be there */
group_prune(PurpleConnection * gc,PurpleGroup * group,struct mwSametimeGroup * stgroup)1064 static void group_prune(PurpleConnection *gc, PurpleGroup *group,
1065 struct mwSametimeGroup *stgroup) {
1066
1067 PurpleAccount *acct;
1068 PurpleBlistNode *gn, *cn, *bn;
1069
1070 GHashTable *stusers;
1071 GList *prune = NULL;
1072 GList *ul, *utl;
1073
1074 g_return_if_fail(group != NULL);
1075
1076 DEBUG_INFO("pruning membership of group %s\n", NSTR(purple_group_get_name(group)));
1077
1078 acct = purple_connection_get_account(gc);
1079 g_return_if_fail(acct != NULL);
1080
1081 stusers = g_hash_table_new(g_str_hash, g_str_equal);
1082
1083 /* build a hash table for quick lookup while pruning the group
1084 contents */
1085 utl = mwSametimeGroup_getUsers(stgroup);
1086 for(ul = utl; ul; ul = ul->next) {
1087 const char *id = mwSametimeUser_getUser(ul->data);
1088 g_hash_table_insert(stusers, (char *) id, ul->data);
1089 DEBUG_INFO("server copy has %s\n", NSTR(id));
1090 }
1091 g_list_free(utl);
1092
1093 gn = (PurpleBlistNode *) group;
1094
1095 for(cn = purple_blist_node_get_first_child(gn);
1096 cn;
1097 cn = purple_blist_node_get_sibling_next(cn)) {
1098 if(! PURPLE_BLIST_NODE_IS_CONTACT(cn)) continue;
1099
1100 for(bn = purple_blist_node_get_first_child(cn);
1101 bn;
1102 bn = purple_blist_node_get_sibling_next(bn)) {
1103 PurpleBuddy *gb = (PurpleBuddy *) bn;
1104
1105 if(! PURPLE_BLIST_NODE_IS_BUDDY(bn)) continue;
1106
1107 /* if the account is correct and they're not in our table, mark
1108 them for pruning */
1109 if(purple_buddy_get_account(gb) == acct && !g_hash_table_lookup(stusers, purple_buddy_get_name(gb))) {
1110 DEBUG_INFO("marking %s for pruning\n", NSTR(purple_buddy_get_name(gb)));
1111 prune = g_list_prepend(prune, gb);
1112 }
1113 }
1114 }
1115 DEBUG_INFO("done marking\n");
1116
1117 g_hash_table_destroy(stusers);
1118
1119 if(prune) {
1120 purple_account_remove_buddies(acct, prune, NULL);
1121 while(prune) {
1122 purple_blist_remove_buddy(prune->data);
1123 prune = g_list_delete_link(prune, prune);
1124 }
1125 }
1126 }
1127
1128
1129 /** synch the entries from a st list into the purple blist, removing any
1130 existing buddies that aren't in the st list */
blist_sync(PurpleConnection * gc,struct mwSametimeList * stlist)1131 static void blist_sync(PurpleConnection *gc, struct mwSametimeList *stlist) {
1132
1133 PurpleAccount *acct;
1134 PurpleBuddyList *blist;
1135 PurpleBlistNode *gn;
1136
1137 GHashTable *stgroups;
1138 GList *g_prune = NULL;
1139
1140 GList *gl, *gtl;
1141
1142 const char *acct_n;
1143
1144 DEBUG_INFO("synchronizing local buddy list from server list\n");
1145
1146 acct = purple_connection_get_account(gc);
1147 g_return_if_fail(acct != NULL);
1148
1149 acct_n = purple_account_get_username(acct);
1150
1151 blist = purple_get_blist();
1152 g_return_if_fail(blist != NULL);
1153
1154 /* build a hash table for quick lookup while pruning the local
1155 list, mapping group name to group structure */
1156 stgroups = g_hash_table_new(g_str_hash, g_str_equal);
1157
1158 gtl = mwSametimeList_getGroups(stlist);
1159 for(gl = gtl; gl; gl = gl->next) {
1160 const char *name = mwSametimeGroup_getName(gl->data);
1161 g_hash_table_insert(stgroups, (char *) name, gl->data);
1162 }
1163 g_list_free(gtl);
1164
1165 /* find all groups which should be pruned from the local list */
1166 for(gn = purple_blist_get_root(); gn;
1167 gn = purple_blist_node_get_sibling_next(gn)) {
1168 PurpleGroup *grp = (PurpleGroup *) gn;
1169 const char *gname, *owner;
1170 struct mwSametimeGroup *stgrp;
1171
1172 if(! PURPLE_BLIST_NODE_IS_GROUP(gn)) continue;
1173
1174 /* group not belonging to this account */
1175 if(! purple_group_on_account(grp, acct))
1176 continue;
1177
1178 /* dynamic group belonging to this account. don't prune contents */
1179 owner = purple_blist_node_get_string(gn, GROUP_KEY_OWNER);
1180 if(owner && purple_strequal(owner, acct_n))
1181 continue;
1182
1183 /* we actually are synching by this key as opposed to the group
1184 title, which can be different things in the st list */
1185 gname = purple_blist_node_get_string(gn, GROUP_KEY_NAME);
1186 if(! gname) gname = purple_group_get_name(grp);
1187
1188 stgrp = g_hash_table_lookup(stgroups, gname);
1189 if(! stgrp) {
1190 /* remove the whole group */
1191 DEBUG_INFO("marking group %s for pruning\n", purple_group_get_name(grp));
1192 g_prune = g_list_prepend(g_prune, grp);
1193
1194 } else {
1195 /* synch the group contents */
1196 group_prune(gc, grp, stgrp);
1197 }
1198 }
1199 DEBUG_INFO("done marking groups\n");
1200
1201 /* don't need this anymore */
1202 g_hash_table_destroy(stgroups);
1203
1204 /* prune all marked groups */
1205 while(g_prune) {
1206 PurpleGroup *grp = g_prune->data;
1207 PurpleBlistNode *gn = (PurpleBlistNode *) grp;
1208 const char *owner;
1209 gboolean del = TRUE;
1210
1211 owner = purple_blist_node_get_string(gn, GROUP_KEY_OWNER);
1212 if(owner && !purple_strequal(owner, acct_n)) {
1213 /* it's a specialty group belonging to another account with some
1214 of our members in it, so don't fully delete it */
1215 del = FALSE;
1216 }
1217
1218 group_clear(g_prune->data, acct, del);
1219 g_prune = g_list_delete_link(g_prune, g_prune);
1220 }
1221
1222 /* done with the pruning, let's merge in the additions */
1223 blist_merge(gc, stlist);
1224 }
1225
1226
1227 /** callback passed to the storage service when it's told to load the
1228 st list */
fetch_blist_cb(struct mwServiceStorage * srvc,guint32 result,struct mwStorageUnit * item,gpointer data)1229 static void fetch_blist_cb(struct mwServiceStorage *srvc,
1230 guint32 result, struct mwStorageUnit *item,
1231 gpointer data) {
1232
1233 struct mwPurplePluginData *pd = data;
1234 struct mwSametimeList *stlist;
1235
1236 struct mwGetBuffer *b;
1237
1238 g_return_if_fail(result == ERR_SUCCESS);
1239
1240 /* check our preferences for loading */
1241 if(BLIST_PREF_IS_LOCAL()) {
1242 DEBUG_INFO("preferences indicate not to load remote buddy list\n");
1243 return;
1244 }
1245
1246 b = mwGetBuffer_wrap(mwStorageUnit_asOpaque(item));
1247
1248 stlist = mwSametimeList_new();
1249 mwSametimeList_get(b, stlist);
1250
1251 /* merge or synch depending on preferences */
1252 if(BLIST_PREF_IS_MERGE() || BLIST_PREF_IS_STORE()) {
1253 blist_merge(pd->gc, stlist);
1254
1255 } else if(BLIST_PREF_IS_SYNCH()) {
1256 blist_sync(pd->gc, stlist);
1257 }
1258
1259 mwSametimeList_free(stlist);
1260 mwGetBuffer_free(b);
1261 }
1262
1263
1264 /** signal triggered when a conversation is opened in Purple */
conversation_created_cb(PurpleConversation * g_conv,struct mwPurplePluginData * pd)1265 static void conversation_created_cb(PurpleConversation *g_conv,
1266 struct mwPurplePluginData *pd) {
1267
1268 /* we need to tell the IM service to negotiate features for the
1269 conversation right away, otherwise it'll wait until the first
1270 message is sent before offering NotesBuddy features. Therefore
1271 whenever Purple creates a conversation, we'll immediately open the
1272 channel to the other side and figure out what the target can
1273 handle. Unfortunately, this makes us vulnerable to Psychic Mode,
1274 whereas a more lazy negotiation based on the first message
1275 would not */
1276
1277 PurpleConnection *gc;
1278 struct mwIdBlock who = { 0, 0 };
1279 struct mwConversation *conv;
1280
1281 gc = purple_conversation_get_gc(g_conv);
1282 if(pd->gc != gc)
1283 return; /* not ours */
1284
1285 if(purple_conversation_get_type(g_conv) != PURPLE_CONV_TYPE_IM)
1286 return; /* wrong type */
1287
1288 who.user = (char *) purple_conversation_get_name(g_conv);
1289 conv = mwServiceIm_getConversation(pd->srvc_im, &who);
1290
1291 convo_features(conv);
1292
1293 if(mwConversation_isClosed(conv))
1294 mwConversation_open(conv);
1295 }
1296
1297
blist_menu_nab(PurpleBlistNode * node,gpointer data)1298 static void blist_menu_nab(PurpleBlistNode *node, gpointer data) {
1299 struct mwPurplePluginData *pd = data;
1300 PurpleConnection *gc;
1301
1302 PurpleGroup *group = (PurpleGroup *) node;
1303
1304 GString *str;
1305 char *tmp;
1306 const char *gname;
1307
1308 g_return_if_fail(pd != NULL);
1309
1310 gc = pd->gc;
1311 g_return_if_fail(gc != NULL);
1312
1313 g_return_if_fail(PURPLE_BLIST_NODE_IS_GROUP(node));
1314
1315 str = g_string_new(NULL);
1316
1317 tmp = (char *) purple_blist_node_get_string(node, GROUP_KEY_NAME);
1318 gname = purple_group_get_name(group);
1319
1320 g_string_append_printf(str, _("<b>Group Title:</b> %s<br>"), gname);
1321 g_string_append_printf(str, _("<b>Notes Group ID:</b> %s<br>"), tmp);
1322
1323 tmp = g_strdup_printf(_("Info for Group %s"), gname);
1324
1325 purple_notify_formatted(gc, tmp, _("Notes Address Book Information"),
1326 NULL, str->str, NULL, NULL);
1327
1328 g_free(tmp);
1329 g_string_free(str, TRUE);
1330 }
1331
1332
1333 /** The normal blist menu prpl function doesn't get called for groups,
1334 so we use the blist-node-extended-menu signal to trigger this
1335 handler */
blist_node_menu_cb(PurpleBlistNode * node,GList ** menu,struct mwPurplePluginData * pd)1336 static void blist_node_menu_cb(PurpleBlistNode *node,
1337 GList **menu, struct mwPurplePluginData *pd) {
1338 const char *owner;
1339 PurpleAccount *acct;
1340 PurpleMenuAction *act;
1341
1342 /* we only want groups */
1343 if(! PURPLE_BLIST_NODE_IS_GROUP(node)) return;
1344
1345 acct = purple_connection_get_account(pd->gc);
1346 g_return_if_fail(acct != NULL);
1347
1348 /* better make sure we're connected */
1349 if(! purple_account_is_connected(acct)) return;
1350
1351 #if 0
1352 /* if there's anyone in the group for this acct, offer to invite
1353 them all to a conference */
1354 if(purple_group_on_account(group, acct)) {
1355 act = purple_menu_action_new(_("Invite Group to Conference..."),
1356 PURPLE_CALLBACK(blist_menu_group_invite),
1357 pd, NULL);
1358 *menu = g_list_append(*menu, NULL);
1359 }
1360 #endif
1361
1362 /* check if it's a NAB group for this account */
1363 owner = purple_blist_node_get_string(node, GROUP_KEY_OWNER);
1364 if(owner && purple_strequal(owner, purple_account_get_username(acct))) {
1365 act = purple_menu_action_new(_("Get Notes Address Book Info"),
1366 PURPLE_CALLBACK(blist_menu_nab), pd, NULL);
1367 *menu = g_list_append(*menu, act);
1368 }
1369 }
1370
1371
1372 /* lifted this from oldstatus, since HEAD doesn't do this at login
1373 anymore. */
blist_init(PurpleAccount * acct)1374 static void blist_init(PurpleAccount *acct) {
1375 PurpleBlistNode *gnode, *cnode, *bnode;
1376 GList *add_buds = NULL;
1377
1378 for(gnode = purple_blist_get_root(); gnode;
1379 gnode = purple_blist_node_get_sibling_next(gnode)) {
1380 if(! PURPLE_BLIST_NODE_IS_GROUP(gnode)) continue;
1381
1382 for(cnode = purple_blist_node_get_first_child(gnode);
1383 cnode;
1384 cnode = purple_blist_node_get_sibling_next(cnode)) {
1385 if(! PURPLE_BLIST_NODE_IS_CONTACT(cnode))
1386 continue;
1387 for(bnode = purple_blist_node_get_first_child(cnode);
1388 bnode;
1389 bnode = purple_blist_node_get_sibling_next(bnode)) {
1390 PurpleBuddy *b;
1391 if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
1392 continue;
1393
1394 b = (PurpleBuddy *)bnode;
1395 if(purple_buddy_get_account(b) == acct) {
1396 add_buds = g_list_append(add_buds, b);
1397 }
1398 }
1399 }
1400 }
1401
1402 if(add_buds) {
1403 purple_account_add_buddies(acct, add_buds);
1404 g_list_free(add_buds);
1405 }
1406 }
1407
1408
1409 /** Last thing to happen from a started session */
services_starting(struct mwPurplePluginData * pd)1410 static void services_starting(struct mwPurplePluginData *pd) {
1411
1412 PurpleConnection *gc;
1413 PurpleAccount *acct;
1414 struct mwStorageUnit *unit;
1415 PurpleBlistNode *l;
1416
1417 gc = pd->gc;
1418 acct = purple_connection_get_account(gc);
1419
1420 /* grab the buddy list from the server */
1421 unit = mwStorageUnit_new(mwStore_AWARE_LIST);
1422 mwServiceStorage_load(pd->srvc_store, unit, fetch_blist_cb, pd, NULL);
1423
1424 /* find all the NAB groups and subscribe to them */
1425 for(l = purple_blist_get_root(); l;
1426 l = purple_blist_node_get_sibling_next(l)) {
1427 PurpleGroup *group = (PurpleGroup *) l;
1428 enum mwSametimeGroupType gt;
1429 const char *owner;
1430
1431 if(! PURPLE_BLIST_NODE_IS_GROUP(l)) continue;
1432
1433 /* if the group is ownerless, or has an owner and we're not it,
1434 skip it */
1435 owner = purple_blist_node_get_string(l, GROUP_KEY_OWNER);
1436 if(!owner || !purple_strequal(owner, purple_account_get_username(acct)))
1437 continue;
1438
1439 gt = purple_blist_node_get_int(l, GROUP_KEY_TYPE);
1440 if(gt == mwSametimeGroup_DYNAMIC)
1441 group_add(pd, group);
1442 }
1443
1444 /* set the aware attributes */
1445 /* indicate we understand what AV prefs are, but don't support any */
1446 mwServiceAware_setAttributeBoolean(pd->srvc_aware,
1447 mwAttribute_AV_PREFS_SET, TRUE);
1448 mwServiceAware_unsetAttribute(pd->srvc_aware, mwAttribute_MICROPHONE);
1449 mwServiceAware_unsetAttribute(pd->srvc_aware, mwAttribute_SPEAKERS);
1450 mwServiceAware_unsetAttribute(pd->srvc_aware, mwAttribute_VIDEO_CAMERA);
1451
1452 /* ... but we can do file transfers! */
1453 mwServiceAware_setAttributeBoolean(pd->srvc_aware,
1454 mwAttribute_FILE_TRANSFER, TRUE);
1455
1456 blist_init(acct);
1457 }
1458
1459
session_loginRedirect(struct mwSession * session,const char * host)1460 static void session_loginRedirect(struct mwSession *session,
1461 const char *host) {
1462 struct mwPurplePluginData *pd;
1463 PurpleConnection *gc;
1464 PurpleAccount *account;
1465 guint port;
1466 const char *current_host;
1467
1468 pd = mwSession_getClientData(session);
1469 gc = pd->gc;
1470 account = purple_connection_get_account(gc);
1471 port = purple_account_get_int(account, MW_KEY_PORT, MW_PLUGIN_DEFAULT_PORT);
1472 current_host = purple_account_get_string(account, MW_KEY_HOST,
1473 MW_PLUGIN_DEFAULT_HOST);
1474
1475 if(purple_account_get_bool(account, MW_KEY_FORCE, FALSE) ||
1476 !host || purple_strequal(current_host, host) ||
1477 (purple_proxy_connect(gc, account, host, port, connect_cb, pd) == NULL)) {
1478
1479 /* if we're configured to force logins, or if we're being
1480 redirected to the already configured host, or if we couldn't
1481 connect to the new host, we'll force the login instead */
1482
1483 mwSession_forceLogin(session);
1484 }
1485 }
1486
1487
1488 static void mw_prpl_set_status(PurpleAccount *acct, PurpleStatus *status);
1489
1490
1491 /** called from mw_session_stateChange when the session's state is
1492 mwSession_STARTED. Any finalizing of start-up stuff should go
1493 here */
session_started(struct mwPurplePluginData * pd)1494 static void session_started(struct mwPurplePluginData *pd) {
1495 PurpleStatus *status;
1496 PurpleAccount *acct;
1497
1498 /* set out initial status */
1499 acct = purple_connection_get_account(pd->gc);
1500 status = purple_account_get_active_status(acct);
1501 mw_prpl_set_status(acct, status);
1502
1503 /* start watching for new conversations */
1504 purple_signal_connect(purple_conversations_get_handle(),
1505 "conversation-created", pd,
1506 PURPLE_CALLBACK(conversation_created_cb), pd);
1507
1508 /* watch for group extended menu items */
1509 purple_signal_connect(purple_blist_get_handle(),
1510 "blist-node-extended-menu", pd,
1511 PURPLE_CALLBACK(blist_node_menu_cb), pd);
1512
1513 /* use our services to do neat things */
1514 services_starting(pd);
1515 }
1516
1517
session_stopping(struct mwPurplePluginData * pd)1518 static void session_stopping(struct mwPurplePluginData *pd) {
1519 /* stop watching the signals from session_started */
1520 purple_signals_disconnect_by_handle(pd);
1521 }
1522
1523
mw_session_stateChange(struct mwSession * session,enum mwSessionState state,gpointer info)1524 static void mw_session_stateChange(struct mwSession *session,
1525 enum mwSessionState state,
1526 gpointer info) {
1527 struct mwPurplePluginData *pd;
1528 PurpleConnection *gc;
1529 const char *msg = NULL;
1530
1531 pd = mwSession_getClientData(session);
1532 gc = pd->gc;
1533
1534 switch(state) {
1535 case mwSession_STARTING:
1536 msg = _("Sending Handshake");
1537 purple_connection_update_progress(gc, msg, 2, MW_CONNECT_STEPS);
1538 break;
1539
1540 case mwSession_HANDSHAKE:
1541 msg = _("Waiting for Handshake Acknowledgement");
1542 purple_connection_update_progress(gc, msg, 3, MW_CONNECT_STEPS);
1543 break;
1544
1545 case mwSession_HANDSHAKE_ACK:
1546 msg = _("Handshake Acknowledged, Sending Login");
1547 purple_connection_update_progress(gc, msg, 4, MW_CONNECT_STEPS);
1548 break;
1549
1550 case mwSession_LOGIN:
1551 msg = _("Waiting for Login Acknowledgement");
1552 purple_connection_update_progress(gc, msg, 5, MW_CONNECT_STEPS);
1553 break;
1554
1555 case mwSession_LOGIN_REDIR:
1556 msg = _("Login Redirected");
1557 purple_connection_update_progress(gc, msg, 6, MW_CONNECT_STEPS);
1558 session_loginRedirect(session, info);
1559 break;
1560
1561 case mwSession_LOGIN_CONT:
1562 msg = _("Forcing Login");
1563 purple_connection_update_progress(gc, msg, 7, MW_CONNECT_STEPS);
1564 break;
1565
1566 case mwSession_LOGIN_ACK:
1567 msg = _("Login Acknowledged");
1568 purple_connection_update_progress(gc, msg, 8, MW_CONNECT_STEPS);
1569 break;
1570
1571 case mwSession_STARTED:
1572 msg = _("Starting Services");
1573 purple_connection_update_progress(gc, msg, 9, MW_CONNECT_STEPS);
1574
1575 session_started(pd);
1576
1577 msg = _("Connected");
1578 purple_connection_update_progress(gc, msg, 10, MW_CONNECT_STEPS);
1579 purple_connection_set_state(gc, PURPLE_CONNECTED);
1580 break;
1581
1582 case mwSession_STOPPING:
1583
1584 session_stopping(pd);
1585
1586 if(GPOINTER_TO_UINT(info) & ERR_FAILURE) {
1587 char *err = mwError(GPOINTER_TO_UINT(info));
1588 PurpleConnectionError reason;
1589 switch (GPOINTER_TO_UINT(info)) {
1590 case VERSION_MISMATCH:
1591 reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
1592 break;
1593
1594 case USER_RESTRICTED:
1595 case INCORRECT_LOGIN:
1596 case USER_UNREGISTERED:
1597 case GUEST_IN_USE:
1598 reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
1599 break;
1600
1601 case ENCRYPT_MISMATCH:
1602 case ERR_ENCRYPT_NO_SUPPORT:
1603 case ERR_NO_COMMON_ENCRYPT:
1604 reason = PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR;
1605 break;
1606
1607 case VERIFICATION_DOWN:
1608 reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE;
1609 break;
1610
1611 case MULTI_SERVER_LOGIN:
1612 case MULTI_SERVER_LOGIN2:
1613 reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE;
1614 break;
1615
1616 default:
1617 reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
1618 }
1619 purple_connection_error_reason(gc, reason, err);
1620 g_free(err);
1621 }
1622 break;
1623
1624 case mwSession_STOPPED:
1625 break;
1626
1627 case mwSession_UNKNOWN:
1628 default:
1629 DEBUG_WARN("session in unknown state\n");
1630 }
1631 }
1632
1633
mw_session_setPrivacyInfo(struct mwSession * session)1634 static void mw_session_setPrivacyInfo(struct mwSession *session) {
1635 struct mwPurplePluginData *pd;
1636 PurpleConnection *gc;
1637 PurpleAccount *acct;
1638 struct mwPrivacyInfo *privacy;
1639 GSList *l, **ll;
1640 guint count;
1641
1642 DEBUG_INFO("privacy information set from server\n");
1643
1644 g_return_if_fail(session != NULL);
1645
1646 pd = mwSession_getClientData(session);
1647 g_return_if_fail(pd != NULL);
1648
1649 gc = pd->gc;
1650 g_return_if_fail(gc != NULL);
1651
1652 acct = purple_connection_get_account(gc);
1653 g_return_if_fail(acct != NULL);
1654
1655 privacy = mwSession_getPrivacyInfo(session);
1656 count = privacy->count;
1657
1658 ll = (privacy->deny)? &acct->deny: &acct->permit;
1659 for(l = *ll; l; l = l->next) g_free(l->data);
1660 g_slist_free(*ll);
1661 l = *ll = NULL;
1662
1663 while(count--) {
1664 struct mwUserItem *u = privacy->users + count;
1665 l = g_slist_prepend(l, g_strdup(u->id));
1666 }
1667 *ll = l;
1668 }
1669
1670
mw_session_setUserStatus(struct mwSession * session)1671 static void mw_session_setUserStatus(struct mwSession *session) {
1672 struct mwPurplePluginData *pd;
1673 PurpleConnection *gc;
1674 struct mwAwareIdBlock idb = { mwAware_USER, NULL, NULL };
1675 struct mwUserStatus *stat;
1676
1677 g_return_if_fail(session != NULL);
1678
1679 pd = mwSession_getClientData(session);
1680 g_return_if_fail(pd != NULL);
1681
1682 gc = pd->gc;
1683 g_return_if_fail(gc != NULL);
1684
1685 idb.user = mwSession_getProperty(session, mwSession_AUTH_USER_ID);
1686 stat = mwSession_getUserStatus(session);
1687
1688 /* trigger an update of our own status if we're in the buddy list */
1689 mwServiceAware_setStatus(pd->srvc_aware, &idb, stat);
1690 }
1691
1692
mw_session_admin(struct mwSession * session,const char * text)1693 static void mw_session_admin(struct mwSession *session,
1694 const char *text) {
1695 PurpleConnection *gc;
1696 PurpleAccount *acct;
1697 const char *host;
1698 const char *msg;
1699 char *prim;
1700
1701 gc = session_to_gc(session);
1702 g_return_if_fail(gc != NULL);
1703
1704 acct = purple_connection_get_account(gc);
1705 g_return_if_fail(acct != NULL);
1706
1707 host = purple_account_get_string(acct, MW_KEY_HOST, NULL);
1708
1709 msg = _("A Sametime administrator has issued the following announcement"
1710 " on server %s");
1711 prim = g_strdup_printf(msg, NSTR(host));
1712
1713 purple_notify_message(gc, PURPLE_NOTIFY_MSG_INFO,
1714 _("Sametime Administrator Announcement"),
1715 prim, text, NULL, NULL);
1716
1717 g_free(prim);
1718 }
1719
1720
1721 /** called from read_cb, attempts to read available data from sock and
1722 pass it to the session, passing back the return code from the read
1723 call for handling in read_cb */
read_recv(struct mwSession * session,int sock)1724 static int read_recv(struct mwSession *session, int sock) {
1725 guchar buf[BUF_LEN];
1726 int len;
1727
1728 len = read(sock, buf, BUF_LEN);
1729 if(len > 0) {
1730 mwSession_recv(session, buf, len);
1731 }
1732
1733 return len;
1734 }
1735
1736
1737 /** callback triggered from purple_input_add, watches the socked for
1738 available data to be processed by the session */
read_cb(gpointer data,gint source,PurpleInputCondition cond)1739 static void read_cb(gpointer data, gint source, PurpleInputCondition cond) {
1740 struct mwPurplePluginData *pd = data;
1741 int ret = 0, err = 0;
1742
1743 g_return_if_fail(pd != NULL);
1744
1745 ret = read_recv(pd->session, pd->socket);
1746
1747 /* normal operation ends here */
1748 if(ret > 0) return;
1749
1750 /* fetch the global error value */
1751 err = errno;
1752
1753 /* read problem occurred if we're here, so we'll need to take care of
1754 it and clean up internal state */
1755
1756 if(pd->socket) {
1757 close(pd->socket);
1758 pd->socket = 0;
1759 }
1760
1761 if(pd->gc->inpa) {
1762 purple_input_remove(pd->gc->inpa);
1763 pd->gc->inpa = 0;
1764 }
1765
1766 if(! ret) {
1767 DEBUG_INFO("connection reset\n");
1768 purple_connection_error_reason(pd->gc,
1769 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1770 _("Server closed the connection"));
1771
1772 } else if(ret < 0) {
1773 const gchar *err_str = g_strerror(err);
1774 char *msg = NULL;
1775
1776 DEBUG_INFO("error in read callback: %s\n", err_str);
1777
1778 msg = g_strdup_printf(_("Lost connection with server: %s"), err_str);
1779 purple_connection_error_reason(pd->gc,
1780 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1781 msg);
1782 g_free(msg);
1783 }
1784 }
1785
1786
1787 /** Callback passed to purple_proxy_connect when an account is logged
1788 in, and if the session logging in receives a redirect message */
connect_cb(gpointer data,gint source,const gchar * error_message)1789 static void connect_cb(gpointer data, gint source, const gchar *error_message) {
1790
1791 struct mwPurplePluginData *pd = data;
1792 PurpleConnection *gc = pd->gc;
1793
1794 if(source < 0) {
1795 /* connection failed */
1796
1797 if(pd->socket) {
1798 /* this is a redirect connect, force login on existing socket */
1799 mwSession_forceLogin(pd->session);
1800
1801 } else {
1802 /* this is a regular connect, error out */
1803 gchar *tmp = g_strdup_printf(_("Unable to connect: %s"),
1804 error_message);
1805 purple_connection_error_reason(pd->gc,
1806 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1807 tmp);
1808 g_free(tmp);
1809 }
1810
1811 return;
1812 }
1813
1814 if(pd->socket) {
1815 /* stop any existing login attempt */
1816 mwSession_stop(pd->session, ERR_SUCCESS);
1817 }
1818
1819 pd->socket = source;
1820 gc->inpa = purple_input_add(source, PURPLE_INPUT_READ,
1821 read_cb, pd);
1822
1823 mwSession_start(pd->session);
1824 }
1825
1826
mw_session_announce(struct mwSession * s,struct mwLoginInfo * from,gboolean may_reply,const char * text)1827 static void mw_session_announce(struct mwSession *s,
1828 struct mwLoginInfo *from,
1829 gboolean may_reply,
1830 const char *text) {
1831 struct mwPurplePluginData *pd;
1832 PurpleAccount *acct;
1833 PurpleConversation *conv;
1834 PurpleBuddy *buddy;
1835 char *who = from->user_id;
1836 char *msg;
1837
1838 pd = mwSession_getClientData(s);
1839 acct = purple_connection_get_account(pd->gc);
1840 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, acct);
1841 if(! conv) conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, who);
1842
1843 buddy = purple_find_buddy(acct, who);
1844 if(buddy) who = (char *) purple_buddy_get_contact_alias(buddy);
1845
1846 who = g_strdup_printf(_("Announcement from %s"), who);
1847 msg = purple_markup_linkify(text);
1848
1849 purple_conversation_write(conv, who, msg ? msg : "", PURPLE_MESSAGE_RECV, time(NULL));
1850 g_free(who);
1851 g_free(msg);
1852 }
1853
1854
1855 static struct mwSessionHandler mw_session_handler = {
1856 mw_session_io_write,
1857 mw_session_io_close,
1858 mw_session_clear,
1859 mw_session_stateChange,
1860 mw_session_setPrivacyInfo,
1861 mw_session_setUserStatus,
1862 mw_session_admin,
1863 mw_session_announce,
1864 };
1865
1866
mw_aware_on_attrib(struct mwServiceAware * srvc,struct mwAwareAttribute * attrib)1867 static void mw_aware_on_attrib(struct mwServiceAware *srvc,
1868 struct mwAwareAttribute *attrib) {
1869
1870 ; /** @todo handle server attributes. There may be some stuff we
1871 actually want to look for, but I'm not aware of anything right
1872 now.*/
1873 }
1874
1875
mw_aware_clear(struct mwServiceAware * srvc)1876 static void mw_aware_clear(struct mwServiceAware *srvc) {
1877 ; /* nothing for now */
1878 }
1879
1880
1881 static struct mwAwareHandler mw_aware_handler = {
1882 mw_aware_on_attrib,
1883 mw_aware_clear,
1884 };
1885
1886
mw_srvc_aware_new(struct mwSession * s)1887 static struct mwServiceAware *mw_srvc_aware_new(struct mwSession *s) {
1888 struct mwServiceAware *srvc;
1889 srvc = mwServiceAware_new(s, &mw_aware_handler);
1890 return srvc;
1891 };
1892
1893
mw_conf_invited(struct mwConference * conf,struct mwLoginInfo * inviter,const char * invitation)1894 static void mw_conf_invited(struct mwConference *conf,
1895 struct mwLoginInfo *inviter,
1896 const char *invitation) {
1897
1898 struct mwServiceConference *srvc;
1899 struct mwSession *session;
1900 struct mwPurplePluginData *pd;
1901 PurpleConnection *gc;
1902
1903 char *c_inviter, *c_name, *c_topic, *c_invitation;
1904 GHashTable *ht;
1905
1906 srvc = mwConference_getService(conf);
1907 session = mwService_getSession(MW_SERVICE(srvc));
1908 pd = mwSession_getClientData(session);
1909 gc = pd->gc;
1910
1911 ht = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
1912
1913 c_inviter = g_strdup(inviter->user_id);
1914 g_hash_table_insert(ht, CHAT_KEY_CREATOR, c_inviter);
1915
1916 c_name = g_strdup(mwConference_getName(conf));
1917 g_hash_table_insert(ht, CHAT_KEY_NAME, c_name);
1918
1919 c_topic = g_strdup(mwConference_getTitle(conf));
1920 g_hash_table_insert(ht, CHAT_KEY_TOPIC, c_topic);
1921
1922 c_invitation = g_strdup(invitation);
1923 g_hash_table_insert(ht, CHAT_KEY_INVITE, c_invitation);
1924
1925 DEBUG_INFO("received invitation from '%s' to join ('%s','%s'): '%s'\n",
1926 NSTR(c_inviter), NSTR(c_name),
1927 NSTR(c_topic), NSTR(c_invitation));
1928
1929 if(! c_topic) c_topic = "(no title)";
1930 if(! c_invitation) c_invitation = "(no message)";
1931 serv_got_chat_invite(gc, c_topic, c_inviter, c_invitation, ht);
1932 }
1933
1934
1935 /* The following mess helps us relate a mwConference to a PurpleConvChat
1936 in the various forms by which either may be indicated */
1937
1938 #define CONF_TO_ID(conf) (GPOINTER_TO_INT(conf))
1939 #define ID_TO_CONF(pd, id) (conf_find_by_id((pd), (id)))
1940
1941 #define CHAT_TO_ID(chat) (purple_conv_chat_get_id(chat))
1942 #define ID_TO_CHAT(id) (purple_find_chat(id))
1943
1944 #define CHAT_TO_CONF(pd, chat) (ID_TO_CONF((pd), CHAT_TO_ID(chat)))
1945 #define CONF_TO_CHAT(conf) (ID_TO_CHAT(CONF_TO_ID(conf)))
1946
1947
1948 static struct mwConference *
conf_find_by_id(struct mwPurplePluginData * pd,int id)1949 conf_find_by_id(struct mwPurplePluginData *pd, int id) {
1950
1951 struct mwServiceConference *srvc = pd->srvc_conf;
1952 struct mwConference *conf = NULL;
1953 GList *l, *ll;
1954
1955 ll = mwServiceConference_getConferences(srvc);
1956 for(l = ll; l; l = l->next) {
1957 struct mwConference *c = l->data;
1958 PurpleConvChat *h = mwConference_getClientData(c);
1959
1960 if(CHAT_TO_ID(h) == id) {
1961 conf = c;
1962 break;
1963 }
1964 }
1965 g_list_free(ll);
1966
1967 return conf;
1968 }
1969
1970
mw_conf_opened(struct mwConference * conf,GList * members)1971 static void mw_conf_opened(struct mwConference *conf, GList *members) {
1972 struct mwServiceConference *srvc;
1973 struct mwSession *session;
1974 struct mwPurplePluginData *pd;
1975 PurpleConnection *gc;
1976 PurpleConversation *g_conf;
1977
1978 const char *n = mwConference_getName(conf);
1979 const char *t = mwConference_getTitle(conf);
1980
1981 DEBUG_INFO("conf %s opened, %u initial members\n",
1982 NSTR(n), g_list_length(members));
1983
1984 srvc = mwConference_getService(conf);
1985 session = mwService_getSession(MW_SERVICE(srvc));
1986 pd = mwSession_getClientData(session);
1987 gc = pd->gc;
1988
1989 if(! t) t = "(no title)";
1990 g_conf = serv_got_joined_chat(gc, CONF_TO_ID(conf), t);
1991
1992 mwConference_setClientData(conf, PURPLE_CONV_CHAT(g_conf), NULL);
1993
1994 for(; members; members = members->next) {
1995 struct mwLoginInfo *peer = members->data;
1996 purple_conv_chat_add_user(PURPLE_CONV_CHAT(g_conf), peer->user_id,
1997 NULL, PURPLE_CBFLAGS_NONE, FALSE);
1998 }
1999 }
2000
2001
mw_conf_closed(struct mwConference * conf,guint32 reason)2002 static void mw_conf_closed(struct mwConference *conf, guint32 reason) {
2003 struct mwServiceConference *srvc;
2004 struct mwSession *session;
2005 struct mwPurplePluginData *pd;
2006 PurpleConnection *gc;
2007
2008 const char *n = mwConference_getName(conf);
2009 char *msg = mwError(reason);
2010
2011 DEBUG_INFO("conf %s closed, 0x%08x\n", NSTR(n), reason);
2012
2013 srvc = mwConference_getService(conf);
2014 session = mwService_getSession(MW_SERVICE(srvc));
2015 pd = mwSession_getClientData(session);
2016 gc = pd->gc;
2017
2018 serv_got_chat_left(gc, CONF_TO_ID(conf));
2019
2020 purple_notify_error(gc, _("Conference Closed"), NULL, msg);
2021 g_free(msg);
2022 }
2023
2024
mw_conf_peer_joined(struct mwConference * conf,struct mwLoginInfo * peer)2025 static void mw_conf_peer_joined(struct mwConference *conf,
2026 struct mwLoginInfo *peer) {
2027
2028 PurpleConvChat *g_conf;
2029
2030 const char *n = mwConference_getName(conf);
2031
2032 DEBUG_INFO("%s joined conf %s\n", NSTR(peer->user_id), NSTR(n));
2033
2034 g_conf = mwConference_getClientData(conf);
2035 g_return_if_fail(g_conf != NULL);
2036
2037 purple_conv_chat_add_user(g_conf, peer->user_id,
2038 NULL, PURPLE_CBFLAGS_NONE, TRUE);
2039 }
2040
2041
mw_conf_peer_parted(struct mwConference * conf,struct mwLoginInfo * peer)2042 static void mw_conf_peer_parted(struct mwConference *conf,
2043 struct mwLoginInfo *peer) {
2044
2045 PurpleConvChat *g_conf;
2046
2047 const char *n = mwConference_getName(conf);
2048
2049 DEBUG_INFO("%s left conf %s\n", NSTR(peer->user_id), NSTR(n));
2050
2051 g_conf = mwConference_getClientData(conf);
2052 g_return_if_fail(g_conf != NULL);
2053
2054 purple_conv_chat_remove_user(g_conf, peer->user_id, NULL);
2055 }
2056
2057
mw_conf_text(struct mwConference * conf,struct mwLoginInfo * who,const char * text)2058 static void mw_conf_text(struct mwConference *conf,
2059 struct mwLoginInfo *who, const char *text) {
2060
2061 struct mwServiceConference *srvc;
2062 struct mwSession *session;
2063 struct mwPurplePluginData *pd;
2064 PurpleConnection *gc;
2065 char *esc;
2066
2067 if(! text) return;
2068
2069 srvc = mwConference_getService(conf);
2070 session = mwService_getSession(MW_SERVICE(srvc));
2071 pd = mwSession_getClientData(session);
2072 gc = pd->gc;
2073
2074 esc = g_markup_escape_text(text, -1);
2075 serv_got_chat_in(gc, CONF_TO_ID(conf), who->user_id, 0, esc, time(NULL));
2076 g_free(esc);
2077 }
2078
2079
mw_conf_typing(struct mwConference * conf,struct mwLoginInfo * who,gboolean typing)2080 static void mw_conf_typing(struct mwConference *conf,
2081 struct mwLoginInfo *who, gboolean typing) {
2082
2083 /* purple really has no good way to expose this to the user. */
2084
2085 const char *n = mwConference_getName(conf);
2086 const char *w = who->user_id;
2087
2088 if(typing) {
2089 DEBUG_INFO("%s in conf %s: <typing>\n", NSTR(w), NSTR(n));
2090
2091 } else {
2092 DEBUG_INFO("%s in conf %s: <stopped typing>\n", NSTR(w), NSTR(n));
2093 }
2094 }
2095
2096
mw_conf_clear(struct mwServiceConference * srvc)2097 static void mw_conf_clear(struct mwServiceConference *srvc) {
2098 ;
2099 }
2100
2101
2102 static struct mwConferenceHandler mw_conference_handler = {
2103 mw_conf_invited,
2104 mw_conf_opened,
2105 mw_conf_closed,
2106 mw_conf_peer_joined,
2107 mw_conf_peer_parted,
2108 mw_conf_text,
2109 mw_conf_typing,
2110 mw_conf_clear,
2111 };
2112
2113
mw_srvc_conf_new(struct mwSession * s)2114 static struct mwServiceConference *mw_srvc_conf_new(struct mwSession *s) {
2115 struct mwServiceConference *srvc;
2116 srvc = mwServiceConference_new(s, &mw_conference_handler);
2117 return srvc;
2118 }
2119
2120
2121 /** size of an outgoing file transfer chunk */
2122 #define MW_FT_LEN (BUF_LONG * 2)
2123
2124
ft_incoming_cancel(PurpleXfer * xfer)2125 static void ft_incoming_cancel(PurpleXfer *xfer) {
2126 /* incoming transfer rejected or cancelled in-progress */
2127 struct mwFileTransfer *ft = xfer->data;
2128 if(ft) mwFileTransfer_reject(ft);
2129 }
2130
2131
ft_incoming_init(PurpleXfer * xfer)2132 static void ft_incoming_init(PurpleXfer *xfer) {
2133 /* incoming transfer accepted */
2134
2135 /* - accept the mwFileTransfer
2136 - open/create the local FILE "wb"
2137 - stick the FILE's fp in xfer->dest_fp
2138 */
2139
2140 struct mwFileTransfer *ft;
2141 FILE *fp;
2142
2143 ft = xfer->data;
2144
2145 fp = g_fopen(xfer->local_filename, "wb");
2146 if(! fp) {
2147 mwFileTransfer_cancel(ft);
2148 return;
2149 }
2150
2151 xfer->dest_fp = fp;
2152 mwFileTransfer_accept(ft);
2153 }
2154
2155
mw_ft_offered(struct mwFileTransfer * ft)2156 static void mw_ft_offered(struct mwFileTransfer *ft) {
2157 /*
2158 - create a purple ft object
2159 - offer it
2160 */
2161
2162 struct mwServiceFileTransfer *srvc;
2163 struct mwSession *session;
2164 struct mwPurplePluginData *pd;
2165 PurpleConnection *gc;
2166 PurpleAccount *acct;
2167 const char *who;
2168 PurpleXfer *xfer;
2169
2170 /* @todo add some safety checks */
2171 srvc = mwFileTransfer_getService(ft);
2172 session = mwService_getSession(MW_SERVICE(srvc));
2173 pd = mwSession_getClientData(session);
2174 gc = pd->gc;
2175 acct = purple_connection_get_account(gc);
2176
2177 who = mwFileTransfer_getUser(ft)->user;
2178
2179 DEBUG_INFO("file transfer %p offered\n", ft);
2180 DEBUG_INFO(" from: %s\n", NSTR(who));
2181 DEBUG_INFO(" file: %s\n", NSTR(mwFileTransfer_getFileName(ft)));
2182 DEBUG_INFO(" size: %u\n", mwFileTransfer_getFileSize(ft));
2183 DEBUG_INFO(" text: %s\n", NSTR(mwFileTransfer_getMessage(ft)));
2184
2185 xfer = purple_xfer_new(acct, PURPLE_XFER_RECEIVE, who);
2186 if (xfer)
2187 {
2188 purple_xfer_ref(xfer);
2189 mwFileTransfer_setClientData(ft, xfer, (GDestroyNotify) purple_xfer_unref);
2190 xfer->data = ft;
2191
2192 purple_xfer_set_init_fnc(xfer, ft_incoming_init);
2193 purple_xfer_set_cancel_recv_fnc(xfer, ft_incoming_cancel);
2194 purple_xfer_set_request_denied_fnc(xfer, ft_incoming_cancel);
2195
2196 purple_xfer_set_filename(xfer, mwFileTransfer_getFileName(ft));
2197 purple_xfer_set_size(xfer, mwFileTransfer_getFileSize(ft));
2198 purple_xfer_set_message(xfer, mwFileTransfer_getMessage(ft));
2199
2200 purple_xfer_request(xfer);
2201 }
2202 }
2203
2204
ft_send(struct mwFileTransfer * ft,FILE * fp)2205 static void ft_send(struct mwFileTransfer *ft, FILE *fp) {
2206 guchar buf[MW_FT_LEN];
2207 struct mwOpaque o = { MW_FT_LEN, buf };
2208 guint32 rem;
2209 PurpleXfer *xfer;
2210
2211 xfer = mwFileTransfer_getClientData(ft);
2212
2213 rem = mwFileTransfer_getRemaining(ft);
2214 if(rem < MW_FT_LEN) o.len = rem;
2215
2216 if (fread(buf, (size_t)o.len, 1, fp) == 1) {
2217
2218 /* calculate progress and display it */
2219 xfer->bytes_sent += o.len;
2220 xfer->bytes_remaining -= o.len;
2221 purple_xfer_update_progress(xfer);
2222
2223 mwFileTransfer_send(ft, &o);
2224
2225 } else {
2226 int err = errno;
2227 DEBUG_WARN("problem reading from file %s: %s\n",
2228 NSTR(mwFileTransfer_getFileName(ft)), g_strerror(err));
2229
2230 mwFileTransfer_cancel(ft);
2231 }
2232 }
2233
2234
mw_ft_opened(struct mwFileTransfer * ft)2235 static void mw_ft_opened(struct mwFileTransfer *ft) {
2236 /*
2237 - get purple ft from client data in ft
2238 - set the state to active
2239 */
2240
2241 PurpleXfer *xfer;
2242
2243 xfer = mwFileTransfer_getClientData(ft);
2244
2245 if(! xfer) {
2246 mwFileTransfer_cancel(ft);
2247 mwFileTransfer_free(ft);
2248 g_return_if_reached();
2249 }
2250
2251 if(purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) {
2252 xfer->dest_fp = g_fopen(xfer->local_filename, "rb");
2253 if (xfer->dest_fp)
2254 ft_send(ft, xfer->dest_fp);
2255 }
2256 }
2257
2258
mw_ft_closed(struct mwFileTransfer * ft,guint32 code)2259 static void mw_ft_closed(struct mwFileTransfer *ft, guint32 code) {
2260 /*
2261 - get purple ft from client data in ft
2262 - indicate rejection/cancelation/completion
2263 - free the file transfer itself
2264 */
2265
2266 PurpleXfer *xfer;
2267
2268 xfer = mwFileTransfer_getClientData(ft);
2269 if(xfer) {
2270 xfer->data = NULL;
2271
2272 if(! mwFileTransfer_getRemaining(ft)) {
2273 purple_xfer_set_completed(xfer, TRUE);
2274 purple_xfer_end(xfer);
2275
2276 } else if(mwFileTransfer_isCancelLocal(ft)) {
2277 /* calling purple_xfer_cancel_local is redundant, since that's
2278 probably what triggered this function to be called */
2279 ;
2280
2281 } else if(mwFileTransfer_isCancelRemote(ft)) {
2282 /* steal the reference for the xfer */
2283 mwFileTransfer_setClientData(ft, NULL, NULL);
2284 purple_xfer_cancel_remote(xfer);
2285
2286 /* drop the stolen reference */
2287 purple_xfer_unref(xfer);
2288 return;
2289 }
2290 }
2291
2292 mwFileTransfer_free(ft);
2293 }
2294
2295
mw_ft_recv(struct mwFileTransfer * ft,struct mwOpaque * data)2296 static void mw_ft_recv(struct mwFileTransfer *ft,
2297 struct mwOpaque *data) {
2298 /*
2299 - get purple ft from client data in ft
2300 - update transfered percentage
2301 - if done, destroy the ft, disassociate from purple ft
2302 */
2303
2304 PurpleXfer *xfer;
2305 FILE *fp;
2306 size_t wc;
2307
2308 xfer = mwFileTransfer_getClientData(ft);
2309 g_return_if_fail(xfer != NULL);
2310
2311 fp = xfer->dest_fp;
2312 g_return_if_fail(fp != NULL);
2313
2314 /* we must collect and save our precious data */
2315 wc = fwrite(data->data, 1, data->len, fp);
2316 if (wc != data->len) {
2317 DEBUG_ERROR("failed to write data\n");
2318 purple_xfer_cancel_local(xfer);
2319 return;
2320 }
2321
2322 /* update the progress */
2323 xfer->bytes_sent += data->len;
2324 xfer->bytes_remaining -= data->len;
2325 purple_xfer_update_progress(xfer);
2326
2327 /* let the other side know we got it, and to send some more */
2328 mwFileTransfer_ack(ft);
2329 }
2330
2331
mw_ft_ack(struct mwFileTransfer * ft)2332 static void mw_ft_ack(struct mwFileTransfer *ft) {
2333 PurpleXfer *xfer;
2334
2335 xfer = mwFileTransfer_getClientData(ft);
2336 g_return_if_fail(xfer != NULL);
2337 g_return_if_fail(xfer->watcher == 0);
2338
2339 if(! mwFileTransfer_getRemaining(ft)) {
2340 purple_xfer_set_completed(xfer, TRUE);
2341 purple_xfer_end(xfer);
2342
2343 } else if(mwFileTransfer_isOpen(ft)) {
2344 ft_send(ft, xfer->dest_fp);
2345 }
2346 }
2347
2348
mw_ft_clear(struct mwServiceFileTransfer * srvc)2349 static void mw_ft_clear(struct mwServiceFileTransfer *srvc) {
2350 ;
2351 }
2352
2353
2354 static struct mwFileTransferHandler mw_ft_handler = {
2355 mw_ft_offered,
2356 mw_ft_opened,
2357 mw_ft_closed,
2358 mw_ft_recv,
2359 mw_ft_ack,
2360 mw_ft_clear,
2361 };
2362
2363
mw_srvc_ft_new(struct mwSession * s)2364 static struct mwServiceFileTransfer *mw_srvc_ft_new(struct mwSession *s) {
2365 struct mwServiceFileTransfer *srvc;
2366 GHashTable *ft_map;
2367
2368 ft_map = g_hash_table_new(g_direct_hash, g_direct_equal);
2369
2370 srvc = mwServiceFileTransfer_new(s, &mw_ft_handler);
2371 mwService_setClientData(MW_SERVICE(srvc), ft_map,
2372 (GDestroyNotify) g_hash_table_destroy);
2373
2374 return srvc;
2375 }
2376
2377
convo_data_free(struct convo_data * cd)2378 static void convo_data_free(struct convo_data *cd) {
2379 GList *l;
2380
2381 /* clean the queue */
2382 for(l = cd->queue; l; l = g_list_delete_link(l, l)) {
2383 struct convo_msg *m = l->data;
2384 if(m->clear) m->clear(m->data);
2385 g_free(m);
2386 }
2387
2388 g_free(cd);
2389 }
2390
2391
2392 /** allocates a convo_data structure and associates it with the
2393 conversation in the client data slot */
convo_data_new(struct mwConversation * conv)2394 static void convo_data_new(struct mwConversation *conv) {
2395 struct convo_data *cd;
2396
2397 g_return_if_fail(conv != NULL);
2398
2399 if(mwConversation_getClientData(conv))
2400 return;
2401
2402 cd = g_new0(struct convo_data, 1);
2403 cd->conv = conv;
2404
2405 mwConversation_setClientData(conv, cd, (GDestroyNotify) convo_data_free);
2406 }
2407
2408
convo_get_gconv(struct mwConversation * conv)2409 static PurpleConversation *convo_get_gconv(struct mwConversation *conv) {
2410 struct mwServiceIm *srvc;
2411 struct mwSession *session;
2412 struct mwPurplePluginData *pd;
2413 PurpleConnection *gc;
2414 PurpleAccount *acct;
2415
2416 struct mwIdBlock *idb;
2417
2418 srvc = mwConversation_getService(conv);
2419 session = mwService_getSession(MW_SERVICE(srvc));
2420 pd = mwSession_getClientData(session);
2421 gc = pd->gc;
2422 acct = purple_connection_get_account(gc);
2423
2424 idb = mwConversation_getTarget(conv);
2425
2426 return purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
2427 idb->user, acct);
2428 }
2429
2430
convo_queue(struct mwConversation * conv,enum mwImSendType type,gconstpointer data)2431 static void convo_queue(struct mwConversation *conv,
2432 enum mwImSendType type, gconstpointer data) {
2433
2434 struct convo_data *cd;
2435 struct convo_msg *m;
2436
2437 convo_data_new(conv);
2438 cd = mwConversation_getClientData(conv);
2439
2440 m = g_new0(struct convo_msg, 1);
2441 m->type = type;
2442
2443 switch(type) {
2444 case mwImSend_PLAIN:
2445 m->data = g_strdup(data);
2446 m->clear = g_free;
2447 break;
2448
2449 case mwImSend_TYPING:
2450 default:
2451 m->data = (gpointer) data;
2452 m->clear = NULL;
2453 }
2454
2455 cd->queue = g_list_append(cd->queue, m);
2456 }
2457
2458
2459 /* Does what it takes to get an error displayed for a conversation */
convo_error(struct mwConversation * conv,guint32 err)2460 static void convo_error(struct mwConversation *conv, guint32 err) {
2461 PurpleConversation *gconv;
2462 char *tmp, *text;
2463 struct mwIdBlock *idb;
2464
2465 idb = mwConversation_getTarget(conv);
2466
2467 tmp = mwError(err);
2468 text = g_strconcat(_("Unable to send message: "), tmp, NULL);
2469
2470 gconv = convo_get_gconv(conv);
2471 if(gconv && !purple_conv_present_error(idb->user, gconv->account, text)) {
2472
2473 g_free(text);
2474 text = g_strdup_printf(_("Unable to send message to %s:"),
2475 (idb->user)? idb->user: "(unknown)");
2476 purple_notify_error(purple_account_get_connection(gconv->account),
2477 NULL, text, tmp);
2478 }
2479
2480 g_free(tmp);
2481 g_free(text);
2482 }
2483
2484
convo_queue_send(struct mwConversation * conv)2485 static void convo_queue_send(struct mwConversation *conv) {
2486 struct convo_data *cd;
2487 GList *l;
2488
2489 cd = mwConversation_getClientData(conv);
2490
2491 for(l = cd->queue; l; l = g_list_delete_link(l, l)) {
2492 struct convo_msg *m = l->data;
2493
2494 mwConversation_send(conv, m->type, m->data);
2495
2496 if(m->clear) m->clear(m->data);
2497 g_free(m);
2498 }
2499
2500 cd->queue = NULL;
2501 }
2502
2503
2504 /** called when a mw conversation leaves a purple conversation to
2505 inform the purple conversation that it's unsafe to offer any *cool*
2506 features. */
convo_nofeatures(struct mwConversation * conv)2507 static void convo_nofeatures(struct mwConversation *conv) {
2508 PurpleConversation *gconv;
2509 PurpleConnection *gc;
2510
2511 gconv = convo_get_gconv(conv);
2512 if(! gconv) return;
2513
2514 gc = purple_conversation_get_gc(gconv);
2515 if(! gc) return;
2516
2517 purple_conversation_set_features(gconv, gc->flags);
2518 }
2519
2520
2521 /** called when a mw conversation and purple conversation come together,
2522 to inform the purple conversation of what features to offer the
2523 user */
convo_features(struct mwConversation * conv)2524 static void convo_features(struct mwConversation *conv) {
2525 PurpleConversation *gconv;
2526 PurpleConnectionFlags feat;
2527
2528 gconv = convo_get_gconv(conv);
2529 if(! gconv) return;
2530
2531 feat = purple_conversation_get_features(gconv);
2532
2533 if(mwConversation_isOpen(conv)) {
2534 if(mwConversation_supports(conv, mwImSend_HTML)) {
2535 feat |= PURPLE_CONNECTION_HTML;
2536 } else {
2537 feat &= ~PURPLE_CONNECTION_HTML;
2538 }
2539
2540 if(mwConversation_supports(conv, mwImSend_MIME)) {
2541 feat &= ~PURPLE_CONNECTION_NO_IMAGES;
2542 } else {
2543 feat |= PURPLE_CONNECTION_NO_IMAGES;
2544 }
2545
2546 DEBUG_INFO("conversation features set to 0x%04x\n", feat);
2547 purple_conversation_set_features(gconv, feat);
2548
2549 } else {
2550 convo_nofeatures(conv);
2551 }
2552 }
2553
2554
mw_conversation_opened(struct mwConversation * conv)2555 static void mw_conversation_opened(struct mwConversation *conv) {
2556 struct mwServiceIm *srvc;
2557 struct mwSession *session;
2558 struct mwPurplePluginData *pd;
2559 PurpleConnection *gc;
2560 PurpleAccount *acct;
2561
2562 struct convo_dat *cd;
2563
2564 srvc = mwConversation_getService(conv);
2565 session = mwService_getSession(MW_SERVICE(srvc));
2566 pd = mwSession_getClientData(session);
2567 gc = pd->gc;
2568 acct = purple_connection_get_account(gc);
2569
2570 /* set up the queue */
2571 cd = mwConversation_getClientData(conv);
2572 if(cd) {
2573 convo_queue_send(conv);
2574
2575 if(! convo_get_gconv(conv)) {
2576 mwConversation_free(conv);
2577 return;
2578 }
2579
2580 } else {
2581 convo_data_new(conv);
2582 }
2583
2584 { /* record the client key for the buddy */
2585 PurpleBuddy *buddy;
2586 struct mwLoginInfo *info;
2587 info = mwConversation_getTargetInfo(conv);
2588
2589 buddy = purple_find_buddy(acct, info->user_id);
2590 if(buddy) {
2591 purple_blist_node_set_int((PurpleBlistNode *) buddy,
2592 BUDDY_KEY_CLIENT, info->type);
2593 }
2594 }
2595
2596 convo_features(conv);
2597 }
2598
2599
mw_conversation_closed(struct mwConversation * conv,guint32 reason)2600 static void mw_conversation_closed(struct mwConversation *conv,
2601 guint32 reason) {
2602
2603 struct convo_data *cd;
2604
2605 g_return_if_fail(conv != NULL);
2606
2607 /* if there's an error code and a non-typing message in the queue,
2608 print an error message to the conversation */
2609 cd = mwConversation_getClientData(conv);
2610 if(reason && cd && cd->queue) {
2611 GList *l;
2612 for(l = cd->queue; l; l = l->next) {
2613 struct convo_msg *m = l->data;
2614 if(m->type != mwImSend_TYPING) {
2615 convo_error(conv, reason);
2616 break;
2617 }
2618 }
2619 }
2620
2621 #if 0
2622 /* don't do this, to prevent the occasional weird sending of
2623 formatted messages as plaintext when the other end closes the
2624 conversation after we've begun composing the message */
2625 convo_nofeatures(conv);
2626 #endif
2627
2628 mwConversation_removeClientData(conv);
2629 }
2630
2631
im_recv_text(struct mwConversation * conv,struct mwPurplePluginData * pd,const char * msg)2632 static void im_recv_text(struct mwConversation *conv,
2633 struct mwPurplePluginData *pd,
2634 const char *msg) {
2635
2636 struct mwIdBlock *idb;
2637 char *txt, *esc;
2638 const char *t;
2639
2640 idb = mwConversation_getTarget(conv);
2641
2642 txt = purple_utf8_try_convert(msg);
2643 t = txt? txt: msg;
2644
2645 esc = g_markup_escape_text(t, -1);
2646 serv_got_im(pd->gc, idb->user, esc, 0, time(NULL));
2647 g_free(esc);
2648
2649 g_free(txt);
2650 }
2651
2652
im_recv_typing(struct mwConversation * conv,struct mwPurplePluginData * pd,gboolean typing)2653 static void im_recv_typing(struct mwConversation *conv,
2654 struct mwPurplePluginData *pd,
2655 gboolean typing) {
2656
2657 struct mwIdBlock *idb;
2658 idb = mwConversation_getTarget(conv);
2659
2660 serv_got_typing(pd->gc, idb->user, 0,
2661 typing? PURPLE_TYPING: PURPLE_NOT_TYPING);
2662 }
2663
2664
im_recv_html(struct mwConversation * conv,struct mwPurplePluginData * pd,const char * msg)2665 static void im_recv_html(struct mwConversation *conv,
2666 struct mwPurplePluginData *pd,
2667 const char *msg) {
2668 struct mwIdBlock *idb;
2669 char *t1, *t2;
2670 const char *t;
2671
2672 idb = mwConversation_getTarget(conv);
2673
2674 /* ensure we're receiving UTF8 */
2675 t1 = purple_utf8_try_convert(msg);
2676 t = t1? t1: msg;
2677
2678 /* convert entities to UTF8 so they'll log correctly */
2679 t2 = purple_utf8_ncr_decode(t);
2680 t = t2? t2: t;
2681
2682 serv_got_im(pd->gc, idb->user, t, 0, time(NULL));
2683
2684 g_free(t1);
2685 g_free(t2);
2686 }
2687
2688
im_recv_subj(struct mwConversation * conv,struct mwPurplePluginData * pd,const char * subj)2689 static void im_recv_subj(struct mwConversation *conv,
2690 struct mwPurplePluginData *pd,
2691 const char *subj) {
2692
2693 /** @todo somehow indicate receipt of a conversation subject. It
2694 would also be nice if we added a /topic command for the
2695 protocol */
2696 ;
2697 }
2698
2699
2700 /** generate "cid:908@20582notesbuddy" from "<908@20582notesbuddy>" */
make_cid(const char * cid)2701 static char *make_cid(const char *cid) {
2702 gsize n;
2703 char *c, *d;
2704
2705 g_return_val_if_fail(cid != NULL, NULL);
2706
2707 n = strlen(cid);
2708 g_return_val_if_fail(n > 2, NULL);
2709
2710 c = g_strndup(cid+1, n-2);
2711 d = g_strdup_printf("cid:%s", c);
2712
2713 g_free(c);
2714 return d;
2715 }
2716
2717
im_recv_mime(struct mwConversation * conv,struct mwPurplePluginData * pd,const char * data)2718 static void im_recv_mime(struct mwConversation *conv,
2719 struct mwPurplePluginData *pd,
2720 const char *data) {
2721
2722 GHashTable *img_by_cid;
2723 GList *images;
2724
2725 GString *str;
2726
2727 PurpleMimeDocument *doc;
2728 GList *parts;
2729
2730 img_by_cid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
2731 images = NULL;
2732
2733 /* don't want the contained string to ever be NULL */
2734 str = g_string_new("");
2735
2736 doc = purple_mime_document_parse(data);
2737
2738 /* handle all the MIME parts */
2739 parts = purple_mime_document_get_parts(doc);
2740 for(; parts; parts = parts->next) {
2741 PurpleMimePart *part = parts->data;
2742 const char *type;
2743
2744 type = purple_mime_part_get_field(part, "content-type");
2745 DEBUG_INFO("MIME part Content-Type: %s\n", NSTR(type));
2746
2747 if(! type) {
2748 ; /* feh */
2749
2750 } else if(purple_str_has_prefix(type, "image")) {
2751 /* put images into the image store */
2752
2753 guchar *d_dat;
2754 gsize d_len;
2755 char *cid;
2756 int img;
2757
2758 /* obtain and unencode the data */
2759 purple_mime_part_get_data_decoded(part, &d_dat, &d_len);
2760
2761 /* look up the content id */
2762 cid = (char *) purple_mime_part_get_field(part, "Content-ID");
2763 cid = make_cid(cid);
2764
2765 /* add image to the purple image store */
2766 img = purple_imgstore_add_with_id(d_dat, d_len, cid);
2767
2768 /* map the cid to the image store identifier */
2769 g_hash_table_insert(img_by_cid, cid, GINT_TO_POINTER(img));
2770
2771 /* recall the image for dereferencing later */
2772 images = g_list_append(images, GINT_TO_POINTER(img));
2773
2774 } else if(purple_str_has_prefix(type, "text")) {
2775
2776 /* concatenate all the text parts together */
2777 guchar *data;
2778 gsize len;
2779
2780 purple_mime_part_get_data_decoded(part, &data, &len);
2781 g_string_append(str, (const char *)data);
2782 g_free(data);
2783 }
2784 }
2785
2786 purple_mime_document_free(doc);
2787
2788 /* @todo should put this in its own function */
2789 { /* replace each IMG tag's SRC attribute with an ID attribute. This
2790 actually modifies the contents of str */
2791 GData *attribs;
2792 char *start, *end;
2793 char *tmp = str->str;
2794
2795 while(*tmp && purple_markup_find_tag("img", tmp, (const char **) &start,
2796 (const char **) &end, &attribs)) {
2797
2798 char *alt, *align, *border, *src;
2799 int img = 0;
2800
2801 alt = g_datalist_get_data(&attribs, "alt");
2802 align = g_datalist_get_data(&attribs, "align");
2803 border = g_datalist_get_data(&attribs, "border");
2804 src = g_datalist_get_data(&attribs, "src");
2805
2806 if(src)
2807 img = GPOINTER_TO_INT(g_hash_table_lookup(img_by_cid, src));
2808
2809 if(img) {
2810 GString *atstr;
2811 gsize len = (end - start);
2812 gsize mov;
2813
2814 atstr = g_string_new("");
2815 if(alt) g_string_append_printf(atstr, " alt=\"%s\"", alt);
2816 if(align) g_string_append_printf(atstr, " align=\"%s\"", align);
2817 if(border) g_string_append_printf(atstr, " border=\"%s\"", border);
2818
2819 mov = g_snprintf(start, len, "<img%s id=\"%i\"", atstr->str, img);
2820 while(mov < len) start[mov++] = ' ';
2821
2822 g_string_free(atstr, TRUE);
2823 }
2824
2825 g_datalist_clear(&attribs);
2826 tmp = end + 1;
2827 }
2828 }
2829
2830 im_recv_html(conv, pd, str->str);
2831
2832 g_string_free(str, TRUE);
2833
2834 /* clean up the cid table */
2835 g_hash_table_destroy(img_by_cid);
2836
2837 /* dereference all the imgages */
2838 while(images) {
2839 purple_imgstore_unref_by_id(GPOINTER_TO_INT(images->data));
2840 images = g_list_delete_link(images, images);
2841 }
2842 }
2843
2844
mw_conversation_recv(struct mwConversation * conv,enum mwImSendType type,gconstpointer msg)2845 static void mw_conversation_recv(struct mwConversation *conv,
2846 enum mwImSendType type,
2847 gconstpointer msg) {
2848 struct mwServiceIm *srvc;
2849 struct mwSession *session;
2850 struct mwPurplePluginData *pd;
2851
2852 srvc = mwConversation_getService(conv);
2853 session = mwService_getSession(MW_SERVICE(srvc));
2854 pd = mwSession_getClientData(session);
2855
2856 switch(type) {
2857 case mwImSend_PLAIN:
2858 im_recv_text(conv, pd, msg);
2859 break;
2860
2861 case mwImSend_TYPING:
2862 im_recv_typing(conv, pd, !! msg);
2863 break;
2864
2865 case mwImSend_HTML:
2866 im_recv_html(conv, pd, msg);
2867 break;
2868
2869 case mwImSend_SUBJECT:
2870 im_recv_subj(conv, pd, msg);
2871 break;
2872
2873 case mwImSend_MIME:
2874 im_recv_mime(conv, pd, msg);
2875 break;
2876
2877 default:
2878 DEBUG_INFO("conversation received strange type, 0x%04x\n", type);
2879 ; /* erm... */
2880 }
2881 }
2882
2883
mw_place_invite(struct mwConversation * conv,const char * message,const char * title,const char * name)2884 static void mw_place_invite(struct mwConversation *conv,
2885 const char *message,
2886 const char *title, const char *name) {
2887 struct mwServiceIm *srvc;
2888 struct mwSession *session;
2889 struct mwPurplePluginData *pd;
2890
2891 struct mwIdBlock *idb;
2892 GHashTable *ht;
2893
2894 srvc = mwConversation_getService(conv);
2895 session = mwService_getSession(MW_SERVICE(srvc));
2896 pd = mwSession_getClientData(session);
2897
2898 idb = mwConversation_getTarget(conv);
2899
2900 ht = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
2901 g_hash_table_insert(ht, CHAT_KEY_CREATOR, g_strdup(idb->user));
2902 g_hash_table_insert(ht, CHAT_KEY_NAME, g_strdup(name));
2903 g_hash_table_insert(ht, CHAT_KEY_TOPIC, g_strdup(title));
2904 g_hash_table_insert(ht, CHAT_KEY_INVITE, g_strdup(message));
2905 g_hash_table_insert(ht, CHAT_KEY_IS_PLACE, g_strdup("")); /* ugh */
2906
2907 if(! title) title = "(no title)";
2908 if(! message) message = "(no message)";
2909 serv_got_chat_invite(pd->gc, title, idb->user, message, ht);
2910
2911 mwConversation_close(conv, ERR_SUCCESS);
2912 mwConversation_free(conv);
2913 }
2914
2915
mw_im_clear(struct mwServiceIm * srvc)2916 static void mw_im_clear(struct mwServiceIm *srvc) {
2917 ;
2918 }
2919
2920
2921 static struct mwImHandler mw_im_handler = {
2922 mw_conversation_opened,
2923 mw_conversation_closed,
2924 mw_conversation_recv,
2925 mw_place_invite,
2926 mw_im_clear,
2927 };
2928
2929
mw_srvc_im_new(struct mwSession * s)2930 static struct mwServiceIm *mw_srvc_im_new(struct mwSession *s) {
2931 struct mwServiceIm *srvc;
2932 srvc = mwServiceIm_new(s, &mw_im_handler);
2933 mwServiceIm_setClientType(srvc, mwImClient_NOTESBUDDY);
2934 return srvc;
2935 }
2936
2937
2938 /* The following helps us relate a mwPlace to a PurpleConvChat in the
2939 various forms by which either may be indicated. Uses some of
2940 the similar macros from the conference service above */
2941
2942 #define PLACE_TO_ID(place) (GPOINTER_TO_INT(place))
2943 #define ID_TO_PLACE(pd, id) (place_find_by_id((pd), (id)))
2944
2945 #define CHAT_TO_PLACE(pd, chat) (ID_TO_PLACE((pd), CHAT_TO_ID(chat)))
2946 #define PLACE_TO_CHAT(place) (ID_TO_CHAT(PLACE_TO_ID(place)))
2947
2948
2949 static struct mwPlace *
place_find_by_id(struct mwPurplePluginData * pd,int id)2950 place_find_by_id(struct mwPurplePluginData *pd, int id) {
2951 struct mwServicePlace *srvc = pd->srvc_place;
2952 struct mwPlace *place = NULL;
2953 GList *l;
2954
2955 l = (GList *) mwServicePlace_getPlaces(srvc);
2956 for(; l; l = l->next) {
2957 struct mwPlace *p = l->data;
2958 PurpleConvChat *h = PURPLE_CONV_CHAT(mwPlace_getClientData(p));
2959
2960 if(CHAT_TO_ID(h) == id) {
2961 place = p;
2962 break;
2963 }
2964 }
2965
2966 return place;
2967 }
2968
2969
mw_place_opened(struct mwPlace * place)2970 static void mw_place_opened(struct mwPlace *place) {
2971 struct mwServicePlace *srvc;
2972 struct mwSession *session;
2973 struct mwPurplePluginData *pd;
2974 PurpleConnection *gc;
2975 PurpleConversation *gconf;
2976
2977 GList *members, *l;
2978
2979 const char *n = mwPlace_getName(place);
2980 const char *t = mwPlace_getTitle(place);
2981
2982 srvc = mwPlace_getService(place);
2983 session = mwService_getSession(MW_SERVICE(srvc));
2984 pd = mwSession_getClientData(session);
2985 gc = pd->gc;
2986
2987 members = mwPlace_getMembers(place);
2988
2989 DEBUG_INFO("place %s opened, %u initial members\n",
2990 NSTR(n), g_list_length(members));
2991
2992 if(! t) t = "(no title)";
2993 gconf = serv_got_joined_chat(gc, PLACE_TO_ID(place), t);
2994
2995 mwPlace_setClientData(place, gconf, NULL);
2996
2997 for(l = members; l; l = l->next) {
2998 struct mwIdBlock *idb = l->data;
2999 purple_conv_chat_add_user(PURPLE_CONV_CHAT(gconf), idb->user,
3000 NULL, PURPLE_CBFLAGS_NONE, FALSE);
3001 }
3002 g_list_free(members);
3003 }
3004
3005
mw_place_closed(struct mwPlace * place,guint32 code)3006 static void mw_place_closed(struct mwPlace *place, guint32 code) {
3007 struct mwServicePlace *srvc;
3008 struct mwSession *session;
3009 struct mwPurplePluginData *pd;
3010 PurpleConnection *gc;
3011
3012 const char *n = mwPlace_getName(place);
3013 char *msg = mwError(code);
3014
3015 DEBUG_INFO("place %s closed, 0x%08x\n", NSTR(n), code);
3016
3017 srvc = mwPlace_getService(place);
3018 session = mwService_getSession(MW_SERVICE(srvc));
3019 pd = mwSession_getClientData(session);
3020 gc = pd->gc;
3021
3022 serv_got_chat_left(gc, PLACE_TO_ID(place));
3023
3024 purple_notify_error(gc, _("Place Closed"), NULL, msg);
3025 g_free(msg);
3026 }
3027
3028
mw_place_peerJoined(struct mwPlace * place,const struct mwIdBlock * peer)3029 static void mw_place_peerJoined(struct mwPlace *place,
3030 const struct mwIdBlock *peer) {
3031 PurpleConversation *gconf;
3032
3033 const char *n = mwPlace_getName(place);
3034
3035 DEBUG_INFO("%s joined place %s\n", NSTR(peer->user), NSTR(n));
3036
3037 gconf = mwPlace_getClientData(place);
3038 g_return_if_fail(gconf != NULL);
3039
3040 purple_conv_chat_add_user(PURPLE_CONV_CHAT(gconf), peer->user,
3041 NULL, PURPLE_CBFLAGS_NONE, TRUE);
3042 }
3043
3044
mw_place_peerParted(struct mwPlace * place,const struct mwIdBlock * peer)3045 static void mw_place_peerParted(struct mwPlace *place,
3046 const struct mwIdBlock *peer) {
3047 PurpleConversation *gconf;
3048
3049 const char *n = mwPlace_getName(place);
3050
3051 DEBUG_INFO("%s left place %s\n", NSTR(peer->user), NSTR(n));
3052
3053 gconf = mwPlace_getClientData(place);
3054 g_return_if_fail(gconf != NULL);
3055
3056 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(gconf), peer->user, NULL);
3057 }
3058
3059
mw_place_peerSetAttribute(struct mwPlace * place,const struct mwIdBlock * peer,guint32 attr,struct mwOpaque * o)3060 static void mw_place_peerSetAttribute(struct mwPlace *place,
3061 const struct mwIdBlock *peer,
3062 guint32 attr, struct mwOpaque *o) {
3063 ;
3064 }
3065
3066
mw_place_peerUnsetAttribute(struct mwPlace * place,const struct mwIdBlock * peer,guint32 attr)3067 static void mw_place_peerUnsetAttribute(struct mwPlace *place,
3068 const struct mwIdBlock *peer,
3069 guint32 attr) {
3070 ;
3071 }
3072
3073
mw_place_message(struct mwPlace * place,const struct mwIdBlock * who,const char * msg)3074 static void mw_place_message(struct mwPlace *place,
3075 const struct mwIdBlock *who,
3076 const char *msg) {
3077 struct mwServicePlace *srvc;
3078 struct mwSession *session;
3079 struct mwPurplePluginData *pd;
3080 PurpleConnection *gc;
3081 char *esc;
3082
3083 if(! msg) return;
3084
3085 srvc = mwPlace_getService(place);
3086 session = mwService_getSession(MW_SERVICE(srvc));
3087 pd = mwSession_getClientData(session);
3088 gc = pd->gc;
3089
3090 esc = g_markup_escape_text(msg, -1);
3091 serv_got_chat_in(gc, PLACE_TO_ID(place), who->user, 0, esc, time(NULL));
3092 g_free(esc);
3093 }
3094
3095
mw_place_clear(struct mwServicePlace * srvc)3096 static void mw_place_clear(struct mwServicePlace *srvc) {
3097 ;
3098 }
3099
3100
3101 static struct mwPlaceHandler mw_place_handler = {
3102 mw_place_opened,
3103 mw_place_closed,
3104 mw_place_peerJoined,
3105 mw_place_peerParted,
3106 mw_place_peerSetAttribute,
3107 mw_place_peerUnsetAttribute,
3108 mw_place_message,
3109 mw_place_clear,
3110 };
3111
3112
mw_srvc_place_new(struct mwSession * s)3113 static struct mwServicePlace *mw_srvc_place_new(struct mwSession *s) {
3114 struct mwServicePlace *srvc;
3115 srvc = mwServicePlace_new(s, &mw_place_handler);
3116 return srvc;
3117 }
3118
3119
mw_srvc_resolve_new(struct mwSession * s)3120 static struct mwServiceResolve *mw_srvc_resolve_new(struct mwSession *s) {
3121 struct mwServiceResolve *srvc;
3122 srvc = mwServiceResolve_new(s);
3123 return srvc;
3124 }
3125
3126
mw_srvc_store_new(struct mwSession * s)3127 static struct mwServiceStorage *mw_srvc_store_new(struct mwSession *s) {
3128 struct mwServiceStorage *srvc;
3129 srvc = mwServiceStorage_new(s);
3130 return srvc;
3131 }
3132
3133
3134 /** allocate and associate a mwPurplePluginData with a PurpleConnection */
mwPurplePluginData_new(PurpleConnection * gc)3135 static struct mwPurplePluginData *mwPurplePluginData_new(PurpleConnection *gc) {
3136 struct mwPurplePluginData *pd;
3137
3138 g_return_val_if_fail(gc != NULL, NULL);
3139
3140 pd = g_new0(struct mwPurplePluginData, 1);
3141 pd->gc = gc;
3142 pd->session = mwSession_new(&mw_session_handler);
3143 pd->srvc_aware = mw_srvc_aware_new(pd->session);
3144 pd->srvc_conf = mw_srvc_conf_new(pd->session);
3145 pd->srvc_ft = mw_srvc_ft_new(pd->session);
3146 pd->srvc_im = mw_srvc_im_new(pd->session);
3147 pd->srvc_place = mw_srvc_place_new(pd->session);
3148 pd->srvc_resolve = mw_srvc_resolve_new(pd->session);
3149 pd->srvc_store = mw_srvc_store_new(pd->session);
3150 pd->group_list_map = g_hash_table_new(g_direct_hash, g_direct_equal);
3151 pd->sock_buf = purple_circ_buffer_new(0);
3152
3153 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_aware));
3154 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_conf));
3155 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_ft));
3156 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_im));
3157 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_place));
3158 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_resolve));
3159 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_store));
3160
3161 mwSession_addCipher(pd->session, mwCipher_new_RC2_40(pd->session));
3162 mwSession_addCipher(pd->session, mwCipher_new_RC2_128(pd->session));
3163
3164 mwSession_setClientData(pd->session, pd, NULL);
3165 gc->proto_data = pd;
3166
3167 return pd;
3168 }
3169
3170
mwPurplePluginData_free(struct mwPurplePluginData * pd)3171 static void mwPurplePluginData_free(struct mwPurplePluginData *pd) {
3172 g_return_if_fail(pd != NULL);
3173
3174 pd->gc->proto_data = NULL;
3175
3176 mwSession_removeService(pd->session, mwService_AWARE);
3177 mwSession_removeService(pd->session, mwService_CONFERENCE);
3178 mwSession_removeService(pd->session, mwService_FILE_TRANSFER);
3179 mwSession_removeService(pd->session, mwService_IM);
3180 mwSession_removeService(pd->session, mwService_PLACE);
3181 mwSession_removeService(pd->session, mwService_RESOLVE);
3182 mwSession_removeService(pd->session, mwService_STORAGE);
3183
3184 mwService_free(MW_SERVICE(pd->srvc_aware));
3185 mwService_free(MW_SERVICE(pd->srvc_conf));
3186 mwService_free(MW_SERVICE(pd->srvc_ft));
3187 mwService_free(MW_SERVICE(pd->srvc_im));
3188 mwService_free(MW_SERVICE(pd->srvc_place));
3189 mwService_free(MW_SERVICE(pd->srvc_resolve));
3190 mwService_free(MW_SERVICE(pd->srvc_store));
3191
3192 mwCipher_free(mwSession_getCipher(pd->session, mwCipher_RC2_40));
3193 mwCipher_free(mwSession_getCipher(pd->session, mwCipher_RC2_128));
3194
3195 mwSession_free(pd->session);
3196
3197 g_hash_table_destroy(pd->group_list_map);
3198 purple_circ_buffer_destroy(pd->sock_buf);
3199
3200 g_free(pd);
3201 }
3202
3203
mw_prpl_list_icon(PurpleAccount * a,PurpleBuddy * b)3204 static const char *mw_prpl_list_icon(PurpleAccount *a, PurpleBuddy *b) {
3205 /* my little green dude is a chopped up version of the aim running
3206 guy. First, cut off the head and store someplace safe. Then,
3207 take the left-half side of the body and throw it away. Make a
3208 copy of the remaining body, and flip it horizontally. Now attach
3209 the two pieces into an X shape, and drop the head back on the
3210 top, being careful to center it. Then, just change the color
3211 saturation to bring the red down a bit, and voila! */
3212
3213 /* then, throw all of that away and use sodipodi to make a new
3214 icon. You know, LIKE A REAL MAN. */
3215
3216 return "meanwhile";
3217 }
3218
3219
mw_prpl_list_emblem(PurpleBuddy * b)3220 static const char* mw_prpl_list_emblem(PurpleBuddy *b)
3221 {
3222 if(buddy_is_external(b))
3223 return "external";
3224
3225 return NULL;
3226 }
3227
3228
mw_prpl_status_text(PurpleBuddy * b)3229 static char *mw_prpl_status_text(PurpleBuddy *b) {
3230 PurpleConnection *gc;
3231 struct mwPurplePluginData *pd;
3232 struct mwAwareIdBlock t = { mwAware_USER, (char *)purple_buddy_get_name(b), NULL };
3233 const char *ret = NULL;
3234
3235 if ((gc = purple_account_get_connection(purple_buddy_get_account(b)))
3236 && (pd = gc->proto_data))
3237 ret = mwServiceAware_getText(pd->srvc_aware, &t);
3238
3239 return (ret && g_utf8_validate(ret, -1, NULL)) ? g_markup_escape_text(ret, -1): NULL;
3240 }
3241
3242
status_text(PurpleBuddy * b)3243 static const char *status_text(PurpleBuddy *b) {
3244 PurplePresence *presence;
3245 PurpleStatus *status;
3246
3247 presence = purple_buddy_get_presence(b);
3248 status = purple_presence_get_active_status(presence);
3249
3250 return purple_status_get_name(status);
3251 }
3252
3253
user_supports(struct mwServiceAware * srvc,const char * who,guint32 feature)3254 static gboolean user_supports(struct mwServiceAware *srvc,
3255 const char *who, guint32 feature) {
3256
3257 const struct mwAwareAttribute *attr;
3258 struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL };
3259
3260 attr = mwServiceAware_getAttribute(srvc, &idb, feature);
3261 return (attr != NULL) && mwAwareAttribute_asBoolean(attr);
3262 }
3263
3264
user_supports_text(struct mwServiceAware * srvc,const char * who)3265 static char *user_supports_text(struct mwServiceAware *srvc, const char *who) {
3266 const char *feat[] = {NULL, NULL, NULL, NULL, NULL};
3267 const char **f = feat;
3268
3269 if(user_supports(srvc, who, mwAttribute_AV_PREFS_SET)) {
3270 gboolean mic, speak, video;
3271
3272 mic = user_supports(srvc, who, mwAttribute_MICROPHONE);
3273 speak = user_supports(srvc, who, mwAttribute_SPEAKERS);
3274 video = user_supports(srvc, who, mwAttribute_VIDEO_CAMERA);
3275
3276 if(mic) *f++ = _("Microphone");
3277 if(speak) *f++ = _("Speakers");
3278 if(video) *f++ = _("Video Camera");
3279 }
3280
3281 if(user_supports(srvc, who, mwAttribute_FILE_TRANSFER))
3282 *f++ = _("File Transfer");
3283
3284 return (*feat)? g_strjoinv(", ", (char **)feat): NULL;
3285 /* jenni loves siege */
3286 }
3287
3288
mw_prpl_tooltip_text(PurpleBuddy * b,PurpleNotifyUserInfo * user_info,gboolean full)3289 static void mw_prpl_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full) {
3290 PurpleConnection *gc;
3291 struct mwPurplePluginData *pd = NULL;
3292 struct mwAwareIdBlock idb = { mwAware_USER, (char *)purple_buddy_get_name(b), NULL };
3293
3294 const char *message = NULL;
3295 const char *status;
3296 char *tmp;
3297
3298 if ((gc = purple_account_get_connection(purple_buddy_get_account(b)))
3299 && (pd = gc->proto_data))
3300 message = mwServiceAware_getText(pd->srvc_aware, &idb);
3301
3302 status = status_text(b);
3303
3304 if(message != NULL && g_utf8_validate(message, -1, NULL) && purple_utf8_strcasecmp(status, message)) {
3305 tmp = g_markup_escape_text(message, -1);
3306 purple_notify_user_info_add_pair(user_info, status, tmp);
3307 g_free(tmp);
3308
3309 } else {
3310 purple_notify_user_info_add_pair(user_info, _("Status"), status);
3311 }
3312
3313 if(full && pd != NULL) {
3314 tmp = user_supports_text(pd->srvc_aware, purple_buddy_get_name(b));
3315 if(tmp) {
3316 purple_notify_user_info_add_pair(user_info, _("Supports"), tmp);
3317 g_free(tmp);
3318 }
3319
3320 if(buddy_is_external(b)) {
3321 purple_notify_user_info_add_pair(user_info, NULL, _("External User"));
3322 }
3323 }
3324 }
3325
mw_prpl_status_types(PurpleAccount * acct)3326 static GList *mw_prpl_status_types(PurpleAccount *acct)
3327 {
3328 GList *types = NULL;
3329 PurpleStatusType *type;
3330
3331 type = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE,
3332 MW_STATE_ACTIVE, NULL, TRUE, TRUE, FALSE,
3333 MW_STATE_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING),
3334 NULL);
3335 types = g_list_append(types, type);
3336
3337 type = purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY,
3338 MW_STATE_AWAY, NULL, TRUE, TRUE, FALSE,
3339 MW_STATE_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING),
3340 NULL);
3341 types = g_list_append(types, type);
3342
3343 type = purple_status_type_new_with_attrs(PURPLE_STATUS_UNAVAILABLE,
3344 MW_STATE_BUSY, _("Do Not Disturb"), TRUE, TRUE, FALSE,
3345 MW_STATE_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING),
3346 NULL);
3347 types = g_list_append(types, type);
3348
3349 type = purple_status_type_new_full(PURPLE_STATUS_OFFLINE,
3350 MW_STATE_OFFLINE, NULL, TRUE, TRUE, FALSE);
3351 types = g_list_append(types, type);
3352
3353 return types;
3354 }
3355
3356
conf_create_prompt_cancel(PurpleBuddy * buddy,PurpleRequestFields * fields)3357 static void conf_create_prompt_cancel(PurpleBuddy *buddy,
3358 PurpleRequestFields *fields) {
3359 ; /* nothing to do */
3360 }
3361
3362
conf_create_prompt_join(PurpleBuddy * buddy,PurpleRequestFields * fields)3363 static void conf_create_prompt_join(PurpleBuddy *buddy,
3364 PurpleRequestFields *fields) {
3365 PurpleAccount *acct;
3366 PurpleConnection *gc;
3367 struct mwPurplePluginData *pd;
3368 struct mwServiceConference *srvc;
3369
3370 PurpleRequestField *f;
3371
3372 const char *topic, *invite;
3373 struct mwConference *conf;
3374 struct mwIdBlock idb = { NULL, NULL };
3375
3376 acct = purple_buddy_get_account(buddy);
3377 gc = purple_account_get_connection(acct);
3378 pd = gc->proto_data;
3379 srvc = pd->srvc_conf;
3380
3381 f = purple_request_fields_get_field(fields, CHAT_KEY_TOPIC);
3382 topic = purple_request_field_string_get_value(f);
3383
3384 f = purple_request_fields_get_field(fields, CHAT_KEY_INVITE);
3385 invite = purple_request_field_string_get_value(f);
3386
3387 conf = mwConference_new(srvc, topic);
3388 mwConference_open(conf);
3389
3390 idb.user = (char *)purple_buddy_get_name(buddy);
3391 mwConference_invite(conf, &idb, invite);
3392 }
3393
3394
blist_menu_conf_create(PurpleBuddy * buddy,const char * msg)3395 static void blist_menu_conf_create(PurpleBuddy *buddy, const char *msg) {
3396
3397 PurpleRequestFields *fields;
3398 PurpleRequestFieldGroup *g;
3399 PurpleRequestField *f;
3400
3401 PurpleAccount *acct;
3402 PurpleConnection *gc;
3403
3404 const char *msgA;
3405 const char *msgB;
3406 char *msg1;
3407
3408 g_return_if_fail(buddy != NULL);
3409
3410 acct = purple_buddy_get_account(buddy);
3411 g_return_if_fail(acct != NULL);
3412
3413 gc = purple_account_get_connection(acct);
3414 g_return_if_fail(gc != NULL);
3415
3416 fields = purple_request_fields_new();
3417
3418 g = purple_request_field_group_new(NULL);
3419 purple_request_fields_add_group(fields, g);
3420
3421 f = purple_request_field_string_new(CHAT_KEY_TOPIC, _("Topic"), NULL, FALSE);
3422 purple_request_field_group_add_field(g, f);
3423
3424 f = purple_request_field_string_new(CHAT_KEY_INVITE, _("Message"), msg, FALSE);
3425 purple_request_field_group_add_field(g, f);
3426
3427 msgA = _("Create conference with user");
3428 msgB = _("Please enter a topic for the new conference, and an invitation"
3429 " message to be sent to %s");
3430 msg1 = g_strdup_printf(msgB, purple_buddy_get_name(buddy));
3431
3432 purple_request_fields(gc, _("New Conference"),
3433 msgA, msg1, fields,
3434 _("Create"), G_CALLBACK(conf_create_prompt_join),
3435 _("Cancel"), G_CALLBACK(conf_create_prompt_cancel),
3436 acct, purple_buddy_get_name(buddy), NULL,
3437 buddy);
3438 g_free(msg1);
3439 }
3440
3441
conf_select_prompt_cancel(PurpleBuddy * buddy,PurpleRequestFields * fields)3442 static void conf_select_prompt_cancel(PurpleBuddy *buddy,
3443 PurpleRequestFields *fields) {
3444 ;
3445 }
3446
3447
conf_select_prompt_invite(PurpleBuddy * buddy,PurpleRequestFields * fields)3448 static void conf_select_prompt_invite(PurpleBuddy *buddy,
3449 PurpleRequestFields *fields) {
3450 PurpleRequestField *f;
3451 GList *l;
3452 const char *msg;
3453
3454 f = purple_request_fields_get_field(fields, CHAT_KEY_INVITE);
3455 msg = purple_request_field_string_get_value(f);
3456
3457 f = purple_request_fields_get_field(fields, "conf");
3458 l = purple_request_field_list_get_selected(f);
3459
3460 if(l) {
3461 gpointer d = purple_request_field_list_get_data(f, l->data);
3462
3463 if(GPOINTER_TO_INT(d) == 0x01) {
3464 blist_menu_conf_create(buddy, msg);
3465
3466 } else {
3467 struct mwIdBlock idb = { (char *)purple_buddy_get_name(buddy), NULL };
3468 mwConference_invite(d, &idb, msg);
3469 }
3470 }
3471 }
3472
3473
blist_menu_conf_list(PurpleBuddy * buddy,GList * confs)3474 static void blist_menu_conf_list(PurpleBuddy *buddy,
3475 GList *confs) {
3476
3477 PurpleRequestFields *fields;
3478 PurpleRequestFieldGroup *g;
3479 PurpleRequestField *f;
3480
3481 PurpleAccount *acct;
3482 PurpleConnection *gc;
3483
3484 const char *msgA;
3485 const char *msgB;
3486 char *msg;
3487
3488 acct = purple_buddy_get_account(buddy);
3489 g_return_if_fail(acct != NULL);
3490
3491 gc = purple_account_get_connection(acct);
3492 g_return_if_fail(gc != NULL);
3493
3494 fields = purple_request_fields_new();
3495
3496 g = purple_request_field_group_new(NULL);
3497 purple_request_fields_add_group(fields, g);
3498
3499 f = purple_request_field_list_new("conf", _("Available Conferences"));
3500 purple_request_field_list_set_multi_select(f, FALSE);
3501 for(; confs; confs = confs->next) {
3502 struct mwConference *c = confs->data;
3503 purple_request_field_list_add_icon(f, mwConference_getTitle(c), NULL, c);
3504 }
3505 purple_request_field_list_add_icon(f, _("Create New Conference..."),
3506 NULL, GINT_TO_POINTER(0x01));
3507 purple_request_field_group_add_field(g, f);
3508
3509 f = purple_request_field_string_new(CHAT_KEY_INVITE, "Message", NULL, FALSE);
3510 purple_request_field_group_add_field(g, f);
3511
3512 msgA = _("Invite user to a conference");
3513 msgB = _("Select a conference from the list below to send an invite to"
3514 " user %s. Select \"Create New Conference\" if you'd like to"
3515 " create a new conference to invite this user to.");
3516 msg = g_strdup_printf(msgB, purple_buddy_get_name(buddy));
3517
3518 purple_request_fields(gc, _("Invite to Conference"),
3519 msgA, msg, fields,
3520 _("Invite"), G_CALLBACK(conf_select_prompt_invite),
3521 _("Cancel"), G_CALLBACK(conf_select_prompt_cancel),
3522 acct, purple_buddy_get_name(buddy), NULL,
3523 buddy);
3524 g_free(msg);
3525 }
3526
3527
blist_menu_conf(PurpleBlistNode * node,gpointer data)3528 static void blist_menu_conf(PurpleBlistNode *node, gpointer data) {
3529 PurpleBuddy *buddy = (PurpleBuddy *) node;
3530 PurpleAccount *acct;
3531 PurpleConnection *gc;
3532 struct mwPurplePluginData *pd;
3533 GList *l;
3534
3535 g_return_if_fail(node != NULL);
3536 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
3537
3538 acct = purple_buddy_get_account(buddy);
3539 g_return_if_fail(acct != NULL);
3540
3541 gc = purple_account_get_connection(acct);
3542 g_return_if_fail(gc != NULL);
3543
3544 pd = gc->proto_data;
3545 g_return_if_fail(pd != NULL);
3546
3547 /*
3548 - get a list of all conferences on this session
3549 - if none, prompt to create one, and invite buddy to it
3550 - else, prompt to select a conference or create one
3551 */
3552
3553 l = mwServiceConference_getConferences(pd->srvc_conf);
3554 if(l) {
3555 blist_menu_conf_list(buddy, l);
3556 g_list_free(l);
3557
3558 } else {
3559 blist_menu_conf_create(buddy, NULL);
3560 }
3561 }
3562
3563
3564 #if 0
3565 static void blist_menu_announce(PurpleBlistNode *node, gpointer data) {
3566 PurpleBuddy *buddy = (PurpleBuddy *) node;
3567 PurpleAccount *acct;
3568 PurpleConnection *gc;
3569 struct mwPurplePluginData *pd;
3570 struct mwSession *session;
3571 char *rcpt_name;
3572 GList *rcpt;
3573
3574 g_return_if_fail(node != NULL);
3575 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
3576
3577 acct = buddy->account;
3578 g_return_if_fail(acct != NULL);
3579
3580 gc = purple_account_get_connection(acct);
3581 g_return_if_fail(gc != NULL);
3582
3583 pd = gc->proto_data;
3584 g_return_if_fail(pd != NULL);
3585
3586 rcpt_name = g_strdup_printf("@U %s", buddy->name);
3587 rcpt = g_list_prepend(NULL, rcpt_name);
3588
3589 session = pd->session;
3590 mwSession_sendAnnounce(session, FALSE,
3591 "This is a TEST announcement. Please ignore.",
3592 rcpt);
3593
3594 g_list_free(rcpt);
3595 g_free(rcpt_name);
3596 }
3597 #endif
3598
3599
mw_prpl_blist_node_menu(PurpleBlistNode * node)3600 static GList *mw_prpl_blist_node_menu(PurpleBlistNode *node) {
3601 GList *l = NULL;
3602 PurpleMenuAction *act;
3603
3604 if(! PURPLE_BLIST_NODE_IS_BUDDY(node))
3605 return l;
3606
3607 l = g_list_append(l, NULL);
3608
3609 act = purple_menu_action_new(_("Invite to Conference..."),
3610 PURPLE_CALLBACK(blist_menu_conf), NULL, NULL);
3611 l = g_list_append(l, act);
3612
3613 #if 0
3614 act = purple_menu_action_new(_("Send TEST Announcement"),
3615 PURPLE_CALLBACK(blist_menu_announce), NULL, NULL);
3616 l = g_list_append(l, act);
3617 #endif
3618
3619 /** note: this never gets called for a PurpleGroup, have to use the
3620 blist-node-extended-menu signal for that. The function
3621 blist_node_menu_cb is assigned to this signal in the function
3622 services_starting */
3623
3624 return l;
3625 }
3626
3627
mw_prpl_chat_info(PurpleConnection * gc)3628 static GList *mw_prpl_chat_info(PurpleConnection *gc) {
3629 GList *l = NULL;
3630 struct proto_chat_entry *pce;
3631
3632 pce = g_new0(struct proto_chat_entry, 1);
3633 pce->label = _("Topic:");
3634 pce->identifier = CHAT_KEY_TOPIC;
3635 l = g_list_append(l, pce);
3636
3637 return l;
3638 }
3639
3640
mw_prpl_chat_info_defaults(PurpleConnection * gc,const char * name)3641 static GHashTable *mw_prpl_chat_info_defaults(PurpleConnection *gc,
3642 const char *name) {
3643 GHashTable *table;
3644
3645 g_return_val_if_fail(gc != NULL, NULL);
3646
3647 table = g_hash_table_new_full(g_str_hash, g_str_equal,
3648 NULL, g_free);
3649
3650 g_hash_table_insert(table, CHAT_KEY_NAME, g_strdup(name));
3651 g_hash_table_insert(table, CHAT_KEY_INVITE, NULL);
3652
3653 return table;
3654 }
3655
3656
3657 static void mw_prpl_login(PurpleAccount *acct);
3658
3659
mw_prpl_login(PurpleAccount * account)3660 static void mw_prpl_login(PurpleAccount *account) {
3661 PurpleConnection *gc;
3662 struct mwPurplePluginData *pd;
3663
3664 char *user, *pass, *host;
3665 guint port;
3666
3667 gc = purple_account_get_connection(account);
3668 pd = mwPurplePluginData_new(gc);
3669
3670 /* while we do support images, the default is to not offer it */
3671 gc->flags |= PURPLE_CONNECTION_NO_IMAGES;
3672
3673 user = g_strdup(purple_account_get_username(account));
3674
3675 host = strrchr(user, ':');
3676 if(host) {
3677 /* annoying user split from 1.2.0, need to undo it */
3678 *host++ = '\0';
3679 purple_account_set_string(account, MW_KEY_HOST, host);
3680 purple_account_set_username(account, user);
3681
3682 } else {
3683 host = (char *) purple_account_get_string(account, MW_KEY_HOST,
3684 MW_PLUGIN_DEFAULT_HOST);
3685 }
3686
3687 if(! host || ! *host) {
3688 /* somehow, we don't have a host to connect to. Well, we need one
3689 to actually continue, so let's ask the user directly. */
3690 g_free(user);
3691 purple_connection_error_reason(gc,
3692 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
3693 _("A server is required to connect this account"));
3694 return;
3695 }
3696
3697 pass = g_strdup(purple_account_get_password(account));
3698 port = purple_account_get_int(account, MW_KEY_PORT, MW_PLUGIN_DEFAULT_PORT);
3699
3700 DEBUG_INFO("user: '%s'\n", user);
3701 DEBUG_INFO("host: '%s'\n", host);
3702 DEBUG_INFO("port: %u\n", port);
3703
3704 mwSession_setProperty(pd->session, mwSession_NO_SECRET,
3705 (char *) no_secret, NULL);
3706 mwSession_setProperty(pd->session, mwSession_AUTH_USER_ID, user, g_free);
3707 mwSession_setProperty(pd->session, mwSession_AUTH_PASSWORD, pass, g_free);
3708
3709 if(purple_account_get_bool(account, MW_KEY_FAKE_IT, FALSE)) {
3710 guint client, major, minor;
3711
3712 /* if we're faking the login, let's also fake the version we're
3713 reporting. Let's also allow the actual values to be specified */
3714
3715 client = purple_account_get_int(account, MW_KEY_CLIENT, mwLogin_BINARY);
3716 major = purple_account_get_int(account, MW_KEY_MAJOR, 0x001e);
3717 minor = purple_account_get_int(account, MW_KEY_MINOR, 0x196f);
3718
3719 DEBUG_INFO("client id: 0x%04x\n", client);
3720 DEBUG_INFO("client major: 0x%04x\n", major);
3721 DEBUG_INFO("client minor: 0x%04x\n", minor);
3722
3723 mwSession_setProperty(pd->session, mwSession_CLIENT_TYPE_ID,
3724 GUINT_TO_POINTER(client), NULL);
3725
3726 mwSession_setProperty(pd->session, mwSession_CLIENT_VER_MAJOR,
3727 GUINT_TO_POINTER(major), NULL);
3728
3729 mwSession_setProperty(pd->session, mwSession_CLIENT_VER_MINOR,
3730 GUINT_TO_POINTER(minor), NULL);
3731 }
3732
3733 purple_connection_update_progress(gc, _("Connecting"), 1, MW_CONNECT_STEPS);
3734
3735 if (purple_proxy_connect(gc, account, host, port, connect_cb, pd) == NULL) {
3736 purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
3737 _("Unable to connect"));
3738 }
3739 }
3740
3741
mw_prpl_close(PurpleConnection * gc)3742 static void mw_prpl_close(PurpleConnection *gc) {
3743 struct mwPurplePluginData *pd;
3744
3745 g_return_if_fail(gc != NULL);
3746
3747 pd = gc->proto_data;
3748 g_return_if_fail(pd != NULL);
3749
3750 /* get rid of the blist save timeout */
3751 if(pd->save_event) {
3752 purple_timeout_remove(pd->save_event);
3753 pd->save_event = 0;
3754 blist_store(pd);
3755 }
3756
3757 /* stop the session */
3758 mwSession_stop(pd->session, 0x00);
3759
3760 /* no longer necessary */
3761 gc->proto_data = NULL;
3762
3763 /* stop watching the socket */
3764 if(gc->inpa) {
3765 purple_input_remove(gc->inpa);
3766 gc->inpa = 0;
3767 }
3768
3769 /* clean up the rest */
3770 mwPurplePluginData_free(pd);
3771 }
3772
3773
mw_rand(void)3774 static int mw_rand(void) {
3775 static int seed = 0;
3776
3777 /* for diversity, not security. don't touch */
3778 srand(time(NULL) ^ seed);
3779 seed = rand();
3780
3781 return seed;
3782 }
3783
3784
3785 /** generates a random-ish content id string */
im_mime_content_id(void)3786 static char *im_mime_content_id(void) {
3787 return g_strdup_printf("%03x@%05xmeanwhile",
3788 mw_rand() & 0xfff, mw_rand() & 0xfffff);
3789 }
3790
3791
3792 /** generates a multipart/related content type with a random-ish
3793 boundary value */
im_mime_content_type(void)3794 static char *im_mime_content_type(void) {
3795 return g_strdup_printf("multipart/related; boundary=related_MW%03x_%04x",
3796 mw_rand() & 0xfff, mw_rand() & 0xffff);
3797 }
3798
3799
3800 /** determine content type from extension. Not so happy about this,
3801 but I don't want to actually write image type detection */
im_mime_img_content_type(PurpleStoredImage * img)3802 static char *im_mime_img_content_type(PurpleStoredImage *img) {
3803 const char *fn = purple_imgstore_get_filename(img);
3804 const char *ct = NULL;
3805
3806 ct = strrchr(fn, '.');
3807 if(! ct) {
3808 ct = "image";
3809
3810 } else if(purple_strequal(".png", ct)) {
3811 ct = "image/png";
3812
3813 } else if(purple_strequal(".jpg", ct)) {
3814 ct = "image/jpeg";
3815
3816 } else if(purple_strequal(".jpeg", ct)) {
3817 ct = "image/jpeg";
3818
3819 } else if(purple_strequal(".gif", ct)) {
3820 ct = "image/gif";
3821
3822 } else {
3823 ct = "image";
3824 }
3825
3826 return g_strdup_printf("%s; name=\"%s\"", ct, fn);
3827 }
3828
3829
im_mime_img_content_disp(PurpleStoredImage * img)3830 static char *im_mime_img_content_disp(PurpleStoredImage *img) {
3831 const char *fn = purple_imgstore_get_filename(img);
3832 return g_strdup_printf("attachment; filename=\"%s\"", fn);
3833 }
3834
3835
3836 /** turn an IM with embedded images into a multi-part mime document */
im_mime_convert(PurpleConnection * gc,struct mwConversation * conv,const char * message)3837 static char *im_mime_convert(PurpleConnection *gc,
3838 struct mwConversation *conv,
3839 const char *message) {
3840 GString *str;
3841 PurpleMimeDocument *doc;
3842 PurpleMimePart *part;
3843
3844 GData *attr;
3845 char *tmp, *start, *end;
3846
3847 str = g_string_new(NULL);
3848
3849 doc = purple_mime_document_new();
3850
3851 purple_mime_document_set_field(doc, "Mime-Version", "1.0");
3852 purple_mime_document_set_field(doc, "Content-Disposition", "inline");
3853
3854 tmp = im_mime_content_type();
3855 purple_mime_document_set_field(doc, "Content-Type", tmp);
3856 g_free(tmp);
3857
3858 tmp = (char *) message;
3859 while(*tmp && purple_markup_find_tag("img", tmp, (const char **) &start,
3860 (const char **) &end, &attr)) {
3861 char *id;
3862 PurpleStoredImage *img = NULL;
3863
3864 gsize len = (start - tmp);
3865
3866 /* append the in-between-tags text */
3867 if(len) g_string_append_len(str, tmp, len);
3868
3869 /* find the imgstore data by the id tag */
3870 id = g_datalist_get_data(&attr, "id");
3871 if(id && *id)
3872 img = purple_imgstore_find_by_id(atoi(id));
3873
3874 if(img) {
3875 char *cid;
3876 gpointer data;
3877 size_t size;
3878
3879 part = purple_mime_part_new(doc);
3880
3881 data = im_mime_img_content_disp(img);
3882 purple_mime_part_set_field(part, "Content-Disposition", data);
3883 g_free(data);
3884
3885 data = im_mime_img_content_type(img);
3886 purple_mime_part_set_field(part, "Content-Type", data);
3887 g_free(data);
3888
3889 cid = im_mime_content_id();
3890 data = g_strdup_printf("<%s>", cid);
3891 purple_mime_part_set_field(part, "Content-ID", data);
3892 g_free(data);
3893
3894 purple_mime_part_set_field(part, "Content-transfer-encoding", "base64");
3895
3896 /* obtain and base64 encode the image data, and put it in the
3897 mime part */
3898 size = purple_imgstore_get_size(img);
3899 data = purple_base64_encode(purple_imgstore_get_data(img), (gsize) size);
3900 purple_mime_part_set_data(part, data);
3901 g_free(data);
3902
3903 /* append the modified tag */
3904 g_string_append_printf(str, "<img src=\"cid:%s\">", cid);
3905 g_free(cid);
3906
3907 } else {
3908 /* append the literal image tag, since we couldn't find a
3909 relative imgstore object */
3910 gsize len = (end - start) + 1;
3911 g_string_append_len(str, start, len);
3912 }
3913
3914 g_datalist_clear(&attr);
3915 tmp = end + 1;
3916 }
3917
3918 /* append left-overs */
3919 g_string_append(str, tmp);
3920
3921 /* add the text/html part */
3922 part = purple_mime_part_new(doc);
3923 purple_mime_part_set_field(part, "Content-Disposition", "inline");
3924
3925 tmp = purple_utf8_ncr_encode(str->str);
3926 purple_mime_part_set_field(part, "Content-Type", "text/html");
3927 purple_mime_part_set_field(part, "Content-Transfer-Encoding", "7bit");
3928 purple_mime_part_set_data(part, tmp);
3929 g_free(tmp);
3930
3931 g_string_free(str, TRUE);
3932
3933 str = g_string_new(NULL);
3934 purple_mime_document_write(doc, str);
3935 tmp = str->str;
3936 g_string_free(str, FALSE);
3937
3938 return tmp;
3939 }
3940
3941
mw_prpl_send_im(PurpleConnection * gc,const char * name,const char * message,PurpleMessageFlags flags)3942 static int mw_prpl_send_im(PurpleConnection *gc,
3943 const char *name,
3944 const char *message,
3945 PurpleMessageFlags flags) {
3946
3947 struct mwPurplePluginData *pd;
3948 struct mwIdBlock who = { (char *) name, NULL };
3949 struct mwConversation *conv;
3950
3951 g_return_val_if_fail(gc != NULL, 0);
3952 pd = gc->proto_data;
3953
3954 g_return_val_if_fail(pd != NULL, 0);
3955
3956 conv = mwServiceIm_getConversation(pd->srvc_im, &who);
3957
3958 /* this detection of features to determine how to send the message
3959 (plain, html, or mime) is flawed because the other end of the
3960 conversation could close their channel at any time, rendering any
3961 existing formatting in an outgoing message innapropriate. The end
3962 result is that it may be possible that the other side of the
3963 conversation will receive a plaintext message with html contents,
3964 which is bad. I'm not sure how to fix this correctly. */
3965
3966 if(strstr(message, "<img ") || strstr(message, "<IMG "))
3967 flags |= PURPLE_MESSAGE_IMAGES;
3968
3969 if(mwConversation_isOpen(conv)) {
3970 char *tmp;
3971 int ret;
3972
3973 if((flags & PURPLE_MESSAGE_IMAGES) &&
3974 mwConversation_supports(conv, mwImSend_MIME)) {
3975 /* send a MIME message */
3976
3977 tmp = im_mime_convert(gc, conv, message);
3978 ret = mwConversation_send(conv, mwImSend_MIME, tmp);
3979 g_free(tmp);
3980
3981 } else if(mwConversation_supports(conv, mwImSend_HTML)) {
3982 /* send an HTML message */
3983
3984 char *ncr;
3985 ncr = purple_utf8_ncr_encode(message);
3986 tmp = purple_strdup_withhtml(ncr);
3987 g_free(ncr);
3988
3989 ret = mwConversation_send(conv, mwImSend_HTML, tmp);
3990 g_free(tmp);
3991
3992 } else {
3993 /* default to text */
3994 tmp = purple_markup_strip_html(message);
3995 ret = mwConversation_send(conv, mwImSend_PLAIN, tmp);
3996 g_free(tmp);
3997 }
3998
3999 return !ret;
4000
4001 } else {
4002
4003 /* queue up the message safely as plain text */
4004 char *tmp = purple_markup_strip_html(message);
4005 convo_queue(conv, mwImSend_PLAIN, tmp);
4006 g_free(tmp);
4007
4008 if(! mwConversation_isPending(conv))
4009 mwConversation_open(conv);
4010
4011 return 1;
4012 }
4013 }
4014
4015
mw_prpl_send_typing(PurpleConnection * gc,const char * name,PurpleTypingState state)4016 static unsigned int mw_prpl_send_typing(PurpleConnection *gc,
4017 const char *name,
4018 PurpleTypingState state) {
4019
4020 struct mwPurplePluginData *pd;
4021 struct mwIdBlock who = { (char *) name, NULL };
4022 struct mwConversation *conv;
4023
4024 gpointer t = GINT_TO_POINTER(!! state);
4025
4026 g_return_val_if_fail(gc != NULL, 0);
4027 pd = gc->proto_data;
4028
4029 g_return_val_if_fail(pd != NULL, 0);
4030
4031 conv = mwServiceIm_getConversation(pd->srvc_im, &who);
4032
4033 if(mwConversation_isOpen(conv)) {
4034 mwConversation_send(conv, mwImSend_TYPING, t);
4035
4036 } else if((state == PURPLE_TYPING) || (state == PURPLE_TYPED)) {
4037 /* only open a channel for sending typing notification, not for
4038 when typing has stopped. There's no point in re-opening a
4039 channel just to tell someone that this side isn't typing. */
4040
4041 convo_queue(conv, mwImSend_TYPING, t);
4042
4043 if(! mwConversation_isPending(conv)) {
4044 mwConversation_open(conv);
4045 }
4046 }
4047
4048 return 0;
4049 }
4050
4051
mw_client_name(guint16 type)4052 static const char *mw_client_name(guint16 type) {
4053 switch(type) {
4054 case mwLogin_LIB:
4055 return "Lotus Binary Library";
4056
4057 case mwLogin_JAVA_WEB:
4058 return "Lotus Java Client Applet";
4059
4060 case mwLogin_BINARY:
4061 return "Lotus Sametime Connect";
4062
4063 case mwLogin_JAVA_APP:
4064 return "Lotus Java Client Application";
4065
4066 case mwLogin_LINKS:
4067 return "Lotus Sametime Links";
4068
4069 case mwLogin_NOTES_6_5:
4070 case mwLogin_NOTES_6_5_3:
4071 case mwLogin_NOTES_7_0_beta:
4072 case mwLogin_NOTES_7_0:
4073 return "Lotus Notes Client";
4074
4075 case mwLogin_ICT:
4076 case mwLogin_ICT_1_7_8_2:
4077 case mwLogin_ICT_SIP:
4078 return "IBM Community Tools";
4079
4080 case mwLogin_NOTESBUDDY_4_14:
4081 case mwLogin_NOTESBUDDY_4_15:
4082 case mwLogin_NOTESBUDDY_4_16:
4083 return "Alphaworks NotesBuddy";
4084
4085 case 0x1305:
4086 case 0x1306:
4087 case 0x1307:
4088 return "Lotus Sametime Connect 7.5";
4089
4090 case mwLogin_SANITY:
4091 return "Sanity";
4092
4093 case mwLogin_ST_PERL:
4094 return "ST-Send-Message";
4095
4096 case mwLogin_TRILLIAN:
4097 case mwLogin_TRILLIAN_IBM:
4098 return "Trillian";
4099
4100 case mwLogin_MEANWHILE:
4101 return "Meanwhile";
4102
4103 default:
4104 return NULL;
4105 }
4106 }
4107
4108
mw_prpl_get_info(PurpleConnection * gc,const char * who)4109 static void mw_prpl_get_info(PurpleConnection *gc, const char *who) {
4110
4111 struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL };
4112
4113 struct mwPurplePluginData *pd;
4114 PurpleAccount *acct;
4115 PurpleBuddy *b;
4116 PurpleNotifyUserInfo *user_info;
4117 char *tmp;
4118 const char *tmp2;
4119
4120 g_return_if_fail(who != NULL);
4121 g_return_if_fail(*who != '\0');
4122
4123 pd = gc->proto_data;
4124
4125 acct = purple_connection_get_account(gc);
4126 b = purple_find_buddy(acct, who);
4127 user_info = purple_notify_user_info_new();
4128
4129 if(purple_str_has_prefix(who, "@E ")) {
4130 purple_notify_user_info_add_pair(user_info, _("External User"), NULL);
4131 }
4132
4133 purple_notify_user_info_add_pair(user_info, _("User ID"), who);
4134
4135 if(b) {
4136 guint32 type;
4137
4138 if(purple_buddy_get_server_alias(b)) {
4139 purple_notify_user_info_add_pair(user_info, _("Full Name"), purple_buddy_get_server_alias(b));
4140 }
4141
4142 type = purple_blist_node_get_int((PurpleBlistNode *) b, BUDDY_KEY_CLIENT);
4143 if(type) {
4144 tmp = g_strdup(mw_client_name(type));
4145 if (!tmp)
4146 tmp = g_strdup_printf(_("Unknown (0x%04x)<br>"), type);
4147
4148 purple_notify_user_info_add_pair(user_info, _("Last Known Client"), tmp);
4149
4150 g_free(tmp);
4151 }
4152 }
4153
4154 tmp = user_supports_text(pd->srvc_aware, who);
4155 if(tmp) {
4156 purple_notify_user_info_add_pair(user_info, _("Supports"), tmp);
4157 g_free(tmp);
4158 }
4159
4160 if(b) {
4161 purple_notify_user_info_add_pair(user_info, _("Status"), status_text(b));
4162
4163 /* XXX Is this adding a status message in its own section rather than with the "Status" label? */
4164 tmp2 = mwServiceAware_getText(pd->srvc_aware, &idb);
4165 if(tmp2 && g_utf8_validate(tmp2, -1, NULL)) {
4166 tmp = g_markup_escape_text(tmp2, -1);
4167 purple_notify_user_info_add_section_break(user_info);
4168 purple_notify_user_info_add_pair(user_info, NULL, tmp);
4169 g_free(tmp);
4170 }
4171 }
4172
4173 /* @todo emit a signal to allow a plugin to override the display of
4174 this notification, so that it can create its own */
4175
4176 purple_notify_userinfo(gc, who, user_info, NULL, NULL);
4177 purple_notify_user_info_destroy(user_info);
4178 }
4179
4180
mw_prpl_set_status(PurpleAccount * acct,PurpleStatus * status)4181 static void mw_prpl_set_status(PurpleAccount *acct, PurpleStatus *status) {
4182 PurpleConnection *gc;
4183 const char *state;
4184 char *message = NULL;
4185 struct mwSession *session;
4186 struct mwUserStatus stat;
4187
4188 g_return_if_fail(acct != NULL);
4189 gc = purple_account_get_connection(acct);
4190
4191 state = purple_status_get_id(status);
4192
4193 DEBUG_INFO("Set status to %s\n", purple_status_get_name(status));
4194
4195 g_return_if_fail(gc != NULL);
4196
4197 session = gc_to_session(gc);
4198 g_return_if_fail(session != NULL);
4199
4200 /* get a working copy of the current status */
4201 mwUserStatus_clone(&stat, mwSession_getUserStatus(session));
4202
4203 /* determine the state */
4204 if(purple_strequal(state, MW_STATE_ACTIVE)) {
4205 stat.status = mwStatus_ACTIVE;
4206
4207 } else if(purple_strequal(state, MW_STATE_AWAY)) {
4208 stat.status = mwStatus_AWAY;
4209
4210 } else if(purple_strequal(state, MW_STATE_BUSY)) {
4211 stat.status = mwStatus_BUSY;
4212 }
4213
4214 /* determine the message */
4215 message = (char *) purple_status_get_attr_string(status, MW_STATE_MESSAGE);
4216
4217 if(message) {
4218 /* all the possible non-NULL values of message up to this point
4219 are const, so we don't need to free them */
4220 message = purple_markup_strip_html(message);
4221 }
4222
4223 /* out with the old */
4224 g_free(stat.desc);
4225
4226 /* in with the new */
4227 stat.desc = (char *) message;
4228
4229 mwSession_setUserStatus(session, &stat);
4230 mwUserStatus_clear(&stat);
4231 }
4232
4233
mw_prpl_set_idle(PurpleConnection * gc,int t)4234 static void mw_prpl_set_idle(PurpleConnection *gc, int t) {
4235 struct mwSession *session;
4236 struct mwUserStatus stat;
4237
4238
4239 session = gc_to_session(gc);
4240 g_return_if_fail(session != NULL);
4241
4242 mwUserStatus_clone(&stat, mwSession_getUserStatus(session));
4243
4244 if(t) {
4245 time_t now = time(NULL);
4246 stat.time = now - t;
4247
4248 } else {
4249 stat.time = 0;
4250 }
4251
4252 if(t > 0 && stat.status == mwStatus_ACTIVE) {
4253 /* we were active and went idle, so change the status to IDLE. */
4254 stat.status = mwStatus_IDLE;
4255
4256 } else if(t == 0 && stat.status == mwStatus_IDLE) {
4257 /* we only become idle automatically, so change back to ACTIVE */
4258 stat.status = mwStatus_ACTIVE;
4259 }
4260
4261 mwSession_setUserStatus(session, &stat);
4262 mwUserStatus_clear(&stat);
4263 }
4264
4265
notify_im(PurpleConnection * gc,GList * row,void * user_data)4266 static void notify_im(PurpleConnection *gc, GList *row, void *user_data) {
4267 PurpleAccount *acct;
4268 PurpleConversation *conv;
4269 char *id;
4270
4271 acct = purple_connection_get_account(gc);
4272 id = g_list_nth_data(row, 1);
4273 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
4274 if(! conv) conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
4275 purple_conversation_present(conv);
4276 }
4277
4278
notify_add(PurpleConnection * gc,GList * row,void * user_data)4279 static void notify_add(PurpleConnection *gc, GList *row, void *user_data) {
4280 BuddyAddData *data = user_data;
4281 const char *group_name = NULL;
4282
4283 if (data && data->group) {
4284 group_name = purple_group_get_name(data->group);
4285 }
4286
4287 purple_blist_request_add_buddy(purple_connection_get_account(gc),
4288 g_list_nth_data(row, 1), group_name,
4289 g_list_nth_data(row, 0));
4290 }
4291
4292
notify_close(gpointer data)4293 static void notify_close(gpointer data) {
4294 if (data) {
4295 g_free(data);
4296 }
4297 }
4298
4299
multi_resolved_query(struct mwResolveResult * result,PurpleConnection * gc,gpointer data)4300 static void multi_resolved_query(struct mwResolveResult *result,
4301 PurpleConnection *gc, gpointer data) {
4302 GList *l;
4303 const char *msgA;
4304 const char *msgB;
4305 char *msg;
4306
4307 PurpleNotifySearchResults *sres;
4308 PurpleNotifySearchColumn *scol;
4309
4310 sres = purple_notify_searchresults_new();
4311
4312 scol = purple_notify_searchresults_column_new(_("User Name"));
4313 purple_notify_searchresults_column_add(sres, scol);
4314
4315 scol = purple_notify_searchresults_column_new(_("Sametime ID"));
4316 purple_notify_searchresults_column_add(sres, scol);
4317
4318 purple_notify_searchresults_button_add(sres, PURPLE_NOTIFY_BUTTON_IM,
4319 notify_im);
4320
4321 purple_notify_searchresults_button_add(sres, PURPLE_NOTIFY_BUTTON_ADD,
4322 notify_add);
4323
4324 for(l = result->matches; l; l = l->next) {
4325 struct mwResolveMatch *match = l->data;
4326 GList *row = NULL;
4327
4328 DEBUG_INFO("multi resolve: %s, %s\n",
4329 NSTR(match->id), NSTR(match->name));
4330
4331 if(!match->id || !match->name)
4332 continue;
4333
4334 row = g_list_append(row, g_strdup(match->name));
4335 row = g_list_append(row, g_strdup(match->id));
4336 purple_notify_searchresults_row_add(sres, row);
4337 }
4338
4339 msgA = _("An ambiguous user ID was entered");
4340 msgB = _("The identifier '%s' may possibly refer to any of the following"
4341 " users. Please select the correct user from the list below to"
4342 " add them to your buddy list.");
4343 msg = g_strdup_printf(msgB, result->name);
4344
4345 purple_notify_searchresults(gc, _("Select User"),
4346 msgA, msg, sres, notify_close, data);
4347
4348 g_free(msg);
4349 }
4350
4351
add_buddy_resolved(struct mwServiceResolve * srvc,guint32 id,guint32 code,GList * results,gpointer b)4352 static void add_buddy_resolved(struct mwServiceResolve *srvc,
4353 guint32 id, guint32 code, GList *results,
4354 gpointer b) {
4355
4356 struct mwResolveResult *res = NULL;
4357 BuddyAddData *data = b;
4358 PurpleBuddy *buddy = NULL;
4359 PurpleConnection *gc;
4360 struct mwPurplePluginData *pd;
4361
4362 g_return_if_fail(data != NULL);
4363
4364 buddy = data->buddy;
4365
4366 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
4367 pd = gc->proto_data;
4368
4369 if(results)
4370 res = results->data;
4371
4372 if(!code && res && res->matches) {
4373 if(!res->matches->next) {
4374 struct mwResolveMatch *match = res->matches->data;
4375
4376 /* only one? that might be the right one! */
4377 if(!purple_strequal(res->name, match->id)) {
4378 /* uh oh, the single result isn't identical to the search
4379 term, better safe then sorry, so let's make sure it's who
4380 the user meant to add */
4381 purple_blist_remove_buddy(buddy);
4382 multi_resolved_query(res, gc, data);
4383
4384 } else {
4385
4386 /* same person, set the server alias */
4387 purple_blist_server_alias_buddy(buddy, match->name);
4388 purple_blist_node_set_string((PurpleBlistNode *) buddy,
4389 BUDDY_KEY_NAME, match->name);
4390
4391 /* subscribe to awareness */
4392 buddy_add(pd, buddy);
4393
4394 blist_schedule(pd);
4395
4396 g_free(data);
4397 }
4398
4399 } else {
4400 /* prompt user if more than one match was returned */
4401 purple_blist_remove_buddy(buddy);
4402 multi_resolved_query(res, gc, data);
4403 }
4404
4405 return;
4406 }
4407
4408 #if 0
4409 /* fall-through indicates that we couldn't find a matching user in
4410 the resolve service (ether error or zero results), so we remove
4411 this buddy */
4412
4413 /* note: I can't really think of a good reason to alter the buddy
4414 list in any way. There has been at least one report where the
4415 resolve service isn't returning correct results anyway, so let's
4416 just leave them in the list. I'm just going to if0 this section
4417 out unless I can think of a very good reason to do this. -siege */
4418
4419 DEBUG_INFO("no such buddy in community\n");
4420 purple_blist_remove_buddy(buddy);
4421 blist_schedule(pd);
4422
4423 if(res && res->name) {
4424 /* compose and display an error message */
4425 const char *msgA;
4426 const char *msgB;
4427 char *msg;
4428
4429 msgA = _("Unable to add user: user not found");
4430
4431 msgB = _("The identifier '%s' did not match any users in your"
4432 " Sametime community. This entry has been removed from"
4433 " your buddy list.");
4434 msg = g_strdup_printf(msgB, NSTR(res->name));
4435
4436 purple_notify_error(gc, _("Unable to add user"), msgA, msg);
4437
4438 g_free(msg);
4439 }
4440 #endif
4441 }
4442
4443
mw_prpl_add_buddy(PurpleConnection * gc,PurpleBuddy * buddy,PurpleGroup * group)4444 static void mw_prpl_add_buddy(PurpleConnection *gc,
4445 PurpleBuddy *buddy,
4446 PurpleGroup *group) {
4447
4448 struct mwPurplePluginData *pd = gc->proto_data;
4449 struct mwServiceResolve *srvc;
4450 GList *query;
4451 enum mwResolveFlag flags;
4452 guint32 req;
4453 BuddyAddData *data;
4454
4455 /* catch external buddies. They won't be in the resolve service */
4456 if(buddy_is_external(buddy)) {
4457 buddy_add(pd, buddy);
4458 return;
4459 }
4460
4461 data = g_new0(BuddyAddData, 1);
4462 data->buddy = buddy;
4463 data->group = group;
4464
4465 srvc = pd->srvc_resolve;
4466
4467 query = g_list_prepend(NULL, (char *)purple_buddy_get_name(buddy));
4468 flags = mwResolveFlag_FIRST | mwResolveFlag_USERS;
4469
4470 req = mwServiceResolve_resolve(srvc, query, flags, add_buddy_resolved,
4471 data, NULL);
4472 g_list_free(query);
4473
4474 if(req == SEARCH_ERROR) {
4475 purple_blist_remove_buddy(buddy);
4476 blist_schedule(pd);
4477 }
4478 }
4479
4480
foreach_add_buddies(PurpleGroup * group,GList * buddies,struct mwPurplePluginData * pd)4481 static void foreach_add_buddies(PurpleGroup *group, GList *buddies,
4482 struct mwPurplePluginData *pd) {
4483 struct mwAwareList *list;
4484
4485 list = list_ensure(pd, group);
4486 mwAwareList_addAware(list, buddies);
4487 g_list_free(buddies);
4488 }
4489
4490
mw_prpl_add_buddies(PurpleConnection * gc,GList * buddies,GList * groups)4491 static void mw_prpl_add_buddies(PurpleConnection *gc,
4492 GList *buddies,
4493 GList *groups) {
4494
4495 struct mwPurplePluginData *pd;
4496 GHashTable *group_sets;
4497 struct mwAwareIdBlock *idbs, *idb;
4498
4499 pd = gc->proto_data;
4500
4501 /* map PurpleGroup:GList of mwAwareIdBlock */
4502 group_sets = g_hash_table_new(g_direct_hash, g_direct_equal);
4503
4504 /* bunch of mwAwareIdBlock allocated at once, free'd at once */
4505 idb = idbs = g_new(struct mwAwareIdBlock, g_list_length(buddies));
4506
4507 /* first pass collects mwAwareIdBlock lists for each group */
4508 for(; buddies; buddies = buddies->next) {
4509 PurpleBuddy *b = buddies->data;
4510 PurpleGroup *g;
4511 const char *fn;
4512 GList *l;
4513
4514 /* nab the saved server alias and stick it on the buddy */
4515 fn = purple_blist_node_get_string((PurpleBlistNode *) b, BUDDY_KEY_NAME);
4516 purple_blist_server_alias_buddy(b, fn);
4517
4518 /* convert PurpleBuddy into a mwAwareIdBlock */
4519 idb->type = mwAware_USER;
4520 idb->user = (char *) purple_buddy_get_name(b);
4521 idb->community = NULL;
4522
4523 /* put idb into the list associated with the buddy's group */
4524 g = purple_buddy_get_group(b);
4525 l = g_hash_table_lookup(group_sets, g);
4526 l = g_list_prepend(l, idb++);
4527 g_hash_table_insert(group_sets, g, l);
4528 }
4529
4530 /* each group's buddies get added in one shot, and schedule the blist
4531 for saving */
4532 g_hash_table_foreach(group_sets, (GHFunc) foreach_add_buddies, pd);
4533 blist_schedule(pd);
4534
4535 /* cleanup */
4536 g_hash_table_destroy(group_sets);
4537 g_free(idbs);
4538 }
4539
4540
mw_prpl_remove_buddy(PurpleConnection * gc,PurpleBuddy * buddy,PurpleGroup * group)4541 static void mw_prpl_remove_buddy(PurpleConnection *gc,
4542 PurpleBuddy *buddy, PurpleGroup *group) {
4543
4544 struct mwPurplePluginData *pd;
4545 struct mwAwareIdBlock idb = { mwAware_USER, (char *)purple_buddy_get_name(buddy), NULL };
4546 struct mwAwareList *list;
4547
4548 GList *rem = g_list_prepend(NULL, &idb);
4549
4550 pd = gc->proto_data;
4551 group = purple_buddy_get_group(buddy);
4552 list = list_ensure(pd, group);
4553
4554 mwAwareList_removeAware(list, rem);
4555 blist_schedule(pd);
4556
4557 g_list_free(rem);
4558 }
4559
4560
privacy_fill(struct mwPrivacyInfo * priv,GSList * members)4561 static void privacy_fill(struct mwPrivacyInfo *priv,
4562 GSList *members) {
4563
4564 struct mwUserItem *u;
4565 guint count;
4566
4567 count = g_slist_length(members);
4568 DEBUG_INFO("privacy_fill: %u members\n", count);
4569
4570 priv->count = count;
4571 priv->users = g_new0(struct mwUserItem, count);
4572
4573 while(count--) {
4574 u = priv->users + count;
4575 u->id = members->data;
4576 members = members->next;
4577 }
4578 }
4579
4580
mw_prpl_set_permit_deny(PurpleConnection * gc)4581 static void mw_prpl_set_permit_deny(PurpleConnection *gc) {
4582 PurpleAccount *acct;
4583 struct mwPurplePluginData *pd;
4584 struct mwSession *session;
4585
4586 struct mwPrivacyInfo privacy = {
4587 FALSE, /* deny */
4588 0, /* count */
4589 NULL, /* users */
4590 };
4591
4592 g_return_if_fail(gc != NULL);
4593
4594 acct = purple_connection_get_account(gc);
4595 g_return_if_fail(acct != NULL);
4596
4597 pd = gc->proto_data;
4598 g_return_if_fail(pd != NULL);
4599
4600 session = pd->session;
4601 g_return_if_fail(session != NULL);
4602
4603 switch(acct->perm_deny) {
4604 case PURPLE_PRIVACY_DENY_USERS:
4605 DEBUG_INFO("PURPLE_PRIVACY_DENY_USERS\n");
4606 privacy_fill(&privacy, acct->deny);
4607 privacy.deny = TRUE;
4608 break;
4609
4610 case PURPLE_PRIVACY_ALLOW_ALL:
4611 DEBUG_INFO("PURPLE_PRIVACY_ALLOW_ALL\n");
4612 privacy.deny = TRUE;
4613 break;
4614
4615 case PURPLE_PRIVACY_ALLOW_USERS:
4616 DEBUG_INFO("PURPLE_PRIVACY_ALLOW_USERS\n");
4617 privacy_fill(&privacy, acct->permit);
4618 privacy.deny = FALSE;
4619 break;
4620
4621 case PURPLE_PRIVACY_DENY_ALL:
4622 DEBUG_INFO("PURPLE_PRIVACY_DENY_ALL\n");
4623 privacy.deny = FALSE;
4624 break;
4625
4626 default:
4627 DEBUG_INFO("acct->perm_deny is 0x%x\n", acct->perm_deny);
4628 return;
4629 }
4630
4631 mwSession_setPrivacyInfo(session, &privacy);
4632 g_free(privacy.users);
4633 }
4634
4635
mw_prpl_add_permit(PurpleConnection * gc,const char * name)4636 static void mw_prpl_add_permit(PurpleConnection *gc, const char *name) {
4637 mw_prpl_set_permit_deny(gc);
4638 }
4639
4640
mw_prpl_add_deny(PurpleConnection * gc,const char * name)4641 static void mw_prpl_add_deny(PurpleConnection *gc, const char *name) {
4642 mw_prpl_set_permit_deny(gc);
4643 }
4644
4645
mw_prpl_rem_permit(PurpleConnection * gc,const char * name)4646 static void mw_prpl_rem_permit(PurpleConnection *gc, const char *name) {
4647 mw_prpl_set_permit_deny(gc);
4648 }
4649
4650
mw_prpl_rem_deny(PurpleConnection * gc,const char * name)4651 static void mw_prpl_rem_deny(PurpleConnection *gc, const char *name) {
4652 mw_prpl_set_permit_deny(gc);
4653 }
4654
4655
conf_find(struct mwServiceConference * srvc,const char * name)4656 static struct mwConference *conf_find(struct mwServiceConference *srvc,
4657 const char *name) {
4658 GList *l, *ll;
4659 struct mwConference *conf = NULL;
4660
4661 ll = mwServiceConference_getConferences(srvc);
4662 for(l = ll; l; l = l->next) {
4663 struct mwConference *c = l->data;
4664 if(purple_strequal(name, mwConference_getName(c))) {
4665 conf = c;
4666 break;
4667 }
4668 }
4669 g_list_free(ll);
4670
4671 return conf;
4672 }
4673
4674
mw_prpl_join_chat(PurpleConnection * gc,GHashTable * components)4675 static void mw_prpl_join_chat(PurpleConnection *gc,
4676 GHashTable *components) {
4677
4678 struct mwPurplePluginData *pd;
4679 char *c, *t;
4680
4681 pd = gc->proto_data;
4682
4683 c = g_hash_table_lookup(components, CHAT_KEY_NAME);
4684 t = g_hash_table_lookup(components, CHAT_KEY_TOPIC);
4685
4686 if(g_hash_table_lookup(components, CHAT_KEY_IS_PLACE)) {
4687 /* use place service */
4688 struct mwServicePlace *srvc;
4689 struct mwPlace *place = NULL;
4690
4691 srvc = pd->srvc_place;
4692 place = mwPlace_new(srvc, c, t);
4693 mwPlace_open(place);
4694
4695 } else {
4696 /* use conference service */
4697 struct mwServiceConference *srvc;
4698 struct mwConference *conf = NULL;
4699
4700 srvc = pd->srvc_conf;
4701 if(c) conf = conf_find(srvc, c);
4702
4703 if(conf) {
4704 DEBUG_INFO("accepting conference invitation\n");
4705 mwConference_accept(conf);
4706
4707 } else {
4708 DEBUG_INFO("creating new conference\n");
4709 conf = mwConference_new(srvc, t);
4710 mwConference_open(conf);
4711 }
4712 }
4713 }
4714
4715
mw_prpl_reject_chat(PurpleConnection * gc,GHashTable * components)4716 static void mw_prpl_reject_chat(PurpleConnection *gc,
4717 GHashTable *components) {
4718
4719 struct mwPurplePluginData *pd;
4720 struct mwServiceConference *srvc;
4721 char *c;
4722
4723 pd = gc->proto_data;
4724 srvc = pd->srvc_conf;
4725
4726 if(g_hash_table_lookup(components, CHAT_KEY_IS_PLACE)) {
4727 ; /* nothing needs doing */
4728
4729 } else {
4730 /* reject conference */
4731 c = g_hash_table_lookup(components, CHAT_KEY_NAME);
4732 if(c) {
4733 struct mwConference *conf = conf_find(srvc, c);
4734 if(conf) mwConference_reject(conf, ERR_SUCCESS, "Declined");
4735 }
4736 }
4737 }
4738
4739
mw_prpl_get_chat_name(GHashTable * components)4740 static char *mw_prpl_get_chat_name(GHashTable *components) {
4741 return g_hash_table_lookup(components, CHAT_KEY_NAME);
4742 }
4743
4744
mw_prpl_chat_invite(PurpleConnection * gc,int id,const char * invitation,const char * who)4745 static void mw_prpl_chat_invite(PurpleConnection *gc,
4746 int id,
4747 const char *invitation,
4748 const char *who) {
4749
4750 struct mwPurplePluginData *pd;
4751 struct mwConference *conf;
4752 struct mwPlace *place;
4753 struct mwIdBlock idb = { (char *) who, NULL };
4754
4755 pd = gc->proto_data;
4756 g_return_if_fail(pd != NULL);
4757
4758 conf = ID_TO_CONF(pd, id);
4759
4760 if(conf) {
4761 mwConference_invite(conf, &idb, invitation);
4762 return;
4763 }
4764
4765 place = ID_TO_PLACE(pd, id);
4766 g_return_if_fail(place != NULL);
4767
4768 /* @todo: use the IM service for invitation */
4769 mwPlace_legacyInvite(place, &idb, invitation);
4770 }
4771
4772
mw_prpl_chat_leave(PurpleConnection * gc,int id)4773 static void mw_prpl_chat_leave(PurpleConnection *gc,
4774 int id) {
4775
4776 struct mwPurplePluginData *pd;
4777 struct mwConference *conf;
4778
4779 pd = gc->proto_data;
4780
4781 g_return_if_fail(pd != NULL);
4782 conf = ID_TO_CONF(pd, id);
4783
4784 if(conf) {
4785 mwConference_destroy(conf, ERR_SUCCESS, "Leaving");
4786
4787 } else {
4788 struct mwPlace *place = ID_TO_PLACE(pd, id);
4789 g_return_if_fail(place != NULL);
4790
4791 mwPlace_destroy(place, ERR_SUCCESS);
4792 }
4793 }
4794
4795
mw_prpl_chat_whisper(PurpleConnection * gc,int id,const char * who,const char * message)4796 static void mw_prpl_chat_whisper(PurpleConnection *gc,
4797 int id,
4798 const char *who,
4799 const char *message) {
4800
4801 mw_prpl_send_im(gc, who, message, 0);
4802 }
4803
4804
mw_prpl_chat_send(PurpleConnection * gc,int id,const char * message,PurpleMessageFlags flags)4805 static int mw_prpl_chat_send(PurpleConnection *gc,
4806 int id,
4807 const char *message,
4808 PurpleMessageFlags flags) {
4809
4810 struct mwPurplePluginData *pd;
4811 struct mwConference *conf;
4812 char *msg;
4813 int ret;
4814
4815 pd = gc->proto_data;
4816
4817 g_return_val_if_fail(pd != NULL, 0);
4818 conf = ID_TO_CONF(pd, id);
4819
4820 msg = purple_markup_strip_html(message);
4821
4822 if(conf) {
4823 ret = ! mwConference_sendText(conf, msg);
4824
4825 } else {
4826 struct mwPlace *place = ID_TO_PLACE(pd, id);
4827 g_return_val_if_fail(place != NULL, 0);
4828
4829 ret = ! mwPlace_sendText(place, msg);
4830 }
4831
4832 g_free(msg);
4833 return ret;
4834 }
4835
4836
mw_prpl_keepalive(PurpleConnection * gc)4837 static void mw_prpl_keepalive(PurpleConnection *gc) {
4838 struct mwSession *session;
4839
4840 g_return_if_fail(gc != NULL);
4841
4842 session = gc_to_session(gc);
4843 g_return_if_fail(session != NULL);
4844
4845 mwSession_sendKeepalive(session);
4846 }
4847
4848
mw_prpl_alias_buddy(PurpleConnection * gc,const char * who,const char * alias)4849 static void mw_prpl_alias_buddy(PurpleConnection *gc,
4850 const char *who,
4851 const char *alias) {
4852
4853 struct mwPurplePluginData *pd = gc->proto_data;
4854 g_return_if_fail(pd != NULL);
4855
4856 /* it's a change to the buddy list, so we've gotta reflect that in
4857 the server copy */
4858
4859 blist_schedule(pd);
4860 }
4861
4862
mw_prpl_group_buddy(PurpleConnection * gc,const char * who,const char * old_group,const char * new_group)4863 static void mw_prpl_group_buddy(PurpleConnection *gc,
4864 const char *who,
4865 const char *old_group,
4866 const char *new_group) {
4867
4868 struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL };
4869 GList *gl = g_list_prepend(NULL, &idb);
4870
4871 struct mwPurplePluginData *pd = gc->proto_data;
4872 PurpleGroup *group;
4873 struct mwAwareList *list;
4874
4875 /* add who to new_group's aware list */
4876 group = purple_find_group(new_group);
4877 list = list_ensure(pd, group);
4878 mwAwareList_addAware(list, gl);
4879
4880 /* remove who from old_group's aware list */
4881 group = purple_find_group(old_group);
4882 list = list_ensure(pd, group);
4883 mwAwareList_removeAware(list, gl);
4884
4885 g_list_free(gl);
4886
4887 /* schedule the changes to be saved */
4888 blist_schedule(pd);
4889 }
4890
4891
mw_prpl_rename_group(PurpleConnection * gc,const char * old,PurpleGroup * group,GList * buddies)4892 static void mw_prpl_rename_group(PurpleConnection *gc,
4893 const char *old,
4894 PurpleGroup *group,
4895 GList *buddies) {
4896
4897 struct mwPurplePluginData *pd = gc->proto_data;
4898 g_return_if_fail(pd != NULL);
4899
4900 /* it's a change in the buddy list, so we've gotta reflect that in
4901 the server copy. Also, having this function should prevent all
4902 those buddies from being removed and re-added. We don't really
4903 give a crap what the group is named in Purple other than to record
4904 that as the group name/alias */
4905
4906 blist_schedule(pd);
4907 }
4908
4909
mw_prpl_buddy_free(PurpleBuddy * buddy)4910 static void mw_prpl_buddy_free(PurpleBuddy *buddy) {
4911 /* I don't think we have any cleanup for buddies yet */
4912 ;
4913 }
4914
4915
mw_prpl_convo_closed(PurpleConnection * gc,const char * who)4916 static void mw_prpl_convo_closed(PurpleConnection *gc, const char *who) {
4917 struct mwPurplePluginData *pd = gc->proto_data;
4918 struct mwServiceIm *srvc;
4919 struct mwConversation *conv;
4920 struct mwIdBlock idb = { (char *) who, NULL };
4921
4922 g_return_if_fail(pd != NULL);
4923
4924 srvc = pd->srvc_im;
4925 g_return_if_fail(srvc != NULL);
4926
4927 conv = mwServiceIm_findConversation(srvc, &idb);
4928 if(! conv) return;
4929
4930 if(mwConversation_isOpen(conv))
4931 mwConversation_free(conv);
4932 }
4933
4934
mw_prpl_normalize(const PurpleAccount * account,const char * id)4935 static const char *mw_prpl_normalize(const PurpleAccount *account,
4936 const char *id) {
4937
4938 /* code elsewhere assumes that the return value points to different
4939 memory than the passed value, but it won't free the normalized
4940 data. wtf? */
4941
4942 static char buf[BUF_LEN];
4943 g_strlcpy(buf, id, sizeof(buf));
4944 return buf;
4945 }
4946
4947
mw_prpl_remove_group(PurpleConnection * gc,PurpleGroup * group)4948 static void mw_prpl_remove_group(PurpleConnection *gc, PurpleGroup *group) {
4949 struct mwPurplePluginData *pd;
4950 struct mwAwareList *list;
4951
4952 pd = gc->proto_data;
4953 g_return_if_fail(pd != NULL);
4954 g_return_if_fail(pd->group_list_map != NULL);
4955
4956 list = g_hash_table_lookup(pd->group_list_map, group);
4957
4958 if(list) {
4959 g_hash_table_remove(pd->group_list_map, list);
4960 g_hash_table_remove(pd->group_list_map, group);
4961 mwAwareList_free(list);
4962
4963 blist_schedule(pd);
4964 }
4965 }
4966
4967
mw_prpl_can_receive_file(PurpleConnection * gc,const char * who)4968 static gboolean mw_prpl_can_receive_file(PurpleConnection *gc,
4969 const char *who) {
4970 struct mwPurplePluginData *pd;
4971 struct mwServiceAware *srvc;
4972 PurpleAccount *acct;
4973
4974 g_return_val_if_fail(gc != NULL, FALSE);
4975
4976 pd = gc->proto_data;
4977 g_return_val_if_fail(pd != NULL, FALSE);
4978
4979 srvc = pd->srvc_aware;
4980 g_return_val_if_fail(srvc != NULL, FALSE);
4981
4982 acct = purple_connection_get_account(gc);
4983 g_return_val_if_fail(acct != NULL, FALSE);
4984
4985 return purple_find_buddy(acct, who) &&
4986 user_supports(srvc, who, mwAttribute_FILE_TRANSFER);
4987 }
4988
4989
ft_outgoing_init(PurpleXfer * xfer)4990 static void ft_outgoing_init(PurpleXfer *xfer) {
4991 PurpleAccount *acct;
4992 PurpleConnection *gc;
4993
4994 struct mwPurplePluginData *pd;
4995 struct mwServiceFileTransfer *srvc;
4996 struct mwFileTransfer *ft;
4997
4998 const char *filename;
4999 gsize filesize;
5000 FILE *fp;
5001
5002 struct mwIdBlock idb = { NULL, NULL };
5003
5004 DEBUG_INFO("ft_outgoing_init\n");
5005
5006 acct = purple_xfer_get_account(xfer);
5007 gc = purple_account_get_connection(acct);
5008 pd = gc->proto_data;
5009 srvc = pd->srvc_ft;
5010
5011 filename = purple_xfer_get_local_filename(xfer);
5012 filesize = purple_xfer_get_size(xfer);
5013 idb.user = xfer->who;
5014
5015 purple_xfer_update_progress(xfer);
5016
5017 /* test that we can actually send the file */
5018 fp = g_fopen(filename, "rb");
5019 if(! fp) {
5020 char *msg = g_strdup_printf(_("Error reading file %s: \n%s\n"),
5021 filename, g_strerror(errno));
5022 purple_xfer_error(purple_xfer_get_type(xfer), acct, xfer->who, msg);
5023 g_free(msg);
5024 return;
5025 }
5026 fclose(fp);
5027
5028 {
5029 char *tmp = strrchr(filename, G_DIR_SEPARATOR);
5030 if(tmp++) filename = tmp;
5031 }
5032
5033 ft = mwFileTransfer_new(srvc, &idb, NULL, filename, filesize);
5034
5035 purple_xfer_ref(xfer);
5036 mwFileTransfer_setClientData(ft, xfer, (GDestroyNotify) purple_xfer_unref);
5037 xfer->data = ft;
5038
5039 mwFileTransfer_offer(ft);
5040 }
5041
5042
ft_outgoing_cancel(PurpleXfer * xfer)5043 static void ft_outgoing_cancel(PurpleXfer *xfer) {
5044 struct mwFileTransfer *ft = xfer->data;
5045
5046 DEBUG_INFO("ft_outgoing_cancel called\n");
5047
5048 if(ft) mwFileTransfer_cancel(ft);
5049 }
5050
5051
mw_prpl_new_xfer(PurpleConnection * gc,const char * who)5052 static PurpleXfer *mw_prpl_new_xfer(PurpleConnection *gc, const char *who) {
5053 PurpleAccount *acct;
5054 PurpleXfer *xfer;
5055
5056 acct = purple_connection_get_account(gc);
5057
5058 xfer = purple_xfer_new(acct, PURPLE_XFER_SEND, who);
5059 if (xfer)
5060 {
5061 purple_xfer_set_init_fnc(xfer, ft_outgoing_init);
5062 purple_xfer_set_cancel_send_fnc(xfer, ft_outgoing_cancel);
5063 }
5064
5065 return xfer;
5066 }
5067
mw_prpl_send_file(PurpleConnection * gc,const char * who,const char * file)5068 static void mw_prpl_send_file(PurpleConnection *gc,
5069 const char *who, const char *file) {
5070
5071 PurpleXfer *xfer = mw_prpl_new_xfer(gc, who);
5072
5073 if(file) {
5074 DEBUG_INFO("file != NULL\n");
5075 purple_xfer_request_accepted(xfer, file);
5076
5077 } else {
5078 DEBUG_INFO("file == NULL\n");
5079 purple_xfer_request(xfer);
5080 }
5081 }
5082
5083
5084 static PurplePluginProtocolInfo mw_prpl_info = {
5085 OPT_PROTO_IM_IMAGE,
5086 NULL, /*< set in mw_plugin_init */
5087 NULL, /*< set in mw_plugin_init */
5088 NO_BUDDY_ICONS,
5089 mw_prpl_list_icon,
5090 mw_prpl_list_emblem,
5091 mw_prpl_status_text,
5092 mw_prpl_tooltip_text,
5093 mw_prpl_status_types,
5094 mw_prpl_blist_node_menu,
5095 mw_prpl_chat_info,
5096 mw_prpl_chat_info_defaults,
5097 mw_prpl_login,
5098 mw_prpl_close,
5099 mw_prpl_send_im,
5100 NULL,
5101 mw_prpl_send_typing,
5102 mw_prpl_get_info,
5103 mw_prpl_set_status,
5104 mw_prpl_set_idle,
5105 NULL,
5106 mw_prpl_add_buddy,
5107 mw_prpl_add_buddies,
5108 mw_prpl_remove_buddy,
5109 NULL,
5110 mw_prpl_add_permit,
5111 mw_prpl_add_deny,
5112 mw_prpl_rem_permit,
5113 mw_prpl_rem_deny,
5114 mw_prpl_set_permit_deny,
5115 mw_prpl_join_chat,
5116 mw_prpl_reject_chat,
5117 mw_prpl_get_chat_name,
5118 mw_prpl_chat_invite,
5119 mw_prpl_chat_leave,
5120 mw_prpl_chat_whisper,
5121 mw_prpl_chat_send,
5122 mw_prpl_keepalive,
5123 NULL,
5124 NULL,
5125 NULL,
5126 mw_prpl_alias_buddy,
5127 mw_prpl_group_buddy,
5128 mw_prpl_rename_group,
5129 mw_prpl_buddy_free,
5130 mw_prpl_convo_closed,
5131 mw_prpl_normalize,
5132 NULL,
5133 mw_prpl_remove_group,
5134 NULL,
5135 NULL,
5136 NULL,
5137 NULL,
5138 NULL,
5139 NULL,
5140 mw_prpl_can_receive_file,
5141 mw_prpl_send_file,
5142 mw_prpl_new_xfer,
5143 NULL,
5144 NULL,
5145 NULL,
5146 NULL,
5147 NULL,
5148 NULL,
5149 NULL,
5150 sizeof(PurplePluginProtocolInfo),
5151 NULL,
5152 NULL,
5153 NULL,
5154 NULL,
5155 NULL,
5156 NULL,
5157 NULL,
5158 NULL,
5159 NULL, /* get_cb_alias */
5160 NULL, /* chat_can_receive_file */
5161 NULL, /* chat_send_file */
5162 };
5163
5164
5165 static PurplePluginPrefFrame *
mw_plugin_get_plugin_pref_frame(PurplePlugin * plugin)5166 mw_plugin_get_plugin_pref_frame(PurplePlugin *plugin) {
5167 PurplePluginPrefFrame *frame;
5168 PurplePluginPref *pref;
5169
5170 frame = purple_plugin_pref_frame_new();
5171
5172 pref = purple_plugin_pref_new_with_label(_("Remotely Stored Buddy List"));
5173 purple_plugin_pref_frame_add(frame, pref);
5174
5175
5176 pref = purple_plugin_pref_new_with_name(MW_PRPL_OPT_BLIST_ACTION);
5177 purple_plugin_pref_set_label(pref, _("Buddy List Storage Mode"));
5178
5179 purple_plugin_pref_set_type(pref, PURPLE_PLUGIN_PREF_CHOICE);
5180 purple_plugin_pref_add_choice(pref, _("Local Buddy List Only"),
5181 GINT_TO_POINTER(blist_choice_LOCAL));
5182 purple_plugin_pref_add_choice(pref, _("Merge List from Server"),
5183 GINT_TO_POINTER(blist_choice_MERGE));
5184 purple_plugin_pref_add_choice(pref, _("Merge and Save List to Server"),
5185 GINT_TO_POINTER(blist_choice_STORE));
5186 purple_plugin_pref_add_choice(pref, _("Synchronize List with Server"),
5187 GINT_TO_POINTER(blist_choice_SYNCH));
5188
5189 purple_plugin_pref_frame_add(frame, pref);
5190
5191 return frame;
5192 }
5193
5194
5195 static PurplePluginUiInfo mw_plugin_ui_info = {
5196 mw_plugin_get_plugin_pref_frame,
5197 0, /* page_num */
5198 NULL, /* frame */
5199 NULL,
5200 NULL,
5201 NULL,
5202 NULL
5203 };
5204
5205
st_import_action_cb(PurpleConnection * gc,char * filename)5206 static void st_import_action_cb(PurpleConnection *gc, char *filename) {
5207 struct mwSametimeList *l;
5208
5209 FILE *file;
5210 char buf[BUF_LEN];
5211 size_t len;
5212
5213 GString *str;
5214
5215 file = g_fopen(filename, "r");
5216 g_return_if_fail(file != NULL);
5217
5218 str = g_string_new(NULL);
5219 while( (len = fread(buf, 1, BUF_LEN, file)) ) {
5220 g_string_append_len(str, buf, len);
5221 }
5222
5223 fclose(file);
5224
5225 l = mwSametimeList_load(str->str);
5226 g_string_free(str, TRUE);
5227
5228 blist_merge(gc, l);
5229 mwSametimeList_free(l);
5230 }
5231
5232
5233 /** prompts for a file to import blist from */
st_import_action(PurplePluginAction * act)5234 static void st_import_action(PurplePluginAction *act) {
5235 PurpleConnection *gc;
5236 PurpleAccount *account;
5237 char *title;
5238
5239 gc = act->context;
5240 account = purple_connection_get_account(gc);
5241 title = g_strdup_printf(_("Import Sametime List for Account %s"),
5242 purple_account_get_username(account));
5243
5244 purple_request_file(gc, title, NULL, FALSE,
5245 G_CALLBACK(st_import_action_cb), NULL,
5246 account, NULL, NULL,
5247 gc);
5248
5249 g_free(title);
5250 }
5251
5252
st_export_action_cb(PurpleConnection * gc,char * filename)5253 static void st_export_action_cb(PurpleConnection *gc, char *filename) {
5254 struct mwSametimeList *l;
5255 char *str;
5256 FILE *file;
5257
5258 file = g_fopen(filename, "w");
5259 g_return_if_fail(file != NULL);
5260
5261 l = mwSametimeList_new();
5262 blist_export(gc, l);
5263 str = mwSametimeList_store(l);
5264 mwSametimeList_free(l);
5265
5266 fprintf(file, "%s", str);
5267 fclose(file);
5268
5269 g_free(str);
5270 }
5271
5272
5273 /** prompts for a file to export blist to */
st_export_action(PurplePluginAction * act)5274 static void st_export_action(PurplePluginAction *act) {
5275 PurpleConnection *gc;
5276 PurpleAccount *account;
5277 char *title;
5278
5279 gc = act->context;
5280 account = purple_connection_get_account(gc);
5281 title = g_strdup_printf(_("Export Sametime List for Account %s"),
5282 purple_account_get_username(account));
5283
5284 purple_request_file(gc, title, NULL, TRUE,
5285 G_CALLBACK(st_export_action_cb), NULL,
5286 account, NULL, NULL,
5287 gc);
5288
5289 g_free(title);
5290 }
5291
5292
remote_group_multi_cleanup(gpointer ignore,PurpleRequestFields * fields)5293 static void remote_group_multi_cleanup(gpointer ignore,
5294 PurpleRequestFields *fields) {
5295
5296 PurpleRequestField *f;
5297 GList *l;
5298
5299 f = purple_request_fields_get_field(fields, "group");
5300 l = purple_request_field_list_get_items(f);
5301
5302 for(; l; l = l->next) {
5303 const char *i = l->data;
5304 struct named_id *res;
5305
5306 res = purple_request_field_list_get_data(f, i);
5307
5308 g_free(res->id);
5309 g_free(res->name);
5310 g_free(res);
5311 }
5312 }
5313
5314
remote_group_done(struct mwPurplePluginData * pd,const char * id,const char * name)5315 static void remote_group_done(struct mwPurplePluginData *pd,
5316 const char *id, const char *name) {
5317 PurpleConnection *gc;
5318 PurpleAccount *acct;
5319 PurpleGroup *group;
5320 PurpleBlistNode *gn;
5321 const char *owner;
5322
5323 g_return_if_fail(pd != NULL);
5324
5325 gc = pd->gc;
5326 acct = purple_connection_get_account(gc);
5327
5328 /* collision checking */
5329 group = purple_find_group(name);
5330 if(group) {
5331 const char *msgA;
5332 const char *msgB;
5333 char *msg;
5334
5335 msgA = _("Unable to add group: group exists");
5336 msgB = _("A group named '%s' already exists in your buddy list.");
5337 msg = g_strdup_printf(msgB, name);
5338
5339 purple_notify_error(gc, _("Unable to add group"), msgA, msg);
5340
5341 g_free(msg);
5342 return;
5343 }
5344
5345 group = purple_group_new(name);
5346 gn = (PurpleBlistNode *) group;
5347
5348 owner = purple_account_get_username(acct);
5349
5350 purple_blist_node_set_string(gn, GROUP_KEY_NAME, id);
5351 purple_blist_node_set_int(gn, GROUP_KEY_TYPE, mwSametimeGroup_DYNAMIC);
5352 purple_blist_node_set_string(gn, GROUP_KEY_OWNER, owner);
5353 purple_blist_add_group(group, NULL);
5354
5355 group_add(pd, group);
5356 blist_schedule(pd);
5357 }
5358
5359
remote_group_multi_cb(struct mwPurplePluginData * pd,PurpleRequestFields * fields)5360 static void remote_group_multi_cb(struct mwPurplePluginData *pd,
5361 PurpleRequestFields *fields) {
5362 PurpleRequestField *f;
5363 GList *l;
5364
5365 f = purple_request_fields_get_field(fields, "group");
5366 l = purple_request_field_list_get_selected(f);
5367
5368 if(l) {
5369 const char *i = l->data;
5370 struct named_id *res;
5371
5372 res = purple_request_field_list_get_data(f, i);
5373 remote_group_done(pd, res->id, res->name);
5374 }
5375
5376 remote_group_multi_cleanup(NULL, fields);
5377 }
5378
5379
remote_group_multi(struct mwResolveResult * result,struct mwPurplePluginData * pd)5380 static void remote_group_multi(struct mwResolveResult *result,
5381 struct mwPurplePluginData *pd) {
5382
5383 PurpleRequestFields *fields;
5384 PurpleRequestFieldGroup *g;
5385 PurpleRequestField *f;
5386 GList *l;
5387 const char *msgA;
5388 const char *msgB;
5389 char *msg;
5390
5391 PurpleConnection *gc = pd->gc;
5392
5393 fields = purple_request_fields_new();
5394
5395 g = purple_request_field_group_new(NULL);
5396 purple_request_fields_add_group(fields, g);
5397
5398 f = purple_request_field_list_new("group", _("Possible Matches"));
5399 purple_request_field_list_set_multi_select(f, FALSE);
5400 purple_request_field_set_required(f, TRUE);
5401
5402 for(l = result->matches; l; l = l->next) {
5403 struct mwResolveMatch *match = l->data;
5404 struct named_id *res = g_new0(struct named_id, 1);
5405
5406 res->id = g_strdup(match->id);
5407 res->name = g_strdup(match->name);
5408
5409 purple_request_field_list_add_icon(f, res->name, NULL, res);
5410 }
5411
5412 purple_request_field_group_add_field(g, f);
5413
5414 msgA = _("Notes Address Book group results");
5415 msgB = _("The identifier '%s' may possibly refer to any of the following"
5416 " Notes Address Book groups. Please select the correct group from"
5417 " the list below to add it to your buddy list.");
5418 msg = g_strdup_printf(msgB, result->name);
5419
5420 purple_request_fields(gc, _("Select Notes Address Book"),
5421 msgA, msg, fields,
5422 _("Add Group"), G_CALLBACK(remote_group_multi_cb),
5423 _("Cancel"), G_CALLBACK(remote_group_multi_cleanup),
5424 purple_connection_get_account(gc), result->name, NULL,
5425 pd);
5426
5427 g_free(msg);
5428 }
5429
5430
remote_group_resolved(struct mwServiceResolve * srvc,guint32 id,guint32 code,GList * results,gpointer b)5431 static void remote_group_resolved(struct mwServiceResolve *srvc,
5432 guint32 id, guint32 code, GList *results,
5433 gpointer b) {
5434
5435 struct mwResolveResult *res = NULL;
5436 struct mwSession *session;
5437 struct mwPurplePluginData *pd;
5438 PurpleConnection *gc;
5439
5440 session = mwService_getSession(MW_SERVICE(srvc));
5441 g_return_if_fail(session != NULL);
5442
5443 pd = mwSession_getClientData(session);
5444 g_return_if_fail(pd != NULL);
5445
5446 gc = pd->gc;
5447 g_return_if_fail(gc != NULL);
5448
5449 if(!code && results) {
5450 res = results->data;
5451
5452 if(res->matches) {
5453 remote_group_multi(res, pd);
5454 return;
5455 }
5456 }
5457
5458 if(res && res->name) {
5459 const char *msgA;
5460 const char *msgB;
5461 char *msg;
5462
5463 msgA = _("Unable to add group: group not found");
5464
5465 msgB = _("The identifier '%s' did not match any Notes Address Book"
5466 " groups in your Sametime community.");
5467 msg = g_strdup_printf(msgB, res->name);
5468
5469 purple_notify_error(gc, _("Unable to add group"), msgA, msg);
5470
5471 g_free(msg);
5472 }
5473 }
5474
5475
remote_group_action_cb(PurpleConnection * gc,const char * name)5476 static void remote_group_action_cb(PurpleConnection *gc, const char *name) {
5477 struct mwPurplePluginData *pd;
5478 struct mwServiceResolve *srvc;
5479 GList *query;
5480 enum mwResolveFlag flags;
5481 guint32 req;
5482
5483 pd = gc->proto_data;
5484 srvc = pd->srvc_resolve;
5485
5486 query = g_list_prepend(NULL, (char *) name);
5487 flags = mwResolveFlag_FIRST | mwResolveFlag_GROUPS;
5488
5489 req = mwServiceResolve_resolve(srvc, query, flags, remote_group_resolved,
5490 NULL, NULL);
5491 g_list_free(query);
5492
5493 if(req == SEARCH_ERROR) {
5494 /** @todo display error */
5495 }
5496 }
5497
5498
remote_group_action(PurplePluginAction * act)5499 static void remote_group_action(PurplePluginAction *act) {
5500 PurpleConnection *gc;
5501 const char *msgA;
5502 const char *msgB;
5503
5504 gc = act->context;
5505
5506 msgA = _("Notes Address Book Group");
5507 msgB = _("Enter the name of a Notes Address Book group in the field below"
5508 " to add the group and its members to your buddy list.");
5509
5510 purple_request_input(gc, _("Add Group"), msgA, msgB, NULL,
5511 FALSE, FALSE, NULL,
5512 _("Add"), G_CALLBACK(remote_group_action_cb),
5513 _("Cancel"), NULL,
5514 purple_connection_get_account(gc), NULL, NULL,
5515 gc);
5516 }
5517
5518
search_notify(struct mwResolveResult * result,PurpleConnection * gc)5519 static void search_notify(struct mwResolveResult *result,
5520 PurpleConnection *gc) {
5521 GList *l;
5522 const char *msgA;
5523 const char *msgB;
5524 char *msg1;
5525 char *msg2;
5526
5527 PurpleNotifySearchResults *sres;
5528 PurpleNotifySearchColumn *scol;
5529
5530 sres = purple_notify_searchresults_new();
5531
5532 scol = purple_notify_searchresults_column_new(_("User Name"));
5533 purple_notify_searchresults_column_add(sres, scol);
5534
5535 scol = purple_notify_searchresults_column_new(_("Sametime ID"));
5536 purple_notify_searchresults_column_add(sres, scol);
5537
5538 purple_notify_searchresults_button_add(sres, PURPLE_NOTIFY_BUTTON_IM,
5539 notify_im);
5540
5541 purple_notify_searchresults_button_add(sres, PURPLE_NOTIFY_BUTTON_ADD,
5542 notify_add);
5543
5544 for(l = result->matches; l; l = l->next) {
5545 struct mwResolveMatch *match = l->data;
5546 GList *row = NULL;
5547
5548 if(!match->id || !match->name)
5549 continue;
5550
5551 row = g_list_append(row, g_strdup(match->name));
5552 row = g_list_append(row, g_strdup(match->id));
5553 purple_notify_searchresults_row_add(sres, row);
5554 }
5555
5556 msgA = _("Search results for '%s'");
5557 msgB = _("The identifier '%s' may possibly refer to any of the following"
5558 " users. You may add these users to your buddy list or send them"
5559 " messages with the action buttons below.");
5560
5561 msg1 = g_strdup_printf(msgA, result->name);
5562 msg2 = g_strdup_printf(msgB, result->name);
5563
5564 purple_notify_searchresults(gc, _("Search Results"),
5565 msg1, msg2, sres, notify_close, NULL);
5566
5567 g_free(msg1);
5568 g_free(msg2);
5569 }
5570
5571
search_resolved(struct mwServiceResolve * srvc,guint32 id,guint32 code,GList * results,gpointer b)5572 static void search_resolved(struct mwServiceResolve *srvc,
5573 guint32 id, guint32 code, GList *results,
5574 gpointer b) {
5575
5576 PurpleConnection *gc = b;
5577 struct mwResolveResult *res = NULL;
5578
5579 if(results) res = results->data;
5580
5581 if(!code && res && res->matches) {
5582 search_notify(res, gc);
5583
5584 } else {
5585 const char *msgA;
5586 const char *msgB;
5587 char *msg;
5588
5589 msgA = _("No matches");
5590 msgB = _("The identifier '%s' did not match any users in your"
5591 " Sametime community.");
5592 msg = g_strdup_printf(msgB, (res && res->name) ? NSTR(res->name) : "");
5593
5594 purple_notify_error(gc, _("No Matches"), msgA, msg);
5595
5596 g_free(msg);
5597 }
5598 }
5599
5600
search_action_cb(PurpleConnection * gc,const char * name)5601 static void search_action_cb(PurpleConnection *gc, const char *name) {
5602 struct mwPurplePluginData *pd;
5603 struct mwServiceResolve *srvc;
5604 GList *query;
5605 enum mwResolveFlag flags;
5606 guint32 req;
5607
5608 pd = gc->proto_data;
5609 srvc = pd->srvc_resolve;
5610
5611 query = g_list_prepend(NULL, (char *) name);
5612 flags = mwResolveFlag_FIRST | mwResolveFlag_USERS;
5613
5614 req = mwServiceResolve_resolve(srvc, query, flags, search_resolved,
5615 gc, NULL);
5616 g_list_free(query);
5617
5618 if(req == SEARCH_ERROR) {
5619 /** @todo display error */
5620 }
5621 }
5622
5623
search_action(PurplePluginAction * act)5624 static void search_action(PurplePluginAction *act) {
5625 PurpleConnection *gc;
5626 const char *msgA;
5627 const char *msgB;
5628
5629 gc = act->context;
5630
5631 msgA = _("Search for a user");
5632 msgB = _("Enter a name or partial ID in the field below to search"
5633 " for matching users in your Sametime community.");
5634
5635 purple_request_input(gc, _("User Search"), msgA, msgB, NULL,
5636 FALSE, FALSE, NULL,
5637 _("Search"), G_CALLBACK(search_action_cb),
5638 _("Cancel"), NULL,
5639 purple_connection_get_account(gc), NULL, NULL,
5640 gc);
5641 }
5642
5643
mw_plugin_actions(PurplePlugin * plugin,gpointer context)5644 static GList *mw_plugin_actions(PurplePlugin *plugin, gpointer context) {
5645 PurplePluginAction *act;
5646 GList *l = NULL;
5647
5648 act = purple_plugin_action_new(_("Import Sametime List..."),
5649 st_import_action);
5650 l = g_list_append(l, act);
5651
5652 act = purple_plugin_action_new(_("Export Sametime List..."),
5653 st_export_action);
5654 l = g_list_append(l, act);
5655
5656 act = purple_plugin_action_new(_("Add Notes Address Book Group..."),
5657 remote_group_action);
5658 l = g_list_append(l, act);
5659
5660 act = purple_plugin_action_new(_("User Search..."),
5661 search_action);
5662 l = g_list_append(l, act);
5663
5664 return l;
5665 }
5666
5667
mw_plugin_load(PurplePlugin * plugin)5668 static gboolean mw_plugin_load(PurplePlugin *plugin) {
5669 return TRUE;
5670 }
5671
5672
mw_plugin_unload(PurplePlugin * plugin)5673 static gboolean mw_plugin_unload(PurplePlugin *plugin) {
5674 return TRUE;
5675 }
5676
5677
mw_plugin_destroy(PurplePlugin * plugin)5678 static void mw_plugin_destroy(PurplePlugin *plugin) {
5679 g_log_remove_handler(G_LOG_DOMAIN, log_handler[0]);
5680 g_log_remove_handler("meanwhile", log_handler[1]);
5681 }
5682
5683 static PurplePluginInfo mw_plugin_info =
5684 {
5685 PURPLE_PLUGIN_MAGIC,
5686 PURPLE_MAJOR_VERSION,
5687 PURPLE_MINOR_VERSION,
5688 PURPLE_PLUGIN_PROTOCOL, /**< type */
5689 NULL, /**< ui_requirement */
5690 0, /**< flags */
5691 NULL, /**< dependencies */
5692 PURPLE_PRIORITY_DEFAULT, /**< priority */
5693
5694 PLUGIN_ID, /**< id */
5695 PLUGIN_NAME, /**< name */
5696 DISPLAY_VERSION, /**< version */
5697 PLUGIN_SUMMARY, /**< summary */
5698 PLUGIN_DESC, /**< description */
5699 PLUGIN_AUTHOR, /**< author */
5700 PLUGIN_HOMEPAGE, /**< homepage */
5701
5702 mw_plugin_load, /**< load */
5703 mw_plugin_unload, /**< unload */
5704 mw_plugin_destroy, /**< destroy */
5705
5706 NULL, /**< ui_info */
5707 &mw_prpl_info, /**< extra_info */
5708 &mw_plugin_ui_info, /**< prefs_info */
5709 mw_plugin_actions,
5710
5711 /* padding */
5712 NULL,
5713 NULL,
5714 NULL,
5715 NULL
5716 };
5717
5718
mw_log_handler(const gchar * domain,GLogLevelFlags flags,const gchar * msg,gpointer data)5719 static void mw_log_handler(const gchar *domain, GLogLevelFlags flags,
5720 const gchar *msg, gpointer data) {
5721
5722 if(! (msg && *msg)) return;
5723
5724 /* handle g_log requests via purple's built-in debug logging */
5725 if(flags & G_LOG_LEVEL_ERROR) {
5726 purple_debug_error(domain, "%s\n", msg);
5727
5728 } else if(flags & G_LOG_LEVEL_WARNING) {
5729 purple_debug_warning(domain, "%s\n", msg);
5730
5731 } else {
5732 purple_debug_info(domain, "%s\n", msg);
5733 }
5734 }
5735
5736
mw_plugin_init(PurplePlugin * plugin)5737 static void mw_plugin_init(PurplePlugin *plugin) {
5738 PurpleAccountUserSplit *split;
5739 PurpleAccountOption *opt;
5740 GList *l = NULL;
5741
5742 GLogLevelFlags logflags =
5743 G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION;
5744
5745 /* set up the preferences */
5746 purple_prefs_add_none(MW_PRPL_OPT_BASE);
5747 purple_prefs_add_int(MW_PRPL_OPT_BLIST_ACTION, BLIST_CHOICE_DEFAULT);
5748
5749 /* set up account ID as user:server */
5750 split = purple_account_user_split_new(_("Server"),
5751 MW_PLUGIN_DEFAULT_HOST, ':');
5752 mw_prpl_info.user_splits = g_list_append(mw_prpl_info.user_splits, split);
5753
5754 /* remove dead preferences */
5755 purple_prefs_remove(MW_PRPL_OPT_PSYCHIC);
5756 purple_prefs_remove(MW_PRPL_OPT_SAVE_DYNAMIC);
5757
5758 /* port to connect to */
5759 opt = purple_account_option_int_new(_("Port"), MW_KEY_PORT,
5760 MW_PLUGIN_DEFAULT_PORT);
5761 l = g_list_append(l, opt);
5762
5763 { /* copy the old force login setting from prefs if it's
5764 there. Don't delete the preference, since there may be more
5765 than one account that wants to check for it. */
5766 gboolean b = FALSE;
5767 const char *label = _("Force login (ignore server redirects)");
5768
5769 if(purple_prefs_exists(MW_PRPL_OPT_FORCE_LOGIN))
5770 b = purple_prefs_get_bool(MW_PRPL_OPT_FORCE_LOGIN);
5771
5772 opt = purple_account_option_bool_new(label, MW_KEY_FORCE, b);
5773 l = g_list_append(l, opt);
5774 }
5775
5776 /* pretend to be Sametime Connect */
5777 opt = purple_account_option_bool_new(_("Hide client identity"),
5778 MW_KEY_FAKE_IT, FALSE);
5779 l = g_list_append(l, opt);
5780
5781 mw_prpl_info.protocol_options = l;
5782 l = NULL;
5783
5784 /* forward all our g_log messages to purple. Generally all the logging
5785 calls are using purple_log directly, but the g_return macros will
5786 get caught here */
5787 log_handler[0] = g_log_set_handler(G_LOG_DOMAIN, logflags,
5788 mw_log_handler, NULL);
5789
5790 /* redirect meanwhile's logging to purple's */
5791 log_handler[1] = g_log_set_handler("meanwhile", logflags,
5792 mw_log_handler, NULL);
5793 }
5794
5795
5796 PURPLE_INIT_PLUGIN(sametime, mw_plugin_init, mw_plugin_info);
5797 /* The End. */
5798
5799