Migrate certificates, icons, logs to XDG dirs
[pidgin-git.git] / libpurple / protocols / null / nullprpl.c
blobaf816d9eff3450e1929a6316d7076a233cbe3a58
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, 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
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 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. */
53 #include "internal.h"
55 #include "nullprpl.h"
57 #include "account.h"
58 #include "accountopt.h"
59 #include "buddylist.h"
60 #include "cmds.h"
61 #include "conversation.h"
62 #include "connection.h"
63 #include "debug.h"
64 #include "notify.h"
65 #include "plugins.h"
66 #include "roomlist.h"
67 #include "status.h"
68 #include "util.h"
69 #include "version.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,
83 PurpleConnection *to,
84 gpointer userdata);
86 typedef struct {
87 GcFunc fn;
88 PurpleConnection *from;
89 gpointer userdata;
90 } GcFuncData;
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;
98 typedef struct {
99 char *from;
100 char *message;
101 time_t mtime;
102 PurpleMessageFlags flags;
103 } GOfflineMessage;
106 * helpers
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);
112 else
113 return NULL;
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,
125 gpointer userdata) {
126 GcFuncData gcfdata = { fn, from, userdata };
127 g_list_foreach(purple_connections_get_all(), call_if_nullprotocol,
128 &gcfdata);
132 typedef void(*ChatFunc)(PurpleChatConversation *from, PurpleChatConversation *to,
133 int id, const char *room, gpointer userdata);
135 typedef struct {
136 ChatFunc fn;
137 PurpleChatConversation *from_chat;
138 gpointer userdata;
139 } ChatFuncData;
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);
147 if (chat)
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,
156 chat,
157 userdata };
159 g_list_foreach(purple_connections_get_all(), call_chat_func,
160 &cfdata);
164 static void discover_status(PurpleConnection *from, PurpleConnection *to,
165 gpointer userdata) {
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);
181 } else {
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,
190 gpointer userdata) {
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);
198 * UI callbacks
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);
211 * Protocol functions
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)
222 return "null";
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");
236 char *text;
237 if (message && *message)
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",
243 purple_buddy_get_name(buddy), text);
244 return text;
246 } else {
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,
254 gboolean full) {
255 PurpleConnection *gc = get_null_gc(purple_buddy_get_name(buddy));
257 if (gc) {
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),
265 msg);
266 g_free(msg);
268 if (full) {
269 const char *user_info = purple_account_get_user_info(purple_connection_get_account(gc));
270 if (user_info)
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);
276 } else {
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)
287 GList *types = NULL;
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),
297 NULL);
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),
303 NULL);
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),
309 NULL);
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 */
320 _("Primary title"),
321 _("Secondary title"),
322 _("This is the callback for the NullProtocol menu item."),
323 NULL);
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);
336 } else {
337 return NULL;
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,
355 const char *room) {
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"));
363 return defaults;
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,
399 message->mtime);
400 offline_messages = g_list_next(offline_messages);
402 g_free(message->from);
403 g_free(message->message);
404 g_free(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? */
432 if (to_acct &&
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",
438 from_username, who);
439 purple_conversation_present_error(who, purple_connection_get_account(gc), msg);
440 g_free(msg);
441 return 0;
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;
451 GList *messages;
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);
466 return 1;
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) {
475 switch (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,
484 gpointer typing) {
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,
491 from_username,
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);
502 return 0;
505 static void null_get_info(PurpleConnection *gc, const char *username) {
506 const char *body;
507 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
508 PurpleAccount *acct;
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));
519 g_free(msg);
522 if (acct)
523 body = purple_account_get_user_info(acct);
524 else
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 */
533 info, /* body */
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)),
544 NULL);
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),
566 username);
568 if (buddy_gc) {
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));
576 } else {
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,
580 username,
581 NULL, /* local account id (rarely used) */
582 NULL, /* alias */
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,
603 PurpleGroup *group)
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,
611 GList *groups) {
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 */
668 if (from != to) {
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);
691 } else {
692 char *tmp = g_strdup_printf(_("%s is already in chat room %s."),
693 username,
694 room);
695 purple_debug_info("nullprpl", "%s is already in chat room %s\n", username,
696 room);
697 purple_notify_info(gc, _("Join chat"), _("Join chat"), tmp,
698 purple_request_cpar_from_connection(gc));
699 g_free(tmp);
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(
709 "%s %s %s.",
710 username,
711 _("has rejected your invitation to join the chat room"),
712 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"),
721 message,
722 purple_request_cpar_from_connection(gc));
723 g_free(message);
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);
742 if (to_acct) {
743 PurpleChatConversation *to_conv = purple_conversations_find_chat(purple_account_get_connection(to_acct), id);
744 if (to_conv) {
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)));
752 g_free(tmp);
753 } else {
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) {
765 if (from != to) {
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,
794 time(NULL));
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);
802 if (chat) {
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);
809 return 0;
810 } else {
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);
815 return -1;
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,
825 const char *alias) {
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,
853 const char *input) {
854 return NULL;
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)));
875 char *msg;
877 purple_chat_conversation_set_topic(to, username, topic);
879 if (topic && *topic)
880 msg = g_strdup_printf(_("%s sets topic to: %s"), username, topic);
881 else
882 msg = g_strdup_printf(_("%s clears topic"), username);
884 purple_conversation_write_system_message(PURPLE_CONVERSATION(to),
885 msg, PURPLE_MESSAGE_NO_LOG);
886 g_free(msg);
889 static void null_set_chat_topic(PurpleConnection *gc, int id,
890 const char *topic) {
891 PurpleChatConversation *chat = purple_conversations_find_chat(gc, id);
892 const char *last_topic;
894 if (!chat)
895 return;
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);
913 return FALSE;
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;
921 GList *chats;
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));
962 return 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));
983 return TRUE;
987 * Initialize the protocol instance. see protocol.h for more information.
989 static void
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 */
1000 0, /* min_width */
1001 0, /* min_height */
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.
1027 static void
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;
1036 static void
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;
1048 static void
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;
1068 static void
1069 null_protocol_im_iface_init(PurpleProtocolIMIface *im_iface)
1071 im_iface->send = null_send_im;
1072 im_iface->send_typing = null_send_typing;
1075 static void
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;
1089 static void
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;
1099 static void
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(
1139 "id", "prpl-null",
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,
1152 NULL
1156 static gboolean
1157 plugin_load(PurplePlugin *plugin, GError **error)
1159 PurpleCmdId id;
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);
1167 if (!my_protocol)
1168 return FALSE;
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 */
1178 return TRUE;
1181 static gboolean
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))
1188 return FALSE;
1190 return TRUE;
1193 /* initialize the plugin */
1194 PURPLE_PLUGIN_INIT(null, plugin_query, plugin_load, plugin_unload);