diff options
Diffstat (limited to 'protocols/purple/purple.c')
| -rw-r--r-- | protocols/purple/purple.c | 1152 | 
1 files changed, 1152 insertions, 0 deletions
| diff --git a/protocols/purple/purple.c b/protocols/purple/purple.c new file mode 100644 index 00000000..b8d74ba1 --- /dev/null +++ b/protocols/purple/purple.c @@ -0,0 +1,1152 @@ +/***************************************************************************\ +*                                                                           * +*  BitlBee - An IRC to IM gateway                                           * +*  libpurple module - Main file                                             * +*                                                                           * +*  Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net>              * +*                                                                           * +*  This program is free software; you can redistribute it and/or modify     * +*  it under the terms of the GNU General Public License as published by     * +*  the Free Software Foundation; either version 2 of the License, or        * +*  (at your option) any later version.                                      * +*                                                                           * +*  This program is distributed in the hope that it will be useful,          * +*  but WITHOUT ANY WARRANTY; without even the implied warranty of           * +*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            * +*  GNU General Public License for more details.                             * +*                                                                           * +*  You should have received a copy of the GNU General Public License along  * +*  with this program; if not, write to the Free Software Foundation, Inc.,  * +*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              * +*                                                                           * +\***************************************************************************/ + +#include "bitlbee.h" +#include "help.h" + +#include <stdarg.h> + +#include <glib.h> +#include <purple.h> + +GSList *purple_connections; + +/* This makes me VERY sad... :-( But some libpurple callbacks come in without +   any context so this is the only way to get that. Don't want to support +   libpurple in daemon mode anyway. */ +static bee_t *local_bee; + +static char *set_eval_display_name( set_t *set, char *value ); + +struct im_connection *purple_ic_by_pa( PurpleAccount *pa ) +{ +	GSList *i; +	 +	for( i = purple_connections; i; i = i->next ) +		if( ((struct im_connection *)i->data)->proto_data == pa ) +			return i->data; +	 +	return NULL; +} + +static struct im_connection *purple_ic_by_gc( PurpleConnection *gc ) +{ +	return purple_ic_by_pa( purple_connection_get_account( gc ) ); +} + +static gboolean purple_menu_cmp( const char *a, const char *b ) +{ +	while( *a && *b ) +	{ +		while( *a == '_' ) a ++; +		while( *b == '_' ) b ++; +		if( tolower( *a ) != tolower( *b ) ) +			return FALSE; +		 +		a ++; +		b ++; +	} +	 +	return ( *a == '\0' && *b == '\0' ); +} + +static void purple_init( account_t *acc ) +{ +	PurplePlugin *prpl = purple_plugins_find_with_id( (char*) acc->prpl->data ); +	PurplePluginProtocolInfo *pi = prpl->info->extra_info; +	PurpleAccount *pa; +	GList *i, *st; +	set_t *s; +	char help_title[64]; +	GString *help; +	 +	help = g_string_new( "" ); +	g_string_printf( help, "BitlBee libpurple module %s (%s).\n\nSupported settings:", +	                        (char*) acc->prpl->name, prpl->info->name ); +	 +	/* Convert all protocol_options into per-account setting variables. */ +	for( i = pi->protocol_options; i; i = i->next ) +	{ +		PurpleAccountOption *o = i->data; +		const char *name; +		char *def = NULL; +		set_eval eval = NULL; +		void *eval_data = NULL; +		GList *io = NULL; +		GSList *opts = NULL; +		 +		name = purple_account_option_get_setting( o ); +		 +		switch( purple_account_option_get_type( o ) ) +		{ +		case PURPLE_PREF_STRING: +			def = g_strdup( purple_account_option_get_default_string( o ) ); +			 +			g_string_append_printf( help, "\n* %s (%s), %s, default: %s", +			                        name, purple_account_option_get_text( o ), +			                        "string", def ); +			 +			break; +		 +		case PURPLE_PREF_INT: +			def = g_strdup_printf( "%d", purple_account_option_get_default_int( o ) ); +			eval = set_eval_int; +			 +			g_string_append_printf( help, "\n* %s (%s), %s, default: %s", +			                        name, purple_account_option_get_text( o ), +			                        "integer", def ); +			 +			break; +		 +		case PURPLE_PREF_BOOLEAN: +			if( purple_account_option_get_default_bool( o ) ) +				def = g_strdup( "true" ); +			else +				def = g_strdup( "false" ); +			eval = set_eval_bool; +			 +			g_string_append_printf( help, "\n* %s (%s), %s, default: %s", +			                        name, purple_account_option_get_text( o ), +			                        "boolean", def ); +			 +			break; +		 +		case PURPLE_PREF_STRING_LIST: +			def = g_strdup( purple_account_option_get_default_list_value( o ) ); +			 +			g_string_append_printf( help, "\n* %s (%s), %s, default: %s", +			                        name, purple_account_option_get_text( o ), +			                        "list", def ); +			g_string_append( help, "\n  Possible values: " ); +			 +			for( io = purple_account_option_get_list( o ); io; io = io->next ) +			{ +				PurpleKeyValuePair *kv = io->data; +				opts = g_slist_append( opts, kv->value ); +				/* TODO: kv->value is not a char*, WTF? */ +				if( strcmp( kv->value, kv->key ) != 0 ) +					g_string_append_printf( help, "%s (%s), ", (char*) kv->value, kv->key ); +				else +					g_string_append_printf( help, "%s, ", (char*) kv->value ); +			} +			g_string_truncate( help, help->len - 2 ); +			eval = set_eval_list; +			eval_data = opts; +			 +			break; +			 +		default: +			/** No way to talk to the user right now, invent one when +			this becomes important. +			irc_usermsg( acc->irc, "Setting with unknown type: %s (%d) Expect stuff to break..\n", +			             name, purple_account_option_get_type( o ) ); +			*/ +			name = NULL; +		} +		 +		if( name != NULL ) +		{ +			s = set_add( &acc->set, name, def, eval, acc ); +			s->flags |= ACC_SET_OFFLINE_ONLY; +			s->eval_data = eval_data; +			g_free( def ); +		} +	} +	 +	g_snprintf( help_title, sizeof( help_title ), "purple %s", (char*) acc->prpl->name ); +	help_add_mem( &global.help, help_title, help->str ); +	g_string_free( help, TRUE ); +	 +	s = set_add( &acc->set, "display_name", NULL, set_eval_display_name, acc ); +	s->flags |= ACC_SET_ONLINE_ONLY; +	 +	if( pi->options & OPT_PROTO_MAIL_CHECK ) +	{ +		s = set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc ); +		s->flags |= ACC_SET_OFFLINE_ONLY; +	} +	 +	/* Go through all away states to figure out if away/status messages +	   are possible. */ +	pa = purple_account_new( acc->user, (char*) acc->prpl->data ); +	for( st = purple_account_get_status_types( pa ); st; st = st->next ) +	{ +		PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data ); +		 +		if( prim == PURPLE_STATUS_AVAILABLE ) +		{ +			if( purple_status_type_get_attr( st->data, "message" ) ) +				acc->flags |= ACC_FLAG_STATUS_MESSAGE; +		} +		else if( prim != PURPLE_STATUS_OFFLINE ) +		{ +			if( purple_status_type_get_attr( st->data, "message" ) ) +				acc->flags |= ACC_FLAG_AWAY_MESSAGE; +		} +	} +	purple_accounts_remove( pa ); +} + +static void purple_sync_settings( account_t *acc, PurpleAccount *pa ) +{ +	PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id ); +	PurplePluginProtocolInfo *pi = prpl->info->extra_info; +	GList *i; +	 +	for( i = pi->protocol_options; i; i = i->next ) +	{ +		PurpleAccountOption *o = i->data; +		const char *name; +		set_t *s; +		 +		name = purple_account_option_get_setting( o ); +		s = set_find( &acc->set, name ); +		if( s->value == NULL ) +			continue; +		 +		switch( purple_account_option_get_type( o ) ) +		{ +		case PURPLE_PREF_STRING: +		case PURPLE_PREF_STRING_LIST: +			purple_account_set_string( pa, name, set_getstr( &acc->set, name ) ); +			break; +		 +		case PURPLE_PREF_INT: +			purple_account_set_int( pa, name, set_getint( &acc->set, name ) ); +			break; +		 +		case PURPLE_PREF_BOOLEAN: +			purple_account_set_bool( pa, name, set_getbool( &acc->set, name ) ); +			break; +		 +		default: +			break; +		} +	} +	 +	if( pi->options & OPT_PROTO_MAIL_CHECK ) +		purple_account_set_check_mail( pa, set_getbool( &acc->set, "mail_notifications" ) ); +} + +static void purple_login( account_t *acc ) +{ +	struct im_connection *ic = imcb_new( acc ); +	PurpleAccount *pa; +	 +	if( local_bee != NULL && local_bee != acc->bee ) +	{ +		imcb_error( ic,  "Daemon mode detected. Do *not* try to use libpurple in daemon mode! " +		                 "Please use inetd or ForkDaemon mode instead." ); +		imc_logout( ic, FALSE ); +		return; +	} +	local_bee = acc->bee; +	 +	/* For now this is needed in the _connected() handlers if using +	   GLib event handling, to make sure we're not handling events +	   on dead connections. */ +	purple_connections = g_slist_prepend( purple_connections, ic ); +	 +	ic->proto_data = pa = purple_account_new( acc->user, (char*) acc->prpl->data ); +	purple_account_set_password( pa, acc->pass ); +	purple_sync_settings( acc, pa ); +	 +	purple_account_set_enabled( pa, "BitlBee", TRUE ); +} + +static void purple_logout( struct im_connection *ic ) +{ +	PurpleAccount *pa = ic->proto_data; +	 +	purple_account_set_enabled( pa, "BitlBee", FALSE ); +	purple_connections = g_slist_remove( purple_connections, ic ); +	purple_accounts_remove( pa ); +} + +static int purple_buddy_msg( struct im_connection *ic, char *who, char *message, int flags ) +{ +	PurpleConversation *conv; +	 +	if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM, +	                                                    who, ic->proto_data ) ) == NULL ) +	{ +		conv = purple_conversation_new( PURPLE_CONV_TYPE_IM, +		                                ic->proto_data, who ); +	} +	 +	purple_conv_im_send( purple_conversation_get_im_data( conv ), message ); +	 +	return 1; +} + +static GList *purple_away_states( struct im_connection *ic ) +{ +	PurpleAccount *pa = ic->proto_data; +	GList *st, *ret = NULL; +	 +	for( st = purple_account_get_status_types( pa ); st; st = st->next ) +	{ +		PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data ); +		if( prim != PURPLE_STATUS_AVAILABLE && prim != PURPLE_STATUS_OFFLINE ) +			ret = g_list_append( ret, (void*) purple_status_type_get_name( st->data ) ); +	} +	 +	return ret; +} + +static void purple_set_away( struct im_connection *ic, char *state_txt, char *message ) +{ +	PurpleAccount *pa = ic->proto_data; +	GList *status_types = purple_account_get_status_types( pa ), *st; +	PurpleStatusType *pst = NULL; +	GList *args = NULL; +	 +	for( st = status_types; st; st = st->next ) +	{ +		pst = st->data; +		 +		if( state_txt == NULL && +		    purple_status_type_get_primitive( pst ) == PURPLE_STATUS_AVAILABLE ) +			break; + +		if( state_txt != NULL && +		    g_strcasecmp( state_txt, purple_status_type_get_name( pst ) ) == 0 ) +			break; +	} +	 +	if( message && purple_status_type_get_attr( pst, "message" ) ) +	{ +		args = g_list_append( args, "message" ); +		args = g_list_append( args, message ); +	} +	 +	purple_account_set_status_list( pa, st ? purple_status_type_get_id( pst ) : "away", +		                        TRUE, args ); + +	g_list_free( args ); +} + +static char *set_eval_display_name( set_t *set, char *value ) +{ +	account_t *acc = set->data; +	struct im_connection *ic = acc->ic; +	 +	return NULL; +} + +static void purple_add_buddy( struct im_connection *ic, char *who, char *group ) +{ +	PurpleBuddy *pb; +	 +	pb = purple_buddy_new( (PurpleAccount*) ic->proto_data, who, NULL ); +	purple_blist_add_buddy( pb, NULL, NULL, NULL ); +	purple_account_add_buddy( (PurpleAccount*) ic->proto_data, pb ); +} + +static void purple_remove_buddy( struct im_connection *ic, char *who, char *group ) +{ +	PurpleBuddy *pb; +	 +	pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who ); +	if( pb != NULL ) +	{ +		purple_account_remove_buddy( (PurpleAccount*) ic->proto_data, pb, NULL ); +		purple_blist_remove_buddy( pb ); +	} +} + +static void purple_add_permit( struct im_connection *ic, char *who ) +{ +	PurpleAccount *pa = ic->proto_data; +	 +	purple_privacy_permit_add( pa, who, FALSE ); +} + +static void purple_add_deny( struct im_connection *ic, char *who ) +{ +	PurpleAccount *pa = ic->proto_data; +	 +	purple_privacy_deny_add( pa, who, FALSE ); +} + +static void purple_rem_permit( struct im_connection *ic, char *who ) +{ +	PurpleAccount *pa = ic->proto_data; +	 +	purple_privacy_permit_remove( pa, who, FALSE ); +} + +static void purple_rem_deny( struct im_connection *ic, char *who ) +{ +	PurpleAccount *pa = ic->proto_data; +	 +	purple_privacy_deny_remove( pa, who, FALSE ); +} + +static void purple_get_info( struct im_connection *ic, char *who ) +{ +	serv_get_info( purple_account_get_connection( ic->proto_data ), who ); +} + +static void purple_keepalive( struct im_connection *ic ) +{ +} + +static int purple_send_typing( struct im_connection *ic, char *who, int flags ) +{ +	PurpleTypingState state = PURPLE_NOT_TYPING; +	PurpleConversation *conv; +	 +	if( flags & OPT_TYPING ) +		state = PURPLE_TYPING; +	else if( flags & OPT_THINKING ) +		state = PURPLE_TYPED; +	 +	if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM, +	                                                    who, ic->proto_data ) ) == NULL ) +	{ +		purple_conv_im_set_typing_state( purple_conversation_get_im_data( conv ), state ); +		return 1; +	} +	else +	{ +		return 0; +	} +} + +static void purple_chat_msg( struct groupchat *gc, char *message, int flags ) +{ +	PurpleConversation *pc = gc->data; +	 +	purple_conv_chat_send( purple_conversation_get_chat_data( pc ), message ); +} + +struct groupchat *purple_chat_with( struct im_connection *ic, char *who ) +{ +	/* No, "of course" this won't work this way. Or in fact, it almost +	   does, but it only lets you send msgs to it, you won't receive +	   any. Instead, we have to click the virtual menu item. +	PurpleAccount *pa = ic->proto_data; +	PurpleConversation *pc; +	PurpleConvChat *pcc; +	struct groupchat *gc; +	 +	gc = imcb_chat_new( ic, "BitlBee-libpurple groupchat" ); +	gc->data = pc = purple_conversation_new( PURPLE_CONV_TYPE_CHAT, pa, "BitlBee-libpurple groupchat" ); +	pc->ui_data = gc; +	 +	pcc = PURPLE_CONV_CHAT( pc ); +	purple_conv_chat_add_user( pcc, ic->acc->user, "", 0, TRUE ); +	purple_conv_chat_invite_user( pcc, who, "Please join my chat", FALSE ); +	//purple_conv_chat_add_user( pcc, who, "", 0, TRUE ); +	*/ +	 +	/* There went my nice afternoon. :-( */ +	 +	PurpleAccount *pa = ic->proto_data; +	PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id ); +	PurplePluginProtocolInfo *pi = prpl->info->extra_info; +	PurpleBuddy *pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who ); +	PurpleMenuAction *mi; +	GList *menu; +	void (*callback)(PurpleBlistNode *, gpointer); /* FFFFFFFFFFFFFUUUUUUUUUUUUUU */ +	 +	if( !pb || !pi || !pi->blist_node_menu ) +		return NULL; +	 +	menu = pi->blist_node_menu( &pb->node ); +	while( menu ) +	{ +		mi = menu->data; +		if( purple_menu_cmp( mi->label, "initiate chat" ) || +		    purple_menu_cmp( mi->label, "initiate conference" ) ) +			break; +		menu = menu->next; +	} +	 +	if( menu == NULL ) +		return NULL; +	 +	/* Call the fucker. */ +	callback = (void*) mi->callback; +	callback( &pb->node, menu->data ); +	 +	return NULL; +} + +void purple_chat_invite( struct groupchat *gc, char *who, char *message ) +{ +	PurpleConversation *pc = gc->data; +	PurpleConvChat *pcc = PURPLE_CONV_CHAT( pc ); +	 +	serv_chat_invite( purple_account_get_connection( gc->ic->proto_data ), +	                  purple_conv_chat_get_id( pcc ),  +	                  message && *message ? message : "Please join my chat", +	                  who ); +} + +void purple_chat_leave( struct groupchat *gc ) +{ +	PurpleConversation *pc = gc->data; +	 +	purple_conversation_destroy( pc ); +} + +struct groupchat *purple_chat_join( struct im_connection *ic, const char *room, const char *nick, const char *password ) +{ +	PurpleAccount *pa = ic->proto_data; +	PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id ); +	PurplePluginProtocolInfo *pi = prpl->info->extra_info; +	GHashTable *chat_hash; +	PurpleConversation *conv; +	GList *info, *l; +	 +	if( !pi->chat_info || !pi->chat_info_defaults || +	    !( info = pi->chat_info( purple_account_get_connection( pa ) ) ) ) +	{ +		imcb_error( ic, "Joining chatrooms not supported by this protocol" ); +		return NULL; +	} +	 +	if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_CHAT, room, pa ) ) ) +		purple_conversation_destroy( conv ); +	 +	chat_hash = pi->chat_info_defaults( purple_account_get_connection( pa ), room ); +	 +	for( l = info; l; l = l->next ) +	{ +		struct proto_chat_entry *pce = l->data; +		 +		if( strcmp( pce->identifier, "handle" ) == 0 ) +			g_hash_table_replace( chat_hash, "handle", g_strdup( nick ) ); +		else if( strcmp( pce->identifier, "password" ) == 0 ) +			g_hash_table_replace( chat_hash, "password", g_strdup( password ) ); +		else if( strcmp( pce->identifier, "passwd" ) == 0 ) +			g_hash_table_replace( chat_hash, "passwd", g_strdup( password ) ); +	} +	 +	serv_join_chat( purple_account_get_connection( pa ), chat_hash ); +	 +	return NULL; +} + +void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle ); + +static void purple_ui_init(); + +GHashTable *prplcb_ui_info() +{ +	static GHashTable *ret; +	 +	if( ret == NULL ) +	{ +		ret = g_hash_table_new(g_str_hash, g_str_equal); +		g_hash_table_insert( ret, "name", "BitlBee" ); +		g_hash_table_insert( ret, "version", BITLBEE_VERSION ); +	} +	 +	return ret; +} + +static PurpleCoreUiOps bee_core_uiops =  +{ +	NULL, +	NULL, +	purple_ui_init, +	NULL, +	prplcb_ui_info, +}; + +static void prplcb_conn_progress( PurpleConnection *gc, const char *text, size_t step, size_t step_count ) +{ +	struct im_connection *ic = purple_ic_by_gc( gc ); +	 +	imcb_log( ic, "%s", text ); +} + +static void prplcb_conn_connected( PurpleConnection *gc ) +{ +	struct im_connection *ic = purple_ic_by_gc( gc ); +	const char *dn; +	set_t *s; +	 +	imcb_connected( ic ); +	 +	if( ( dn = purple_connection_get_display_name( gc ) ) && +	    ( s = set_find( &ic->acc->set, "display_name" ) ) ) +	{ +		g_free( s->value ); +		s->value = g_strdup( dn ); +	} +	 +	if( gc->flags & PURPLE_CONNECTION_HTML ) +		ic->flags |= OPT_DOES_HTML; +} + +static void prplcb_conn_disconnected( PurpleConnection *gc ) +{ +	struct im_connection *ic = purple_ic_by_gc( gc ); +	 +	if( ic != NULL ) +	{ +		imc_logout( ic, !gc->wants_to_die ); +	} +} + +static void prplcb_conn_notice( PurpleConnection *gc, const char *text ) +{ +	struct im_connection *ic = purple_ic_by_gc( gc ); +	 +	if( ic != NULL ) +		imcb_log( ic, "%s", text ); +} + +static void prplcb_conn_report_disconnect_reason( PurpleConnection *gc, PurpleConnectionError reason, const char *text ) +{ +	struct im_connection *ic = purple_ic_by_gc( gc ); +	 +	/* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login, +	   should probably handle that. */ +	if( ic != NULL ) +		imcb_error( ic, "%s", text ); +} + +static PurpleConnectionUiOps bee_conn_uiops = +{ +	prplcb_conn_progress, +	prplcb_conn_connected, +	prplcb_conn_disconnected, +	prplcb_conn_notice, +	NULL, +	NULL, +	NULL, +	prplcb_conn_report_disconnect_reason, +}; + +static void prplcb_blist_new( PurpleBlistNode *node ) +{ +	PurpleBuddy *bud = (PurpleBuddy*) node; +	 +	if( node->type == PURPLE_BLIST_BUDDY_NODE ) +	{ +		struct im_connection *ic = purple_ic_by_pa( bud->account ); +		 +		if( ic == NULL ) +			return; +		 +		imcb_add_buddy( ic, bud->name, NULL ); +		if( bud->server_alias ) +		{ +			imcb_rename_buddy( ic, bud->name, bud->server_alias ); +			imcb_buddy_nick_hint( ic, bud->name, bud->server_alias ); +		} +	} +} + +static void prplcb_blist_update( PurpleBuddyList *list, PurpleBlistNode *node ) +{ +	PurpleBuddy *bud = (PurpleBuddy*) node; +	 +	if( node->type == PURPLE_BLIST_BUDDY_NODE ) +	{ +		struct im_connection *ic = purple_ic_by_pa( bud->account ); +		PurpleStatus *as; +		int flags = 0; +		 +		if( ic == NULL ) +			return; +		 +		if( bud->server_alias ) +			imcb_rename_buddy( ic, bud->name, bud->server_alias ); +		 +		flags |= purple_presence_is_online( bud->presence ) ? OPT_LOGGED_IN : 0; +		flags |= purple_presence_is_available( bud->presence ) ? 0 : OPT_AWAY; +		 +		as = purple_presence_get_active_status( bud->presence ); +		 +		imcb_buddy_status( ic, bud->name, flags, purple_status_get_name( as ), +		                   purple_status_get_attr_string( as, "message" ) ); +		 +		imcb_buddy_times( ic, bud->name, +		                  purple_presence_get_login_time( bud->presence ), +		                  purple_presence_get_idle_time( bud->presence ) ); +	} +} + +static void prplcb_blist_remove( PurpleBuddyList *list, PurpleBlistNode *node ) +{ +	/* +	PurpleBuddy *bud = (PurpleBuddy*) node; +	 +	if( node->type == PURPLE_BLIST_BUDDY_NODE ) +	{ +		struct im_connection *ic = purple_ic_by_pa( bud->account ); +		 +		if( ic == NULL ) +			return; +		 +		imcb_remove_buddy( ic, bud->name, NULL ); +	} +	*/ +} + +static PurpleBlistUiOps bee_blist_uiops = +{ +	NULL, +	prplcb_blist_new, +	NULL, +	prplcb_blist_update, +	prplcb_blist_remove, +}; + +void prplcb_conv_new( PurpleConversation *conv ) +{ +	if( conv->type == PURPLE_CONV_TYPE_CHAT ) +	{ +		struct im_connection *ic = purple_ic_by_pa( conv->account ); +		struct groupchat *gc; +		 +		gc = imcb_chat_new( ic, conv->name ); +		conv->ui_data = gc; +		gc->data = conv; +		 +		/* libpurple brokenness: Whatever. Show that we join right away, +		   there's no clear "This is you!" signaling in _add_users so +		   don't even try. */ +		imcb_chat_add_buddy( gc, gc->ic->acc->user ); +	} +} + +void prplcb_conv_free( PurpleConversation *conv ) +{ +	struct groupchat *gc = conv->ui_data; +	 +	imcb_chat_free( gc ); +} + +void prplcb_conv_add_users( PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals ) +{ +	struct groupchat *gc = conv->ui_data; +	GList *b; +	 +	for( b = cbuddies; b; b = b->next ) +	{ +		PurpleConvChatBuddy *pcb = b->data; +		 +		imcb_chat_add_buddy( gc, pcb->name ); +	} +} + +void prplcb_conv_del_users( PurpleConversation *conv, GList *cbuddies ) +{ +	struct groupchat *gc = conv->ui_data; +	GList *b; +	 +	for( b = cbuddies; b; b = b->next ) +		imcb_chat_remove_buddy( gc, b->data, "" ); +} + +void prplcb_conv_chat_msg( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime ) +{ +	struct groupchat *gc = conv->ui_data; +	PurpleBuddy *buddy; +	 +	/* ..._SEND means it's an outgoing message, no need to echo those. */ +	if( flags & PURPLE_MESSAGE_SEND ) +		return; +	 +	buddy = purple_find_buddy( conv->account, who ); +	if( buddy != NULL ) +		who = purple_buddy_get_name( buddy ); +	 +	imcb_chat_msg( gc, who, (char*) message, 0, mtime ); +} + +static void prplcb_conv_im( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime ) +{ +	struct im_connection *ic = purple_ic_by_pa( conv->account ); +	PurpleBuddy *buddy; +	 +	/* ..._SEND means it's an outgoing message, no need to echo those. */ +	if( flags & PURPLE_MESSAGE_SEND ) +		return; +	 +	buddy = purple_find_buddy( conv->account, who ); +	if( buddy != NULL ) +		who = purple_buddy_get_name( buddy ); +	 +	imcb_buddy_msg( ic, (char*) who, (char*) message, 0, mtime ); +} + +static PurpleConversationUiOps bee_conv_uiops =  +{ +	prplcb_conv_new,           /* create_conversation  */ +	prplcb_conv_free,          /* destroy_conversation */ +	prplcb_conv_chat_msg,      /* write_chat           */ +	prplcb_conv_im,            /* write_im             */ +	NULL,                      /* write_conv           */ +	prplcb_conv_add_users,     /* chat_add_users       */ +	NULL,                      /* chat_rename_user     */ +	prplcb_conv_del_users,     /* chat_remove_users    */ +	NULL,                      /* chat_update_user     */ +	NULL,                      /* present              */ +	NULL,                      /* has_focus            */ +	NULL,                      /* custom_smiley_add    */ +	NULL,                      /* custom_smiley_write  */ +	NULL,                      /* custom_smiley_close  */ +	NULL,                      /* send_confirm         */ +}; + +struct prplcb_request_action_data +{ +	void *user_data, *bee_data; +	PurpleRequestActionCb yes, no; +	int yes_i, no_i; +}; + +static void prplcb_request_action_yes( void *data ) +{ +	struct prplcb_request_action_data *pqad = data; +	 +	pqad->yes( pqad->user_data, pqad->yes_i ); +	g_free( pqad ); +} + +static void prplcb_request_action_no( void *data ) +{ +	struct prplcb_request_action_data *pqad = data; +	 +	pqad->no( pqad->user_data, pqad->no_i ); +	g_free( pqad ); +} + +static void *prplcb_request_action( const char *title, const char *primary, const char *secondary, +                                    int default_action, PurpleAccount *account, const char *who, +                                    PurpleConversation *conv, void *user_data, size_t action_count, +                                    va_list actions ) +{ +	struct prplcb_request_action_data *pqad;  +	int i; +	char *q; +	 +	pqad = g_new0( struct prplcb_request_action_data, 1 ); +	 +	for( i = 0; i < action_count; i ++ ) +	{ +		char *caption; +		void *fn; +		 +		caption = va_arg( actions, char* ); +		fn = va_arg( actions, void* ); +		 +		if( strstr( caption, "Accept" ) ) +		{ +			pqad->yes = fn; +			pqad->yes_i = i; +		} +		else if( strstr( caption, "Reject" ) || strstr( caption, "Cancel" ) ) +		{ +			pqad->no = fn; +			pqad->no_i = i; +		} +	} +	 +	pqad->user_data = user_data; +	 +	/* TODO: IRC stuff here :-( */ +	q = g_strdup_printf( "Request: %s\n\n%s\n\n%s", title, primary, secondary ); +	pqad->bee_data = query_add( local_bee->ui_data, purple_ic_by_pa( account ), q, +		prplcb_request_action_yes, prplcb_request_action_no, g_free, pqad ); +	 +	g_free( q ); +	 +	return pqad; +} + +static PurpleRequestUiOps bee_request_uiops = +{ +	NULL, +	NULL, +	prplcb_request_action, +	NULL, +	NULL, +	NULL, +	NULL, +}; + +static void prplcb_privacy_permit_added( PurpleAccount *account, const char *name ) +{ +	struct im_connection *ic = purple_ic_by_pa( account ); +	 +	if( !g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) +		ic->permit = g_slist_prepend( ic->permit, g_strdup( name ) ); +} + +static void prplcb_privacy_permit_removed( PurpleAccount *account, const char *name ) +{ +	struct im_connection *ic = purple_ic_by_pa( account ); +	void *n; +	 +	n = g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp ); +	ic->permit = g_slist_remove( ic->permit, n ); +} + +static void prplcb_privacy_deny_added( PurpleAccount *account, const char *name ) +{ +	struct im_connection *ic = purple_ic_by_pa( account ); +	 +	if( !g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) +		ic->deny = g_slist_prepend( ic->deny, g_strdup( name ) ); +} + +static void prplcb_privacy_deny_removed( PurpleAccount *account, const char *name ) +{ +	struct im_connection *ic = purple_ic_by_pa( account ); +	void *n; +	 +	n = g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp ); +	ic->deny = g_slist_remove( ic->deny, n ); +} + +static PurplePrivacyUiOps bee_privacy_uiops = +{ +	prplcb_privacy_permit_added, +	prplcb_privacy_permit_removed, +	prplcb_privacy_deny_added, +	prplcb_privacy_deny_removed, +}; + +static void prplcb_debug_print( PurpleDebugLevel level, const char *category, const char *arg_s ) +{ +	fprintf( stderr, "DEBUG %s: %s", category, arg_s ); +} + +static PurpleDebugUiOps bee_debug_uiops = +{ +	prplcb_debug_print, +}; + +static guint prplcb_ev_timeout_add( guint interval, GSourceFunc func, gpointer udata ) +{ +	return b_timeout_add( interval, (b_event_handler) func, udata ); +} + +static guint prplcb_ev_input_add( int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata ) +{ +	return b_input_add( fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata ); +} + +static gboolean prplcb_ev_remove( guint id ) +{ +	b_event_remove( (gint) id ); +	return TRUE; +} + +static PurpleEventLoopUiOps glib_eventloops =  +{ +	prplcb_ev_timeout_add, +	prplcb_ev_remove, +	prplcb_ev_input_add, +	prplcb_ev_remove, +}; + +static void *prplcb_notify_email( PurpleConnection *gc, const char *subject, const char *from, +                                  const char *to, const char *url ) +{ +	struct im_connection *ic = purple_ic_by_gc( gc ); +	 +	imcb_log( ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url ); +	 +	return NULL; +} + +static void *prplcb_notify_userinfo( PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info ) +{ +	struct im_connection *ic = purple_ic_by_gc( gc ); +	GString *info = g_string_new( "" ); +	GList *l = purple_notify_user_info_get_entries( user_info ); +	char *key; +	const char *value; +	int n; +	 +	while( l ) +	{ +		PurpleNotifyUserInfoEntry *e = l->data; +		 +		switch( purple_notify_user_info_entry_get_type( e ) ) +		{ +		case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR: +		case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER: +			key = g_strdup( purple_notify_user_info_entry_get_label( e ) ); +			value = purple_notify_user_info_entry_get_value( e ); +			 +			if( key ) +			{ +				strip_html( key ); +				g_string_append_printf( info, "%s: ", key ); +				 +				if( value ) +				{ +					n = strlen( value ) - 1; +					while( isspace( value[n] ) ) +						n --; +					g_string_append_len( info, value, n + 1 ); +				} +				g_string_append_c( info, '\n' ); +				g_free( key ); +			} +			 +			break; +		case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK: +			g_string_append( info, "------------------------\n" ); +			break; +		} +		 +		l = l->next; +	} +	 +	imcb_log( ic, "User %s info:\n%s", who, info->str ); +	g_string_free( info, TRUE ); +	 +	return NULL; +} + +static PurpleNotifyUiOps bee_notify_uiops = +{ +        NULL, +        prplcb_notify_email, +        NULL, +        NULL, +        NULL, +        NULL, +        prplcb_notify_userinfo, +}; + +extern PurpleXferUiOps bee_xfer_uiops; + +static void purple_ui_init() +{ +	purple_blist_set_ui_ops( &bee_blist_uiops ); +	purple_connections_set_ui_ops( &bee_conn_uiops ); +	purple_conversations_set_ui_ops( &bee_conv_uiops ); +	purple_request_set_ui_ops( &bee_request_uiops ); +	purple_notify_set_ui_ops( &bee_notify_uiops ); +	purple_xfers_set_ui_ops( &bee_xfer_uiops ); +	purple_privacy_set_ui_ops( &bee_privacy_uiops ); +	 +	if( getenv( "BITLBEE_DEBUG" ) ) +		purple_debug_set_ui_ops( &bee_debug_uiops ); +} + +void purple_initmodule() +{ +	struct prpl funcs; +	GList *prots; +	GString *help; +	 +	if( B_EV_IO_READ != PURPLE_INPUT_READ || +	    B_EV_IO_WRITE != PURPLE_INPUT_WRITE ) +	{ +		/* FIXME FIXME FIXME FIXME FIXME :-) */ +		exit( 1 ); +	} +	 +	purple_util_set_user_dir("/tmp"); +	purple_debug_set_enabled(FALSE); +	purple_core_set_ui_ops(&bee_core_uiops); +	purple_eventloop_set_ui_ops(&glib_eventloops); +	if( !purple_core_init( "BitlBee") ) +	{ +		/* Initializing the core failed. Terminate. */ +		fprintf( stderr, "libpurple initialization failed.\n" ); +		abort(); +	} +	 +	/* This seems like stateful shit we don't want... */ +	purple_set_blist(purple_blist_new()); +	purple_blist_load(); +	 +	/* Meh? */ +	purple_prefs_load(); +	 +	memset( &funcs, 0, sizeof( funcs ) ); +	funcs.login = purple_login; +	funcs.init = purple_init; +	funcs.logout = purple_logout; +	funcs.buddy_msg = purple_buddy_msg; +	funcs.away_states = purple_away_states; +	funcs.set_away = purple_set_away; +	funcs.add_buddy = purple_add_buddy; +	funcs.remove_buddy = purple_remove_buddy; +	funcs.add_permit = purple_add_permit; +	funcs.add_deny = purple_add_deny; +	funcs.rem_permit = purple_rem_permit; +	funcs.rem_deny = purple_rem_deny; +	funcs.get_info = purple_get_info; +	funcs.keepalive = purple_keepalive; +	funcs.send_typing = purple_send_typing; +	funcs.handle_cmp = g_strcasecmp; +	/* TODO(wilmer): Set these only for protocols that support them? */ +	funcs.chat_msg = purple_chat_msg; +	funcs.chat_with = purple_chat_with; +	funcs.chat_invite = purple_chat_invite; +	funcs.chat_leave = purple_chat_leave; +	funcs.chat_join = purple_chat_join; +	funcs.transfer_request = purple_transfer_request; +	 +	help = g_string_new("BitlBee libpurple module supports the following IM protocols:\n"); +	 +	/* Add a protocol entry to BitlBee's structures for every protocol +	   supported by this libpurple instance. */	 +	for( prots = purple_plugins_get_protocols(); prots; prots = prots->next ) +	{ +		PurplePlugin *prot = prots->data; +		struct prpl *ret; +		 +		ret = g_memdup( &funcs, sizeof( funcs ) ); +		ret->name = ret->data = prot->info->id; +		if( strncmp( ret->name, "prpl-", 5 ) == 0 ) +			ret->name += 5; +		register_protocol( ret ); +		 +		g_string_append_printf( help, "\n* %s (%s)", ret->name, prot->info->name ); +		 +		/* libpurple doesn't define a protocol called OSCAR, but we +		   need it to be compatible with normal BitlBee. */ +		if( g_strcasecmp( prot->info->id, "prpl-aim" ) == 0 ) +		{ +			ret = g_memdup( &funcs, sizeof( funcs ) ); +			ret->name = "oscar"; +			ret->data = prot->info->id; +			register_protocol( ret ); +		} +	} +	 +	g_string_append( help, "\n\nFor used protocols, more information about available " +	                 "settings can be found using \x02help purple <protocol name>\x02" ); +	 +	/* Add a simple dynamically-generated help item listing all +	   the supported protocols. */ +	help_add_mem( &global.help, "purple", help->str ); +	g_string_free( help, TRUE ); +} | 
