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
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
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
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. */
56 #include "accountopt.h"
59 #include "conversation.h"
60 #include "connection.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
,
84 PurpleConnection
*from
;
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
;
98 PurpleMessageFlags flags
;
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
))
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
,
122 GcFuncData gcfdata
= { fn
, from
, userdata
};
123 g_list_foreach(purple_connections_get_all(), call_if_nullprpl
,
128 typedef void(*ChatFunc
)(PurpleConvChat
*from
, PurpleConvChat
*to
,
129 int id
, const char *room
, gpointer userdata
);
133 PurpleConvChat
*from_chat
;
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
);
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
),
156 g_list_foreach(purple_connections_get_all(), call_chat_func
,
161 static void discover_status(PurpleConnection
*from
, PurpleConnection
*to
,
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
);
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
,
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
);
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",
204 purple_account_request_change_user_info(acct
);
207 /* this is set to the actions member of the PurplePluginInfo struct at the
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
);
221 static const char *nullprpl_list_icon(PurpleAccount
*acct
, PurpleBuddy
*buddy
)
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");
237 if (message
&& strlen(message
) > 0)
238 text
= g_strdup_printf("%s: %s", name
, message
);
240 text
= g_strdup(name
);
242 purple_debug_info("nullprpl", "%s's status text is %s\n", buddy
->name
, text
);
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
,
254 PurpleConnection
*gc
= get_nullprpl_gc(buddy
->name
);
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
),
266 const char *user_info
= purple_account_get_user_info(gc
->account
);
268 purple_notify_user_info_add_pair(info
, _("User info"), user_info
);
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
)
283 PurpleStatusType
*type
;
285 purple_debug_info("nullprpl", "returning status types for %s: %s, %s, %s\n",
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
),
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
),
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
),
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 */
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
);
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
,
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"));
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",
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
,
392 offline_messages
= g_list_next(offline_messages
);
394 g_free(message
->from
);
395 g_free(message
->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",
428 purple_conv_present_error(who
, gc
->account
, msg
);
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
;
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
);
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
) {
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
,
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
);
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
);
494 static void nullprpl_get_info(PurpleConnection
*gc
, const char *username
) {
496 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
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
);
508 acct
= purple_accounts_find(username
, NULLPRPL_ID
);
510 body
= purple_account_get_user_info(acct
);
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 */
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
),
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
,
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
,
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
);
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
,
566 NULL
, /* local account id (rarely used) */
573 static void nullprpl_add_buddies(PurpleConnection
*gc
, GList
*buddies
,
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
,
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
,
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
,
648 NULL
, /* user-provided join message, IRC style */
650 TRUE
); /* show a join message */
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
,
658 NULL
, /* user-provided join message, IRC style */
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
);
676 char *tmp
= g_strdup_printf(_("%s is already in chat room %s."),
679 purple_debug_info("nullprpl", "%s is already in chat room %s\n", username
,
681 purple_notify_info(gc
, _("Join chat"), _("Join chat"), 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(
694 _("has rejected your invitation to join the chat 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"),
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
);
725 PurpleConversation
*to_conv
= purple_find_chat(to_acct
->gc
, id
);
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
);
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
) {
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
,
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
;
769 const char *from_username
;
770 PurpleConvChat
*chat
;
771 PurpleConvChatBuddy
*chat_buddy
;
772 PurpleConnection
*to
;
775 to_username
= args
[0];
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
);
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
;
799 *error
= g_strdup_printf(_("%s is not in this chat room."), to_username
);
800 return PURPLE_CMD_RET_FAILED
;
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
,
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
,
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
);
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
);
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
);
863 static void nullprpl_register_user(PurpleAccount
*acct
) {
864 purple_debug_info("nullprpl", "registering account for %s\n",
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
,
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
,
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
;
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
);
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
,
941 static void nullprpl_set_chat_topic(PurpleConnection
*gc
, int id
,
943 PurpleConversation
*conv
= purple_find_chat(gc
, id
);
944 PurpleConvChat
*chat
= purple_conversation_get_chat_data(conv
);
945 const char *last_topic
;
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
);
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
;
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
);
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
,
1032 static gboolean
nullprpl_offline_message(const PurpleBuddy
*buddy
) {
1033 purple_debug_info("nullprpl",
1034 "reporting that offline messages are supported for %s\n",
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 */
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
,
1152 "msg <username> <message>: 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 */
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 */
1185 PURPLE_WEBSITE
, /* homepage */
1188 nullprpl_destroy
, /* destroy */
1190 &prpl_info
, /* extra_info */
1191 NULL
, /* prefs_info */
1192 nullprpl_actions
, /* actions */
1193 NULL
, /* padding... */
1199 PURPLE_INIT_PLUGIN(null
, nullprpl_init
, info
);