I think this was accidentally changed in revision
[pidgin-git.git] / libpurple / protocols / null / nullprpl.c
blob3a8f7972366775701d37158005a94c197280c9fa
1 /**
2 * purple
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.
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!
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.
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.
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.
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
43 #include <stdarg.h>
44 #include <string.h>
45 #include <time.h>
47 #include <glib.h>
49 /* If you're using this as the basis of a prpl that will be distributed
50 * separately from libpurple, remove the internal.h include below and replace
51 * it with code to include your own config.h or similar. If you're going to
52 * provide for translation, you'll also need to setup the gettext macros. */
53 #include "internal.h"
55 #include "account.h"
56 #include "accountopt.h"
57 #include "blist.h"
58 #include "cmds.h"
59 #include "conversation.h"
60 #include "connection.h"
61 #include "debug.h"
62 #include "notify.h"
63 #include "privacy.h"
64 #include "prpl.h"
65 #include "roomlist.h"
66 #include "status.h"
67 #include "util.h"
68 #include "version.h"
71 #define NULLPRPL_ID "prpl-null"
72 static PurplePlugin *_null_protocol = NULL;
74 #define NULL_STATUS_ONLINE "online"
75 #define NULL_STATUS_AWAY "away"
76 #define NULL_STATUS_OFFLINE "offline"
78 typedef void (*GcFunc)(PurpleConnection *from,
79 PurpleConnection *to,
80 gpointer userdata);
82 typedef struct {
83 GcFunc fn;
84 PurpleConnection *from;
85 gpointer userdata;
86 } GcFuncData;
89 * stores offline messages that haven't been delivered yet. maps username
90 * (char *) to GList * of GOfflineMessages. initialized in nullprpl_init.
92 GHashTable* goffline_messages = NULL;
94 typedef struct {
95 char *from;
96 char *message;
97 time_t mtime;
98 PurpleMessageFlags flags;
99 } GOfflineMessage;
102 * helpers
104 static PurpleConnection *get_nullprpl_gc(const char *username) {
105 PurpleAccount *acct = purple_accounts_find(username, NULLPRPL_ID);
106 if (acct && purple_account_is_connected(acct))
107 return acct->gc;
108 else
109 return NULL;
112 static void call_if_nullprpl(gpointer data, gpointer userdata) {
113 PurpleConnection *gc = (PurpleConnection *)(data);
114 GcFuncData *gcfdata = (GcFuncData *)userdata;
116 if (!strcmp(gc->account->protocol_id, NULLPRPL_ID))
117 gcfdata->fn(gcfdata->from, gc, gcfdata->userdata);
120 static void foreach_nullprpl_gc(GcFunc fn, PurpleConnection *from,
121 gpointer userdata) {
122 GcFuncData gcfdata = { fn, from, userdata };
123 g_list_foreach(purple_connections_get_all(), call_if_nullprpl,
124 &gcfdata);
128 typedef void(*ChatFunc)(PurpleConvChat *from, PurpleConvChat *to,
129 int id, const char *room, gpointer userdata);
131 typedef struct {
132 ChatFunc fn;
133 PurpleConvChat *from_chat;
134 gpointer userdata;
135 } ChatFuncData;
137 static void call_chat_func(gpointer data, gpointer userdata) {
138 PurpleConnection *to = (PurpleConnection *)data;
139 ChatFuncData *cfdata = (ChatFuncData *)userdata;
141 int id = cfdata->from_chat->id;
142 PurpleConversation *conv = purple_find_chat(to, id);
143 if (conv) {
144 PurpleConvChat *chat = purple_conversation_get_chat_data(conv);
145 cfdata->fn(cfdata->from_chat, chat, id, conv->name, cfdata->userdata);
149 static void foreach_gc_in_chat(ChatFunc fn, PurpleConnection *from,
150 int id, gpointer userdata) {
151 PurpleConversation *conv = purple_find_chat(from, id);
152 ChatFuncData cfdata = { fn,
153 purple_conversation_get_chat_data(conv),
154 userdata };
156 g_list_foreach(purple_connections_get_all(), call_chat_func,
157 &cfdata);
161 static void discover_status(PurpleConnection *from, PurpleConnection *to,
162 gpointer userdata) {
163 const char *from_username = from->account->username;
164 const char *to_username = to->account->username;
166 if (purple_find_buddy(from->account, to_username)) {
167 PurpleStatus *status = purple_account_get_active_status(to->account);
168 const char *status_id = purple_status_get_id(status);
169 const char *message = purple_status_get_attr_string(status, "message");
171 if (!strcmp(status_id, NULL_STATUS_ONLINE) ||
172 !strcmp(status_id, NULL_STATUS_AWAY) ||
173 !strcmp(status_id, NULL_STATUS_OFFLINE)) {
174 purple_debug_info("nullprpl", "%s sees that %s is %s: %s\n",
175 from_username, to_username, status_id, message);
176 purple_prpl_got_user_status(from->account, to_username, status_id,
177 (message) ? "message" : NULL, message, NULL);
178 } else {
179 purple_debug_error("nullprpl",
180 "%s's buddy %s has an unknown status: %s, %s",
181 from_username, to_username, status_id, message);
186 static void report_status_change(PurpleConnection *from, PurpleConnection *to,
187 gpointer userdata) {
188 purple_debug_info("nullprpl", "notifying %s that %s changed status\n",
189 to->account->username, from->account->username);
190 discover_status(to, from, NULL);
195 * UI callbacks
197 static void nullprpl_input_user_info(PurplePluginAction *action)
199 PurpleConnection *gc = (PurpleConnection *)action->context;
200 PurpleAccount *acct = purple_connection_get_account(gc);
201 purple_debug_info("nullprpl", "showing 'Set User Info' dialog for %s\n",
202 acct->username);
204 purple_account_request_change_user_info(acct);
207 /* this is set to the actions member of the PurplePluginInfo struct at the
208 * bottom.
210 static GList *nullprpl_actions(PurplePlugin *plugin, gpointer context)
212 PurplePluginAction *action = purple_plugin_action_new(
213 _("Set User Info..."), nullprpl_input_user_info);
214 return g_list_append(NULL, action);
219 * prpl functions
221 static const char *nullprpl_list_icon(PurpleAccount *acct, PurpleBuddy *buddy)
223 return "null";
226 static char *nullprpl_status_text(PurpleBuddy *buddy) {
227 purple_debug_info("nullprpl", "getting %s's status text for %s\n",
228 buddy->name, buddy->account->username);
230 if (purple_find_buddy(buddy->account, buddy->name)) {
231 PurplePresence *presence = purple_buddy_get_presence(buddy);
232 PurpleStatus *status = purple_presence_get_active_status(presence);
233 const char *name = purple_status_get_name(status);
234 const char *message = purple_status_get_attr_string(status, "message");
236 char *text;
237 if (message && strlen(message) > 0)
238 text = g_strdup_printf("%s: %s", name, message);
239 else
240 text = g_strdup(name);
242 purple_debug_info("nullprpl", "%s's status text is %s\n", buddy->name, text);
243 return text;
245 } else {
246 purple_debug_info("nullprpl", "...but %s is not logged in\n", buddy->name);
247 return g_strdup("Not logged in");
251 static void nullprpl_tooltip_text(PurpleBuddy *buddy,
252 PurpleNotifyUserInfo *info,
253 gboolean full) {
254 PurpleConnection *gc = get_nullprpl_gc(buddy->name);
256 if (gc) {
257 /* they're logged in */
258 PurplePresence *presence = purple_buddy_get_presence(buddy);
259 PurpleStatus *status = purple_presence_get_active_status(presence);
260 char *msg = nullprpl_status_text(buddy);
261 purple_notify_user_info_add_pair(info, purple_status_get_name(status),
262 msg);
263 g_free(msg);
265 if (full) {
266 const char *user_info = purple_account_get_user_info(gc->account);
267 if (user_info)
268 purple_notify_user_info_add_pair(info, _("User info"), user_info);
271 } else {
272 /* they're not logged in */
273 purple_notify_user_info_add_pair(info, _("User info"), _("not logged in"));
276 purple_debug_info("nullprpl", "showing %s tooltip for %s\n",
277 (full) ? "full" : "short", buddy->name);
280 static GList *nullprpl_status_types(PurpleAccount *acct)
282 GList *types = NULL;
283 PurpleStatusType *type;
285 purple_debug_info("nullprpl", "returning status types for %s: %s, %s, %s\n",
286 acct->username,
287 NULL_STATUS_ONLINE, NULL_STATUS_AWAY, NULL_STATUS_OFFLINE);
289 type = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE,
290 NULL_STATUS_ONLINE, NULL, TRUE, TRUE, FALSE,
291 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
292 NULL);
293 types = g_list_prepend(types, type);
295 type = purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY,
296 NULL_STATUS_AWAY, NULL, TRUE, TRUE, FALSE,
297 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
298 NULL);
299 types = g_list_prepend(types, type);
301 type = purple_status_type_new_with_attrs(PURPLE_STATUS_OFFLINE,
302 NULL_STATUS_OFFLINE, NULL, TRUE, TRUE, FALSE,
303 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
304 NULL);
305 types = g_list_prepend(types, type);
307 return g_list_reverse(types);
310 static void blist_example_menu_item(PurpleBlistNode *node, gpointer userdata) {
311 purple_debug_info("nullprpl", "example menu item clicked on user %s\n",
312 ((PurpleBuddy *)node)->name);
314 purple_notify_info(NULL, /* plugin handle or PurpleConnection */
315 _("Primary title"),
316 _("Secondary title"),
317 _("This is the callback for the nullprpl menu item."));
320 static GList *nullprpl_blist_node_menu(PurpleBlistNode *node) {
321 purple_debug_info("nullprpl", "providing buddy list context menu item\n");
323 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
324 PurpleMenuAction *action = purple_menu_action_new(
325 _("Nullprpl example menu item"),
326 PURPLE_CALLBACK(blist_example_menu_item),
327 NULL, /* userdata passed to the callback */
328 NULL); /* child menu items */
329 return g_list_append(NULL, action);
330 } else {
331 return NULL;
335 static GList *nullprpl_chat_info(PurpleConnection *gc) {
336 struct proto_chat_entry *pce; /* defined in prpl.h */
338 purple_debug_info("nullprpl", "returning chat setting 'room'\n");
340 pce = g_new0(struct proto_chat_entry, 1);
341 pce->label = _("Chat _room");
342 pce->identifier = "room";
343 pce->required = TRUE;
345 return g_list_append(NULL, pce);
348 static GHashTable *nullprpl_chat_info_defaults(PurpleConnection *gc,
349 const char *room) {
350 GHashTable *defaults;
352 purple_debug_info("nullprpl", "returning chat default setting "
353 "'room' = 'default'\n");
355 defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
356 g_hash_table_insert(defaults, "room", g_strdup("default"));
357 return defaults;
360 static void nullprpl_login(PurpleAccount *acct)
362 PurpleConnection *gc = purple_account_get_connection(acct);
363 GList *offline_messages;
365 purple_debug_info("nullprpl", "logging in %s\n", acct->username);
367 purple_connection_update_progress(gc, _("Connecting"),
368 0, /* which connection step this is */
369 2); /* total number of steps */
371 purple_connection_update_progress(gc, _("Connected"),
372 1, /* which connection step this is */
373 2); /* total number of steps */
374 purple_connection_set_state(gc, PURPLE_CONNECTED);
376 /* tell purple about everyone on our buddy list who's connected */
377 foreach_nullprpl_gc(discover_status, gc, NULL);
379 /* notify other nullprpl accounts */
380 foreach_nullprpl_gc(report_status_change, gc, NULL);
382 /* fetch stored offline messages */
383 purple_debug_info("nullprpl", "checking for offline messages for %s\n",
384 acct->username);
385 offline_messages = g_hash_table_lookup(goffline_messages, acct->username);
386 while (offline_messages) {
387 GOfflineMessage *message = (GOfflineMessage *)offline_messages->data;
388 purple_debug_info("nullprpl", "delivering offline message to %s: %s\n",
389 acct->username, message->message);
390 serv_got_im(gc, message->from, message->message, message->flags,
391 message->mtime);
392 offline_messages = g_list_next(offline_messages);
394 g_free(message->from);
395 g_free(message->message);
396 g_free(message);
399 g_list_free(offline_messages);
400 g_hash_table_remove(goffline_messages, &acct->username);
403 static void nullprpl_close(PurpleConnection *gc)
405 /* notify other nullprpl accounts */
406 foreach_nullprpl_gc(report_status_change, gc, NULL);
409 static int nullprpl_send_im(PurpleConnection *gc, const char *who,
410 const char *message, PurpleMessageFlags flags)
412 const char *from_username = gc->account->username;
413 PurpleMessageFlags receive_flags = ((flags & ~PURPLE_MESSAGE_SEND)
414 | PURPLE_MESSAGE_RECV);
415 PurpleAccount *to_acct = purple_accounts_find(who, NULLPRPL_ID);
416 PurpleConnection *to;
418 purple_debug_info("nullprpl", "sending message from %s to %s: %s\n",
419 from_username, who, message);
421 /* is the sender blocked by the recipient's privacy settings? */
422 if (to_acct && !purple_privacy_check(to_acct, gc->account->username)) {
423 char *msg = g_strdup_printf(
424 _("Your message was blocked by %s's privacy settings."), who);
425 purple_debug_info("nullprpl",
426 "discarding; %s is blocked by %s's privacy settings\n",
427 from_username, who);
428 purple_conv_present_error(who, gc->account, msg);
429 g_free(msg);
430 return 0;
433 /* is the recipient online? */
434 to = get_nullprpl_gc(who);
435 if (to) { /* yes, send */
436 serv_got_im(to, from_username, message, receive_flags, time(NULL));
438 } else { /* nope, store as an offline message */
439 GOfflineMessage *offline_message;
440 GList *messages;
442 purple_debug_info("nullprpl",
443 "%s is offline, sending as offline message\n", who);
444 offline_message = g_new0(GOfflineMessage, 1);
445 offline_message->from = g_strdup(from_username);
446 offline_message->message = g_strdup(message);
447 offline_message->mtime = time(NULL);
448 offline_message->flags = receive_flags;
450 messages = g_hash_table_lookup(goffline_messages, who);
451 messages = g_list_append(messages, offline_message);
452 g_hash_table_insert(goffline_messages, g_strdup(who), messages);
455 return 1;
458 static void nullprpl_set_info(PurpleConnection *gc, const char *info) {
459 purple_debug_info("nullprpl", "setting %s's user info to %s\n",
460 gc->account->username, info);
463 static const char *typing_state_to_string(PurpleTypingState typing) {
464 switch (typing) {
465 case PURPLE_NOT_TYPING: return "is not typing";
466 case PURPLE_TYPING: return "is typing";
467 case PURPLE_TYPED: return "stopped typing momentarily";
468 default: return "unknown typing state";
472 static void notify_typing(PurpleConnection *from, PurpleConnection *to,
473 gpointer typing) {
474 const char *from_username = from->account->username;
475 const char *action = typing_state_to_string((PurpleTypingState)typing);
476 purple_debug_info("nullprpl", "notifying %s that %s %s\n",
477 to->account->username, from_username, action);
479 serv_got_typing(to,
480 from_username,
481 0, /* if non-zero, a timeout in seconds after which to
482 * reset the typing status to PURPLE_NOT_TYPING */
483 (PurpleTypingState)typing);
486 static unsigned int nullprpl_send_typing(PurpleConnection *gc, const char *name,
487 PurpleTypingState typing) {
488 purple_debug_info("nullprpl", "%s %s\n", gc->account->username,
489 typing_state_to_string(typing));
490 foreach_nullprpl_gc(notify_typing, gc, (gpointer)typing);
491 return 0;
494 static void nullprpl_get_info(PurpleConnection *gc, const char *username) {
495 const char *body;
496 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
497 PurpleAccount *acct;
499 purple_debug_info("nullprpl", "Fetching %s's user info for %s\n", username,
500 gc->account->username);
502 if (!get_nullprpl_gc(username)) {
503 char *msg = g_strdup_printf(_("%s is not logged in."), username);
504 purple_notify_error(gc, _("User Info"), _("User info not available. "), msg);
505 g_free(msg);
508 acct = purple_accounts_find(username, NULLPRPL_ID);
509 if (acct)
510 body = purple_account_get_user_info(acct);
511 else
512 body = _("No user info.");
513 purple_notify_user_info_add_pair(info, "Info", body);
515 /* show a buddy's user info in a nice dialog box */
516 purple_notify_userinfo(gc, /* connection the buddy info came through */
517 username, /* buddy's username */
518 info, /* body */
519 NULL, /* callback called when dialog closed */
520 NULL); /* userdata for callback */
523 static void nullprpl_set_status(PurpleAccount *acct, PurpleStatus *status) {
524 const char *msg = purple_status_get_attr_string(status, "message");
525 purple_debug_info("nullprpl", "setting %s's status to %s: %s\n",
526 acct->username, purple_status_get_name(status), msg);
528 foreach_nullprpl_gc(report_status_change, get_nullprpl_gc(acct->username),
529 NULL);
532 static void nullprpl_set_idle(PurpleConnection *gc, int idletime) {
533 purple_debug_info("nullprpl",
534 "purple reports that %s has been idle for %d seconds\n",
535 gc->account->username, idletime);
538 static void nullprpl_change_passwd(PurpleConnection *gc, const char *old_pass,
539 const char *new_pass) {
540 purple_debug_info("nullprpl", "%s wants to change their password\n",
541 gc->account->username);
544 static void nullprpl_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
545 PurpleGroup *group)
547 const char *username = gc->account->username;
548 PurpleConnection *buddy_gc = get_nullprpl_gc(buddy->name);
550 purple_debug_info("nullprpl", "adding %s to %s's buddy list\n", buddy->name,
551 username);
553 if (buddy_gc) {
554 PurpleAccount *buddy_acct = buddy_gc->account;
556 discover_status(gc, buddy_gc, NULL);
558 if (purple_find_buddy(buddy_acct, username)) {
559 purple_debug_info("nullprpl", "%s is already on %s's buddy list\n",
560 username, buddy->name);
561 } else {
562 purple_debug_info("nullprpl", "asking %s if they want to add %s\n",
563 buddy->name, username);
564 purple_account_request_add(buddy_acct,
565 username,
566 NULL, /* local account id (rarely used) */
567 NULL, /* alias */
568 NULL); /* message */
573 static void nullprpl_add_buddies(PurpleConnection *gc, GList *buddies,
574 GList *groups) {
575 GList *buddy = buddies;
576 GList *group = groups;
578 purple_debug_info("nullprpl", "adding multiple buddies\n");
580 while (buddy && group) {
581 nullprpl_add_buddy(gc, (PurpleBuddy *)buddy->data, (PurpleGroup *)group->data);
582 buddy = g_list_next(buddy);
583 group = g_list_next(group);
587 static void nullprpl_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
588 PurpleGroup *group)
590 purple_debug_info("nullprpl", "removing %s from %s's buddy list\n",
591 buddy->name, gc->account->username);
594 static void nullprpl_remove_buddies(PurpleConnection *gc, GList *buddies,
595 GList *groups) {
596 GList *buddy = buddies;
597 GList *group = groups;
599 purple_debug_info("nullprpl", "removing multiple buddies\n");
601 while (buddy && group) {
602 nullprpl_remove_buddy(gc, (PurpleBuddy *)buddy->data,
603 (PurpleGroup *)group->data);
604 buddy = g_list_next(buddy);
605 group = g_list_next(group);
610 * nullprpl uses purple's local whitelist and blacklist, stored in blist.xml, as
611 * its authoritative privacy settings, and uses purple's logic (specifically
612 * purple_privacy_check(), from privacy.h), to determine whether messages are
613 * allowed or blocked.
615 static void nullprpl_add_permit(PurpleConnection *gc, const char *name) {
616 purple_debug_info("nullprpl", "%s adds %s to their allowed list\n",
617 gc->account->username, name);
620 static void nullprpl_add_deny(PurpleConnection *gc, const char *name) {
621 purple_debug_info("nullprpl", "%s adds %s to their blocked list\n",
622 gc->account->username, name);
625 static void nullprpl_rem_permit(PurpleConnection *gc, const char *name) {
626 purple_debug_info("nullprpl", "%s removes %s from their allowed list\n",
627 gc->account->username, name);
630 static void nullprpl_rem_deny(PurpleConnection *gc, const char *name) {
631 purple_debug_info("nullprpl", "%s removes %s from their blocked list\n",
632 gc->account->username, name);
635 static void nullprpl_set_permit_deny(PurpleConnection *gc) {
636 /* this is for synchronizing the local black/whitelist with the server.
637 * for nullprpl, it's a noop.
641 static void joined_chat(PurpleConvChat *from, PurpleConvChat *to,
642 int id, const char *room, gpointer userdata) {
643 /* tell their chat window that we joined */
644 purple_debug_info("nullprpl", "%s sees that %s joined chat room %s\n",
645 to->nick, from->nick, room);
646 purple_conv_chat_add_user(to,
647 from->nick,
648 NULL, /* user-provided join message, IRC style */
649 PURPLE_CBFLAGS_NONE,
650 TRUE); /* show a join message */
652 if (from != to) {
653 /* add them to our chat window */
654 purple_debug_info("nullprpl", "%s sees that %s is in chat room %s\n",
655 from->nick, to->nick, room);
656 purple_conv_chat_add_user(from,
657 to->nick,
658 NULL, /* user-provided join message, IRC style */
659 PURPLE_CBFLAGS_NONE,
660 FALSE); /* show a join message */
664 static void nullprpl_join_chat(PurpleConnection *gc, GHashTable *components) {
665 const char *username = gc->account->username;
666 const char *room = g_hash_table_lookup(components, "room");
667 int chat_id = g_str_hash(room);
668 purple_debug_info("nullprpl", "%s is joining chat room %s\n", username, room);
670 if (!purple_find_chat(gc, chat_id)) {
671 serv_got_joined_chat(gc, chat_id, room);
673 /* tell everyone that we joined, and add them if they're already there */
674 foreach_gc_in_chat(joined_chat, gc, chat_id, NULL);
675 } else {
676 char *tmp = g_strdup_printf(_("%s is already in chat room %s."),
677 username,
678 room);
679 purple_debug_info("nullprpl", "%s is already in chat room %s\n", username,
680 room);
681 purple_notify_info(gc, _("Join chat"), _("Join chat"), tmp);
682 g_free(tmp);
686 static void nullprpl_reject_chat(PurpleConnection *gc, GHashTable *components) {
687 const char *invited_by = g_hash_table_lookup(components, "invited_by");
688 const char *room = g_hash_table_lookup(components, "room");
689 const char *username = gc->account->username;
690 PurpleConnection *invited_by_gc = get_nullprpl_gc(invited_by);
691 char *message = g_strdup_printf(
692 "%s %s %s.",
693 username,
694 _("has rejected your invitation to join the chat room"),
695 room);
697 purple_debug_info("nullprpl",
698 "%s has rejected %s's invitation to join chat room %s\n",
699 username, invited_by, room);
701 purple_notify_info(invited_by_gc,
702 _("Chat invitation rejected"),
703 _("Chat invitation rejected"),
704 message);
705 g_free(message);
708 static char *nullprpl_get_chat_name(GHashTable *components) {
709 const char *room = g_hash_table_lookup(components, "room");
710 purple_debug_info("nullprpl", "reporting chat room name '%s'\n", room);
711 return g_strdup(room);
714 static void nullprpl_chat_invite(PurpleConnection *gc, int id,
715 const char *message, const char *who) {
716 const char *username = gc->account->username;
717 PurpleConversation *conv = purple_find_chat(gc, id);
718 const char *room = conv->name;
719 PurpleAccount *to_acct = purple_accounts_find(who, NULLPRPL_ID);
721 purple_debug_info("nullprpl", "%s is inviting %s to join chat room %s\n",
722 username, who, room);
724 if (to_acct) {
725 PurpleConversation *to_conv = purple_find_chat(to_acct->gc, id);
726 if (to_conv) {
727 char *tmp = g_strdup_printf("%s is already in chat room %s.", who, room);
728 purple_debug_info("nullprpl",
729 "%s is already in chat room %s; "
730 "ignoring invitation from %s\n",
731 who, room, username);
732 purple_notify_info(gc, _("Chat invitation"), _("Chat invitation"), tmp);
733 g_free(tmp);
734 } else {
735 GHashTable *components;
736 components = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
737 g_hash_table_replace(components, "room", g_strdup(room));
738 g_hash_table_replace(components, "invited_by", g_strdup(username));
739 serv_got_chat_invite(to_acct->gc, room, username, message, components);
744 static void left_chat_room(PurpleConvChat *from, PurpleConvChat *to,
745 int id, const char *room, gpointer userdata) {
746 if (from != to) {
747 /* tell their chat window that we left */
748 purple_debug_info("nullprpl", "%s sees that %s left chat room %s\n",
749 to->nick, from->nick, room);
750 purple_conv_chat_remove_user(to,
751 from->nick,
752 NULL); /* user-provided message, IRC style */
756 static void nullprpl_chat_leave(PurpleConnection *gc, int id) {
757 PurpleConversation *conv = purple_find_chat(gc, id);
758 purple_debug_info("nullprpl", "%s is leaving chat room %s\n",
759 gc->account->username, conv->name);
761 /* tell everyone that we left */
762 foreach_gc_in_chat(left_chat_room, gc, id, NULL);
765 static PurpleCmdRet send_whisper(PurpleConversation *conv, const gchar *cmd,
766 gchar **args, gchar **error, void *userdata) {
767 const char *to_username;
768 const char *message;
769 const char *from_username;
770 PurpleConvChat *chat;
771 PurpleConvChatBuddy *chat_buddy;
772 PurpleConnection *to;
774 /* parse args */
775 to_username = args[0];
776 message = args[1];
778 if (!to_username || strlen(to_username) == 0) {
779 *error = g_strdup(_("Whisper is missing recipient."));
780 return PURPLE_CMD_RET_FAILED;
781 } else if (!message || strlen(message) == 0) {
782 *error = g_strdup(_("Whisper is missing message."));
783 return PURPLE_CMD_RET_FAILED;
786 from_username = conv->account->username;
787 purple_debug_info("nullprpl", "%s whispers to %s in chat room %s: %s\n",
788 from_username, to_username, conv->name, message);
790 chat = purple_conversation_get_chat_data(conv);
791 chat_buddy = purple_conv_chat_cb_find(chat, to_username);
792 to = get_nullprpl_gc(to_username);
794 if (!chat_buddy) {
795 /* this will be freed by the caller */
796 *error = g_strdup_printf(_("%s is not logged in."), to_username);
797 return PURPLE_CMD_RET_FAILED;
798 } else if (!to) {
799 *error = g_strdup_printf(_("%s is not in this chat room."), to_username);
800 return PURPLE_CMD_RET_FAILED;
801 } else {
802 /* write the whisper in the sender's chat window */
803 char *message_to = g_strdup_printf("%s (to %s)", message, to_username);
804 purple_conv_chat_write(chat, from_username, message_to,
805 PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_WHISPER,
806 time(NULL));
807 g_free(message_to);
809 /* send the whisper */
810 serv_chat_whisper(to, chat->id, from_username, message);
812 return PURPLE_CMD_RET_OK;
816 static void nullprpl_chat_whisper(PurpleConnection *gc, int id, const char *who,
817 const char *message) {
818 const char *username = gc->account->username;
819 PurpleConversation *conv = purple_find_chat(gc, id);
820 purple_debug_info("nullprpl",
821 "%s receives whisper from %s in chat room %s: %s\n",
822 username, who, conv->name, message);
824 /* receive whisper on recipient's account */
825 serv_got_chat_in(gc, id, who, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_WHISPER,
826 message, time(NULL));
829 static void receive_chat_message(PurpleConvChat *from, PurpleConvChat *to,
830 int id, const char *room, gpointer userdata) {
831 const char *message = (const char *)userdata;
832 PurpleConnection *to_gc = get_nullprpl_gc(to->nick);
834 purple_debug_info("nullprpl",
835 "%s receives message from %s in chat room %s: %s\n",
836 to->nick, from->nick, room, message);
837 serv_got_chat_in(to_gc, id, from->nick, PURPLE_MESSAGE_RECV, message,
838 time(NULL));
841 static int nullprpl_chat_send(PurpleConnection *gc, int id, const char *message,
842 PurpleMessageFlags flags) {
843 const char *username = gc->account->username;
844 PurpleConversation *conv = purple_find_chat(gc, id);
846 if (conv) {
847 purple_debug_info("nullprpl",
848 "%s is sending message to chat room %s: %s\n", username,
849 conv->name, message);
851 /* send message to everyone in the chat room */
852 foreach_gc_in_chat(receive_chat_message, gc, id, (gpointer)message);
853 return 0;
854 } else {
855 purple_debug_info("nullprpl",
856 "tried to send message from %s to chat room #%d: %s\n"
857 "but couldn't find chat room",
858 username, id, message);
859 return -1;
863 static void nullprpl_register_user(PurpleAccount *acct) {
864 purple_debug_info("nullprpl", "registering account for %s\n",
865 acct->username);
868 static void nullprpl_get_cb_info(PurpleConnection *gc, int id, const char *who) {
869 PurpleConversation *conv = purple_find_chat(gc, id);
870 purple_debug_info("nullprpl",
871 "retrieving %s's info for %s in chat room %s\n", who,
872 gc->account->username, conv->name);
874 nullprpl_get_info(gc, who);
877 static void nullprpl_alias_buddy(PurpleConnection *gc, const char *who,
878 const char *alias) {
879 purple_debug_info("nullprpl", "%s sets %s's alias to %s\n",
880 gc->account->username, who, alias);
883 static void nullprpl_group_buddy(PurpleConnection *gc, const char *who,
884 const char *old_group,
885 const char *new_group) {
886 purple_debug_info("nullprpl", "%s has moved %s from group %s to group %s\n",
887 gc->account->username, who, old_group, new_group);
890 static void nullprpl_rename_group(PurpleConnection *gc, const char *old_name,
891 PurpleGroup *group, GList *moved_buddies) {
892 purple_debug_info("nullprpl", "%s has renamed group %s to %s\n",
893 gc->account->username, old_name, group->name);
896 static void nullprpl_convo_closed(PurpleConnection *gc, const char *who) {
897 purple_debug_info("nullprpl", "%s's conversation with %s was closed\n",
898 gc->account->username, who);
901 /* normalize a username (e.g. remove whitespace, add default domain, etc.)
902 * for nullprpl, this is a noop.
904 static const char *nullprpl_normalize(const PurpleAccount *acct,
905 const char *input) {
906 return NULL;
909 static void nullprpl_set_buddy_icon(PurpleConnection *gc,
910 PurpleStoredImage *img) {
911 purple_debug_info("nullprpl", "setting %s's buddy icon to %s\n",
912 gc->account->username,
913 img ? purple_imgstore_get_filename(img) : "(null)");
916 static void nullprpl_remove_group(PurpleConnection *gc, PurpleGroup *group) {
917 purple_debug_info("nullprpl", "%s has removed group %s\n",
918 gc->account->username, group->name);
922 static void set_chat_topic_fn(PurpleConvChat *from, PurpleConvChat *to,
923 int id, const char *room, gpointer userdata) {
924 const char *topic = (const char *)userdata;
925 const char *username = from->conv->account->username;
926 char *msg;
928 purple_conv_chat_set_topic(to, username, topic);
930 if (topic && strlen(topic) > 0)
931 msg = g_strdup_printf(_("%s sets topic to: %s"), username, topic);
932 else
933 msg = g_strdup_printf(_("%s clears topic"), username);
935 purple_conv_chat_write(to, username, msg,
936 PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG,
937 time(NULL));
938 g_free(msg);
941 static void nullprpl_set_chat_topic(PurpleConnection *gc, int id,
942 const char *topic) {
943 PurpleConversation *conv = purple_find_chat(gc, id);
944 PurpleConvChat *chat = purple_conversation_get_chat_data(conv);
945 const char *last_topic;
947 if (!chat)
948 return;
950 purple_debug_info("nullprpl", "%s sets topic of chat room '%s' to '%s'\n",
951 gc->account->username, conv->name, topic);
953 last_topic = purple_conv_chat_get_topic(chat);
954 if ((!topic && !last_topic) ||
955 (topic && last_topic && !strcmp(topic, last_topic)))
956 return; /* topic is unchanged, this is a noop */
958 foreach_gc_in_chat(set_chat_topic_fn, gc, id, (gpointer)topic);
961 static gboolean nullprpl_finish_get_roomlist(gpointer roomlist) {
962 purple_roomlist_set_in_progress((PurpleRoomlist *)roomlist, FALSE);
963 return FALSE;
966 static PurpleRoomlist *nullprpl_roomlist_get_list(PurpleConnection *gc) {
967 const char *username = gc->account->username;
968 PurpleRoomlist *roomlist = purple_roomlist_new(gc->account);
969 GList *fields = NULL;
970 PurpleRoomlistField *field;
971 GList *chats;
972 GList *seen_ids = NULL;
974 purple_debug_info("nullprpl", "%s asks for room list; returning:\n", username);
976 /* set up the room list */
977 field = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "room",
978 "room", TRUE /* hidden */);
979 fields = g_list_append(fields, field);
981 field = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_INT, "Id", "Id", FALSE);
982 fields = g_list_append(fields, field);
984 purple_roomlist_set_fields(roomlist, fields);
986 /* add each chat room. the chat ids are cached in seen_ids so that each room
987 * is only returned once, even if multiple users are in it. */
988 for (chats = purple_get_chats(); chats; chats = g_list_next(chats)) {
989 PurpleConversation *conv = (PurpleConversation *)chats->data;
990 PurpleRoomlistRoom *room;
991 const char *name = conv->name;
992 int id = purple_conversation_get_chat_data(conv)->id;
994 /* have we already added this room? */
995 if (g_list_find_custom(seen_ids, name, (GCompareFunc)strcmp))
996 continue; /* yes! try the next one. */
998 /* This cast is OK because this list is only staying around for the life
999 * of this function and none of the conversations are being deleted
1000 * in that timespan. */
1001 seen_ids = g_list_prepend(seen_ids, (char *)name); /* no, it's new. */
1002 purple_debug_info("nullprpl", "%s (%d), ", name, id);
1004 room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, name, NULL);
1005 purple_roomlist_room_add_field(roomlist, room, name);
1006 purple_roomlist_room_add_field(roomlist, room, &id);
1007 purple_roomlist_room_add(roomlist, room);
1010 g_list_free(seen_ids);
1011 purple_timeout_add(1 /* ms */, nullprpl_finish_get_roomlist, roomlist);
1012 return roomlist;
1015 static void nullprpl_roomlist_cancel(PurpleRoomlist *list) {
1016 purple_debug_info("nullprpl", "%s asked to cancel room list request\n",
1017 list->account->username);
1020 static void nullprpl_roomlist_expand_category(PurpleRoomlist *list,
1021 PurpleRoomlistRoom *category) {
1022 purple_debug_info("nullprpl", "%s asked to expand room list category %s\n",
1023 list->account->username, category->name);
1026 /* nullprpl doesn't support file transfer...yet... */
1027 static gboolean nullprpl_can_receive_file(PurpleConnection *gc,
1028 const char *who) {
1029 return FALSE;
1032 static gboolean nullprpl_offline_message(const PurpleBuddy *buddy) {
1033 purple_debug_info("nullprpl",
1034 "reporting that offline messages are supported for %s\n",
1035 buddy->name);
1036 return TRUE;
1041 * prpl stuff. see prpl.h for more information.
1044 static PurplePluginProtocolInfo prpl_info =
1046 OPT_PROTO_NO_PASSWORD | OPT_PROTO_CHAT_TOPIC, /* options */
1047 NULL, /* user_splits, initialized in nullprpl_init() */
1048 NULL, /* protocol_options, initialized in nullprpl_init() */
1049 { /* icon_spec, a PurpleBuddyIconSpec */
1050 "png,jpg,gif", /* format */
1051 0, /* min_width */
1052 0, /* min_height */
1053 128, /* max_width */
1054 128, /* max_height */
1055 10000, /* max_filesize */
1056 PURPLE_ICON_SCALE_DISPLAY, /* scale_rules */
1058 nullprpl_list_icon, /* list_icon */
1059 NULL, /* list_emblem */
1060 nullprpl_status_text, /* status_text */
1061 nullprpl_tooltip_text, /* tooltip_text */
1062 nullprpl_status_types, /* status_types */
1063 nullprpl_blist_node_menu, /* blist_node_menu */
1064 nullprpl_chat_info, /* chat_info */
1065 nullprpl_chat_info_defaults, /* chat_info_defaults */
1066 nullprpl_login, /* login */
1067 nullprpl_close, /* close */
1068 nullprpl_send_im, /* send_im */
1069 nullprpl_set_info, /* set_info */
1070 nullprpl_send_typing, /* send_typing */
1071 nullprpl_get_info, /* get_info */
1072 nullprpl_set_status, /* set_status */
1073 nullprpl_set_idle, /* set_idle */
1074 nullprpl_change_passwd, /* change_passwd */
1075 nullprpl_add_buddy, /* add_buddy */
1076 nullprpl_add_buddies, /* add_buddies */
1077 nullprpl_remove_buddy, /* remove_buddy */
1078 nullprpl_remove_buddies, /* remove_buddies */
1079 nullprpl_add_permit, /* add_permit */
1080 nullprpl_add_deny, /* add_deny */
1081 nullprpl_rem_permit, /* rem_permit */
1082 nullprpl_rem_deny, /* rem_deny */
1083 nullprpl_set_permit_deny, /* set_permit_deny */
1084 nullprpl_join_chat, /* join_chat */
1085 nullprpl_reject_chat, /* reject_chat */
1086 nullprpl_get_chat_name, /* get_chat_name */
1087 nullprpl_chat_invite, /* chat_invite */
1088 nullprpl_chat_leave, /* chat_leave */
1089 nullprpl_chat_whisper, /* chat_whisper */
1090 nullprpl_chat_send, /* chat_send */
1091 NULL, /* keepalive */
1092 nullprpl_register_user, /* register_user */
1093 nullprpl_get_cb_info, /* get_cb_info */
1094 NULL, /* get_cb_away */
1095 nullprpl_alias_buddy, /* alias_buddy */
1096 nullprpl_group_buddy, /* group_buddy */
1097 nullprpl_rename_group, /* rename_group */
1098 NULL, /* buddy_free */
1099 nullprpl_convo_closed, /* convo_closed */
1100 nullprpl_normalize, /* normalize */
1101 nullprpl_set_buddy_icon, /* set_buddy_icon */
1102 nullprpl_remove_group, /* remove_group */
1103 NULL, /* get_cb_real_name */
1104 nullprpl_set_chat_topic, /* set_chat_topic */
1105 NULL, /* find_blist_chat */
1106 nullprpl_roomlist_get_list, /* roomlist_get_list */
1107 nullprpl_roomlist_cancel, /* roomlist_cancel */
1108 nullprpl_roomlist_expand_category, /* roomlist_expand_category */
1109 nullprpl_can_receive_file, /* can_receive_file */
1110 NULL, /* send_file */
1111 NULL, /* new_xfer */
1112 nullprpl_offline_message, /* offline_message */
1113 NULL, /* whiteboard_prpl_ops */
1114 NULL, /* send_raw */
1115 NULL, /* roomlist_room_serialize */
1116 NULL, /* unregister_user */
1117 NULL, /* send_attention */
1118 NULL, /* get_attention_types */
1119 sizeof(PurplePluginProtocolInfo), /* struct_size */
1120 NULL, /* get_account_text_table */
1121 NULL, /* initiate_media */
1122 NULL, /* get_media_caps */
1123 NULL, /* set_public_alias */
1124 NULL, /* get_public_alias */
1125 NULL /* get_moods */
1128 static void nullprpl_init(PurplePlugin *plugin)
1130 /* see accountopt.h for information about user splits and protocol options */
1131 PurpleAccountUserSplit *split = purple_account_user_split_new(
1132 _("Example user split"), /* text shown to user */
1133 "default", /* default value */
1134 '@'); /* field separator */
1135 PurpleAccountOption *option = purple_account_option_string_new(
1136 _("Example option"), /* text shown to user */
1137 "example", /* pref name */
1138 "default"); /* default value */
1140 purple_debug_info("nullprpl", "starting up\n");
1142 prpl_info.user_splits = g_list_append(NULL, split);
1143 prpl_info.protocol_options = g_list_append(NULL, option);
1145 /* register whisper chat command, /msg */
1146 purple_cmd_register("msg",
1147 "ws", /* args: recipient and message */
1148 PURPLE_CMD_P_DEFAULT, /* priority */
1149 PURPLE_CMD_FLAG_CHAT,
1150 "prpl-null",
1151 send_whisper,
1152 "msg &lt;username&gt; &lt;message&gt;: send a private message, aka a whisper",
1153 NULL); /* userdata */
1155 /* get ready to store offline messages */
1156 goffline_messages = g_hash_table_new_full(g_str_hash, /* hash fn */
1157 g_str_equal, /* key comparison fn */
1158 g_free, /* key free fn */
1159 NULL); /* value free fn */
1161 _null_protocol = plugin;
1164 static void nullprpl_destroy(PurplePlugin *plugin) {
1165 purple_debug_info("nullprpl", "shutting down\n");
1169 static PurplePluginInfo info =
1171 PURPLE_PLUGIN_MAGIC, /* magic */
1172 PURPLE_MAJOR_VERSION, /* major_version */
1173 PURPLE_MINOR_VERSION, /* minor_version */
1174 PURPLE_PLUGIN_PROTOCOL, /* type */
1175 NULL, /* ui_requirement */
1176 0, /* flags */
1177 NULL, /* dependencies */
1178 PURPLE_PRIORITY_DEFAULT, /* priority */
1179 NULLPRPL_ID, /* id */
1180 "Null - Testing Plugin", /* name */
1181 DISPLAY_VERSION, /* version */
1182 N_("Null Protocol Plugin"), /* summary */
1183 N_("Null Protocol Plugin"), /* description */
1184 NULL, /* author */
1185 PURPLE_WEBSITE, /* homepage */
1186 NULL, /* load */
1187 NULL, /* unload */
1188 nullprpl_destroy, /* destroy */
1189 NULL, /* ui_info */
1190 &prpl_info, /* extra_info */
1191 NULL, /* prefs_info */
1192 nullprpl_actions, /* actions */
1193 NULL, /* padding... */
1194 NULL,
1195 NULL,
1196 NULL,
1199 PURPLE_INIT_PLUGIN(null, nullprpl_init, info);