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, 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
21 * protocol 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
49 /* If you're using this as the basis of a protocol 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. */
58 #include "accountopt.h"
59 #include "buddylist.h"
61 #include "conversation.h"
62 #include "connection.h"
72 * reference to the protocol instance, used for registering signals, prefs,
73 * etc. it is set when the protocol is added in plugin_load and is required
74 * for removing the protocol in plugin_unload.
76 static PurpleProtocol
*my_protocol
= NULL
;
78 #define NULL_STATUS_ONLINE "online"
79 #define NULL_STATUS_AWAY "away"
80 #define NULL_STATUS_OFFLINE "offline"
82 typedef void (*GcFunc
)(PurpleConnection
*from
,
88 PurpleConnection
*from
;
93 * stores offline messages that haven't been delivered yet. maps username
94 * (char *) to GList * of GOfflineMessages. initialized in plugin_load.
96 GHashTable
* goffline_messages
= NULL
;
102 PurpleMessageFlags flags
;
108 static PurpleConnection
*get_null_gc(const char *username
) {
109 PurpleAccount
*acct
= purple_accounts_find(username
, "null");
110 if (acct
&& purple_account_is_connected(acct
))
111 return purple_account_get_connection(acct
);
116 static void call_if_nullprotocol(gpointer data
, gpointer userdata
) {
117 PurpleConnection
*gc
= (PurpleConnection
*)(data
);
118 GcFuncData
*gcfdata
= (GcFuncData
*)userdata
;
120 if (!strcmp(purple_account_get_protocol_id(purple_connection_get_account(gc
)), "null"))
121 gcfdata
->fn(gcfdata
->from
, gc
, gcfdata
->userdata
);
124 static void foreach_null_gc(GcFunc fn
, PurpleConnection
*from
,
126 GcFuncData gcfdata
= { fn
, from
, userdata
};
127 g_list_foreach(purple_connections_get_all(), call_if_nullprotocol
,
132 typedef void(*ChatFunc
)(PurpleChatConversation
*from
, PurpleChatConversation
*to
,
133 int id
, const char *room
, gpointer userdata
);
137 PurpleChatConversation
*from_chat
;
141 static void call_chat_func(gpointer data
, gpointer userdata
) {
142 PurpleConnection
*to
= (PurpleConnection
*)data
;
143 ChatFuncData
*cfdata
= (ChatFuncData
*)userdata
;
145 int id
= purple_chat_conversation_get_id(cfdata
->from_chat
);
146 PurpleChatConversation
*chat
= purple_conversations_find_chat(to
, id
);
148 cfdata
->fn(cfdata
->from_chat
, chat
, id
,
149 purple_conversation_get_name(PURPLE_CONVERSATION(chat
)), cfdata
->userdata
);
152 static void foreach_gc_in_chat(ChatFunc fn
, PurpleConnection
*from
,
153 int id
, gpointer userdata
) {
154 PurpleChatConversation
*chat
= purple_conversations_find_chat(from
, id
);
155 ChatFuncData cfdata
= { fn
,
159 g_list_foreach(purple_connections_get_all(), call_chat_func
,
164 static void discover_status(PurpleConnection
*from
, PurpleConnection
*to
,
166 const char *from_username
= purple_account_get_username(purple_connection_get_account(from
));
167 const char *to_username
= purple_account_get_username(purple_connection_get_account(to
));
169 if (purple_blist_find_buddy(purple_connection_get_account(from
), to_username
)) {
170 PurpleStatus
*status
= purple_account_get_active_status(purple_connection_get_account(to
));
171 const char *status_id
= purple_status_get_id(status
);
172 const char *message
= purple_status_get_attr_string(status
, "message");
174 if (!strcmp(status_id
, NULL_STATUS_ONLINE
) ||
175 !strcmp(status_id
, NULL_STATUS_AWAY
) ||
176 !strcmp(status_id
, NULL_STATUS_OFFLINE
)) {
177 purple_debug_info("nullprpl", "%s sees that %s is %s: %s\n",
178 from_username
, to_username
, status_id
, message
);
179 purple_protocol_got_user_status(purple_connection_get_account(from
), to_username
, status_id
,
180 (message
) ? "message" : NULL
, message
, NULL
);
182 purple_debug_error("nullprpl",
183 "%s's buddy %s has an unknown status: %s, %s",
184 from_username
, to_username
, status_id
, message
);
189 static void report_status_change(PurpleConnection
*from
, PurpleConnection
*to
,
191 purple_debug_info("nullprpl", "notifying %s that %s changed status\n",
192 purple_account_get_username(purple_connection_get_account(to
)), purple_account_get_username(purple_connection_get_account(from
)));
193 discover_status(to
, from
, NULL
);
200 static void null_input_user_info(PurpleProtocolAction
*action
)
202 PurpleConnection
*gc
= action
->connection
;
203 PurpleAccount
*acct
= purple_connection_get_account(gc
);
204 purple_debug_info("nullprpl", "showing 'Set User Info' dialog for %s\n",
205 purple_account_get_username(acct
));
207 purple_account_request_change_user_info(acct
);
213 static GList
*null_get_actions(PurpleConnection
*gc
)
215 PurpleProtocolAction
*action
= purple_protocol_action_new(
216 _("Set User Info..."), null_input_user_info
);
217 return g_list_append(NULL
, action
);
220 static const char *null_list_icon(PurpleAccount
*acct
, PurpleBuddy
*buddy
)
225 static char *null_status_text(PurpleBuddy
*buddy
) {
226 purple_debug_info("nullprpl", "getting %s's status text for %s\n",
227 purple_buddy_get_name(buddy
),
228 purple_account_get_username(purple_buddy_get_account(buddy
)));
230 if (purple_blist_find_buddy(purple_buddy_get_account(buddy
), purple_buddy_get_name(buddy
))) {
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
&& *message
)
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",
243 purple_buddy_get_name(buddy
), text
);
247 purple_debug_info("nullprpl", "...but %s is not logged in\n", purple_buddy_get_name(buddy
));
248 return g_strdup("Not logged in");
252 static void null_tooltip_text(PurpleBuddy
*buddy
,
253 PurpleNotifyUserInfo
*info
,
255 PurpleConnection
*gc
= get_null_gc(purple_buddy_get_name(buddy
));
258 /* they're logged in */
259 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
260 PurpleStatus
*status
= purple_presence_get_active_status(presence
);
261 char *msg
= null_status_text(buddy
);
262 /* TODO: Check whether it's correct to call add_pair_html,
263 or if we should be using add_pair_plaintext */
264 purple_notify_user_info_add_pair_html(info
, purple_status_get_name(status
),
269 const char *user_info
= purple_account_get_user_info(purple_connection_get_account(gc
));
271 /* TODO: Check whether it's correct to call add_pair_html,
272 or if we should be using add_pair_plaintext */
273 purple_notify_user_info_add_pair_html(info
, _("User info"), user_info
);
277 /* they're not logged in */
278 purple_notify_user_info_add_pair_plaintext(info
, _("User info"), _("not logged in"));
281 purple_debug_info("nullprpl", "showing %s tooltip for %s\n",
282 (full
) ? "full" : "short", purple_buddy_get_name(buddy
));
285 static GList
*null_status_types(PurpleAccount
*acct
)
288 PurpleStatusType
*type
;
290 purple_debug_info("nullprpl", "returning status types for %s: %s, %s, %s\n",
291 purple_account_get_username(acct
),
292 NULL_STATUS_ONLINE
, NULL_STATUS_AWAY
, NULL_STATUS_OFFLINE
);
294 type
= purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE
,
295 NULL_STATUS_ONLINE
, NULL
, TRUE
, TRUE
, FALSE
,
296 "message", _("Message"), purple_value_new(G_TYPE_STRING
),
298 types
= g_list_prepend(types
, type
);
300 type
= purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY
,
301 NULL_STATUS_AWAY
, NULL
, TRUE
, TRUE
, FALSE
,
302 "message", _("Message"), purple_value_new(G_TYPE_STRING
),
304 types
= g_list_prepend(types
, type
);
306 type
= purple_status_type_new_with_attrs(PURPLE_STATUS_OFFLINE
,
307 NULL_STATUS_OFFLINE
, NULL
, TRUE
, TRUE
, FALSE
,
308 "message", _("Message"), purple_value_new(G_TYPE_STRING
),
310 types
= g_list_prepend(types
, type
);
312 return g_list_reverse(types
);
315 static void blist_example_menu_item(PurpleBlistNode
*node
, gpointer userdata
) {
316 purple_debug_info("nullprpl", "example menu item clicked on user %s\n",
317 purple_buddy_get_name(PURPLE_BUDDY(node
)));
319 purple_notify_info(NULL
, /* plugin handle or PurpleConnection */
321 _("Secondary title"),
322 _("This is the callback for the NullProtocol menu item."),
326 static GList
*null_blist_node_menu(PurpleBlistNode
*node
) {
327 purple_debug_info("nullprpl", "providing buddy list context menu item\n");
329 if (PURPLE_IS_BUDDY(node
)) {
330 PurpleMenuAction
*action
= purple_menu_action_new(
331 _("NullProtocol example menu item"),
332 PURPLE_CALLBACK(blist_example_menu_item
),
333 NULL
, /* userdata passed to the callback */
334 NULL
); /* child menu items */
335 return g_list_append(NULL
, action
);
341 static GList
*null_chat_info(PurpleConnection
*gc
) {
342 PurpleProtocolChatEntry
*pce
; /* defined in protocols.h */
344 purple_debug_info("nullprpl", "returning chat setting 'room'\n");
346 pce
= g_new0(PurpleProtocolChatEntry
, 1);
347 pce
->label
= _("Chat _room");
348 pce
->identifier
= "room";
349 pce
->required
= TRUE
;
351 return g_list_append(NULL
, pce
);
354 static GHashTable
*null_chat_info_defaults(PurpleConnection
*gc
,
356 GHashTable
*defaults
;
358 purple_debug_info("nullprpl", "returning chat default setting "
359 "'room' = 'default'\n");
361 defaults
= g_hash_table_new_full(g_str_hash
, g_str_equal
, NULL
, g_free
);
362 g_hash_table_insert(defaults
, "room", g_strdup("default"));
366 static void null_login(PurpleAccount
*acct
)
368 PurpleConnection
*gc
= purple_account_get_connection(acct
);
369 GList
*offline_messages
;
371 purple_debug_info("nullprpl", "logging in %s\n", purple_account_get_username(acct
));
373 purple_connection_set_flags(gc
, PURPLE_CONNECTION_FLAG_NO_IMAGES
);
375 purple_connection_update_progress(gc
, _("Connecting"),
376 0, /* which connection step this is */
377 2); /* total number of steps */
379 purple_connection_update_progress(gc
, _("Connected"),
380 1, /* which connection step this is */
381 2); /* total number of steps */
382 purple_connection_set_state(gc
, PURPLE_CONNECTION_CONNECTED
);
384 /* tell purple about everyone on our buddy list who's connected */
385 foreach_null_gc(discover_status
, gc
, NULL
);
387 /* notify other nullprotocol accounts */
388 foreach_null_gc(report_status_change
, gc
, NULL
);
390 /* fetch stored offline messages */
391 purple_debug_info("nullprpl", "checking for offline messages for %s\n",
392 purple_account_get_username(acct
));
393 offline_messages
= g_hash_table_lookup(goffline_messages
, purple_account_get_username(acct
));
394 while (offline_messages
) {
395 GOfflineMessage
*message
= (GOfflineMessage
*)offline_messages
->data
;
396 purple_debug_info("nullprpl", "delivering offline message to %s: %s\n",
397 purple_account_get_username(acct
), message
->message
);
398 purple_serv_got_im(gc
, message
->from
, message
->message
, message
->flags
,
400 offline_messages
= g_list_next(offline_messages
);
402 g_free(message
->from
);
403 g_free(message
->message
);
407 g_list_free(offline_messages
);
408 g_hash_table_remove(goffline_messages
, purple_account_get_username(acct
));
411 static void null_close(PurpleConnection
*gc
)
413 /* notify other nullprotocol accounts */
414 foreach_null_gc(report_status_change
, gc
, NULL
);
417 static int null_send_im(PurpleConnection
*gc
, PurpleMessage
*msg
)
419 const char *from_username
= purple_account_get_username(purple_connection_get_account(gc
));
420 const gchar
*who
= purple_message_get_recipient(msg
);
421 PurpleMessageFlags receive_flags
;
422 PurpleAccount
*to_acct
= purple_accounts_find(who
, "null");
423 PurpleConnection
*to
;
424 const gchar
*message
= purple_message_get_contents(msg
);
426 receive_flags
= ((purple_message_get_flags(msg
) & ~PURPLE_MESSAGE_SEND
) | PURPLE_MESSAGE_RECV
);
428 purple_debug_info("nullprpl", "sending message from %s to %s: %s\n",
429 from_username
, who
, message
);
431 /* is the sender blocked by the recipient's privacy settings? */
433 !purple_account_privacy_check(to_acct
, purple_account_get_username(purple_connection_get_account(gc
)))) {
434 char *msg
= g_strdup_printf(
435 _("Your message was blocked by %s's privacy settings."), who
);
436 purple_debug_info("nullprpl",
437 "discarding; %s is blocked by %s's privacy settings\n",
439 purple_conversation_present_error(who
, purple_connection_get_account(gc
), msg
);
444 /* is the recipient online? */
445 to
= get_null_gc(who
);
446 if (to
) { /* yes, send */
447 purple_serv_got_im(to
, from_username
, message
, receive_flags
, time(NULL
));
449 } else { /* nope, store as an offline message */
450 GOfflineMessage
*offline_message
;
453 purple_debug_info("nullprpl",
454 "%s is offline, sending as offline message\n", who
);
455 offline_message
= g_new0(GOfflineMessage
, 1);
456 offline_message
->from
= g_strdup(from_username
);
457 offline_message
->message
= g_strdup(message
);
458 offline_message
->mtime
= time(NULL
);
459 offline_message
->flags
= receive_flags
;
461 messages
= g_hash_table_lookup(goffline_messages
, who
);
462 messages
= g_list_append(messages
, offline_message
);
463 g_hash_table_insert(goffline_messages
, g_strdup(who
), messages
);
469 static void null_set_info(PurpleConnection
*gc
, const char *info
) {
470 purple_debug_info("nullprpl", "setting %s's user info to %s\n",
471 purple_account_get_username(purple_connection_get_account(gc
)), info
);
474 static const char *typing_state_to_string(PurpleIMTypingState typing
) {
476 case PURPLE_IM_NOT_TYPING
: return "is not typing";
477 case PURPLE_IM_TYPING
: return "is typing";
478 case PURPLE_IM_TYPED
: return "stopped typing momentarily";
479 default: return "unknown typing state";
483 static void notify_typing(PurpleConnection
*from
, PurpleConnection
*to
,
485 const char *from_username
= purple_account_get_username(purple_connection_get_account(from
));
486 const char *action
= typing_state_to_string((PurpleIMTypingState
)typing
);
487 purple_debug_info("nullprpl", "notifying %s that %s %s\n",
488 purple_account_get_username(purple_connection_get_account(to
)), from_username
, action
);
490 purple_serv_got_typing(to
,
492 0, /* if non-zero, a timeout in seconds after which to
493 * reset the typing status to PURPLE_IM_NOT_TYPING */
494 (PurpleIMTypingState
)typing
);
497 static unsigned int null_send_typing(PurpleConnection
*gc
, const char *name
,
498 PurpleIMTypingState typing
) {
499 purple_debug_info("nullprpl", "%s %s\n", purple_account_get_username(purple_connection_get_account(gc
)),
500 typing_state_to_string(typing
));
501 foreach_null_gc(notify_typing
, gc
, (gpointer
)typing
);
505 static void null_get_info(PurpleConnection
*gc
, const char *username
) {
507 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
510 purple_debug_info("nullprpl", "Fetching %s's user info for %s\n", username
,
511 purple_account_get_username(purple_connection_get_account(gc
)));
513 acct
= purple_accounts_find(username
, "null");
515 if (!get_null_gc(username
)) {
516 char *msg
= g_strdup_printf(_("%s is not logged in."), username
);
517 purple_notify_error(gc
, _("User Info"), _("User info not available. "), msg
,
518 purple_request_cpar_from_account(acct
));
523 body
= purple_account_get_user_info(acct
);
525 body
= _("No user info.");
526 /* TODO: Check whether it's correct to call add_pair_html,
527 or if we should be using add_pair_plaintext */
528 purple_notify_user_info_add_pair_html(info
, "Info", body
);
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 */
534 NULL
, /* callback called when dialog closed */
535 NULL
); /* userdata for callback */
538 static void null_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 purple_account_get_username(acct
), purple_status_get_name(status
), msg
);
543 foreach_null_gc(report_status_change
, get_null_gc(purple_account_get_username(acct
)),
547 static void null_set_idle(PurpleConnection
*gc
, int idletime
) {
548 purple_debug_info("nullprpl",
549 "purple reports that %s has been idle for %d seconds\n",
550 purple_account_get_username(purple_connection_get_account(gc
)), idletime
);
553 static void null_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 purple_account_get_username(purple_connection_get_account(gc
)));
559 static void null_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
,
560 PurpleGroup
*group
, const char *message
)
562 const char *username
= purple_account_get_username(purple_connection_get_account(gc
));
563 PurpleConnection
*buddy_gc
= get_null_gc(purple_buddy_get_name(buddy
));
565 purple_debug_info("nullprpl", "adding %s to %s's buddy list\n", purple_buddy_get_name(buddy
),
569 PurpleAccount
*buddy_acct
= purple_connection_get_account(buddy_gc
);
571 discover_status(gc
, buddy_gc
, NULL
);
573 if (purple_blist_find_buddy(buddy_acct
, username
)) {
574 purple_debug_info("nullprpl", "%s is already on %s's buddy list\n",
575 username
, purple_buddy_get_name(buddy
));
577 purple_debug_info("nullprpl", "asking %s if they want to add %s\n",
578 purple_buddy_get_name(buddy
), username
);
579 purple_account_request_add(buddy_acct
,
581 NULL
, /* local account id (rarely used) */
583 message
); /* message */
588 static void null_add_buddies(PurpleConnection
*gc
, GList
*buddies
,
589 GList
*groups
, const char *message
) {
590 GList
*buddy
= buddies
;
591 GList
*group
= groups
;
593 purple_debug_info("nullprpl", "adding multiple buddies\n");
595 while (buddy
&& group
) {
596 null_add_buddy(gc
, (PurpleBuddy
*)buddy
->data
, (PurpleGroup
*)group
->data
, message
);
597 buddy
= g_list_next(buddy
);
598 group
= g_list_next(group
);
602 static void null_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
,
605 purple_debug_info("nullprpl", "removing %s from %s's buddy list\n",
606 purple_buddy_get_name(buddy
),
607 purple_account_get_username(purple_connection_get_account(gc
)));
610 static void null_remove_buddies(PurpleConnection
*gc
, GList
*buddies
,
612 GList
*buddy
= buddies
;
613 GList
*group
= groups
;
615 purple_debug_info("nullprpl", "removing multiple buddies\n");
617 while (buddy
&& group
) {
618 null_remove_buddy(gc
, (PurpleBuddy
*)buddy
->data
,
619 (PurpleGroup
*)group
->data
);
620 buddy
= g_list_next(buddy
);
621 group
= g_list_next(group
);
626 * nullprotocol uses purple's local whitelist and blacklist, stored in blist.xml, as
627 * its authoritative privacy settings, and uses purple's logic (specifically
628 * purple_privacy_check(), from privacy.h), to determine whether messages are
629 * allowed or blocked.
631 static void null_add_permit(PurpleConnection
*gc
, const char *name
) {
632 purple_debug_info("nullprpl", "%s adds %s to their allowed list\n",
633 purple_account_get_username(purple_connection_get_account(gc
)), name
);
636 static void null_add_deny(PurpleConnection
*gc
, const char *name
) {
637 purple_debug_info("nullprpl", "%s adds %s to their blocked list\n",
638 purple_account_get_username(purple_connection_get_account(gc
)), name
);
641 static void null_rem_permit(PurpleConnection
*gc
, const char *name
) {
642 purple_debug_info("nullprpl", "%s removes %s from their allowed list\n",
643 purple_account_get_username(purple_connection_get_account(gc
)), name
);
646 static void null_rem_deny(PurpleConnection
*gc
, const char *name
) {
647 purple_debug_info("nullprpl", "%s removes %s from their blocked list\n",
648 purple_account_get_username(purple_connection_get_account(gc
)), name
);
651 static void null_set_permit_deny(PurpleConnection
*gc
) {
652 /* this is for synchronizing the local black/whitelist with the server.
653 * for nullprotocol, it's a noop.
657 static void joined_chat(PurpleChatConversation
*from
, PurpleChatConversation
*to
,
658 int id
, const char *room
, gpointer userdata
) {
659 /* tell their chat window that we joined */
660 purple_debug_info("nullprpl", "%s sees that %s joined chat room %s\n",
661 purple_chat_conversation_get_nick(to
), purple_chat_conversation_get_nick(from
), room
);
662 purple_chat_conversation_add_user(to
,
663 purple_chat_conversation_get_nick(from
),
664 NULL
, /* user-provided join message, IRC style */
665 PURPLE_CHAT_USER_NONE
,
666 TRUE
); /* show a join message */
669 /* add them to our chat window */
670 purple_debug_info("nullprpl", "%s sees that %s is in chat room %s\n",
671 purple_chat_conversation_get_nick(from
), purple_chat_conversation_get_nick(to
), room
);
672 purple_chat_conversation_add_user(from
,
673 purple_chat_conversation_get_nick(to
),
674 NULL
, /* user-provided join message, IRC style */
675 PURPLE_CHAT_USER_NONE
,
676 FALSE
); /* show a join message */
680 static void null_join_chat(PurpleConnection
*gc
, GHashTable
*components
) {
681 const char *username
= purple_account_get_username(purple_connection_get_account(gc
));
682 const char *room
= g_hash_table_lookup(components
, "room");
683 int chat_id
= g_str_hash(room
);
684 purple_debug_info("nullprpl", "%s is joining chat room %s\n", username
, room
);
686 if (!purple_conversations_find_chat(gc
, chat_id
)) {
687 purple_serv_got_joined_chat(gc
, chat_id
, room
);
689 /* tell everyone that we joined, and add them if they're already there */
690 foreach_gc_in_chat(joined_chat
, gc
, chat_id
, NULL
);
692 char *tmp
= g_strdup_printf(_("%s is already in chat room %s."),
695 purple_debug_info("nullprpl", "%s is already in chat room %s\n", username
,
697 purple_notify_info(gc
, _("Join chat"), _("Join chat"), tmp
,
698 purple_request_cpar_from_connection(gc
));
703 static void null_reject_chat(PurpleConnection
*gc
, GHashTable
*components
) {
704 const char *invited_by
= g_hash_table_lookup(components
, "invited_by");
705 const char *room
= g_hash_table_lookup(components
, "room");
706 const char *username
= purple_account_get_username(purple_connection_get_account(gc
));
707 PurpleConnection
*invited_by_gc
= get_null_gc(invited_by
);
708 char *message
= g_strdup_printf(
711 _("has rejected your invitation to join the chat room"),
714 purple_debug_info("nullprpl",
715 "%s has rejected %s's invitation to join chat room %s\n",
716 username
, invited_by
, room
);
718 purple_notify_info(invited_by_gc
,
719 _("Chat invitation rejected"),
720 _("Chat invitation rejected"),
722 purple_request_cpar_from_connection(gc
));
726 static char *null_get_chat_name(GHashTable
*components
) {
727 const char *room
= g_hash_table_lookup(components
, "room");
728 purple_debug_info("nullprpl", "reporting chat room name '%s'\n", room
);
729 return g_strdup(room
);
732 static void null_chat_invite(PurpleConnection
*gc
, int id
,
733 const char *message
, const char *who
) {
734 const char *username
= purple_account_get_username(purple_connection_get_account(gc
));
735 PurpleChatConversation
*chat
= purple_conversations_find_chat(gc
, id
);
736 const char *room
= purple_conversation_get_name(PURPLE_CONVERSATION(chat
));
737 PurpleAccount
*to_acct
= purple_accounts_find(who
, "null");
739 purple_debug_info("nullprpl", "%s is inviting %s to join chat room %s\n",
740 username
, who
, room
);
743 PurpleChatConversation
*to_conv
= purple_conversations_find_chat(purple_account_get_connection(to_acct
), id
);
745 char *tmp
= g_strdup_printf("%s is already in chat room %s.", who
, room
);
746 purple_debug_info("nullprpl",
747 "%s is already in chat room %s; "
748 "ignoring invitation from %s\n",
749 who
, room
, username
);
750 purple_notify_info(gc
, _("Chat invitation"), _("Chat invitation"), tmp
,
751 purple_request_cpar_from_conversation(PURPLE_CONVERSATION(to_conv
)));
754 GHashTable
*components
;
755 components
= g_hash_table_new_full(g_str_hash
, g_str_equal
, NULL
, g_free
);
756 g_hash_table_replace(components
, "room", g_strdup(room
));
757 g_hash_table_replace(components
, "invited_by", g_strdup(username
));
758 purple_serv_got_chat_invite(purple_account_get_connection(to_acct
), room
, username
, message
, components
);
763 static void left_chat_room(PurpleChatConversation
*from
, PurpleChatConversation
*to
,
764 int id
, const char *room
, gpointer userdata
) {
766 /* tell their chat window that we left */
767 purple_debug_info("nullprpl", "%s sees that %s left chat room %s\n",
768 purple_chat_conversation_get_nick(to
), purple_chat_conversation_get_nick(from
), room
);
769 purple_chat_conversation_remove_user(to
,
770 purple_chat_conversation_get_nick(from
),
771 NULL
); /* user-provided message, IRC style */
775 static void null_chat_leave(PurpleConnection
*gc
, int id
) {
776 PurpleChatConversation
*chat
= purple_conversations_find_chat(gc
, id
);
777 purple_debug_info("nullprpl", "%s is leaving chat room %s\n",
778 purple_account_get_username(purple_connection_get_account(gc
)),
779 purple_conversation_get_name(PURPLE_CONVERSATION(chat
)));
781 /* tell everyone that we left */
782 foreach_gc_in_chat(left_chat_room
, gc
, id
, NULL
);
785 static void receive_chat_message(PurpleChatConversation
*from
, PurpleChatConversation
*to
,
786 int id
, const char *room
, gpointer userdata
) {
787 const char *message
= (const char *)userdata
;
788 PurpleConnection
*to_gc
= get_null_gc(purple_chat_conversation_get_nick(to
));
790 purple_debug_info("nullprpl",
791 "%s receives message from %s in chat room %s: %s\n",
792 purple_chat_conversation_get_nick(to
), purple_chat_conversation_get_nick(from
), room
, message
);
793 purple_serv_got_chat_in(to_gc
, id
, purple_chat_conversation_get_nick(from
), PURPLE_MESSAGE_RECV
, message
,
797 static int null_chat_send(PurpleConnection
*gc
, int id
, PurpleMessage
*msg
) {
798 const char *username
= purple_account_get_username(purple_connection_get_account(gc
));
799 PurpleChatConversation
*chat
= purple_conversations_find_chat(gc
, id
);
800 const gchar
*message
= purple_message_get_contents(msg
);
803 purple_debug_info("nullprpl",
804 "%s is sending message to chat room %s: %s\n", username
,
805 purple_conversation_get_name(PURPLE_CONVERSATION(chat
)), message
);
807 /* send message to everyone in the chat room */
808 foreach_gc_in_chat(receive_chat_message
, gc
, id
, (gpointer
)message
);
811 purple_debug_info("nullprpl",
812 "tried to send message from %s to chat room #%d: %s\n"
813 "but couldn't find chat room",
814 username
, id
, message
);
819 static void null_register_user(PurpleAccount
*acct
) {
820 purple_debug_info("nullprpl", "registering account for %s\n",
821 purple_account_get_username(acct
));
824 static void null_alias_buddy(PurpleConnection
*gc
, const char *who
,
826 purple_debug_info("nullprpl", "%s sets %s's alias to %s\n",
827 purple_account_get_username(purple_connection_get_account(gc
)), who
, alias
);
830 static void null_group_buddy(PurpleConnection
*gc
, const char *who
,
831 const char *old_group
,
832 const char *new_group
) {
833 purple_debug_info("nullprpl", "%s has moved %s from group %s to group %s\n",
834 purple_account_get_username(purple_connection_get_account(gc
)), who
, old_group
, new_group
);
837 static void null_rename_group(PurpleConnection
*gc
, const char *old_name
,
838 PurpleGroup
*group
, GList
*moved_buddies
) {
839 purple_debug_info("nullprpl", "%s has renamed group %s to %s\n",
840 purple_account_get_username(purple_connection_get_account(gc
)), old_name
,
841 purple_group_get_name(group
));
844 static void null_convo_closed(PurpleConnection
*gc
, const char *who
) {
845 purple_debug_info("nullprpl", "%s's conversation with %s was closed\n",
846 purple_account_get_username(purple_connection_get_account(gc
)), who
);
849 /* normalize a username (e.g. remove whitespace, add default domain, etc.)
850 * for nullprotocol, this is a noop.
852 static const char *null_normalize(const PurpleAccount
*acct
,
857 static void null_set_buddy_icon(PurpleConnection
*gc
,
858 PurpleStoredImage
*img
) {
859 purple_debug_info("nullprpl", "setting %s's buddy icon to %s\n",
860 purple_account_get_username(purple_connection_get_account(gc
)),
861 img
? purple_imgstore_get_filename(img
) : "(null)");
864 static void null_remove_group(PurpleConnection
*gc
, PurpleGroup
*group
) {
865 purple_debug_info("nullprpl", "%s has removed group %s\n",
866 purple_account_get_username(purple_connection_get_account(gc
)),
867 purple_group_get_name(group
));
871 static void set_chat_topic_fn(PurpleChatConversation
*from
, PurpleChatConversation
*to
,
872 int id
, const char *room
, gpointer userdata
) {
873 const char *topic
= (const char *)userdata
;
874 const char *username
= purple_account_get_username(purple_conversation_get_account(PURPLE_CONVERSATION(from
)));
877 purple_chat_conversation_set_topic(to
, username
, topic
);
880 msg
= g_strdup_printf(_("%s sets topic to: %s"), username
, topic
);
882 msg
= g_strdup_printf(_("%s clears topic"), username
);
884 purple_conversation_write_system_message(PURPLE_CONVERSATION(to
),
885 msg
, PURPLE_MESSAGE_NO_LOG
);
889 static void null_set_chat_topic(PurpleConnection
*gc
, int id
,
891 PurpleChatConversation
*chat
= purple_conversations_find_chat(gc
, id
);
892 const char *last_topic
;
897 purple_debug_info("nullprpl", "%s sets topic of chat room '%s' to '%s'\n",
898 purple_account_get_username(purple_connection_get_account(gc
)),
899 purple_conversation_get_name(PURPLE_CONVERSATION(chat
)), topic
);
901 last_topic
= purple_chat_conversation_get_topic(chat
);
902 if ((!topic
&& !last_topic
) ||
903 (topic
&& last_topic
&& !strcmp(topic
, last_topic
)))
904 return; /* topic is unchanged, this is a noop */
906 foreach_gc_in_chat(set_chat_topic_fn
, gc
, id
, (gpointer
)topic
);
909 static gboolean
null_finish_get_roomlist(gpointer roomlist
) {
910 purple_roomlist_set_in_progress(PURPLE_ROOMLIST(roomlist
), FALSE
);
911 g_object_unref(roomlist
);
916 static PurpleRoomlist
*null_roomlist_get_list(PurpleConnection
*gc
) {
917 const char *username
= purple_account_get_username(purple_connection_get_account(gc
));
918 PurpleRoomlist
*roomlist
= purple_roomlist_new(purple_connection_get_account(gc
));
919 GList
*fields
= NULL
;
920 PurpleRoomlistField
*field
;
922 GList
*seen_ids
= NULL
;
924 purple_debug_info("nullprpl", "%s asks for room list; returning:\n", username
);
926 /* set up the room list */
927 field
= purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING
, "room",
928 "room", TRUE
/* hidden */);
929 fields
= g_list_append(fields
, field
);
931 field
= purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_INT
, "Id", "Id", FALSE
);
932 fields
= g_list_append(fields
, field
);
934 purple_roomlist_set_fields(roomlist
, fields
);
936 /* add each chat room. the chat ids are cached in seen_ids so that each room
937 * is only returned once, even if multiple users are in it. */
938 for (chats
= purple_conversations_get_chats(); chats
; chats
= g_list_next(chats
)) {
939 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(chats
->data
);
940 PurpleRoomlistRoom
*room
;
941 const char *name
= purple_conversation_get_name(PURPLE_CONVERSATION(chat
));
942 int id
= purple_chat_conversation_get_id(chat
);
944 /* have we already added this room? */
945 if (g_list_find_custom(seen_ids
, name
, (GCompareFunc
)strcmp
))
946 continue; /* yes! try the next one. */
948 /* This cast is OK because this list is only staying around for the life
949 * of this function and none of the conversations are being deleted
950 * in that timespan. */
951 seen_ids
= g_list_prepend(seen_ids
, (char *)name
); /* no, it's new. */
952 purple_debug_info("nullprpl", "%s (%d), ", name
, id
);
954 room
= purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM
, name
, NULL
);
955 purple_roomlist_room_add_field(roomlist
, room
, name
);
956 purple_roomlist_room_add_field(roomlist
, room
, &id
);
957 purple_roomlist_room_add(roomlist
, room
);
960 g_list_free(seen_ids
);
961 purple_timeout_add(1 /* ms */, null_finish_get_roomlist
, g_object_ref(roomlist
));
965 static void null_roomlist_cancel(PurpleRoomlist
*list
) {
966 PurpleAccount
*account
= purple_roomlist_get_account(list
);
967 purple_debug_info("nullprpl", "%s asked to cancel room list request\n",
968 purple_account_get_username(account
));
971 static void null_roomlist_expand_category(PurpleRoomlist
*list
,
972 PurpleRoomlistRoom
*category
) {
973 PurpleAccount
*account
= purple_roomlist_get_account(list
);
974 purple_debug_info("nullprpl", "%s asked to expand room list category %s\n",
975 purple_account_get_username(account
),
976 purple_roomlist_room_get_name(category
));
979 static gboolean
null_offline_message(const PurpleBuddy
*buddy
) {
980 purple_debug_info("nullprpl",
981 "reporting that offline messages are supported for %s\n",
982 purple_buddy_get_name(buddy
));
987 * Initialize the protocol instance. see protocol.h for more information.
990 null_protocol_init(PurpleProtocol
*protocol
)
992 PurpleAccountUserSplit
*split
;
993 PurpleAccountOption
*option
;
995 protocol
->id
= "prpl-null";
996 protocol
->name
= "Null - Testing Protocol";
997 protocol
->options
= OPT_PROTO_NO_PASSWORD
| OPT_PROTO_CHAT_TOPIC
;
998 protocol
->icon_spec
= purple_buddy_icon_spec_new(
999 "png,jpg,gif", /* format */
1002 128, /* max_width */
1003 128, /* max_height */
1004 10000, /* max_filesize */
1005 PURPLE_ICON_SCALE_DISPLAY
/* scale_rules */
1008 /* see accountopt.h for information about user splits and protocol options */
1009 split
= purple_account_user_split_new(
1010 _("Example user split"), /* text shown to user */
1011 "default", /* default value */
1012 '@'); /* field separator */
1013 option
= purple_account_option_string_new(
1014 _("Example option"), /* text shown to user */
1015 "example", /* pref name */
1016 "default"); /* default value */
1018 protocol
->user_splits
= g_list_append(NULL
, split
);
1019 protocol
->account_options
= g_list_append(NULL
, option
);
1023 * Initialize the protocol class and interfaces.
1024 * see protocol.h for more information.
1028 null_protocol_class_init(PurpleProtocolClass
*klass
)
1030 klass
->login
= null_login
;
1031 klass
->close
= null_close
;
1032 klass
->status_types
= null_status_types
;
1033 klass
->list_icon
= null_list_icon
;
1037 null_protocol_client_iface_init(PurpleProtocolClientIface
*client_iface
)
1039 client_iface
->get_actions
= null_get_actions
;
1040 client_iface
->status_text
= null_status_text
;
1041 client_iface
->tooltip_text
= null_tooltip_text
;
1042 client_iface
->blist_node_menu
= null_blist_node_menu
;
1043 client_iface
->convo_closed
= null_convo_closed
;
1044 client_iface
->normalize
= null_normalize
;
1045 client_iface
->offline_message
= null_offline_message
;
1049 null_protocol_server_iface_init(PurpleProtocolServerIface
*server_iface
)
1051 server_iface
->register_user
= null_register_user
;
1052 server_iface
->set_info
= null_set_info
;
1053 server_iface
->get_info
= null_get_info
;
1054 server_iface
->set_status
= null_set_status
;
1055 server_iface
->set_idle
= null_set_idle
;
1056 server_iface
->change_passwd
= null_change_passwd
;
1057 server_iface
->add_buddy
= null_add_buddy
;
1058 server_iface
->add_buddies
= null_add_buddies
;
1059 server_iface
->remove_buddy
= null_remove_buddy
;
1060 server_iface
->remove_buddies
= null_remove_buddies
;
1061 server_iface
->alias_buddy
= null_alias_buddy
;
1062 server_iface
->group_buddy
= null_group_buddy
;
1063 server_iface
->rename_group
= null_rename_group
;
1064 server_iface
->set_buddy_icon
= null_set_buddy_icon
;
1065 server_iface
->remove_group
= null_remove_group
;
1069 null_protocol_im_iface_init(PurpleProtocolIMIface
*im_iface
)
1071 im_iface
->send
= null_send_im
;
1072 im_iface
->send_typing
= null_send_typing
;
1076 null_protocol_chat_iface_init(PurpleProtocolChatIface
*chat_iface
)
1078 chat_iface
->info
= null_chat_info
;
1079 chat_iface
->info_defaults
= null_chat_info_defaults
;
1080 chat_iface
->join
= null_join_chat
;
1081 chat_iface
->reject
= null_reject_chat
;
1082 chat_iface
->get_name
= null_get_chat_name
;
1083 chat_iface
->invite
= null_chat_invite
;
1084 chat_iface
->leave
= null_chat_leave
;
1085 chat_iface
->send
= null_chat_send
;
1086 chat_iface
->set_topic
= null_set_chat_topic
;
1090 null_protocol_privacy_iface_init(PurpleProtocolPrivacyIface
*privacy_iface
)
1092 privacy_iface
->add_permit
= null_add_permit
;
1093 privacy_iface
->add_deny
= null_add_deny
;
1094 privacy_iface
->rem_permit
= null_rem_permit
;
1095 privacy_iface
->rem_deny
= null_rem_deny
;
1096 privacy_iface
->set_permit_deny
= null_set_permit_deny
;
1100 null_protocol_roomlist_iface_init(PurpleProtocolRoomlistIface
*roomlist_iface
)
1102 roomlist_iface
->get_list
= null_roomlist_get_list
;
1103 roomlist_iface
->cancel
= null_roomlist_cancel
;
1104 roomlist_iface
->expand_category
= null_roomlist_expand_category
;
1108 * define the null protocol type. this macro defines
1109 * null_protocol_register_type(PurplePlugin *) which is called in plugin_load()
1110 * to register this type with the type system, and null_protocol_get_type()
1111 * which returns the registered GType.
1113 PURPLE_DEFINE_TYPE_EXTENDED(
1114 NullProtocol
, null_protocol
, PURPLE_TYPE_PROTOCOL
, 0,
1116 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CLIENT_IFACE
,
1117 null_protocol_client_iface_init
)
1119 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_SERVER_IFACE
,
1120 null_protocol_server_iface_init
)
1122 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_IM_IFACE
,
1123 null_protocol_im_iface_init
)
1125 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CHAT_IFACE
,
1126 null_protocol_chat_iface_init
)
1128 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_PRIVACY_IFACE
,
1129 null_protocol_privacy_iface_init
)
1131 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_ROOMLIST_IFACE
,
1132 null_protocol_roomlist_iface_init
)
1135 static PurplePluginInfo
*
1136 plugin_query(GError
**error
)
1138 return purple_plugin_info_new(
1140 "name", "Null Protocol",
1141 "version", DISPLAY_VERSION
,
1142 "category", N_("Protocol"),
1143 "summary", N_("Null Protocol Plugin"),
1144 "description", N_("Null Protocol Plugin"),
1145 "website", PURPLE_WEBSITE
,
1146 "abi-version", PURPLE_ABI_VERSION
,
1148 /* If you're using this protocol plugin as the basis of a plugin that will
1149 * be distributed separately from libpurple, do not include these flags. */
1150 "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL
|
1151 PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD
,
1157 plugin_load(PurplePlugin
*plugin
, GError
**error
)
1161 /* register the NULL_TYPE_PROTOCOL type in the type system. this function
1162 * is defined by PURPLE_DEFINE_TYPE_EXTENDED. */
1163 null_protocol_register_type(plugin
);
1165 /* add the protocol to the core */
1166 my_protocol
= purple_protocols_add(NULL_TYPE_PROTOCOL
, error
);
1170 purple_debug_info("nullprpl", "starting up\n");
1172 /* get ready to store offline messages */
1173 goffline_messages
= g_hash_table_new_full(g_str_hash
, /* hash fn */
1174 g_str_equal
, /* key comparison fn */
1175 g_free
, /* key free fn */
1176 NULL
); /* value free fn */
1182 plugin_unload(PurplePlugin
*plugin
, GError
**error
)
1184 purple_debug_info("nullprpl", "shutting down\n");
1186 /* remove the protocol from the core */
1187 if (!purple_protocols_remove(my_protocol
, error
))
1193 /* initialize the plugin */
1194 PURPLE_PLUGIN_INIT(null
, plugin_query
, plugin_load
, plugin_unload
);