summaryrefslogtreecommitdiffstats
path: root/chat-irc/bitlbee/all.patch
diff options
context:
space:
mode:
Diffstat (limited to 'chat-irc/bitlbee/all.patch')
-rw-r--r--chat-irc/bitlbee/all.patch1231
1 files changed, 1231 insertions, 0 deletions
diff --git a/chat-irc/bitlbee/all.patch b/chat-irc/bitlbee/all.patch
new file mode 100644
index 0000000000..c844eb58b0
--- /dev/null
+++ b/chat-irc/bitlbee/all.patch
@@ -0,0 +1,1231 @@
+diff -Bur bitlbee-0.92/bitlbee.c bitlbee-all/bitlbee.c
+--- bitlbee-0.92/bitlbee.c 2005-02-23 10:48:31.000000000 -0500
++++ bitlbee-all/bitlbee.c 2005-04-16 18:08:48.000000000 -0400
+@@ -262,7 +262,7 @@
+ {
+ fgetc( fp );
+ line = deobfucrypt( irc, s );
+- root_command_string( irc, ru, line );
++ root_command_string( irc, ru, line, 0 );
+ g_free( line );
+ }
+ fclose( fp );
+@@ -280,7 +280,7 @@
+ if( set_getint( irc, "auto_connect" ) )
+ {
+ strcpy( s, "account on" ); /* Can't do this directly because r_c_s alters the string */
+- root_command_string( irc, ru, s );
++ root_command_string( irc, ru, s, 0 );
+ }
+
+ return( 1 );
+@@ -451,7 +451,7 @@
+ g_main_quit( global.loop );
+ }
+
+-int root_command_string( irc_t *irc, user_t *u, char *command )
++int root_command_string( irc_t *irc, user_t *u, char *command, int flags )
+ {
+ char *cmd[IRC_MAX_ARGS];
+ char *s;
+diff -Bur bitlbee-0.92/bitlbee.h bitlbee-all/bitlbee.h
+--- bitlbee-0.92/bitlbee.h 2005-02-23 17:26:59.000000000 -0500
++++ bitlbee-all/bitlbee.h 2005-04-16 18:00:20.000000000 -0400
+@@ -119,7 +119,7 @@
+ gboolean bitlbee_io_current_client_read( GIOChannel *source, GIOCondition condition, gpointer data );
+ gboolean bitlbee_io_current_client_write( GIOChannel *source, GIOCondition condition, gpointer data );
+
+-int root_command_string( irc_t *irc, user_t *u, char *command );
++int root_command_string( irc_t *irc, user_t *u, char *command, int flags );
+ int root_command( irc_t *irc, char *command[] );
+ int bitlbee_load( irc_t *irc, char *password );
+ int bitlbee_save( irc_t *irc );
+diff -Bur bitlbee-0.92/commands.c bitlbee-all/commands.c
+--- bitlbee-0.92/commands.c 2005-02-08 16:48:35.000000000 -0500
++++ bitlbee-all/commands.c 2005-04-16 18:04:27.000000000 -0400
+@@ -128,7 +128,7 @@
+ if( checkie == -2 )
+ {
+ setpassnc( irc, cmd[1] );
+- root_command_string( irc, user_find( irc, irc->mynick ), "save" );
++ root_command_string( irc, user_find( irc, irc->mynick ), "save", 0 );
+ irc->status = USTATUS_IDENTIFIED;
+ }
+ else
+diff -Bur bitlbee-0.92/irc.c bitlbee-all/irc.c
+--- bitlbee-0.92/irc.c 2005-02-23 18:32:02.000000000 -0500
++++ bitlbee-all/irc.c 2005-05-23 18:56:42.000000000 -0400
+@@ -576,7 +576,7 @@
+ else
+ irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
+ }
+- else if( g_strcasecmp( cmd[0], "PRIVMSG" ) == 0 )
++ else if( g_strcasecmp( cmd[0], "PRIVMSG" ) == 0 || g_strcasecmp( cmd[0], "NOTICE" ) == 0 )
+ {
+ if( !( cmd[1] && cmd[2] ) )
+ {
+@@ -620,7 +620,7 @@
+ {
+ irc->is_private = 1;
+ }
+- irc_send( irc, cmd[1], cmd[2] );
++ irc_send( irc, cmd[1], cmd[2], (g_strcasecmp( cmd[0], "NOTICE") == 0) ? IM_FLAG_AWAY : 0 );
+ }
+ }
+ else if( g_strcasecmp( cmd[0], "QUIT" ) == 0 )
+@@ -1289,7 +1289,7 @@
+ irc_reply( irc, 482, "%s :Invite impossible; User/Channel non-existent or incompatible", channel );
+ }
+
+-int irc_send( irc_t *irc, char *nick, char *s )
++int irc_send( irc_t *irc, char *nick, char *s, int flags )
+ {
+ struct conversation *c = NULL;
+ user_t *u = NULL;
+@@ -1377,7 +1377,7 @@
+ }
+
+ if( u->send_handler )
+- return( u->send_handler( irc, u, s ) );
++ return( u->send_handler( irc, u, s, flags ) );
+ }
+ else if( c && c->gc && c->gc->prpl )
+ {
+@@ -1392,7 +1392,7 @@
+ user_t *u = data;
+
+ u->sendbuf[u->sendbuf_len-2] = 0; /* Cut off the last newline */
+- serv_send_im( u->gc->irc, u, u->sendbuf );
++ serv_send_im( u->gc->irc, u, u->sendbuf, 0 );
+
+ g_free( u->sendbuf );
+ u->sendbuf = NULL;
+@@ -1402,7 +1402,7 @@
+ return( FALSE );
+ }
+
+-int buddy_send_handler( irc_t *irc, user_t *u, char *msg )
++int buddy_send_handler( irc_t *irc, user_t *u, char *msg, int flags )
+ {
+ if( !u || !u->gc ) return( 0 );
+
+@@ -1432,7 +1432,7 @@
+ }
+ else
+ {
+- return( serv_send_im( irc, u, msg ) );
++ return( serv_send_im( irc, u, msg, flags ) );
+ }
+ }
+
+diff -Bur bitlbee-0.92/irc.h bitlbee-all/irc.h
+--- bitlbee-0.92/irc.h 2005-02-08 15:58:30.000000000 -0500
++++ bitlbee-all/irc.h 2005-04-16 17:58:09.000000000 -0400
+@@ -137,11 +137,11 @@
+ void irc_whois( irc_t *irc, char *nick );
+ int irc_away( irc_t *irc, char *away );
+
+-int irc_send( irc_t *irc, char *nick, char *s );
++int irc_send( irc_t *irc, char *nick, char *s, int flags );
+ int irc_privmsg( irc_t *irc, user_t *u, char *type, char *to, char *prefix, char *msg );
+ int irc_msgfrom( irc_t *irc, char *nick, char *msg );
+ int irc_noticefrom( irc_t *irc, char *nick, char *msg );
+
+-int buddy_send_handler( irc_t *irc, user_t *u, char *msg );
++int buddy_send_handler( irc_t *irc, user_t *u, char *msg, int flags );
+
+ #endif
+diff -Bur bitlbee-0.92/protocols/nogaim.c bitlbee-all/protocols/nogaim.c
+--- bitlbee-0.92/protocols/nogaim.c 2005-02-23 10:47:58.000000000 -0500
++++ bitlbee-all/protocols/nogaim.c 2005-05-15 22:51:55.000000000 -0400
+@@ -167,7 +167,7 @@
+ should be a compare function inside the PRPL module, but I do it this
+ way for now because I don't want to touch the Gaim code too much since
+ it's not going to be here for too long anymore. */
+-int handle_cmp( char *a, char *b, int protocol )
++int handle_cmp( const char *a, const char *b, int protocol )
+ {
+ if( protocol == PROTO_TOC || protocol == PROTO_ICQ )
+ {
+@@ -283,6 +283,9 @@
+ msg = buf;
+ else
+ msg = text;
++
++ if( g_strcasecmp( set_getstr(gc->irc, "html" ), "strip" ) == 0 )
++ strip_html( msg );
+
+ irc_usermsg( gc->irc, "%s - %s", proto_name[gc->protocol], msg );
+ }
+@@ -596,6 +599,11 @@
+ for( c = gc->conversations; c; c = c->next )
+ remove_chat_buddy_silent( c, handle );
+ }
++
++ if( ( type & UC_UNAVAILABLE ) && ( gc->prpl->get_away ) )
++ {
++ // gc->prpl->get_away( gc, handle );
++ }
+
+ if( ( type & UC_UNAVAILABLE ) && ( gc->protocol == PROTO_OSCAR || gc->protocol == PROTO_TOC ) )
+ {
+@@ -628,6 +636,20 @@
+ }
+ }
+
++void serv_got_away( struct gaim_connection *gc, char *handle, char *away)
++{
++ user_t *u;
++
++ u = user_findhandle( gc, handle );
++ if(!u || !away) return;
++ if(u->away)
++ g_free(u->away);
++
++ u->away = g_strdup(away);
++ if( g_strcasecmp( set_getstr( gc->irc, "html" ), "strip" ) == 0 )
++ strip_html(u->away);
++}
++
+ void serv_got_im( struct gaim_connection *gc, char *handle, char *msg, guint32 flags, time_t mtime, gint len )
+ {
+ irc_t *irc = gc->irc;
+@@ -829,7 +851,7 @@
+ irc_usermsg( b->gc->irc, "User %s added to conversation %d", handle, b->id );
+
+ /* It might be yourself! */
+- if( g_strcasecmp( handle, b->gc->user->username ) == 0 )
++ if( handle_cmp( handle, b->gc->user->username, b->gc->protocol ) == 0 )
+ {
+ u = user_find( b->gc->irc, b->gc->irc->nick );
+ if( !b->joined )
+@@ -993,15 +1015,21 @@
+ return( set_eval_bool( irc, set, value ) );
+ }
+
+-int serv_send_im( irc_t *irc, user_t *u, char *msg )
++int serv_send_im( irc_t *irc, user_t *u, char *msg, int flags )
+ {
+ char buf[8192];
+
+ if( g_strncasecmp( set_getstr( irc, "charset" ), "none", 4 ) != 0 &&
+ do_iconv( set_getstr( irc, "charset" ), "UTF-8", msg, buf, 0, 8192 ) != -1 )
+ msg = buf;
++
++ if( u->gc->flags & OPT_CONN_HTML) {
++ char * html = escape_html(msg);
++ strncpy(buf, html, 8192);
++ g_free(html);
++ }
+
+- return( ((struct gaim_connection *)u->gc)->prpl->send_im( u->gc, u->handle, msg, strlen( msg ), 0 ) );
++ return( ((struct gaim_connection *)u->gc)->prpl->send_im( u->gc, u->handle, msg, strlen( msg ), flags ) );
+ }
+
+ int serv_send_chat( irc_t *irc, struct gaim_connection *gc, int id, char *msg )
+@@ -1011,6 +1039,12 @@
+ if( g_strncasecmp( set_getstr( irc, "charset" ), "none", 4 ) != 0 &&
+ do_iconv( set_getstr( irc, "charset" ), "UTF-8", msg, buf, 0, 8192 ) != -1 )
+ msg = buf;
++
++ if( gc->flags & OPT_CONN_HTML) {
++ char * html = escape_html(msg);
++ strncpy(buf, html, 8192);
++ g_free(html);
++ }
+
+ return( gc->prpl->chat_send( gc, id, msg ) );
+ }
+diff -Bur bitlbee-0.92/protocols/nogaim.h bitlbee-all/protocols/nogaim.h
+--- bitlbee-0.92/protocols/nogaim.h 2004-10-29 19:42:07.000000000 -0400
++++ bitlbee-all/protocols/nogaim.h 2005-05-15 20:19:20.000000000 -0400
+@@ -249,7 +249,7 @@
+ extern struct prpl *proto_prpl[16];
+
+ /* nogaim.c */
+-int serv_send_im(irc_t *irc, user_t *u, char *msg);
++int serv_send_im(irc_t *irc, user_t *u, char *msg, int flags);
+ int serv_send_chat(irc_t *irc, struct gaim_connection *gc, int id, char *msg );
+
+ G_MODULE_EXPORT signed int do_iconv( char *from_cs, char *to_cs, char *src, char *dst, size_t size, size_t maxbuf );
+@@ -258,7 +258,7 @@
+ void nogaim_init();
+ int proto_away( struct gaim_connection *gc, char *away );
+ char *set_eval_away_devoice( irc_t *irc, set_t *set, char *value );
+-int handle_cmp( char *a, char *b, int protocol );
++int handle_cmp(const char *a,const char *b, int protocol );
+
+ gboolean auto_reconnect( gpointer data );
+ void cancel_auto_reconnect( struct account *a );
+@@ -297,6 +297,7 @@
+
+ /* server.c */
+ G_MODULE_EXPORT void serv_got_update( struct gaim_connection *gc, char *handle, int loggedin, int evil, time_t signon, time_t idle, int type, guint caps );
++G_MODULE_EXPORT void serv_got_away( struct gaim_connection *gc, char *handle, char *away);
+ G_MODULE_EXPORT void serv_got_im( struct gaim_connection *gc, char *handle, char *msg, guint32 flags, time_t mtime, gint len );
+ G_MODULE_EXPORT void serv_got_typing( struct gaim_connection *gc, char *handle, int timeout );
+ G_MODULE_EXPORT void serv_got_chat_invite( struct gaim_connection *gc, char *handle, char *who, char *msg, GList *data );
+@@ -314,6 +315,7 @@
+ G_MODULE_EXPORT char *normalize( const char *s );
+ G_MODULE_EXPORT time_t get_time( int year, int month, int day, int hour, int min, int sec );
+ G_MODULE_EXPORT void strip_html( char *msg );
++G_MODULE_EXPORT char * escape_html(const char *html);
+ G_MODULE_EXPORT void info_string_append(GString *str, char *newline, char *name, char *value);
+
+ #ifdef WITH_MSN
+diff -Bur bitlbee-0.92/protocols/oscar/aim.h bitlbee-all/protocols/oscar/aim.h
+--- bitlbee-0.92/protocols/oscar/aim.h 2004-10-10 07:24:06.000000000 -0400
++++ bitlbee-all/protocols/oscar/aim.h 2005-05-23 21:57:53.000000000 -0400
+@@ -439,6 +439,7 @@
+ int aim_addtlvtochain_raw(aim_tlvlist_t **list, const guint16 t, const guint16 l, const guint8 *v);
+ int aim_addtlvtochain_caps(aim_tlvlist_t **list, const guint16 t, const guint32 caps);
+ int aim_addtlvtochain_noval(aim_tlvlist_t **list, const guint16 type);
++int aim_addtlvtochain_chatroom(aim_tlvlist_t **list, guint16 type, guint16 exchange, const char *roomname, guint16 instance);
+ int aim_addtlvtochain_userinfo(aim_tlvlist_t **list, guint16 type, aim_userinfo_t *ui);
+ int aim_addtlvtochain_frozentlvlist(aim_tlvlist_t **list, guint16 type, aim_tlvlist_t **tl);
+ int aim_counttlvchain(aim_tlvlist_t **list);
+@@ -633,6 +634,12 @@
+ unsigned short instance;
+ };
+
++struct aim_chat_invitation {
++ struct gaim_connection * gc;
++ char * name;
++ guint8 exchange;
++};
++
+ #define AIM_IMFLAGS_AWAY 0x0001 /* mark as an autoreply */
+ #define AIM_IMFLAGS_ACK 0x0002 /* request a receipt notice */
+ #define AIM_IMFLAGS_UNICODE 0x0004
+@@ -811,6 +818,9 @@
+ aim_conn_t *aim_directim_connect(aim_session_t *, const char *sn, const char *addr, const guint8 *cookie);
+
+ int aim_send_im_ch2_geticqmessage(aim_session_t *sess, const char *sn, int type);
++int aim_send_im_ch2_chatinvite(aim_session_t *sess, const char *sn, const char *msg, unsigned short exchange, const char *roomname, unsigned short instance);
++int aim_im_sendmtn(aim_session_t *sess, guint16 type1, const char *sn, guint16 type2);
++
+ aim_conn_t *aim_sendfile_initiate(aim_session_t *, const char *destsn, const char *filename, guint16 numfiles, guint32 totsize);
+
+ aim_conn_t *aim_getfile_initiate(aim_session_t *sess, aim_conn_t *conn, const char *destsn);
+diff -Bur bitlbee-0.92/protocols/oscar/aim_cbtypes.h bitlbee-all/protocols/oscar/aim_cbtypes.h
+--- bitlbee-0.92/protocols/oscar/aim_cbtypes.h 2004-07-14 08:28:33.000000000 -0400
++++ bitlbee-all/protocols/oscar/aim_cbtypes.h 2005-04-05 21:35:58.000000000 -0400
+@@ -99,6 +99,7 @@
+ #define AIM_CB_MSG_MISSEDCALL 0x000a
+ #define AIM_CB_MSG_CLIENTAUTORESP 0x000b
+ #define AIM_CB_MSG_ACK 0x000c
++#define AIM_CB_MSG_MTN 0x0014
+ #define AIM_CB_MSG_DEFAULT 0xffff
+
+ /*
+diff -Bur bitlbee-0.92/protocols/oscar/chat.c bitlbee-all/protocols/oscar/chat.c
+--- bitlbee-0.92/protocols/oscar/chat.c 2004-10-10 07:24:06.000000000 -0400
++++ bitlbee-all/protocols/oscar/chat.c 2005-03-26 16:47:45.000000000 -0500
+@@ -182,31 +182,6 @@
+ return 0;
+ }
+
+-static int aim_addtlvtochain_chatroom(aim_tlvlist_t **list, guint16 type, guint16 exchange, const char *roomname, guint16 instance)
+-{
+- guint8 *buf;
+- int buflen;
+- aim_bstream_t bs;
+-
+- buflen = 2 + 1 + strlen(roomname) + 2;
+-
+- if (!(buf = g_malloc(buflen)))
+- return 0;
+-
+- aim_bstream_init(&bs, buf, buflen);
+-
+- aimbs_put16(&bs, exchange);
+- aimbs_put8(&bs, strlen(roomname));
+- aimbs_putraw(&bs, (guint8 *)roomname, strlen(roomname));
+- aimbs_put16(&bs, instance);
+-
+- aim_addtlvtochain_raw(list, type, aim_bstream_curpos(&bs), buf);
+-
+- g_free(buf);
+-
+- return 0;
+-}
+-
+ /*
+ * Join a room of name roomname. This is the first step to joining an
+ * already created room. It's basically a Service Request for
+diff -Bur bitlbee-0.92/protocols/oscar/im.c bitlbee-all/protocols/oscar/im.c
+--- bitlbee-0.92/protocols/oscar/im.c 2004-10-10 07:24:06.000000000 -0400
++++ bitlbee-all/protocols/oscar/im.c 2005-05-23 21:55:50.000000000 -0400
+@@ -365,6 +365,94 @@
+ }
+
+ /*
++ * Subtype 0x0006 - Send a chat invitation.
++ */
++int aim_send_im_ch2_chatinvite(aim_session_t *sess, const char *sn, const char *msg, guint16 exchange, const char *roomname, guint16 instance)
++{
++ aim_conn_t *conn;
++ aim_frame_t *fr;
++ aim_snacid_t snacid;
++ int i;
++ aim_msgcookie_t *cookie;
++ struct aim_invite_priv *priv;
++ guint8 ck[8];
++ aim_tlvlist_t *otl = NULL, *itl = NULL;
++ guint8 *hdr;
++ int hdrlen;
++ aim_bstream_t hdrbs;
++
++ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
++ return -EINVAL;
++
++ if (!sn || !msg || !roomname)
++ return -EINVAL;
++
++ for (i = 0; i < 8; i++)
++ ck[i] = (guint8)rand();
++
++ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152+strlen(sn)+strlen(roomname)+strlen(msg))))
++ return -ENOMEM;
++
++ snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, sn, strlen(sn)+1);
++ aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
++
++ /* XXX should be uncached by an unwritten 'invite accept' handler */
++ if ((priv = g_malloc(sizeof(struct aim_invite_priv)))) {
++ priv->sn = g_strdup(sn);
++ priv->roomname = g_strdup(roomname);
++ priv->exchange = exchange;
++ priv->instance = instance;
++ }
++
++ if ((cookie = aim_mkcookie(ck, AIM_COOKIETYPE_INVITE, priv)))
++ aim_cachecookie(sess, cookie);
++ else
++ g_free(priv);
++
++ /* ICBM Header */
++ aimbs_putraw(&fr->data, ck, 8); /* Cookie */
++ aimbs_put16(&fr->data, 0x0002); /* Channel */
++ aimbs_put8(&fr->data, strlen(sn)); /* Screename length */
++ aimbs_putraw(&fr->data, sn, strlen(sn)); /* Screenname */
++
++ /*
++ * TLV t(0005)
++ *
++ * Everything else is inside this TLV.
++ *
++ * Sigh. AOL was rather inconsistent right here. So we have
++ * to play some minor tricks. Right inside the type 5 is some
++ * raw data, followed by a series of TLVs.
++ *
++ */
++ hdrlen = 2+8+16+6+4+4+strlen(msg)+4+2+1+strlen(roomname)+2;
++ hdr = g_malloc(hdrlen);
++ aim_bstream_init(&hdrbs, hdr, hdrlen);
++
++ aimbs_put16(&hdrbs, 0x0000); /* Unknown! */
++ aimbs_putraw(&hdrbs, ck, sizeof(ck)); /* I think... */
++ aim_putcap(&hdrbs, AIM_CAPS_CHAT);
++
++ aim_addtlvtochain16(&itl, 0x000a, 0x0001);
++ aim_addtlvtochain_noval(&itl, 0x000f);
++ aim_addtlvtochain_raw(&itl, 0x000c, strlen(msg), msg);
++ aim_addtlvtochain_chatroom(&itl, 0x2711, exchange, roomname, instance);
++ aim_writetlvchain(&hdrbs, &itl);
++
++ aim_addtlvtochain_raw(&otl, 0x0005, aim_bstream_curpos(&hdrbs), hdr);
++
++ aim_writetlvchain(&fr->data, &otl);
++
++ g_free(hdr);
++ aim_freetlvchain(&itl);
++ aim_freetlvchain(&otl);
++
++ aim_tx_enqueue(sess, fr);
++
++ return 0;
++}
++
++/*
+ * This is also performance sensitive. (If you can believe it...)
+ *
+ */
+@@ -1987,6 +2075,90 @@
+ return ret;
+ }
+
++/*
++ * Subtype 0x0014 - Send a mini typing notification (mtn) packet.
++ *
++ * This is supported by winaim5 and newer, MacAIM bleh and newer, iChat bleh and newer,
++ * and Gaim 0.60 and newer.
++ *
++ */
++int aim_im_sendmtn(aim_session_t *sess, guint16 type1, const char *sn, guint16 type2)
++{
++ aim_conn_t *conn;
++ aim_frame_t *fr;
++ aim_snacid_t snacid;
++
++ if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0002)))
++ return -EINVAL;
++
++ if (!sn)
++ return -EINVAL;
++
++ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+11+strlen(sn)+2)))
++ return -ENOMEM;
++
++ snacid = aim_cachesnac(sess, 0x0004, 0x0014, 0x0000, NULL, 0);
++ aim_putsnac(&fr->data, 0x0004, 0x0014, 0x0000, snacid);
++
++ /*
++ * 8 days of light
++ * Er, that is to say, 8 bytes of 0's
++ */
++ aimbs_put16(&fr->data, 0x0000);
++ aimbs_put16(&fr->data, 0x0000);
++ aimbs_put16(&fr->data, 0x0000);
++ aimbs_put16(&fr->data, 0x0000);
++
++ /*
++ * Type 1 (should be 0x0001 for mtn)
++ */
++ aimbs_put16(&fr->data, type1);
++
++ /*
++ * Dest sn
++ */
++ aimbs_put8(&fr->data, strlen(sn));
++ aimbs_putraw(&fr->data, sn, strlen(sn));
++
++ /*
++ * Type 2 (should be 0x0000, 0x0001, or 0x0002 for mtn)
++ */
++ aimbs_put16(&fr->data, type2);
++
++ aim_tx_enqueue(sess, fr);
++
++ return 0;
++}
++
++/*
++ * Subtype 0x0014 - Receive a mini typing notification (mtn) packet.
++ *
++ * This is supported by winaim5 and newer, MacAIM bleh and newer, iChat bleh and newer,
++ * and Gaim 0.60 and newer.
++ *
++ */
++static int mtn_receive(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
++{
++ int ret = 0;
++ aim_rxcallback_t userfunc;
++ char *sn;
++ guint8 snlen;
++ guint16 type1, type2;
++
++ aim_bstream_advance(bs, 8); /* Unknown - All 0's */
++ type1 = aimbs_get16(bs);
++ snlen = aimbs_get8(bs);
++ sn = aimbs_getstr(bs, snlen);
++ type2 = aimbs_get16(bs);
++
++ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
++ ret = userfunc(sess, rx, type1, sn, type2);
++
++ g_free(sn);
++
++ return ret;
++}
++
+ static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+ {
+
+@@ -2002,6 +2174,8 @@
+ return clientautoresp(sess, mod, rx, snac, bs);
+ else if (snac->subtype == 0x000c)
+ return msgack(sess, mod, rx, snac, bs);
++ else if (snac->subtype == 0x0014)
++ return mtn_receive(sess, mod, rx, snac, bs);
+
+ return 0;
+ }
+diff -Bur bitlbee-0.92/protocols/oscar/info.c bitlbee-all/protocols/oscar/info.c
+--- bitlbee-0.92/protocols/oscar/info.c 2004-10-10 07:24:06.000000000 -0400
++++ bitlbee-all/protocols/oscar/info.c 2005-03-29 19:19:41.000000000 -0500
+@@ -614,8 +614,10 @@
+ {
+ aim_userinfo_t userinfo;
+ char *text_encoding = NULL, *text = NULL;
++ guint16 text_length = 0;
+ aim_rxcallback_t userfunc;
+ aim_tlvlist_t *tlvlist;
++ aim_tlv_t *tlv;
+ aim_snac_t *origsnac = NULL;
+ struct aim_priv_inforeq *inforeq;
+ int ret = 0;
+@@ -649,10 +651,18 @@
+ */
+ if (inforeq->infotype == AIM_GETINFO_GENERALINFO) {
+ text_encoding = aim_gettlv_str(tlvlist, 0x0001, 1);
+- text = aim_gettlv_str(tlvlist, 0x0002, 1);
++ if((tlv = aim_gettlv(tlvlist, 0x0002, 1))) {
++ text = g_new0(char, tlv->length);
++ memcpy(text, tlv->value, tlv->length);
++ text_length = tlv->length;
++ }
+ } else if (inforeq->infotype == AIM_GETINFO_AWAYMESSAGE) {
+ text_encoding = aim_gettlv_str(tlvlist, 0x0003, 1);
+- text = aim_gettlv_str(tlvlist, 0x0004, 1);
++ if((tlv = aim_gettlv(tlvlist, 0x0004, 1))) {
++ text = g_new0(char, tlv->length);
++ memcpy(text, tlv->value, tlv->length);
++ text_length = tlv->length;
++ }
+ } else if (inforeq->infotype == AIM_GETINFO_CAPABILITIES) {
+ aim_tlv_t *ct;
+
+@@ -667,7 +677,7 @@
+ }
+
+ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+- ret = userfunc(sess, rx, &userinfo, inforeq->infotype, text_encoding, text);
++ ret = userfunc(sess, rx, &userinfo, inforeq->infotype, text_encoding, text, text_length);
+
+ g_free(text_encoding);
+ g_free(text);
+diff -Bur bitlbee-0.92/protocols/oscar/oscar.c bitlbee-all/protocols/oscar/oscar.c
+--- bitlbee-0.92/protocols/oscar/oscar.c 2005-02-23 17:26:59.000000000 -0500
++++ bitlbee-all/protocols/oscar/oscar.c 2005-05-24 22:33:02.000000000 -0400
+@@ -54,7 +54,7 @@
+ /* Don't know if support for UTF8 is really working. For now it's UTF16 here.
+ static int gaim_caps = AIM_CAPS_UTF8; */
+
+-static int gaim_caps = AIM_CAPS_INTEROP | AIM_CAPS_ICHAT;
++static int gaim_caps = AIM_CAPS_INTEROP | AIM_CAPS_ICHAT | AIM_CAPS_CHAT;
+ static guint8 gaim_features[] = {0x01, 0x01, 0x01, 0x02};
+
+ struct oscar_data {
+@@ -82,6 +82,8 @@
+ gboolean icq;
+ GSList *evilhack;
+
++ GSList *info_requests; /*Used to separate away message requests the user made from automatic ones*/
++
+ struct {
+ guint maxbuddies; /* max users you can watch */
+ guint maxwatchers; /* max users who can watch you */
+@@ -148,7 +150,6 @@
+ return tmp;
+ }
+
+-#if 0
+ static struct chat_connection *find_oscar_chat(struct gaim_connection *gc, int id) {
+ GSList *g = ((struct oscar_data *)gc->proto_data)->oscar_chats;
+ struct chat_connection *c = NULL;
+@@ -163,7 +164,7 @@
+
+ return c;
+ }
+-#endif
++
+
+ static struct chat_connection *find_oscar_chat_by_conn(struct gaim_connection *gc,
+ aim_conn_t *conn) {
+@@ -215,6 +216,8 @@
+ static int gaim_ssi_parseack (aim_session_t *, aim_frame_t *, ...);
+
+ static int gaim_icqinfo (aim_session_t *, aim_frame_t *, ...);
++static int gaim_parseaiminfo (aim_session_t *, aim_frame_t *, ...);
++static int gaim_parsemtn (aim_session_t *, aim_frame_t *, ...);
+
+ static char *msgerrreason[] = {
+ "Invalid error",
+@@ -422,6 +425,11 @@
+ odata->create_rooms = g_slist_remove(odata->create_rooms, cr);
+ g_free(cr);
+ }
++ while(odata->info_requests) {
++ char * data = odata->info_requests->data;
++ odata->info_requests = g_slist_remove(odata->info_requests, data);
++ g_free(data);
++ }
+ if (odata->email)
+ g_free(odata->email);
+ if (odata->newp)
+@@ -548,6 +556,8 @@
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_RIGHTSINFO, gaim_ssi_parserights, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_LIST, gaim_ssi_parselist, 0);
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_SRVACK, gaim_ssi_parseack, 0);
++ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_USERINFO, gaim_parseaiminfo, 0);
++ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_MTN, gaim_parsemtn, 0);
+
+ ((struct oscar_data *)gc->proto_data)->conn = bosconn;
+ for (i = 0; i < (int)strlen(info->bosip); i++) {
+@@ -1071,33 +1081,6 @@
+ return 1;
+ }
+
+-static int incomingim_chan2(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args) {
+-#if 0
+- struct gaim_connection *gc = sess->aux_data;
+-#endif
+-
+- if (args->status != AIM_RENDEZVOUS_PROPOSE)
+- return 1;
+-#if 0
+- if (args->reqclass & AIM_CAPS_CHAT) {
+- char *name = extract_name(args->info.chat.roominfo.name);
+- int *exch = g_new0(int, 1);
+- GList *m = NULL;
+- m = g_list_append(m, g_strdup(name ? name : args->info.chat.roominfo.name));
+- *exch = args->info.chat.roominfo.exchange;
+- m = g_list_append(m, exch);
+- serv_got_chat_invite(gc,
+- name ? name : args->info.chat.roominfo.name,
+- userinfo->sn,
+- (char *)args->msg,
+- m);
+- if (name)
+- g_free(name);
+- }
+-#endif
+- return 1;
+-}
+-
+ static void gaim_icq_authgrant(gpointer w, struct icq_auth *data) {
+ char *uin, message;
+ struct oscar_data *od = (struct oscar_data *)data->gc->proto_data;
+@@ -1201,6 +1184,12 @@
+ return 1;
+ }
+
++int handle_cmp_aim(const char * a, const char * b) {
++ return handle_cmp(a, b, PROTO_TOC);
++}
++
++static int incomingim_chan2(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args);
++
+ static int gaim_parse_incoming_im(aim_session_t *sess, aim_frame_t *fr, ...) {
+ int channel, ret = 0;
+ aim_userinfo_t *userinfo;
+@@ -1357,6 +1346,10 @@
+ char *destn;
+ guint16 reason;
+ char buf[1024];
++ GSList *l;
++ struct oscar_data *od;
++
++ od = ((struct gaim_connection*)sess->aux_data)->proto_data;
+
+ va_start(ap, fr);
+ reason = (guint16)va_arg(ap, unsigned int);
+@@ -1367,6 +1360,11 @@
+ (reason < msgerrreasonlen) ? msgerrreason[reason] : _("Reason unknown"));
+ do_error_dialog(sess->aux_data, buf, _("Gaim - Error"));
+
++ if((l = g_slist_find_custom(od->info_requests, destn, (GCompareFunc)handle_cmp_aim))) {
++ g_free(l->data);
++ od->info_requests = g_slist_remove(od->info_requests, l);
++ }
++
+ return 1;
+ }
+
+@@ -1663,6 +1661,7 @@
+ va_end(ap);
+
+ /* Maybe senderwarn and recverwarn should be user preferences... */
++ params->flags = 0x0000000b;
+ params->maxmsglen = 8000;
+ params->minmsginterval = 0;
+
+@@ -1869,11 +1868,12 @@
+ struct oscar_data *odata = (struct oscar_data *)g->proto_data;
+ if (odata->icq)
+ aim_icq_getallinfo(odata->sess, name);
+- else
+- /* people want the away message on the top, so we get the away message
+- * first and then get the regular info, since it's too difficult to
+- * insert in the middle. i hate people. */
++ else {
+ aim_getinfo(odata->sess, odata->conn, name, AIM_GETINFO_AWAYMESSAGE);
++ aim_getinfo(odata->sess, odata->conn, name, AIM_GETINFO_GENERALINFO);
++ if(!g_slist_find_custom(odata->info_requests, name, (GCompareFunc)handle_cmp_aim))
++ odata->info_requests = g_slist_append(odata->info_requests, g_strdup(name));
++ }
+ }
+
+ static void oscar_get_away(struct gaim_connection *g, char *who) {
+@@ -1885,7 +1885,7 @@
+ if (budlight->caps & AIM_CAPS_ICQSERVERRELAY)
+ aim_send_im_ch2_geticqmessage(odata->sess, who, (budlight->uc & 0xff80) >> 7);
+ } else
+- aim_getinfo(odata->sess, odata->conn, who, AIM_GETINFO_GENERALINFO);
++ aim_getinfo(odata->sess, odata->conn, who, AIM_GETINFO_AWAYMESSAGE);
+ }
+
+ static void oscar_set_away_aim(struct gaim_connection *gc, struct oscar_data *od, const char *message)
+@@ -1977,7 +1977,8 @@
+ static void oscar_remove_buddy(struct gaim_connection *g, char *name, char *group) {
+ struct oscar_data *odata = (struct oscar_data *)g->proto_data;
+ struct aim_ssi_item *ssigroup;
+- while ((ssigroup = aim_ssi_itemlist_findparent(odata->sess->ssi.items, name)) && !aim_ssi_delbuddies(odata->sess, odata->conn, ssigroup->name, &name, 1));
++ while ((ssigroup = aim_ssi_itemlist_findparent(odata->sess->ssi.items, name)) &&
++ !aim_ssi_delbuddies(odata->sess, odata->conn, ssigroup->name, &name, 1));
+ }
+
+ static int gaim_ssi_parserights(aim_session_t *sess, aim_frame_t *fr, ...) {
+@@ -2286,6 +2287,144 @@
+
+ }
+
++static char *oscar_encoding_extract(const char *encoding)
++{
++ char *ret = NULL;
++ char *begin, *end;
++
++ g_return_val_if_fail(encoding != NULL, NULL);
++
++ /* Make sure encoding begins with charset= */
++ if (strncmp(encoding, "text/plain; charset=", 20) &&
++ strncmp(encoding, "text/aolrtf; charset=", 21) &&
++ strncmp(encoding, "text/x-aolrtf; charset=", 23))
++ {
++ return NULL;
++ }
++
++ begin = strchr(encoding, '"');
++ end = strrchr(encoding, '"');
++
++ if ((begin == NULL) || (end == NULL) || (begin >= end))
++ return NULL;
++
++ ret = g_strndup(begin+1, (end-1) - begin);
++
++ return ret;
++}
++
++static char *oscar_encoding_to_utf8(char *encoding, char *text, int textlen)
++{
++ char *utf8 = g_new0(char, 8192);
++
++ if ((encoding == NULL) || encoding[0] == '\0') {
++ /* gaim_debug_info("oscar", "Empty encoding, assuming UTF-8\n");*/
++ } else if (!g_strcasecmp(encoding, "iso-8859-1")) {
++ do_iconv("iso-8859-1", "UTF-8", text, utf8, textlen, 8192);
++ } else if (!g_strcasecmp(encoding, "ISO-8859-1-Windows-3.1-Latin-1")) {
++ do_iconv("Windows-1252", "UTF-8", text, utf8, textlen, 8192);
++ } else if (!g_strcasecmp(encoding, "unicode-2-0")) {
++ do_iconv("UCS-2BE", "UTF-8", text, utf8, textlen, 8192);
++ } else if (g_strcasecmp(encoding, "us-ascii") && strcmp(encoding, "utf-8")) {
++ /* gaim_debug_warning("oscar", "Unrecognized character encoding \"%s\", "
++ "attempting to convert to UTF-8 anyway\n", encoding);*/
++ do_iconv(encoding, "UTF-8", text, utf8, textlen, 8192);
++ }
++
++ /*
++ * If utf8 is still NULL then either the encoding is us-ascii/utf-8 or
++ * we have been unable to convert the text to utf-8 from the encoding
++ * that was specified. So we check if the text is valid utf-8 then
++ * just copy it.
++ */
++ if (*utf8 == 0) {
++ if (textlen != 0 && *text != '\0'
++ && !g_utf8_validate(text, textlen, NULL))
++ strcpy(utf8, _("(There was an error receiving this message. The buddy you are speaking to most likely has a buggy client.)"));
++ else
++ strncpy(utf8, text, textlen);
++ }
++
++ return utf8;
++}
++
++static int gaim_parseaiminfo(aim_session_t *sess, aim_frame_t *fr, ...)
++{
++ struct gaim_connection *gc = sess->aux_data;
++ struct oscar_data *od = gc->proto_data;
++ va_list ap;
++ aim_userinfo_t *userinfo;
++ guint16 infotype;
++ char *text_encoding = NULL, *text = NULL, *extracted_encoding = NULL;
++ guint16 text_length;
++ char *utf8 = NULL;
++ GSList *l;
++
++ va_start(ap, fr);
++ userinfo = va_arg(ap, aim_userinfo_t *);
++ infotype = va_arg(ap, int);
++ text_encoding = va_arg(ap, char*);
++ text = va_arg(ap, char*);
++ text_length = va_arg(ap, int);
++ va_end(ap);
++
++ if(text_encoding)
++ extracted_encoding = oscar_encoding_extract(text_encoding);
++ if(infotype == AIM_GETINFO_GENERALINFO) {
++ /*Display idle time*/
++ char buff[256];
++ struct tm idletime;
++ if(userinfo->idletime) {
++ memset(&idletime, 0, sizeof(struct tm));
++ idletime.tm_mday = (userinfo->idletime / 60) / 24;
++ idletime.tm_hour = (userinfo->idletime / 60) % 24;
++ idletime.tm_min = userinfo->idletime % 60;
++ idletime.tm_sec = 0;
++ strftime(buff, 256, _("%d days %H hours %M minutes"), &idletime);
++ serv_got_crap(gc, "%s: %s", _("Idle Time"), buff);
++ }
++
++ if(text) {
++ utf8 = oscar_encoding_to_utf8(extracted_encoding, text, text_length);
++ serv_got_crap(gc, "%s\n%s", _("User Info"), utf8);
++ } else {
++ serv_got_crap(gc, _("No user info available."));
++ }
++ } else if(infotype == AIM_GETINFO_AWAYMESSAGE && userinfo->flags & AIM_FLAG_AWAY) {
++ utf8 = oscar_encoding_to_utf8(extracted_encoding, text, text_length);
++ if((l = g_slist_find_custom(od->info_requests, userinfo->sn, (GCompareFunc)handle_cmp_aim))) {
++ /*If the user requested info, display it, otherwise just pass it on*/
++ g_free(l->data);
++ od->info_requests = g_slist_remove(od->info_requests, l);
++ serv_got_crap(gc, "%s\n%s", _("Away Message"), utf8);
++ }
++ serv_got_away(gc, userinfo->sn, utf8);
++ }
++
++ g_free(utf8);
++
++ return 1;
++}
++
++int gaim_parsemtn(aim_session_t *sess, aim_frame_t *fr, ...)
++{
++ struct gaim_connection * gc = sess->aux_data;
++ va_list ap;
++ guint16 type1, type2;
++ char * sn;
++
++ va_start(ap, fr);
++ type1 = va_arg(ap, int);
++ sn = va_arg(ap, char*);
++ type2 = va_arg(ap, int);
++ va_end(ap);
++
++ if(type2 == 0x0001 || type2 == 0x0002)
++ serv_got_typing(gc, sn, 0);
++
++ return 1;
++}
++
+ static char *oscar_get_status_string( struct gaim_connection *gc, int number )
+ {
+ struct oscar_data *od = gc->proto_data;
+@@ -2314,6 +2453,177 @@
+ }
+ }
+
++int oscar_chat_send(struct gaim_connection * gc, int id, char *message)
++{
++ struct oscar_data * od = (struct oscar_data*)gc->proto_data;
++ struct chat_connection * ccon;
++
++ if(!(ccon = find_oscar_chat(gc, id)))
++ return -1;
++
++ int ret;
++ guint8 len = strlen(message);
++ char *s;
++
++ for (s = message; *s; s++)
++ if (*s & 128)
++ break;
++
++ /* Message contains high ASCII chars, time for some translation! */
++ if (*s) {
++ s = g_malloc(BUF_LONG);
++ /* Try if we can put it in an ISO8859-1 string first.
++ If we can't, fall back to UTF16. */
++ if ((ret = do_iconv("UTF-8", "ISO8859-1", message, s, len, BUF_LONG)) >= 0) {
++ len = ret;
++ } else if ((ret = do_iconv("UTF-8", "UNICODEBIG", message, s, len, BUF_LONG)) >= 0) {
++ len = ret;
++ } else {
++ /* OOF, translation failed... Oh well.. */
++ g_free( s );
++ s = message;
++ }
++ } else {
++ s = message;
++ }
++
++ ret = aim_chat_send_im(od->sess, ccon->conn, AIM_CHATFLAGS_NOREFLECT, s, len);
++
++ if (s != message) {
++ g_free(s);
++ }
++
++ return (ret >= 0);
++}
++
++void oscar_chat_invite(struct gaim_connection * gc, int id, char *message, char *who)
++{
++ struct oscar_data * od = (struct oscar_data *)gc->proto_data;
++ struct chat_connection *ccon = find_oscar_chat(gc, id);
++
++ if (ccon == NULL)
++ return;
++
++ aim_send_im_ch2_chatinvite(od->sess, who, message ? message : "",
++ ccon->exchange, ccon->name, 0x0);
++}
++
++void oscar_chat_kill(struct gaim_connection *gc, struct chat_connection *cc)
++{
++ struct oscar_data *od = (struct oscar_data *)gc->proto_data;
++
++ /* Notify the conversation window that we've left the chat */
++ serv_got_chat_left(gc, cc->id);
++
++ /* Destroy the chat_connection */
++ od->oscar_chats = g_slist_remove(od->oscar_chats, cc);
++ if (cc->inpa > 0)
++ gaim_input_remove(cc->inpa);
++ aim_conn_kill(od->sess, &cc->conn);
++ g_free(cc->name);
++ g_free(cc->show);
++ g_free(cc);
++}
++
++void oscar_chat_leave(struct gaim_connection * gc, int id)
++{
++ struct chat_connection * ccon = find_oscar_chat(gc, id);
++
++ if(ccon == NULL)
++ return;
++
++ oscar_chat_kill(gc, ccon);
++}
++
++int oscar_chat_join(struct gaim_connection * gc, char * name)
++{
++ struct oscar_data * od = (struct oscar_data *)gc->proto_data;
++
++ aim_conn_t * cur;
++
++ if((cur = aim_getconn_type(od->sess, AIM_CONN_TYPE_CHATNAV))) {
++
++ return (aim_chatnav_createroom(od->sess, cur, name, 4) == 0);
++
++ } else {
++ struct create_room * cr = g_new0(struct create_room, 1);
++ cr->exchange = 4;
++ cr->name = g_strdup(name);
++ od->create_rooms = g_slist_append(od->create_rooms, cr);
++ aim_reqservice(od->sess, od->conn, AIM_CONN_TYPE_CHATNAV);
++ return 1;
++ }
++}
++
++int oscar_chat_open(struct gaim_connection * gc, char *who)
++{
++ struct oscar_data * od = (struct oscar_data *)gc->proto_data;
++
++ static int chat_id = 0;
++ char * chatname = g_new0(char, strlen(gc->username)+4);
++ g_snprintf(chatname, strlen(gc->username) + 4, "%s%d", gc->username, chat_id++);
++
++ int ret = oscar_chat_join(gc, chatname);
++
++ aim_send_im_ch2_chatinvite(od->sess, who, "",
++ 4, chatname, 0x0);
++ g_free(chatname);
++
++ return ret;
++}
++
++void oscar_accept_chat(gpointer w, struct aim_chat_invitation * inv)
++{
++ oscar_chat_join(inv->gc, inv->name);
++ g_free(inv->name);
++ g_free(inv);
++}
++
++void oscar_reject_chat(gpointer w, struct aim_chat_invitation * inv)
++{
++ g_free(inv->name);
++ g_free(inv);
++}
++
++static int incomingim_chan2(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args) {
++ struct gaim_connection *gc = sess->aux_data;
++
++ if (args->status != AIM_RENDEZVOUS_PROPOSE)
++ return 1;
++
++ if (args->reqclass & AIM_CAPS_CHAT) {
++ char *name = extract_name(args->info.chat.roominfo.name);
++ int *exch = g_new0(int, 1);
++ GList *m = NULL;
++ m = g_list_append(m, g_strdup(name ? name : args->info.chat.roominfo.name));
++ *exch = args->info.chat.roominfo.exchange;
++ m = g_list_append(m, exch);
++
++ char txt[1024];
++
++ g_snprintf( txt, 1024, "Got an invitation to chatroom %s from %s: %s", name, userinfo->sn, args->msg );
++
++ struct aim_chat_invitation * inv = g_new0(struct aim_chat_invitation, 1);
++
++ inv->gc = gc;
++ inv->exchange = *exch;
++ inv->name = g_strdup(name);
++
++ do_ask_dialog( gc, txt, inv, oscar_accept_chat, oscar_reject_chat);
++
++ if (name)
++ g_free(name);
++ }
++
++ return 1;
++}
++
++int oscar_send_typing(struct gaim_connection *gc, char * who, int typing)
++{
++ struct oscar_data *od = gc->proto_data;
++ return( aim_im_sendmtn(od->sess, 1, who, typing ? 0x0002 : 0x0000) );
++}
++
+ static struct prpl *my_protocol = NULL;
+
+ void oscar_init(struct prpl *ret) {
+@@ -2327,6 +2637,10 @@
+ ret->get_away = oscar_get_away;
+ ret->add_buddy = oscar_add_buddy;
+ ret->remove_buddy = oscar_remove_buddy;
++ ret->chat_send = oscar_chat_send;
++ ret->chat_invite = oscar_chat_invite;
++ ret->chat_leave = oscar_chat_leave;
++ ret->chat_open = oscar_chat_open;
+ ret->add_permit = oscar_add_permit;
+ ret->add_deny = oscar_add_deny;
+ ret->rem_permit = oscar_rem_permit;
+@@ -2335,5 +2649,7 @@
+ ret->keepalive = oscar_keepalive;
+ ret->get_status_string = oscar_get_status_string;
+
++ ret->send_typing = oscar_send_typing;
++
+ my_protocol = ret;
+ }
+diff -Bur bitlbee-0.92/protocols/oscar/tlv.c bitlbee-all/protocols/oscar/tlv.c
+--- bitlbee-0.92/protocols/oscar/tlv.c 2004-09-21 16:34:44.000000000 -0400
++++ bitlbee-all/protocols/oscar/tlv.c 2005-03-26 16:48:17.000000000 -0500
+@@ -373,6 +373,31 @@
+ return buflen;
+ }
+
++int aim_addtlvtochain_chatroom(aim_tlvlist_t **list, guint16 type, guint16 exchange, const char *roomname, guint16 instance)
++{
++ guint8 *buf;
++ int buflen;
++ aim_bstream_t bs;
++
++ buflen = 2 + 1 + strlen(roomname) + 2;
++
++ if (!(buf = g_malloc(buflen)))
++ return 0;
++
++ aim_bstream_init(&bs, buf, buflen);
++
++ aimbs_put16(&bs, exchange);
++ aimbs_put8(&bs, strlen(roomname));
++ aimbs_putraw(&bs, (guint8 *)roomname, strlen(roomname));
++ aimbs_put16(&bs, instance);
++
++ aim_addtlvtochain_raw(list, type, aim_bstream_curpos(&bs), buf);
++
++ g_free(buf);
++
++ return 0;
++}
++
+ /**
+ * aim_writetlvchain - Write a TLV chain into a data buffer.
+ * @buf: Destination buffer
+diff -Bur bitlbee-0.92/protocols/util.c bitlbee-all/protocols/util.c
+--- bitlbee-0.92/protocols/util.c 2004-10-10 07:24:06.000000000 -0400
++++ bitlbee-all/protocols/util.c 2005-04-23 15:47:58.000000000 -0400
+@@ -280,7 +280,7 @@
+ { "lt", '<' },
+ { "gt", '>' },
+ { "amp", '&' },
+- { "quot", '\'' },
++ { "quot", '"' },
+ { "dquot", '"' },
+ { "aacute", 'á' },
+ { "eacute", 'é' },
+@@ -325,6 +325,8 @@
+
+ if( *in )
+ {
++ if( g_strncasecmp( cs+1, "br", 2) == 0 )
++ *(s++) = '\n';
+ in ++;
+ }
+ else
+@@ -365,7 +367,40 @@
+
+ strcpy( start, out );
+ g_free( out );
+-}
++}
++
++char * escape_html(const char *html)
++{
++ const char *c = html;
++ GString *ret;
++
++ if (html == NULL)
++ return NULL;
++
++ ret = g_string_new("");
++
++ while (*c) {
++ switch (*c) {
++ case '&':
++ ret = g_string_append(ret, "&amp;");
++ break;
++ case '<':
++ ret = g_string_append(ret, "&lt;");
++ break;
++ case '>':
++ ret = g_string_append(ret, "&gt;");
++ break;
++ case '"':
++ ret = g_string_append(ret, "&quot;");
++ break;
++ default:
++ ret = g_string_append_c(ret, *c);
++ }
++ c++;
++ }
++
++ return g_string_free(ret, FALSE);
++}
+
+ void info_string_append(GString *str, char *newline, char *name, char *value)
+ {
+diff -Bur bitlbee-0.92/user.h bitlbee-all/user.h
+--- bitlbee-0.92/user.h 2004-10-29 19:58:00.000000000 -0400
++++ bitlbee-all/user.h 2005-04-16 18:08:07.000000000 -0400
+@@ -43,7 +43,7 @@
+ int sendbuf_len;
+ guint sendbuf_timer;
+
+- int (*send_handler) ( irc_t *irc, struct __USER *u, char *msg );
++ int (*send_handler) ( irc_t *irc, struct __USER *u, char *msg, int flags );
+
+ struct __USER *next;
+ } user_t;