diff options
Diffstat (limited to 'protocols/nogaim.c')
| -rw-r--r-- | protocols/nogaim.c | 708 | 
1 files changed, 708 insertions, 0 deletions
| diff --git a/protocols/nogaim.c b/protocols/nogaim.c new file mode 100644 index 00000000..8fb85ea7 --- /dev/null +++ b/protocols/nogaim.c @@ -0,0 +1,708 @@ +  /********************************************************************\ +  * BitlBee -- An IRC to other IM-networks gateway                     * +  *                                                                    * +  * Copyright 2002-2010 Wilmer van der Gaast and others                * +  \********************************************************************/ + +/* + * nogaim + * + * Gaim without gaim - for BitlBee + * + * This file contains functions called by the Gaim IM-modules. It's written + * from scratch for BitlBee and doesn't contain any code from Gaim anymore + * (except for the function names). + */ + +/* +  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 with +  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; +  if not, write to the Free Software Foundation, Inc., 59 Temple Place, +  Suite 330, Boston, MA  02111-1307  USA +*/ + +#define BITLBEE_CORE +#include <ctype.h> + +#include "nogaim.h" + +GSList *connections; + +#ifdef WITH_PLUGINS +gboolean load_plugin(char *path) +{ +	void (*init_function) (void); +	 +	GModule *mod = g_module_open(path, G_MODULE_BIND_LAZY); + +	if(!mod) { +		log_message(LOGLVL_ERROR, "Can't find `%s', not loading (%s)\n", path, g_module_error()); +		return FALSE; +	} + +	if(!g_module_symbol(mod,"init_plugin",(gpointer *) &init_function)) { +		log_message(LOGLVL_WARNING, "Can't find function `init_plugin' in `%s'\n", path); +		return FALSE; +	} + +	init_function(); + +	return TRUE; +} + +void load_plugins(void) +{ +	GDir *dir; +	GError *error = NULL; + +	dir = g_dir_open(global.conf->plugindir, 0, &error); + +	if (dir) { +		const gchar *entry; +		char *path; + +		while ((entry = g_dir_read_name(dir))) { +			path = g_build_filename(global.conf->plugindir, entry, NULL); +			if(!path) { +				log_message(LOGLVL_WARNING, "Can't build path for %s\n", entry); +				continue; +			} + +			load_plugin(path); + +			g_free(path); +		} + +		g_dir_close(dir); +	} +} +#endif + +GList *protocols = NULL; +   +void register_protocol (struct prpl *p) +{ +	int i; +	gboolean refused = global.conf->protocols != NULL; +  +	for (i = 0; global.conf->protocols && global.conf->protocols[i]; i++) + 	{ + 		if (g_strcasecmp(p->name, global.conf->protocols[i]) == 0) +			refused = FALSE; + 	} + +	if (refused) +		log_message(LOGLVL_WARNING, "Protocol %s disabled\n", p->name); +	else +		protocols = g_list_append(protocols, p); +} + +struct prpl *find_protocol(const char *name) +{ +	GList *gl; +	 +	for( gl = protocols; gl; gl = gl->next ) + 	{ + 		struct prpl *proto = gl->data; + 		 + 		if( g_strcasecmp( proto->name, name ) == 0 ) +			return proto; + 	} + 	 + 	return NULL; +} + +void nogaim_init() +{ +	extern void msn_initmodule(); +	extern void oscar_initmodule(); +	extern void byahoo_initmodule(); +	extern void jabber_initmodule(); +	extern void twitter_initmodule(); +	extern void purple_initmodule(); + +#ifdef WITH_MSN +	msn_initmodule(); +#endif + +#ifdef WITH_OSCAR +	oscar_initmodule(); +#endif +	 +#ifdef WITH_YAHOO +	byahoo_initmodule(); +#endif +	 +#ifdef WITH_JABBER +	jabber_initmodule(); +#endif + +#ifdef WITH_TWITTER +	twitter_initmodule(); +#endif + +#ifdef WITH_PURPLE +	purple_initmodule(); +#endif + +#ifdef WITH_PLUGINS +	load_plugins(); +#endif +} + +GSList *get_connections() { return connections; } + +struct im_connection *imcb_new( account_t *acc ) +{ +	struct im_connection *ic; +	 +	ic = g_new0( struct im_connection, 1 ); +	 +	ic->bee = acc->bee; +	ic->acc = acc; +	acc->ic = ic; +	 +	connections = g_slist_append( connections, ic ); +	 +	return( ic ); +} + +void imc_free( struct im_connection *ic ) +{ +	account_t *a; +	 +	/* Destroy the pointer to this connection from the account list */ +	for( a = ic->bee->accounts; a; a = a->next ) +		if( a->ic == ic ) +		{ +			a->ic = NULL; +			break; +		} +	 +	connections = g_slist_remove( connections, ic ); +	g_free( ic ); +} + +static void serv_got_crap( struct im_connection *ic, char *format, ... ) +{ +	va_list params; +	char *text; +	account_t *a; +	 +	va_start( params, format ); +	text = g_strdup_vprintf( format, params ); +	va_end( params ); + +	if( ( g_strcasecmp( set_getstr( &ic->bee->set, "strip_html" ), "always" ) == 0 ) || +	    ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->bee->set, "strip_html" ) ) ) +		strip_html( text ); +	 +	/* Try to find a different connection on the same protocol. */ +	for( a = ic->bee->accounts; a; a = a->next ) +		if( a->prpl == ic->acc->prpl && a->ic != ic ) +			break; +	 +	/* If we found one, include the screenname in the message. */ +	if( a ) +		/* FIXME(wilmer): ui_log callback or so */ +		irc_usermsg( ic->bee->ui_data, "%s(%s) - %s", ic->acc->prpl->name, ic->acc->user, text ); +	else +		irc_usermsg( ic->bee->ui_data, "%s - %s", ic->acc->prpl->name, text ); +	 +	g_free( text ); +} + +void imcb_log( struct im_connection *ic, char *format, ... ) +{ +	va_list params; +	char *text; +	 +	va_start( params, format ); +	text = g_strdup_vprintf( format, params ); +	va_end( params ); +	 +	if( ic->flags & OPT_LOGGED_IN ) +		serv_got_crap( ic, "%s", text ); +	else +		serv_got_crap( ic, "Logging in: %s", text ); +	 +	g_free( text ); +} + +void imcb_error( struct im_connection *ic, char *format, ... ) +{ +	va_list params; +	char *text; +	 +	va_start( params, format ); +	text = g_strdup_vprintf( format, params ); +	va_end( params ); +	 +	if( ic->flags & OPT_LOGGED_IN ) +		serv_got_crap( ic, "Error: %s", text ); +	else +		serv_got_crap( ic, "Login error: %s", text ); +	 +	g_free( text ); +} + +static gboolean send_keepalive( gpointer d, gint fd, b_input_condition cond ) +{ +	struct im_connection *ic = d; +	 +	if( ic->acc->prpl->keepalive ) +		ic->acc->prpl->keepalive( ic ); +	 +	return TRUE; +} + +void imcb_connected( struct im_connection *ic ) +{ +	/* MSN servers sometimes redirect you to a different server and do +	   the whole login sequence again, so these "late" calls to this +	   function should be handled correctly. (IOW, ignored) */ +	if( ic->flags & OPT_LOGGED_IN ) +		return; +	 +	imcb_log( ic, "Logged in" ); +	 +	b_event_remove( ic->keepalive ); +	ic->keepalive = b_timeout_add( 60000, send_keepalive, ic ); +	ic->flags |= OPT_LOGGED_IN; +	 +	/* Necessary to send initial presence status, even if we're not away. */ +	imc_away_send_update( ic ); +	 +	/* Apparently we're connected successfully, so reset the +	   exponential backoff timer. */ +	ic->acc->auto_reconnect_delay = 0; +	 +	if( ic->bee->ui->imc_connected ) +		ic->bee->ui->imc_connected( ic ); +} + +gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond ) +{ +	account_t *a = data; +	 +	a->reconnect = 0; +	account_on( a->bee, a ); +	 +	return( FALSE );	/* Only have to run the timeout once */ +} + +void cancel_auto_reconnect( account_t *a ) +{ +	b_event_remove( a->reconnect ); +	a->reconnect = 0; +} + +void imc_logout( struct im_connection *ic, int allow_reconnect ) +{ +	bee_t *bee = ic->bee; +	account_t *a; +	GSList *l; +	int delay; +	 +	/* Nested calls might happen sometimes, this is probably the best +	   place to catch them. */ +	if( ic->flags & OPT_LOGGING_OUT ) +		return; +	else +		ic->flags |= OPT_LOGGING_OUT; +	 +	if( ic->bee->ui->imc_disconnected ) +		ic->bee->ui->imc_disconnected( ic ); +	 +	imcb_log( ic, "Signing off.." ); +	 +	for( l = bee->users; l; ) +	{ +		bee_user_t *bu = l->data; +		GSList *next = l->next; +		 +		if( bu->ic == ic ) +			bee_user_free( bee, bu ); +		 +		l = next; +	} +	 +	b_event_remove( ic->keepalive ); +	ic->keepalive = 0; +	ic->acc->prpl->logout( ic ); +	b_event_remove( ic->inpa ); +	 +	g_free( ic->away ); +	ic->away = NULL; +	 +	query_del_by_conn( (irc_t*) ic->bee->ui_data, ic ); +	 +	for( a = bee->accounts; a; a = a->next ) +		if( a->ic == ic ) +			break; +	 +	if( !a ) +	{ +		/* Uhm... This is very sick. */ +	} +	else if( allow_reconnect && set_getbool( &bee->set, "auto_reconnect" ) && +	         set_getbool( &a->set, "auto_reconnect" ) && +	         ( delay = account_reconnect_delay( a ) ) > 0 ) +	{ +		imcb_log( ic, "Reconnecting in %d seconds..", delay ); +		a->reconnect = b_timeout_add( delay * 1000, auto_reconnect, a ); +	} +	 +	imc_free( ic ); +} + +void imcb_ask( struct im_connection *ic, char *msg, void *data, +               query_callback doit, query_callback dont ) +{ +	query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, g_free, data ); +} + +void imcb_ask_with_free( struct im_connection *ic, char *msg, void *data, +                         query_callback doit, query_callback dont, query_callback myfree ) +{ +	query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, myfree, data ); +} + +void imcb_add_buddy( struct im_connection *ic, const char *handle, const char *group ) +{ +	bee_user_t *bu; +	bee_t *bee = ic->bee; +	bee_group_t *oldg; +	 +	if( !( bu = bee_user_by_handle( bee, ic, handle ) ) ) +		bu = bee_user_new( bee, ic, handle, 0 ); +	 +	oldg = bu->group; +	bu->group = bee_group_by_name( bee, group, TRUE ); +	 +	if( bee->ui->user_group && bu->group != oldg ) +		bee->ui->user_group( bee, bu ); +} + +void imcb_rename_buddy( struct im_connection *ic, const char *handle, const char *fullname ) +{ +	bee_t *bee = ic->bee; +	bee_user_t *bu = bee_user_by_handle( bee, ic, handle ); +	 +	if( !bu || !fullname ) return; +	 +	if( !bu->fullname || strcmp( bu->fullname, fullname ) != 0 ) +	{ +		g_free( bu->fullname ); +		bu->fullname = g_strdup( fullname ); +		 +		if( bee->ui->user_fullname ) +			bee->ui->user_fullname( bee, bu ); +	} +} + +void imcb_remove_buddy( struct im_connection *ic, const char *handle, char *group ) +{ +	bee_user_free( ic->bee, bee_user_by_handle( ic->bee, ic, handle ) ); +} + +/* Mainly meant for ICQ (and now also for Jabber conferences) to allow IM +   modules to suggest a nickname for a handle. */ +void imcb_buddy_nick_hint( struct im_connection *ic, const char *handle, const char *nick ) +{ +	bee_t *bee = ic->bee; +	bee_user_t *bu = bee_user_by_handle( bee, ic, handle ); +	 +	if( !bu || !nick ) return; +	 +	g_free( bu->nick ); +	bu->nick = g_strdup( nick ); +	 +	if( bee->ui->user_nick_hint ) +		bee->ui->user_nick_hint( bee, bu, nick ); +} + + +struct imcb_ask_cb_data +{ +	struct im_connection *ic; +	char *handle; +}; + +static void imcb_ask_auth_cb_no( void *data ) +{ +	struct imcb_ask_cb_data *cbd = data; +	 +	cbd->ic->acc->prpl->auth_deny( cbd->ic, cbd->handle ); +	 +	g_free( cbd->handle ); +	g_free( cbd ); +} + +static void imcb_ask_auth_cb_yes( void *data ) +{ +	struct imcb_ask_cb_data *cbd = data; +	 +	cbd->ic->acc->prpl->auth_allow( cbd->ic, cbd->handle ); +	 +	g_free( cbd->handle ); +	g_free( cbd ); +} + +void imcb_ask_auth( struct im_connection *ic, const char *handle, const char *realname ) +{ +	struct imcb_ask_cb_data *data = g_new0( struct imcb_ask_cb_data, 1 ); +	char *s, *realname_ = NULL; +	 +	if( realname != NULL ) +		realname_ = g_strdup_printf( " (%s)", realname ); +	 +	s = g_strdup_printf( "The user %s%s wants to add you to his/her buddy list.", +	                     handle, realname_ ? realname_ : "" ); +	 +	g_free( realname_ ); +	 +	data->ic = ic; +	data->handle = g_strdup( handle ); +	query_add( (irc_t *) ic->bee->ui_data, ic, s, +	           imcb_ask_auth_cb_yes, imcb_ask_auth_cb_no, g_free, data ); +} + + +static void imcb_ask_add_cb_no( void *data ) +{ +	g_free( ((struct imcb_ask_cb_data*)data)->handle ); +	g_free( data ); +} + +static void imcb_ask_add_cb_yes( void *data ) +{ +	struct imcb_ask_cb_data *cbd = data; +	 +	cbd->ic->acc->prpl->add_buddy( cbd->ic, cbd->handle, NULL ); +	 +	imcb_ask_add_cb_no( data ); +} + +void imcb_ask_add( struct im_connection *ic, const char *handle, const char *realname ) +{ +	struct imcb_ask_cb_data *data = g_new0( struct imcb_ask_cb_data, 1 ); +	char *s; +	 +	/* TODO: Make a setting for this! */ +	if( bee_user_by_handle( ic->bee, ic, handle ) != NULL ) +		return; +	 +	s = g_strdup_printf( "The user %s is not in your buddy list yet. Do you want to add him/her now?", handle ); +	 +	data->ic = ic; +	data->handle = g_strdup( handle ); +	query_add( (irc_t *) ic->bee->ui_data, ic, s, +	           imcb_ask_add_cb_yes, imcb_ask_add_cb_no, g_free, data ); +} + +struct bee_user *imcb_buddy_by_handle( struct im_connection *ic, const char *handle ) +{ +	return bee_user_by_handle( ic->bee, ic, handle ); +} + +/* The plan is to not allow straight calls to prpl functions anymore, but do +   them all from some wrappers. We'll start to define some down here: */ + +int imc_chat_msg( struct groupchat *c, char *msg, int flags ) +{ +	char *buf = NULL; +	 +	if( ( c->ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<html>", 6 ) != 0 ) ) +	{ +		buf = escape_html( msg ); +		msg = buf; +	} +	 +	c->ic->acc->prpl->chat_msg( c, msg, flags ); +	g_free( buf ); +	 +	return 1; +} + +static char *imc_away_state_find( GList *gcm, char *away, char **message ); + +int imc_away_send_update( struct im_connection *ic ) +{ +	char *away, *msg = NULL; +	 +	if( ic->acc->prpl->away_states == NULL || +	    ic->acc->prpl->set_away == NULL ) +		return 0; +	 +	away = set_getstr( &ic->acc->set, "away" ) ? +	     : set_getstr( &ic->bee->set, "away" ); +	if( away && *away ) +	{ +		GList *m = ic->acc->prpl->away_states( ic ); +		msg = ic->acc->flags & ACC_FLAG_AWAY_MESSAGE ? away : NULL; +		away = imc_away_state_find( m, away, &msg ) ? : m->data; +	} +	else if( ic->acc->flags & ACC_FLAG_STATUS_MESSAGE ) +	{ +		away = NULL; +		msg = set_getstr( &ic->acc->set, "status" ) ? +		    : set_getstr( &ic->bee->set, "status" ); +	} +	 +	ic->acc->prpl->set_away( ic, away, msg ); +	 +	return 1; +} + +static char *imc_away_alias_list[8][5] = +{ +	{ "Away from computer", "Away", "Extended away", NULL }, +	{ "NA", "N/A", "Not available", NULL }, +	{ "Busy", "Do not disturb", "DND", "Occupied", NULL }, +	{ "Be right back", "BRB", NULL }, +	{ "On the phone", "Phone", "On phone", NULL }, +	{ "Out to lunch", "Lunch", "Food", NULL }, +	{ "Invisible", "Hidden" }, +	{ NULL } +}; + +static char *imc_away_state_find( GList *gcm, char *away, char **message ) +{ +	GList *m; +	int i, j; +	 +	for( m = gcm; m; m = m->next ) +		if( g_strncasecmp( m->data, away, strlen( m->data ) ) == 0 ) +		{ +			/* At least the Yahoo! module works better if message +			   contains no data unless it adds something to what +			   we have in state already. */ +			if( strlen( m->data ) == strlen( away ) ) +				*message = NULL; +			 +			return m->data; +		} +	 +	for( i = 0; *imc_away_alias_list[i]; i ++ ) +	{ +		int keep_message; +		 +		for( j = 0; imc_away_alias_list[i][j]; j ++ ) +			if( g_strncasecmp( away, imc_away_alias_list[i][j], strlen( imc_away_alias_list[i][j] ) ) == 0 ) +			{ +				keep_message = strlen( away ) != strlen( imc_away_alias_list[i][j] ); +				break; +			} +		 +		if( !imc_away_alias_list[i][j] )	/* If we reach the end, this row */ +			continue;			/* is not what we want. Next!    */ +		 +		/* Now find an entry in this row which exists in gcm */ +		for( j = 0; imc_away_alias_list[i][j]; j ++ ) +		{ +			for( m = gcm; m; m = m->next ) +				if( g_strcasecmp( imc_away_alias_list[i][j], m->data ) == 0 ) +				{ +					if( !keep_message ) +						*message = NULL; +					 +					return imc_away_alias_list[i][j]; +				} +		} +		 +		/* No need to look further, apparently this state doesn't +		   have any good alias for this protocol. */ +		break; +	} +	 +	return NULL; +} + +void imc_add_allow( struct im_connection *ic, char *handle ) +{ +	if( g_slist_find_custom( ic->permit, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) == NULL ) +	{ +		ic->permit = g_slist_prepend( ic->permit, g_strdup( handle ) ); +	} +	 +	ic->acc->prpl->add_permit( ic, handle ); +} + +void imc_rem_allow( struct im_connection *ic, char *handle ) +{ +	GSList *l; +	 +	if( ( l = g_slist_find_custom( ic->permit, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) ) +	{ +		g_free( l->data ); +		ic->permit = g_slist_delete_link( ic->permit, l ); +	} +	 +	ic->acc->prpl->rem_permit( ic, handle ); +} + +void imc_add_block( struct im_connection *ic, char *handle ) +{ +	if( g_slist_find_custom( ic->deny, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) == NULL ) +	{ +		ic->deny = g_slist_prepend( ic->deny, g_strdup( handle ) ); +	} +	 +	ic->acc->prpl->add_deny( ic, handle ); +} + +void imc_rem_block( struct im_connection *ic, char *handle ) +{ +	GSList *l; +	 +	if( ( l = g_slist_find_custom( ic->deny, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) ) +	{ +		g_free( l->data ); +		ic->deny = g_slist_delete_link( ic->deny, l ); +	} +	 +	ic->acc->prpl->rem_deny( ic, handle ); +} + +void imcb_clean_handle( struct im_connection *ic, char *handle ) +{ +	/* Accepts a handle and does whatever is necessary to make it +	   BitlBee-friendly. Currently this means removing everything +	   outside 33-127 (ASCII printable excl spaces), @ (only one +	   is allowed) and ! and : */ +	char out[strlen(handle)+1]; +	int s, d; +	 +	s = d = 0; +	while( handle[s] ) +	{ +		if( handle[s] > ' ' && handle[s] != '!' && handle[s] != ':' && +		    ( handle[s] & 0x80 ) == 0 ) +		{ +			if( handle[s] == '@' ) +			{ +				/* See if we got an @ already? */ +				out[d] = 0; +				if( strchr( out, '@' ) ) +					continue; +			} +			 +			out[d++] = handle[s]; +		} +		s ++; +	} +	out[d] = handle[s]; +	 +	strcpy( handle, out ); +} | 
