comparison libpurple/protocols/null/nullprpl.c @ 17453:afee8b840d91

this patch adds nullprpl, a mock protocol plugin, to the libpurple/protocols directory Closes #811 committer: Richard Laager <rlaager@wiktel.com>
author Ryan Barrett <ryanbarrett@users.sourceforge.net>
date Wed, 30 May 2007 01:21:36 +0000
parents
children 29137f15743d
comparison
equal deleted inserted replaced
17447:1a1d9cf5cd79 17453:afee8b840d91
1 /**
2 * purple
3 *
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
7 *
8 * Nullprpl is a mock protocol plugin for Pidgin and libpurple. You can create
9 * accounts with it, sign on and off, add buddies, and send and receive IMs,
10 * all without connecting to a server!
11 *
12 * Beyond that basic functionality, nullprpl supports presence and
13 * away/available messages, offline messages, user info, typing notification,
14 * privacy allow/block lists, chat rooms, whispering, room lists, and protocol
15 * icons and emblems. Notable missing features are file transfer and account
16 * registration and authentication.
17 *
18 * Nullprpl is intended as an example of how to write a libpurple protocol
19 * plugin. It doesn't contain networking code or an event loop, but it does
20 * demonstrate how to use the libpurple API to do pretty much everything a prpl
21 * might need to do.
22 *
23 * Nullprpl is also a useful tool for hacking on Pidgin, Finch, and other
24 * libpurple clients. It's a full-featured protocol plugin, but doesn't depend
25 * on an external server, so it's a quick and easy way to exercise test new
26 * code. It also allows you to work while you're disconnected.
27 *
28 * This program is free software; you can redistribute it and/or modify
29 * it under the terms of the GNU General Public License as published by
30 * the Free Software Foundation; either version 2 of the License, or
31 * (at your option) any later version.
32 *
33 * This program is distributed in the hope that it will be useful,
34 * but WITHOUT ANY WARRANTY; without even the implied warranty of
35 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 * GNU General Public License for more details.
37 *
38 * You should have received a copy of the GNU General Public License
39 * along with this program; if not, write to the Free Software
40 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
41 */
42
43 #include <stdarg.h>
44 #include <string.h>
45 #include <time.h>
46
47 #include <glib.h>
48
49 #include "internal.h"
50 #include "config.h"
51 #include "account.h"
52 #include "accountopt.h"
53 #include "blist.h"
54 #include "cmds.h"
55 #include "conversation.h"
56 #include "connection.h"
57 #include "debug.h"
58 #include "notify.h"
59 #include "privacy.h"
60 #include "prpl.h"
61 #include "roomlist.h"
62 #include "status.h"
63 #include "util.h"
64 #include "version.h"
65
66
67 #define NULLPRPL_ID "prpl-null"
68 static PurplePlugin *_null_protocol = NULL;
69
70 #define NULL_STATUS_ONLINE "online"
71 #define NULL_STATUS_AWAY "away"
72 #define NULL_STATUS_OFFLINE "offline"
73
74 typedef void (*GcFunc)(PurpleConnection *from,
75 PurpleConnection *to,
76 gpointer userdata);
77
78 typedef struct {
79 GcFunc fn;
80 PurpleConnection *from;
81 gpointer userdata;
82 } GcFuncData;
83
84 /*
85 * stores offline messages that haven't been delivered yet. maps username
86 * (char *) to GList * of GOfflineMessages. initialized in nullprpl_init.
87 */
88 GHashTable* goffline_messages = NULL;
89
90 typedef struct {
91 char *from;
92 char *message;
93 time_t mtime;
94 PurpleMessageFlags flags;
95 } GOfflineMessage;
96
97 /*
98 * helpers
99 */
100 static PurpleConnection *get_nullprpl_gc(const char *username) {
101 PurpleAccount *acct = purple_accounts_find(username, NULLPRPL_ID);
102 if (acct && purple_account_is_connected(acct))
103 return acct->gc;
104 else
105 return NULL;
106 }
107
108 static void call_if_nullprpl(gpointer data, gpointer userdata) {
109 PurpleConnection *gc = (PurpleConnection *)(data);
110 GcFuncData *gcfdata = (GcFuncData *)userdata;
111
112 if (!strcmp(gc->account->protocol_id, NULLPRPL_ID))
113 gcfdata->fn(gcfdata->from, gc, gcfdata->userdata);
114 }
115
116 static void foreach_nullprpl_gc(GcFunc fn, PurpleConnection *from,
117 gpointer userdata) {
118 GcFuncData gcfdata = { .fn = fn,
119 .from = from,
120 .userdata = userdata };
121 g_list_foreach(purple_connections_get_all(), call_if_nullprpl,
122 (gpointer)&gcfdata);
123 }
124
125
126 typedef void(*ChatFunc)(PurpleConvChat *from, PurpleConvChat *to,
127 int id, const char *room, gpointer userdata);
128
129 typedef struct {
130 ChatFunc fn;
131 PurpleConvChat *from_chat;
132 gpointer userdata;
133 } ChatFuncData;
134
135 static void call_chat_func(gpointer data, gpointer userdata) {
136 PurpleConnection *to = (PurpleConnection *)data;
137 ChatFuncData *cfdata = (ChatFuncData *)userdata;
138
139 int id = cfdata->from_chat->id;
140 PurpleConversation *conv = purple_find_chat(to, id);
141 if (conv) {
142 PurpleConvChat *chat = purple_conversation_get_chat_data(conv);
143 cfdata->fn(cfdata->from_chat, chat, id, conv->name, cfdata->userdata);
144 }
145 }
146
147 static void foreach_gc_in_chat(ChatFunc fn, PurpleConnection *from,
148 int id, gpointer userdata) {
149 PurpleConversation *conv = purple_find_chat(from, id);
150 ChatFuncData cfdata = { .fn = fn,
151 .from_chat = purple_conversation_get_chat_data(conv),
152 .userdata = userdata };
153
154 g_list_foreach(purple_connections_get_all(), call_chat_func,
155 (gpointer)&cfdata);
156 }
157
158
159 static void discover_status(PurpleConnection *from, PurpleConnection *to,
160 gpointer userdata) {
161 char *from_username = from->account->username;
162 char *to_username = to->account->username;
163
164 if (purple_find_buddy(from->account, to_username)) {
165 PurpleStatus *status = purple_account_get_active_status(to->account);
166 const char *status_id = purple_status_get_id(status);
167 const char *message = purple_status_get_attr_string(status, "message");
168
169 if (!strcmp(status_id, NULL_STATUS_ONLINE) ||
170 !strcmp(status_id, NULL_STATUS_AWAY) ||
171 !strcmp(status_id, NULL_STATUS_OFFLINE)) {
172 purple_debug_info("nullprpl", "%s sees that %s is %s: %s\n",
173 from_username, to_username, status_id, message);
174 purple_prpl_got_user_status(from->account, to_username, status_id,
175 (message) ? "message" : NULL, message, NULL);
176 } else {
177 purple_debug_error("nullprpl",
178 "%s's buddy %s has an unknown status: %s, %s",
179 from_username, to_username, status_id, message);
180 }
181 }
182 }
183
184 static void report_status_change(PurpleConnection *from, PurpleConnection *to,
185 gpointer userdata) {
186 purple_debug_info("nullprpl", "notifying %s that %s changed status\n",
187 to->account->username, from->account->username);
188 discover_status(to, from, NULL);
189 }
190
191
192 /*
193 * UI callbacks
194 */
195 static void nullprpl_input_user_info(PurplePluginAction *action)
196 {
197 PurpleConnection *gc = (PurpleConnection *)action->context;
198 PurpleAccount *acct = purple_connection_get_account(gc);
199 purple_debug_info("nullprpl", "showing 'Set User Info' dialog for %s\n",
200 acct->username);
201
202 purple_account_request_change_user_info(acct);
203 }
204
205 /* this is set to the actions member of the PurplePluginInfo struct at the
206 * bottom.
207 */
208 static GList *nullprpl_actions(PurplePlugin *plugin, gpointer context)
209 {
210 PurplePluginAction *action = purple_plugin_action_new(
211 _("Set User Info..."), nullprpl_input_user_info);
212 return g_list_append(NULL, action);
213 }
214
215
216 /*
217 * prpl functions
218 */
219 static const char *nullprpl_list_icon(PurpleAccount *acct, PurpleBuddy *buddy)
220 {
221 /* shamelessly steal (er, borrow) the meanwhile protocol icon. it's cute! */
222 return "meanwhile";
223 }
224
225 static const char *nullprpl_list_emblem(PurpleBuddy *buddy)
226 {
227 const char* emblem;
228
229 if (get_nullprpl_gc(buddy->name)) {
230 PurplePresence *presence = purple_buddy_get_presence(buddy);
231 PurpleStatus *status = purple_presence_get_active_status(presence);
232 emblem = purple_status_get_name(status);
233 } else {
234 emblem = "offline";
235 }
236
237 purple_debug_info("nullprpl", "using emblem %s for %s's buddy %s\n",
238 emblem, buddy->account->username, buddy->name);
239 return emblem;
240 }
241
242 static char *nullprpl_status_text(PurpleBuddy *buddy) {
243 purple_debug_info("nullprpl", "getting %s's status text for %s\n",
244 buddy->name, buddy->account->username);
245
246 if (purple_find_buddy(buddy->account, buddy->name)) {
247 PurplePresence *presence = purple_buddy_get_presence(buddy);
248 PurpleStatus *status = purple_presence_get_active_status(presence);
249 const char *name = purple_status_get_name(status);
250 const char *message = purple_status_get_attr_string(status, "message");
251
252 char *text;
253 if (message && strlen(message) > 0)
254 text = g_strdup_printf("%s: %s", name, message);
255 else
256 text = g_strdup(name);
257
258 purple_debug_info("nullprpl", "%s's status text is %s\n", buddy->name, text);
259 return text;
260
261 } else {
262 purple_debug_info("nullprpl", "...but %s is not logged in\n", buddy->name);
263 return "Not logged in";
264 }
265 }
266
267 static void nullprpl_tooltip_text(PurpleBuddy *buddy,
268 PurpleNotifyUserInfo *info,
269 gboolean full) {
270 PurpleConnection *gc = get_nullprpl_gc(buddy->name);
271
272 if (gc) {
273 /* they're logged in */
274 PurplePresence *presence = purple_buddy_get_presence(buddy);
275 PurpleStatus *status = purple_presence_get_active_status(presence);
276 const char *msg = nullprpl_status_text(buddy);
277 purple_notify_user_info_add_pair(info, purple_status_get_name(status),
278 msg);
279
280 if (full) {
281 const char *user_info = purple_account_get_user_info(gc->account);
282 if (user_info)
283 purple_notify_user_info_add_pair(info, _("User info"), user_info);
284 }
285
286 } else {
287 /* they're not logged in */
288 purple_notify_user_info_add_pair(info, _("User info"), _("not logged in"));
289 }
290
291 purple_debug_info("nullprpl", "showing %s tooltip for %s\n",
292 (full) ? "full" : "short", buddy->name);
293 }
294
295 static GList *nullprpl_status_types(PurpleAccount *acct)
296 {
297 GList *types = NULL;
298 PurpleStatusType *type;
299
300 purple_debug_info("nullprpl", "returning status types for %s: %s, %s, %s\n",
301 acct->username,
302 NULL_STATUS_ONLINE, NULL_STATUS_AWAY, NULL_STATUS_OFFLINE);
303
304 type = purple_status_type_new(PURPLE_STATUS_AVAILABLE, NULL_STATUS_ONLINE,
305 NULL_STATUS_ONLINE, TRUE);
306 purple_status_type_add_attr(type, "message", _("Online"),
307 purple_value_new(PURPLE_TYPE_STRING));
308 types = g_list_append(types, type);
309
310 type = purple_status_type_new(PURPLE_STATUS_AWAY, NULL_STATUS_AWAY,
311 NULL_STATUS_AWAY, TRUE);
312 purple_status_type_add_attr(type, "message", _("Away"),
313 purple_value_new(PURPLE_TYPE_STRING));
314 types = g_list_append(types, type);
315
316 type = purple_status_type_new(PURPLE_STATUS_OFFLINE, NULL_STATUS_OFFLINE,
317 NULL_STATUS_OFFLINE, TRUE);
318 purple_status_type_add_attr(type, "message", _("Offline"),
319 purple_value_new(PURPLE_TYPE_STRING));
320 types = g_list_append(types, type);
321
322 return types;
323 }
324
325 static void blist_example_menu_item(PurpleBlistNode *node, gpointer userdata) {
326 purple_debug_info("nullprpl", "example menu item clicked on user",
327 ((PurpleBuddy *)node)->name);
328
329 purple_notify_info(NULL, /* plugin handle or PurpleConnection */
330 _("Primary title"),
331 _("Secondary title"),
332 _("This is the callback for the nullprpl menu item."));
333 }
334
335 static GList *nullprpl_blist_node_menu(PurpleBlistNode *node) {
336 purple_debug_info("nullprpl", "providing buddy list context menu item\n");
337
338 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
339 PurpleMenuAction *action = purple_menu_action_new(
340 _("Nullprpl example menu item"),
341 PURPLE_CALLBACK(blist_example_menu_item),
342 NULL, /* userdata passed to the callback */
343 NULL); /* child menu items */
344 return g_list_append(NULL, action);
345 } else {
346 return NULL;
347 }
348 }
349
350 static GList *nullprpl_chat_info(PurpleConnection *gc) {
351 struct proto_chat_entry *pce; /* defined in prpl.h */
352
353 purple_debug_info("nullprpl", "returning chat setting 'room'\n");
354
355 pce = g_new0(struct proto_chat_entry, 1);
356 pce->label = _(_("Chat _room"));
357 pce->identifier = "room";
358 pce->required = TRUE;
359
360 return g_list_append(NULL, pce);
361 }
362
363 static GHashTable *nullprpl_chat_info_defaults(PurpleConnection *gc,
364 const char *room) {
365 GHashTable *defaults;
366
367 purple_debug_info("nullprpl", "returning chat default setting "
368 "'room' = 'default'\n");
369
370 defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
371 g_hash_table_insert(defaults, "room", g_strdup("default"));
372 return defaults;
373 }
374
375 static void nullprpl_login(PurpleAccount *acct)
376 {
377 PurpleConnection *gc = purple_account_get_connection(acct);
378 GList *offline_messages;
379
380 purple_debug_info("nullprpl", "logging in %s\n", acct->username);
381
382 purple_connection_update_progress(gc, _("Connecting"),
383 0, /* which connection step this is */
384 2); /* total number of steps */
385
386 purple_connection_update_progress(gc, _("Connected"),
387 1, /* which connection step this is */
388 2); /* total number of steps */
389 purple_connection_set_state(gc, PURPLE_CONNECTED);
390
391 /* tell purple about everyone on our buddy list who's connected */
392 foreach_nullprpl_gc(discover_status, gc, NULL);
393
394 /* notify other nullprpl accounts */
395 foreach_nullprpl_gc(report_status_change, gc, NULL);
396
397 /* fetch stored offline messages */
398 purple_debug_info("nullprpl", "checking for offline messages for %s\n",
399 acct->username);
400 offline_messages = g_hash_table_lookup(goffline_messages, acct->username);
401 while (offline_messages) {
402 GOfflineMessage *message = (GOfflineMessage *)offline_messages->data;
403 purple_debug_info("nullprpl", "delivering offline message to %s: %s\n",
404 acct->username, message->message);
405 serv_got_im(gc, message->from, message->message, message->flags,
406 message->mtime);
407 offline_messages = g_list_next(offline_messages);
408
409 g_free(message->from);
410 g_free(message->message);
411 g_free(message);
412 }
413
414 g_list_free(offline_messages);
415 g_hash_table_remove(goffline_messages, &acct->username);
416 }
417
418 static void nullprpl_close(PurpleConnection *gc)
419 {
420 /* notify other nullprpl accounts */
421 foreach_nullprpl_gc(report_status_change, gc, NULL);
422 }
423
424 static int nullprpl_send_im(PurpleConnection *gc, const char *who,
425 const char *message, PurpleMessageFlags flags)
426 {
427 const char *from_username = gc->account->username;
428 PurpleMessageFlags receive_flags = ((flags & ~PURPLE_MESSAGE_SEND)
429 | PURPLE_MESSAGE_RECV);
430 PurpleAccount *to_acct = purple_accounts_find(who, NULLPRPL_ID);
431 PurpleConnection *to;
432
433 purple_debug_info("nullprpl", "sending message from %s to %s: %s\n",
434 from_username, who, message);
435
436 /* is the sender blocked by the recipient's privacy settings? */
437 if (!purple_privacy_check(to_acct, gc->account->username)) {
438 char *msg = g_strdup_printf(
439 _("Your message was blocked by %s's privacy settings."), who);
440 purple_debug_info("nullprpl",
441 "discarding; %s is blocked by %s's privacy settings\n",
442 from_username, who);
443 purple_conv_present_error(who, gc->account, msg);
444 g_free(msg);
445 return 0;
446 }
447
448 /* is the recipient online? */
449 to = get_nullprpl_gc(who);
450 if (to) { /* yes, send */
451 serv_got_im(to, from_username, message, receive_flags, time(NULL));
452
453 } else { /* nope, store as an offline message */
454 GOfflineMessage *offline_message;
455 GList *messages;
456
457 purple_debug_info("nullprpl",
458 "%s is offline, sending as offline message\n", who);
459 offline_message = g_new0(GOfflineMessage, 1);
460 offline_message->from = g_strdup(from_username);
461 offline_message->message = g_strdup(message);
462 offline_message->mtime = time(NULL);
463 offline_message->flags = receive_flags;
464
465 messages = g_hash_table_lookup(goffline_messages, who);
466 messages = g_list_append(messages, offline_message);
467 g_hash_table_insert(goffline_messages, g_strdup(who), messages);
468 }
469
470 return 1;
471 }
472
473 static void nullprpl_set_info(PurpleConnection *gc, const char *info) {
474 purple_debug_info("nullprpl", "setting %s's user info to %s\n",
475 gc->account->username, info);
476 }
477
478 static char *typing_state_to_string(PurpleTypingState typing) {
479 switch (typing) {
480 case PURPLE_NOT_TYPING: return "is not typing";
481 case PURPLE_TYPING: return "is typing";
482 case PURPLE_TYPED: return "stopped typing momentarily";
483 default: return "unknown typing state";
484 }
485 }
486
487 static void notify_typing(PurpleConnection *from, PurpleConnection *to,
488 gpointer typing) {
489 char *from_username = from->account->username;
490 char *action = typing_state_to_string((PurpleTypingState)typing);
491 purple_debug_info("nullprpl", "notifying %s that %s %s\n",
492 to->account->username, from_username, action);
493
494 serv_got_typing(to,
495 from_username,
496 0, /* if non-zero, a timeout in seconds after which to
497 * reset the typing status to PURPLE_NOT_TYPING */
498 (PurpleTypingState)typing);
499 }
500
501 static unsigned int nullprpl_send_typing(PurpleConnection *gc, const char *name,
502 PurpleTypingState typing) {
503 purple_debug_info("nullprpl", "%s %s\n", gc->account->username,
504 typing_state_to_string(typing));
505 foreach_nullprpl_gc(notify_typing, gc, (gpointer)typing);
506 return 0;
507 }
508
509 static void nullprpl_get_info(PurpleConnection *gc, const char *username) {
510 const char *body;
511 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
512 PurpleAccount *acct;
513
514 purple_debug_info("nullprpl", "Fetching %s's user info for %s\n", username,
515 gc->account->username);
516
517 if (!get_nullprpl_gc(username)) {
518 char *msg = g_strdup_printf(_("%s is not logged in."), username);
519 purple_notify_error(gc, _("User Info"), _("User info not available. "), msg);
520 g_free(msg);
521 }
522
523 acct = purple_accounts_find(username, NULLPRPL_ID);
524 if (acct)
525 body = purple_account_get_user_info(acct);
526 else
527 body = _("No user info.");
528 purple_notify_user_info_add_pair(info, "Info", body);
529
530 /* show a buddy's user info in a nice dialog box */
531 purple_notify_userinfo(gc, /* connection the buddy info came through */
532 username, /* buddy's username */
533 info, /* body */
534 NULL, /* callback called when dialog closed */
535 NULL); /* userdata for callback */
536 }
537
538 static void nullprpl_set_status(PurpleAccount *acct, PurpleStatus *status) {
539 const char *msg = purple_status_get_attr_string(status, "message");
540 purple_debug_info("nullprpl", "setting %s's status to %s: %s\n",
541 acct->username, purple_status_get_name(status), msg);
542
543 foreach_nullprpl_gc(report_status_change, get_nullprpl_gc(acct->username),
544 NULL);
545 }
546
547 static void nullprpl_set_idle(PurpleConnection *gc, int idletime) {
548 purple_debug_info("nullprpl",
549 "purple reports that %s has been idle for %d seconds\n",
550 gc->account->username, idletime);
551 }
552
553 static void nullprpl_change_passwd(PurpleConnection *gc, const char *old_pass,
554 const char *new_pass) {
555 purple_debug_info("nullprpl", "%s wants to change their password\n",
556 gc->account->username);
557 }
558
559 static void nullprpl_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
560 PurpleGroup *group)
561 {
562 char *username = gc->account->username;
563 PurpleConnection *buddy_gc = get_nullprpl_gc(buddy->name);
564
565 purple_debug_info("nullprpl", "adding %s to %s's buddy list\n", buddy->name,
566 username);
567
568 if (buddy_gc) {
569 PurpleAccount *buddy_acct = buddy_gc->account;
570
571 discover_status(gc, buddy_gc, NULL);
572
573 if (purple_find_buddy(buddy_acct, username)) {
574 purple_debug_info("nullprpl", "%s is already on %s's buddy list\n",
575 username, buddy->name);
576 } else {
577 purple_debug_info("nullprpl", "asking %s if they want to add %s\n",
578 buddy->name, username);
579 purple_account_request_add(buddy_acct,
580 username,
581 NULL, /* local account id (rarely used) */
582 NULL, /* alias */
583 NULL); /* message */
584 }
585 }
586 }
587
588 static void nullprpl_add_buddies(PurpleConnection *gc, GList *buddies,
589 GList *groups) {
590 GList *buddy = buddies;
591 GList *group = groups;
592
593 purple_debug_info("nullprpl", "adding multiple buddies\n");
594
595 while (buddy && group) {
596 nullprpl_add_buddy(gc, (PurpleBuddy *)buddy->data, (PurpleGroup *)group->data);
597 buddy = g_list_next(buddy);
598 group = g_list_next(group);
599 }
600 }
601
602 static void nullprpl_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
603 PurpleGroup *group)
604 {
605 purple_debug_info("nullprpl", "removing %s from %s's buddy list\n",
606 buddy->name, gc->account->username);
607 }
608
609 static void nullprpl_remove_buddies(PurpleConnection *gc, GList *buddies,
610 GList *groups) {
611 GList *buddy = buddies;
612 GList *group = groups;
613
614 purple_debug_info("nullprpl", "removing multiple buddies\n");
615
616 while (buddy && group) {
617 nullprpl_remove_buddy(gc, (PurpleBuddy *)buddy->data,
618 (PurpleGroup *)group->data);
619 buddy = g_list_next(buddy);
620 group = g_list_next(group);
621 }
622 }
623
624 /*
625 * nullprpl uses purple's local whitelist and blacklist, stored in blist.xml, as
626 * its authoritative privacy settings, and uses purple's logic (specifically
627 * purple_privacy_check(), from privacy.h), to determine whether messages are
628 * allowed or blocked.
629 */
630 static void nullprpl_add_permit(PurpleConnection *gc, const char *name) {
631 purple_debug_info("nullprpl", "%s adds %s to their allowed list\n",
632 gc->account->username, name);
633 }
634
635 static void nullprpl_add_deny(PurpleConnection *gc, const char *name) {
636 purple_debug_info("nullprpl", "%s adds %s to their blocked list\n",
637 gc->account->username, name);
638 }
639
640 static void nullprpl_rem_permit(PurpleConnection *gc, const char *name) {
641 purple_debug_info("nullprpl", "%s removes %s from their allowed list\n",
642 gc->account->username, name);
643 }
644
645 static void nullprpl_rem_deny(PurpleConnection *gc, const char *name) {
646 purple_debug_info("nullprpl", "%s removes %s from their blocked list\n",
647 gc->account->username, name);
648 }
649
650 static void nullprpl_set_permit_deny(PurpleConnection *gc) {
651 /* this is for synchronizing the local black/whitelist with the server.
652 * for nullprpl, it's a noop.
653 */
654 }
655
656 static void joined_chat(PurpleConvChat *from, PurpleConvChat *to,
657 int id, const char *room, gpointer userdata) {
658 /* tell their chat window that we joined */
659 purple_debug_info("nullprpl", "%s sees that %s joined chat room %s\n",
660 to->nick, from->nick, room);
661 purple_conv_chat_add_user(to,
662 from->nick,
663 NULL, /* user-provided join message, IRC style */
664 PURPLE_CBFLAGS_NONE,
665 TRUE); /* show a join message */
666
667 if (from != to) {
668 /* add them to our chat window */
669 purple_debug_info("nullprpl", "%s sees that %s is in chat room %s\n",
670 from->nick, to->nick, room);
671 purple_conv_chat_add_user(from,
672 to->nick,
673 NULL, /* user-provided join message, IRC style */
674 PURPLE_CBFLAGS_NONE,
675 FALSE); /* show a join message */
676 }
677 }
678
679 static void nullprpl_join_chat(PurpleConnection *gc, GHashTable *components) {
680 char *username = gc->account->username;
681 char *room = g_hash_table_lookup(components, "room");
682 int chat_id = g_str_hash(room);
683 purple_debug_info("nullprpl", "%s is joining chat room %s\n", username, room);
684
685 if (!purple_find_chat(gc, chat_id)) {
686 serv_got_joined_chat(gc, chat_id, room);
687
688 /* tell everyone that we joined, and add them if they're already there */
689 foreach_gc_in_chat(joined_chat, gc, chat_id, NULL);
690 } else {
691 purple_debug_info("nullprpl", "%s is already in chat room %s\n", username,
692 room);
693 purple_notify_info(gc,
694 _("Join chat"),
695 _("Join chat"),
696 g_strdup_printf("%s is already in chat room %s.",
697 username, room));
698 }
699 }
700
701 static void nullprpl_reject_chat(PurpleConnection *gc, GHashTable *components) {
702 char *invited_by = g_hash_table_lookup(components, "invited_by");
703 char *room = g_hash_table_lookup(components, "room");
704 char *username = gc->account->username;
705 PurpleConnection *invited_by_gc = get_nullprpl_gc(invited_by);
706 char *message = g_strdup_printf(
707 "%s %s %s.",
708 username,
709 _("has rejected your invitation to join the chat room"),
710 room);
711
712 purple_debug_info("nullprpl",
713 "%s has rejected %s's invitation to join chat room %s\n",
714 username, invited_by, room);
715
716 purple_notify_info(invited_by_gc,
717 _("Chat invitation rejected"),
718 _("Chat invitation rejected"),
719 message);
720 }
721
722 static char *nullprpl_get_chat_name(GHashTable *components) {
723 char *room = g_hash_table_lookup(components, "room");
724 purple_debug_info("nullprpl", "reporting chat room name '%s'\n", room);
725 return room;
726 }
727
728 static void nullprpl_chat_invite(PurpleConnection *gc, int id,
729 const char *message, const char *who) {
730 char *username = gc->account->username;
731 PurpleConversation *conv = purple_find_chat(gc, id);
732 char *room = conv->name;
733 PurpleAccount *to_acct = purple_accounts_find(who, NULLPRPL_ID);
734
735 purple_debug_info("nullprpl", "%s is inviting %s to join chat room %s\n",
736 username, who, room);
737
738 if (to_acct) {
739 PurpleConversation *to_conv = purple_find_chat(to_acct->gc, id);
740 if (to_conv) {
741 purple_debug_info("nullprpl",
742 "%s is already in chat room %s; "
743 "ignoring invitation from %s\n",
744 who, room, username);
745 purple_notify_info(gc,
746 _("Chat invitation"),
747 _("Chat invitation"),
748 g_strdup_printf("%s is already in chat room %s.",
749 who, room));
750 } else {
751 GHashTable *components;
752 components = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, free);
753 g_hash_table_replace(components, "room", g_strdup(room));
754 g_hash_table_replace(components, "invited_by", g_strdup(username));
755 serv_got_chat_invite(to_acct->gc, room, username, message, components);
756 }
757 }
758 }
759
760 static void left_chat_room(PurpleConvChat *from, PurpleConvChat *to,
761 int id, const char *room, gpointer userdata) {
762 if (from != to) {
763 /* tell their chat window that we left */
764 purple_debug_info("nullprpl", "%s sees that %s left chat room %s\n",
765 to->nick, from->nick, room);
766 purple_conv_chat_remove_user(to,
767 from->nick,
768 NULL); /* user-provided message, IRC style */
769 }
770 }
771
772 static void nullprpl_chat_leave(PurpleConnection *gc, int id) {
773 PurpleConversation *conv = purple_find_chat(gc, id);
774 purple_debug_info("nullprpl", "%s is leaving chat room %s\n",
775 gc->account->username, conv->name);
776
777 /* tell everyone that we left */
778 foreach_gc_in_chat(left_chat_room, gc, id, NULL);
779 }
780
781 static PurpleCmdRet send_whisper(PurpleConversation *conv, const gchar *cmd,
782 gchar **args, gchar **error, void *userdata) {
783 const char *to_username;
784 const char *message;
785 const char *from_username;
786 PurpleConvChat *chat;
787 PurpleConvChatBuddy *chat_buddy;
788 PurpleConnection *to;
789
790 /* parse args */
791 to_username = args[0];
792 message = args[1];
793
794 if (!to_username || strlen(to_username) == 0) {
795 *error = g_strdup(_("Whisper is missing recipient."));
796 return PURPLE_CMD_RET_FAILED;
797 } else if (!message || strlen(message) == 0) {
798 *error = g_strdup(_("Whisper is missing message."));
799 return PURPLE_CMD_RET_FAILED;
800 }
801
802 from_username = conv->account->username;
803 purple_debug_info("nullprpl", "%s whispers to %s in chat room %s: %s\n",
804 from_username, to_username, conv->name, message);
805
806 chat = purple_conversation_get_chat_data(conv);
807 chat_buddy = purple_conv_chat_cb_find(chat, to_username);
808 to = get_nullprpl_gc(to_username);
809
810 if (!chat_buddy) {
811 /* this will be freed by the caller */
812 *error = g_strdup_printf(_("%s is not logged in."), to_username);
813 return PURPLE_CMD_RET_FAILED;
814 } else if (!to) {
815 *error = g_strdup_printf(_("%s is not in this chat room."), to_username);
816 return PURPLE_CMD_RET_FAILED;
817 } else {
818 /* write the whisper in the sender's chat window */
819 char *message_to = g_strdup_printf("%s (to %s)", message, to_username);
820 purple_conv_chat_write(chat, from_username, message_to,
821 PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_WHISPER,
822 time(NULL));
823 g_free(message_to);
824
825 /* send the whisper */
826 serv_chat_whisper(to, chat->id, from_username, message);
827
828 return PURPLE_CMD_RET_OK;
829 }
830 }
831
832 static void nullprpl_chat_whisper(PurpleConnection *gc, int id, const char *who,
833 const char *message) {
834 char *username = gc->account->username;
835 PurpleConversation *conv = purple_find_chat(gc, id);
836 purple_debug_info("nullprpl",
837 "%s receives whisper from %s in chat room %s: %s\n",
838 username, who, conv->name, message);
839
840 /* receive whisper on recipient's account */
841 serv_got_chat_in(gc, id, who, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_WHISPER,
842 message, time(NULL));
843 }
844
845 static void receive_chat_message(PurpleConvChat *from, PurpleConvChat *to,
846 int id, const char *room, gpointer userdata) {
847 const char *message = (const char *)userdata;
848 PurpleConnection *to_gc = get_nullprpl_gc(to->nick);
849
850 purple_debug_info("nullprpl",
851 "%s receives message from %s in chat room %s: %s\n",
852 to->nick, from->nick, room, message);
853 serv_got_chat_in(to_gc, id, from->nick, PURPLE_MESSAGE_RECV, message,
854 time(NULL));
855 }
856
857 static int nullprpl_chat_send(PurpleConnection *gc, int id, const char *message,
858 PurpleMessageFlags flags) {
859 char *username = gc->account->username;
860 PurpleConversation *conv = purple_find_chat(gc, id);
861
862 if (conv) {
863 purple_debug_info("nullprpl",
864 "%s is sending message to chat room %s: %s\n", username,
865 conv->name, message);
866
867 /* send message to everyone in the chat room */
868 foreach_gc_in_chat(receive_chat_message, gc, id, (gpointer)message);
869 return 0;
870 } else {
871 purple_debug_info("nullprpl",
872 "tried to send message from %s to chat room #%d: %s\n"
873 "but couldn't find chat room",
874 username, id, message);
875 return -1;
876 }
877 }
878
879 static void nullprpl_register_user(PurpleAccount *acct) {
880 purple_debug_info("nullprpl", "registering account for %s\n",
881 acct->username);
882 }
883
884 static void nullprpl_get_cb_info(PurpleConnection *gc, int id, const char *who) {
885 PurpleConversation *conv = purple_find_chat(gc, id);
886 purple_debug_info("nullprpl",
887 "retrieving %s's info for %s in chat room %s\n", who,
888 gc->account->username, conv->name);
889
890 nullprpl_get_info(gc, who);
891 }
892
893 static void nullprpl_alias_buddy(PurpleConnection *gc, const char *who,
894 const char *alias) {
895 purple_debug_info("nullprpl", "%s sets %'s alias to %s\n",
896 gc->account->username, who, alias);
897 }
898
899 static void nullprpl_group_buddy(PurpleConnection *gc, const char *who,
900 const char *old_group,
901 const char *new_group) {
902 purple_debug_info("nullprpl", "%s has moved %s from group %s to group %s\n",
903 who, old_group, new_group);
904 }
905
906 static void nullprpl_rename_group(PurpleConnection *gc, const char *old_name,
907 PurpleGroup *group, GList *moved_buddies) {
908 purple_debug_info("nullprpl", "%s has renamed group %s to %s\n",
909 gc->account->username, old_name, group->name);
910 }
911
912 static void nullprpl_convo_closed(PurpleConnection *gc, const char *who) {
913 purple_debug_info("nullprpl", "%s's conversation with %s was closed\n",
914 gc->account->username, who);
915 }
916
917 /* normalize a username (e.g. remove whitespace, add default domain, etc.)
918 * for nullprpl, this is a noop.
919 */
920 static const char *nullprpl_normalize(const PurpleAccount *acct,
921 const char *input) {
922 return NULL;
923 }
924
925 static void nullprpl_set_buddy_icon(PurpleConnection *gc,
926 PurpleStoredImage *img) {
927 purple_debug_info("nullprpl", "setting %s's buddy icon to %s\n",
928 gc->account->username, purple_imgstore_get_filename(img));
929 }
930
931 static void nullprpl_remove_group(PurpleConnection *gc, PurpleGroup *group) {
932 purple_debug_info("nullprpl", "%s has removed group %s\n",
933 gc->account->username, group->name);
934 }
935
936
937 static void set_chat_topic_fn(PurpleConvChat *from, PurpleConvChat *to,
938 int id, const char *room, gpointer userdata) {
939 const char *topic = (const char *)userdata;
940 const char *username = from->conv->account->username;
941 char *msg;
942
943 purple_conv_chat_set_topic(to, username, topic);
944
945 if (topic && strlen(topic) > 0)
946 msg = g_strdup_printf(_("%s sets topic to: %s"), username, topic);
947 else
948 msg = g_strdup_printf(_("%s clears topic"), username);
949
950 purple_conv_chat_write(to, username, msg,
951 PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG,
952 time(NULL));
953 g_free(msg);
954 }
955
956 static void nullprpl_set_chat_topic(PurpleConnection *gc, int id,
957 const char *topic) {
958 PurpleConversation *conv = purple_find_chat(gc, id);
959 PurpleConvChat *chat = purple_conversation_get_chat_data(conv);
960 const char *last_topic;
961
962 if (!chat)
963 return;
964
965 purple_debug_info("nullprpl", "%s sets topic of chat room '%s' to '%s'\n",
966 gc->account->username, conv->name, topic);
967
968 last_topic = purple_conv_chat_get_topic(chat);
969 if ((!topic && !last_topic) ||
970 (topic && last_topic && !strcmp(topic, last_topic)))
971 return; /* topic is unchanged, this is a noop */
972
973 foreach_gc_in_chat(set_chat_topic_fn, gc, id, (gpointer)topic);
974 }
975
976 static gboolean nullprpl_finish_get_roomlist(gpointer roomlist) {
977 purple_roomlist_set_in_progress((PurpleRoomlist *)roomlist, FALSE);
978 return FALSE;
979 }
980
981 static PurpleRoomlist *nullprpl_roomlist_get_list(PurpleConnection *gc) {
982 char *username = gc->account->username;
983 PurpleRoomlist *roomlist = purple_roomlist_new(gc->account);
984 GList *fields = NULL;
985 PurpleRoomlistField *field;
986 GList *chats;
987 GList *seen_ids = NULL;
988
989 purple_debug_info("nullprpl", "%s asks for room list; returning:\n", username);
990
991 /* set up the room list */
992 field = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "room",
993 "room", TRUE /* hidden */);
994 fields = g_list_append(fields, field);
995
996 field = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_INT, "Id", "Id", FALSE);
997 fields = g_list_append(fields, field);
998
999 purple_roomlist_set_fields(roomlist, fields);
1000
1001 /* add each chat room. the chat ids are cached in seen_ids so that each room
1002 * is only returned once, even if multiple users are in it. */
1003 for (chats = purple_get_chats(); chats; chats = g_list_next(chats)) {
1004 PurpleConversation *conv = (PurpleConversation *)chats->data;
1005 PurpleRoomlistRoom *room;
1006 char *name = conv->name;
1007 int id = purple_conversation_get_chat_data(conv)->id;
1008
1009 /* have we already added this room? */
1010 if (g_list_find_custom(seen_ids, name, (GCompareFunc)strcmp))
1011 continue; /* yes! try the next one. */
1012
1013 seen_ids = g_list_append(seen_ids, name); /* no, it's new. */
1014 purple_debug_info("nullprpl", "%s (%d), ", name, id);
1015
1016 room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, name, NULL);
1017 purple_roomlist_room_add_field(roomlist, room, name);
1018 purple_roomlist_room_add_field(roomlist, room, &id);
1019 purple_roomlist_room_add(roomlist, room);
1020 }
1021
1022 purple_timeout_add(1 /* ms */, nullprpl_finish_get_roomlist, roomlist);
1023 return roomlist;
1024 }
1025
1026 static void nullprpl_roomlist_cancel(PurpleRoomlist *list) {
1027 purple_debug_info("nullprpl", "%s asked to cancel room list request\n",
1028 list->account->username);
1029 }
1030
1031 static void nullprpl_roomlist_expand_category(PurpleRoomlist *list,
1032 PurpleRoomlistRoom *category) {
1033 purple_debug_info("nullprpl", "%s asked to expand room list category %s\n",
1034 list->account->username, category->name);
1035 }
1036
1037 /* nullprpl doesn't support file transfer...yet... */
1038 static gboolean nullprpl_can_receive_file(PurpleConnection *gc,
1039 const char *who) {
1040 return FALSE;
1041 }
1042
1043 static gboolean nullprpl_offline_message(const PurpleBuddy *buddy) {
1044 purple_debug_info("nullprpl",
1045 "reporting that offline messages are supported for %s\n",
1046 buddy->name);
1047 return TRUE;
1048 }
1049
1050
1051 /*
1052 * prpl stuff. see prpl.h for more information.
1053 */
1054
1055 static PurplePluginProtocolInfo prpl_info =
1056 {
1057 OPT_PROTO_NO_PASSWORD | OPT_PROTO_CHAT_TOPIC, /* options */
1058 NULL, /* user_splits, initialized in nullprpl_init() */
1059 NULL, /* protocol_options, initialized in nullprpl_init() */
1060 { /* icon_spec, a PurpleBuddyIconSpec */
1061 "png,jpg,gif", /* format */
1062 0, /* min_width */
1063 0, /* min_height */
1064 128, /* max_width */
1065 128, /* max_height */
1066 10000, /* max_filesize */
1067 PURPLE_ICON_SCALE_DISPLAY, /* scale_rules */
1068 },
1069 nullprpl_list_icon, /* list_icon */
1070 nullprpl_list_emblem, /* list_emblem */
1071 nullprpl_status_text, /* status_text */
1072 nullprpl_tooltip_text, /* tooltip_text */
1073 nullprpl_status_types, /* status_types */
1074 nullprpl_blist_node_menu, /* blist_node_menu */
1075 nullprpl_chat_info, /* chat_info */
1076 nullprpl_chat_info_defaults, /* chat_info_defaults */
1077 nullprpl_login, /* login */
1078 nullprpl_close, /* close */
1079 nullprpl_send_im, /* send_im */
1080 nullprpl_set_info, /* set_info */
1081 nullprpl_send_typing, /* send_typing */
1082 nullprpl_get_info, /* get_info */
1083 nullprpl_set_status, /* set_status */
1084 nullprpl_set_idle, /* set_idle */
1085 nullprpl_change_passwd, /* change_passwd */
1086 nullprpl_add_buddy, /* add_buddy */
1087 nullprpl_add_buddies, /* add_buddies */
1088 nullprpl_remove_buddy, /* remove_buddy */
1089 nullprpl_remove_buddies, /* remove_buddies */
1090 nullprpl_add_permit, /* add_permit */
1091 nullprpl_add_deny, /* add_deny */
1092 nullprpl_rem_permit, /* rem_permit */
1093 nullprpl_rem_deny, /* rem_deny */
1094 nullprpl_set_permit_deny, /* set_permit_deny */
1095 nullprpl_join_chat, /* join_chat */
1096 nullprpl_reject_chat, /* reject_chat */
1097 nullprpl_get_chat_name, /* get_chat_name */
1098 nullprpl_chat_invite, /* chat_invite */
1099 nullprpl_chat_leave, /* chat_leave */
1100 nullprpl_chat_whisper, /* chat_whisper */
1101 nullprpl_chat_send, /* chat_send */
1102 NULL, /* keepalive */
1103 nullprpl_register_user, /* register_user */
1104 nullprpl_get_cb_info, /* get_cb_info */
1105 NULL, /* get_cb_away */
1106 nullprpl_alias_buddy, /* alias_buddy */
1107 nullprpl_group_buddy, /* group_buddy */
1108 nullprpl_rename_group, /* rename_group */
1109 NULL, /* buddy_free */
1110 nullprpl_convo_closed, /* convo_closed */
1111 nullprpl_normalize, /* normalize */
1112 nullprpl_set_buddy_icon, /* set_buddy_icon */
1113 nullprpl_remove_group, /* remove_group */
1114 NULL, /* get_cb_real_name */
1115 nullprpl_set_chat_topic, /* set_chat_topic */
1116 NULL, /* find_blist_chat */
1117 nullprpl_roomlist_get_list, /* roomlist_get_list */
1118 nullprpl_roomlist_cancel, /* roomlist_cancel */
1119 nullprpl_roomlist_expand_category, /* roomlist_expand_category */
1120 nullprpl_can_receive_file, /* can_receive_file */
1121 NULL, /* send_file */
1122 NULL, /* new_xfer */
1123 nullprpl_offline_message, /* offline_message */
1124 NULL, /* whiteboard_prpl_ops */
1125 NULL, /* send_raw */
1126 NULL, /* roomlist_room_serialize */
1127 NULL, /* padding... */
1128 NULL,
1129 NULL,
1130 NULL,
1131 };
1132
1133 static void nullprpl_init(PurplePlugin *plugin)
1134 {
1135 /* see accountopt.h for information about user splits and protocol options */
1136 PurpleAccountUserSplit *split = purple_account_user_split_new(
1137 _("Example user split (unused)"), /* text shown to user */
1138 "default", /* default value */
1139 '@'); /* field separator */
1140 PurpleAccountOption *option = purple_account_option_string_new(
1141 _("Example option (unused)"), /* text shown to user */
1142 "example", /* pref name */
1143 "default"); /* default value */
1144
1145 purple_debug_info("nullprpl", "starting up\n");
1146
1147 prpl_info.user_splits = g_list_append(NULL, split);
1148 prpl_info.protocol_options = g_list_append(NULL, option);
1149
1150 /* register whisper chat command, /msg */
1151 purple_cmd_register("msg",
1152 "ws", /* args: recipient and message */
1153 PURPLE_CMD_P_DEFAULT, /* priority */
1154 PURPLE_CMD_FLAG_CHAT,
1155 "prpl-null",
1156 send_whisper,
1157 "msg &lt;username&gt; &lt;message&gt;: send a private message, aka a whisper",
1158 NULL); /* userdata */
1159
1160 /* get ready to store offline messages */
1161 goffline_messages = g_hash_table_new_full(g_str_hash, /* hash fn */
1162 g_str_equal, /* key comparison fn */
1163 g_free, /* key free fn */
1164 NULL); /* value free fn */
1165
1166 _null_protocol = plugin;
1167 }
1168
1169 static void nullprpl_destroy(PurplePlugin *plugin) {
1170 purple_debug_info("nullprpl", "shutting down\n");
1171 }
1172
1173
1174 static PurplePluginInfo info =
1175 {
1176 PURPLE_PLUGIN_MAGIC, /* magic */
1177 PURPLE_MAJOR_VERSION, /* major_version */
1178 PURPLE_MINOR_VERSION, /* minor_version */
1179 PURPLE_PLUGIN_PROTOCOL, /* type */
1180 NULL, /* ui_requirement */
1181 0, /* flags */
1182 NULL, /* dependencies */
1183 PURPLE_PRIORITY_DEFAULT, /* priority */
1184 NULLPRPL_ID, /* id */
1185 "Nullprpl", /* name */
1186 "0.3", /* version */
1187 "Null Protocol Plugin", /* summary */
1188 "Null Protocol Plugin", /* description */
1189 "Ryan Barrett <nullprpl@ryanb.org>", /* author */
1190 "http://snarfed.org/space/pidgin+null+protocol+plugin", /* homepage */
1191 NULL, /* load */
1192 NULL, /* unload */
1193 nullprpl_destroy, /* destroy */
1194 NULL, /* ui_info */
1195 &prpl_info, /* extra_info */
1196 NULL, /* prefs_info */
1197 nullprpl_actions, /* actions */
1198 NULL, /* padding... */
1199 NULL,
1200 NULL,
1201 NULL,
1202 };
1203
1204 PURPLE_INIT_PLUGIN(null, nullprpl_init, info);