diff options
Diffstat (limited to 'protocols/purple/purple.c')
-rw-r--r-- | protocols/purple/purple.c | 1148 |
1 files changed, 1148 insertions, 0 deletions
diff --git a/protocols/purple/purple.c b/protocols/purple/purple.c new file mode 100644 index 00000000..16ca01de --- /dev/null +++ b/protocols/purple/purple.c @@ -0,0 +1,1148 @@ +/***************************************************************************\ +* * +* 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" ) ); + } +} + +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, 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 ); +} |