aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWilmer van der Gaast <wilmer@gaast.net>2007-11-19 23:16:18 +0000
committerWilmer van der Gaast <wilmer@gaast.net>2007-11-19 23:16:18 +0000
commit256899f2f9eaa43610af53704d5c7ccbd1aace3b (patch)
tree080ec69cc51b963efcd5002d9a15ec2f92df3d16
parentcd428e473fe4428041722fd373badef890edebd9 (diff)
parentef5c1855b406e462fb8b90b517f1672a47bcc4b5 (diff)
Merging Jabber groupchat support.
-rw-r--r--doc/user-guide/commands.xml13
-rw-r--r--doc/user-guide/misc.xml4
-rw-r--r--irc.c45
-rw-r--r--irc.h1
-rw-r--r--irc_commands.c23
-rw-r--r--nick.c14
-rw-r--r--nick.h1
-rw-r--r--protocols/jabber/Makefile2
-rw-r--r--protocols/jabber/conference.c274
-rw-r--r--protocols/jabber/io.c60
-rw-r--r--protocols/jabber/iq.c35
-rw-r--r--protocols/jabber/jabber.c83
-rw-r--r--protocols/jabber/jabber.h65
-rw-r--r--protocols/jabber/jabber_util.c178
-rw-r--r--protocols/jabber/message.c31
-rw-r--r--protocols/jabber/presence.c71
-rw-r--r--protocols/jabber/sasl.c2
-rw-r--r--protocols/jabber/xmltree.c8
-rw-r--r--protocols/jabber/xmltree.h8
-rw-r--r--protocols/msn/sb.c2
-rw-r--r--protocols/nogaim.c166
-rw-r--r--protocols/nogaim.h19
-rw-r--r--protocols/oscar/oscar.c3
-rw-r--r--protocols/yahoo/yahoo.c14
-rw-r--r--root_commands.c33
-rw-r--r--user.c1
26 files changed, 967 insertions, 189 deletions
diff --git a/doc/user-guide/commands.xml b/doc/user-guide/commands.xml
index 05b9abea..ac9bdf11 100644
--- a/doc/user-guide/commands.xml
+++ b/doc/user-guide/commands.xml
@@ -684,6 +684,19 @@
</description>
</bitlbee-setting>
+ <bitlbee-setting name="xmlconsole" type="boolean" scope="account">
+ <default>false</default>
+
+ <description>
+ <para>
+ The Jabber module allows you to add a buddy <emphasis>xmlconsole</emphasis> to your contact list, which will then show you the raw XMPP stream between you and the server. You can also send XMPP packets to this buddy, which will then be sent to the server.
+ </para>
+ <para>
+ If you want to enable this XML console permanently (and at login time already), you can set this setting.
+ </para>
+ </description>
+ </bitlbee-setting>
+
<bitlbee-command name="rename">
<short-description>Rename (renick) a buddy</short-description>
<syntax>rename &lt;oldnick&gt; &lt;newnick&gt;</syntax>
diff --git a/doc/user-guide/misc.xml b/doc/user-guide/misc.xml
index f90ce538..d387d4b3 100644
--- a/doc/user-guide/misc.xml
+++ b/doc/user-guide/misc.xml
@@ -80,6 +80,10 @@ Of course a channel with only two people isn't really exciting yet. So the next
</para>
<para>
+Some protocols (like Jabber) also support named groupchats. BitlBee now supports these too. You can use the <emphasis>join_chat</emphasis> command to join them. See <emphasis>help join_chat</emphasis> for more information.
+</para>
+
+<para>
This is all you'll probably need to know. If you have any problems, please read <emphasis>help groupchats3</emphasis>.
</para>
diff --git a/irc.c b/irc.c
index 0bebf9b0..ebb2876b 100644
--- a/irc.c
+++ b/irc.c
@@ -640,7 +640,7 @@ void irc_names( irc_t *irc, char *channel )
strcat( namelist, " " );
}
}
- else if( ( c = chat_by_channel( channel ) ) )
+ else if( ( c = irc_chat_by_channel( irc, channel ) ) )
{
GList *l;
@@ -787,19 +787,14 @@ void irc_motd( irc_t *irc )
void irc_topic( irc_t *irc, char *channel )
{
- if( g_strcasecmp( channel, irc->channel ) == 0 )
- {
+ struct groupchat *c = irc_chat_by_channel( irc, channel );
+
+ if( c && c->topic )
+ irc_reply( irc, 332, "%s :%s", channel, c->topic );
+ else if( g_strcasecmp( channel, irc->channel ) == 0 )
irc_reply( irc, 332, "%s :%s", channel, CONTROL_TOPIC );
- }
else
- {
- struct groupchat *c = chat_by_channel( channel );
-
- if( c )
- irc_reply( irc, 332, "%s :BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", channel, c->title );
- else
- irc_reply( irc, 331, "%s :No topic for this channel", channel );
- }
+ irc_reply( irc, 331, "%s :No topic for this channel", channel );
}
void irc_umode_set( irc_t *irc, char *s, int allow_priv )
@@ -931,7 +926,7 @@ int irc_send( irc_t *irc, char *nick, char *s, int flags )
if( *nick == '#' || *nick == '&' )
{
- if( !( c = chat_by_channel( nick ) ) )
+ if( !( c = irc_chat_by_channel( irc, nick ) ) )
{
irc_reply( irc, 403, "%s :Channel does not exist", nick );
return( 0 );
@@ -1197,3 +1192,27 @@ static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond )
return TRUE;
}
+
+struct groupchat *irc_chat_by_channel( irc_t *irc, char *channel )
+{
+ struct groupchat *c;
+ account_t *a;
+
+ /* This finds the connection which has a conversation which belongs to this channel */
+ for( a = irc->accounts; a; a = a->next )
+ {
+ if( a->ic == NULL )
+ continue;
+
+ c = a->ic->groupchats;
+ while( c )
+ {
+ if( c->channel && g_strcasecmp( c->channel, channel ) == 0 )
+ return c;
+
+ c = c->next;
+ }
+ }
+
+ return NULL;
+}
diff --git a/irc.h b/irc.h
index 5b770180..8be3579e 100644
--- a/irc.h
+++ b/irc.h
@@ -139,5 +139,6 @@ int irc_msgfrom( irc_t *irc, char *nick, char *msg );
int irc_noticefrom( irc_t *irc, char *nick, char *msg );
void buddy_send_handler( irc_t *irc, user_t *u, char *msg, int flags );
+struct groupchat *irc_chat_by_channel( irc_t *irc, char *channel );
#endif
diff --git a/irc_commands.c b/irc_commands.c
index 8d841aaa..287a126f 100644
--- a/irc_commands.c
+++ b/irc_commands.c
@@ -143,7 +143,7 @@ static void irc_cmd_part( irc_t *irc, char **cmd )
irc_part( irc, u, irc->channel );
irc_join( irc, u, irc->channel );
}
- else if( ( c = chat_by_channel( cmd[1] ) ) )
+ else if( ( c = irc_chat_by_channel( irc, cmd[1] ) ) )
{
user_t *u = user_find( irc, irc->nick );
@@ -200,7 +200,7 @@ static void irc_cmd_join( irc_t *irc, char **cmd )
static void irc_cmd_invite( irc_t *irc, char **cmd )
{
char *nick = cmd[1], *channel = cmd[2];
- struct groupchat *c = chat_by_channel( channel );
+ struct groupchat *c = irc_chat_by_channel( irc, channel );
user_t *u = user_find( irc, nick );
if( u && c && ( u->ic == c->ic ) )
@@ -286,7 +286,7 @@ static void irc_cmd_who( irc_t *irc, char **cmd )
irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->away ? 'G' : 'H', u->realname );
u = u->next;
}
- else if( ( c = chat_by_channel( channel ) ) )
+ else if( ( c = irc_chat_by_channel( irc, channel ) ) )
for( l = c->in_room; l; l = l->next )
{
if( ( u = user_findhandle( c->ic, l->data ) ) )
@@ -420,10 +420,21 @@ static void irc_cmd_watch( irc_t *irc, char **cmd )
static void irc_cmd_topic( irc_t *irc, char **cmd )
{
- if( cmd[2] )
- irc_reply( irc, 482, "%s :Cannot change topic", cmd[1] );
+ char *channel = cmd[1];
+ char *topic = cmd[2];
+
+ if( topic )
+ {
+ /* Send the topic */
+ struct groupchat *c = irc_chat_by_channel( irc, channel );
+ if( c && c->ic && c->ic->acc->prpl->chat_topic )
+ c->ic->acc->prpl->chat_topic( c, topic );
+ }
else
- irc_topic( irc, cmd[1] );
+ {
+ /* Get the topic */
+ irc_topic( irc, channel );
+ }
}
static void irc_cmd_away( irc_t *irc, char **cmd )
diff --git a/nick.c b/nick.c
index 5e24547a..88c3faea 100644
--- a/nick.c
+++ b/nick.c
@@ -56,7 +56,6 @@ char *nick_get( account_t *acc, const char *handle )
{
static char nick[MAX_NICK_LENGTH+1];
char *store_handle, *found_nick;
- int inf_protection = 256;
memset( nick, 0, MAX_NICK_LENGTH + 1 );
@@ -82,6 +81,17 @@ char *nick_get( account_t *acc, const char *handle )
}
g_free( store_handle );
+ /* Make sure the nick doesn't collide with an existing one by adding
+ underscores and that kind of stuff, if necessary. */
+ nick_dedupe( acc, handle, nick );
+
+ return nick;
+}
+
+void nick_dedupe( account_t *acc, const char *handle, char nick[MAX_NICK_LENGTH+1] )
+{
+ int inf_protection = 256;
+
/* Now, find out if the nick is already in use at the moment, and make
subtle changes to make it unique. */
while( !nick_ok( nick ) || user_find( acc->irc, nick ) )
@@ -119,8 +129,6 @@ char *nick_get( account_t *acc, const char *handle )
break;
}
}
-
- return nick;
}
/* Just check if there is a nickname set for this buddy or if we'd have to
diff --git a/nick.h b/nick.h
index bb7816f0..31298275 100644
--- a/nick.h
+++ b/nick.h
@@ -25,6 +25,7 @@
void nick_set( account_t *acc, const char *handle, const char *nick );
char *nick_get( account_t *acc, const char *handle );
+void nick_dedupe( account_t *acc, const char *handle, char nick[MAX_NICK_LENGTH+1] );
int nick_saved( account_t *acc, const char *handle );
void nick_del( account_t *acc, const char *handle );
void nick_strip( char *nick );
diff --git a/protocols/jabber/Makefile b/protocols/jabber/Makefile
index e81e6c5a..e042f812 100644
--- a/protocols/jabber/Makefile
+++ b/protocols/jabber/Makefile
@@ -9,7 +9,7 @@
-include ../../Makefile.settings
# [SH] Program variables
-objects = io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o xmltree.o
+objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o xmltree.o
CFLAGS += -Wall
LFLAGS += -r
diff --git a/protocols/jabber/conference.c b/protocols/jabber/conference.c
new file mode 100644
index 00000000..008bbe63
--- /dev/null
+++ b/protocols/jabber/conference.c
@@ -0,0 +1,274 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Jabber module - Conference rooms *
+* *
+* Copyright 2007 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 "jabber.h"
+
+struct groupchat *jabber_chat_join( struct im_connection *ic, char *room, char *nick, char *password )
+{
+ struct jabber_chat *jc;
+ struct xt_node *node;
+ struct groupchat *c;
+ char *roomjid;
+
+ roomjid = g_strdup_printf( "%s/%s", room, nick );
+ node = xt_new_node( "x", NULL, NULL );
+ xt_add_attr( node, "xmlns", XMLNS_MUC );
+ node = jabber_make_packet( "presence", NULL, roomjid, node );
+
+ if( !jabber_write_packet( ic, node ) )
+ {
+ g_free( roomjid );
+ xt_free_node( node );
+ return NULL;
+ }
+ xt_free_node( node );
+
+ jc = g_new0( struct jabber_chat, 1 );
+ jc->name = jabber_normalize( room );
+
+ if( ( jc->me = jabber_buddy_add( ic, roomjid ) ) == NULL )
+ {
+ g_free( roomjid );
+ g_free( jc->name );
+ g_free( jc );
+ return NULL;
+ }
+
+ /* roomjid isn't normalized yet, and we need an original version
+ of the nick to send a proper presence update. */
+ jc->my_full_jid = roomjid;
+
+ c = imcb_chat_new( ic, room );
+ c->data = jc;
+
+ return c;
+}
+
+void jabber_chat_free( struct groupchat *c )
+{
+ struct jabber_chat *jc = c->data;
+
+ jabber_buddy_remove_bare( c->ic, jc->name );
+
+ g_free( jc->my_full_jid );
+ g_free( jc->name );
+ g_free( jc );
+
+ imcb_chat_free( c );
+}
+
+int jabber_chat_msg( struct groupchat *c, char *message, int flags )
+{
+ struct im_connection *ic = c->ic;
+ struct jabber_chat *jc = c->data;
+ struct xt_node *node;
+
+ node = xt_new_node( "body", message, NULL );
+ node = jabber_make_packet( "message", "groupchat", jc->name, node );
+
+ if( !jabber_write_packet( ic, node ) )
+ {
+ xt_free_node( node );
+ return 0;
+ }
+ xt_free_node( node );
+
+ return 1;
+}
+
+int jabber_chat_topic( struct groupchat *c, char *topic )
+{
+ struct im_connection *ic = c->ic;
+ struct jabber_chat *jc = c->data;
+ struct xt_node *node;
+
+ node = xt_new_node( "subject", topic, NULL );
+ node = jabber_make_packet( "message", "groupchat", jc->name, node );
+
+ if( !jabber_write_packet( ic, node ) )
+ {
+ xt_free_node( node );
+ return 0;
+ }
+ xt_free_node( node );
+
+ return 1;
+}
+
+int jabber_chat_leave( struct groupchat *c, const char *reason )
+{
+ struct im_connection *ic = c->ic;
+ struct jabber_chat *jc = c->data;
+ struct xt_node *node;
+
+ node = xt_new_node( "x", NULL, NULL );
+ xt_add_attr( node, "xmlns", XMLNS_MUC );
+ node = jabber_make_packet( "presence", "unavailable", jc->my_full_jid, node );
+
+ if( !jabber_write_packet( ic, node ) )
+ {
+ xt_free_node( node );
+ return 0;
+ }
+ xt_free_node( node );
+
+ return 1;
+}
+
+/* Not really the same syntax as the normal pkt_ functions, but this isn't
+ called by the xmltree parser directly and this way I can add some extra
+ parameters so we won't have to repeat too many things done by the caller
+ already. */
+void jabber_chat_pkt_presence( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node )
+{
+ struct groupchat *chat;
+ struct xt_node *c;
+ char *type = xt_find_attr( node, "type" );
+ struct jabber_chat *jc;
+ char *s;
+
+ if( ( chat = jabber_chat_by_name( ic, bud->bare_jid ) ) == NULL )
+ {
+ /* How could this happen?? We could do kill( self, 11 )
+ now or just wait for the OS to do it. :-) */
+ return;
+ }
+
+ jc = chat->data;
+
+ if( type == NULL && !( bud->flags & JBFLAG_IS_CHATROOM ) )
+ {
+ bud->flags |= JBFLAG_IS_CHATROOM;
+ /* If this one wasn't set yet, this buddy just joined the chat.
+ Slightly hackish way of finding out eh? ;-) */
+
+ /* This is pretty messy... Here it sets ext_jid to the real
+ JID of the participant. Works for non-anonymized channels.
+ Might break if someone joins a chat twice, though. */
+ for( c = node->children; ( c = xt_find_node( c, "x" ) ); c = c->next )
+ if( ( s = xt_find_attr( c, "xmlns" ) ) &&
+ ( strcmp( s, XMLNS_MUC_USER ) == 0 ) )
+ {
+ c = xt_find_node( c->children, "item" );
+ if( ( s = xt_find_attr( c, "jid" ) ) )
+ {
+ /* Yay, found what we need. :-) */
+ bud->ext_jid = jabber_normalize( s );
+ break;
+ }
+ }
+
+ /* Make up some other handle, if necessary. */
+ if( bud->ext_jid == NULL )
+ {
+ if( bud == jc->me )
+ {
+ bud->ext_jid = jabber_normalize( ic->acc->user );
+ }
+ else
+ {
+ int i;
+
+ /* Don't want the nick to be at the end, so let's
+ think of some slightly different notation to use
+ for anonymous groupchat participants in BitlBee. */
+ bud->ext_jid = g_strdup_printf( "%s=%s", bud->resource, bud->bare_jid );
+
+ /* And strip any unwanted characters. */
+ for( i = 0; bud->resource[i]; i ++ )
+ if( bud->ext_jid[i] == '=' || bud->ext_jid[i] == '@' )
+ bud->ext_jid[i] = '_';
+
+ /* Some program-specific restrictions. */
+ imcb_clean_handle( ic, bud->ext_jid );
+ }
+ bud->flags |= JBFLAG_IS_ANONYMOUS;
+ }
+
+ if( bud != jc->me )
+ {
+ imcb_add_buddy( ic, bud->ext_jid, NULL );
+ imcb_buddy_nick_hint( ic, bud->ext_jid, bud->resource );
+ }
+
+ s = strchr( bud->ext_jid, '/' );
+ if( s ) *s = 0; /* Should NEVER be NULL, but who knows... */
+ imcb_chat_add_buddy( chat, bud->ext_jid );
+ if( s ) *s = '/';
+ }
+ else if( type ) /* type can only be NULL or "unavailable" in this function */
+ {
+ s = strchr( bud->ext_jid, '/' );
+ if( s ) *s = 0;
+ imcb_chat_remove_buddy( chat, bud->ext_jid, NULL );
+ if( bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS )
+ imcb_remove_buddy( ic, bud->ext_jid, NULL );
+ if( s ) *s = '/';
+
+ if( bud == jc->me )
+ jabber_chat_free( chat );
+ }
+}
+
+void jabber_chat_pkt_message( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node )
+{
+ struct xt_node *subject = xt_find_node( node->children, "subject" );
+ struct xt_node *body = xt_find_node( node->children, "body" );
+ struct groupchat *chat;
+ char *s;
+
+ if( bud == NULL )
+ {
+ s = xt_find_attr( node, "from" ); /* pkt_message() already NULL-checked this one. */
+ if( strchr( s, '/' ) == NULL )
+ /* This is fine, the groupchat itself isn't in jd->buddies. */
+ imcb_log( ic, "System message from groupchat %s: %s", s, body? body->text : "NULL" );
+ else
+ /* This, however, isn't fine! */
+ imcb_log( ic, "Groupchat message from unknown participant %s: %s", s, body ? body->text : "NULL" );
+
+ return;
+ }
+ else if( ( chat = jabber_chat_by_name( ic, bud->bare_jid ) ) == NULL )
+ {
+ /* How could this happen?? We could do kill( self, 11 )
+ now or just wait for the OS to do it. :-) */
+ return;
+ }
+
+ if( subject )
+ {
+ s = strchr( bud->ext_jid, '/' );
+ if( s ) *s = 0;
+ imcb_chat_topic( chat, bud->ext_jid, subject->text_len > 0 ?
+ subject->text : NULL, jabber_get_timestamp( node ) );
+ if( s ) *s = '/';
+ }
+ if( body && body->text_len > 0 )
+ {
+ s = strchr( bud->ext_jid, '/' );
+ if( s ) *s = 0;
+ imcb_chat_msg( chat, bud->ext_jid, body->text, 0, jabber_get_timestamp( node ) );
+ if( s ) *s = '/';
+ }
+}
diff --git a/protocols/jabber/io.c b/protocols/jabber/io.c
index 925463a4..61cd142e 100644
--- a/protocols/jabber/io.c
+++ b/protocols/jabber/io.c
@@ -44,6 +44,15 @@ int jabber_write( struct im_connection *ic, char *buf, int len )
struct jabber_data *jd = ic->proto_data;
gboolean ret;
+ if( jd->flags & JFLAG_XMLCONSOLE )
+ {
+ char *msg;
+
+ msg = g_strdup_printf( "TX: %s", buf );
+ imcb_buddy_msg( ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0 );
+ g_free( msg );
+ }
+
if( jd->tx_len == 0 )
{
/* If the queue is empty, allocate a new buffer. */
@@ -426,56 +435,61 @@ static xt_status jabber_pkt_proceed_tls( struct xt_node *node, gpointer data )
static xt_status jabber_pkt_stream_error( struct xt_node *node, gpointer data )
{
struct im_connection *ic = data;
- struct xt_node *c;
- char *s, *type = NULL, *text = NULL;
int allow_reconnect = TRUE;
+ struct jabber_error *err;
- for( c = node->children; c; c = c->next )
- {
- if( !( s = xt_find_attr( c, "xmlns" ) ) ||
- strcmp( s, XMLNS_STREAM_ERROR ) != 0 )
- continue;
-
- if( strcmp( c->name, "text" ) != 0 )
- {
- type = c->name;
- }
- /* Only use the text if it doesn't have an xml:lang attribute,
- if it's empty or if it's set to something English. */
- else if( !( s = xt_find_attr( c, "xml:lang" ) ) ||
- !*s || strncmp( s, "en", 2 ) == 0 )
- {
- text = c->text;
- }
- }
+ err = jabber_error_parse( node, XMLNS_STREAM_ERROR );
/* Tssk... */
- if( type == NULL )
+ if( err->code == NULL )
{
imcb_error( ic, "Unknown stream error reported by server" );
imc_logout( ic, allow_reconnect );
+ jabber_error_free( err );
return XT_ABORT;
}
/* We know that this is a fatal error. If it's a "conflict" error, we
should turn off auto-reconnect to make sure we won't get some nasty
infinite loop! */
- if( strcmp( type, "conflict" ) == 0 )
+ if( strcmp( err->code, "conflict" ) == 0 )
{
imcb_error( ic, "Account and resource used from a different location" );
allow_reconnect = FALSE;
}
else
{
- imcb_error( ic, "Stream error: %s%s%s", type, text ? ": " : "", text ? text : "" );
+ imcb_error( ic, "Stream error: %s%s%s", err->code, err->text ? ": " : "",
+ err->text ? err->text : "" );
}
+ jabber_error_free( err );
imc_logout( ic, allow_reconnect );
return XT_ABORT;
}
+static xt_status jabber_xmlconsole( struct xt_node *node, gpointer data )
+{
+ struct im_connection *ic = data;
+ struct jabber_data *jd = ic->proto_data;
+
+ if( jd->flags & JFLAG_XMLCONSOLE )
+ {
+ char *msg, *pkt;
+
+ pkt = xt_to_string( node );
+ msg = g_strdup_printf( "RX: %s", pkt );
+ imcb_buddy_msg( ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0 );
+ g_free( msg );
+ g_free( pkt );
+ }
+
+ return XT_NEXT;
+}
+
static const struct xt_handler_entry jabber_handlers[] = {
+ { NULL, "stream:stream", jabber_xmlconsole },
{ "stream:stream", "<root>", jabber_end_of_stream },
{ "message", "stream:stream", jabber_pkt_message },
{ "presence", "stream:stream", jabber_pkt_presence },
diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c
index 2aa9d432..4738817a 100644
--- a/protocols/jabber/iq.c
+++ b/protocols/jabber/iq.c
@@ -98,26 +98,25 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data )
}
else if( strcmp( s, XMLNS_DISCOVER ) == 0 )
{
+ const char *features[] = { XMLNS_VERSION,
+ XMLNS_TIME,
+ XMLNS_CHATSTATES,
+ XMLNS_MUC,
+ NULL };
+ const char **f;
+
c = xt_new_node( "identity", NULL, NULL );
xt_add_attr( c, "category", "client" );
xt_add_attr( c, "type", "pc" );
xt_add_attr( c, "name", "BitlBee" );
xt_add_child( reply, c );
- c = xt_new_node( "feature", NULL, NULL );
- xt_add_attr( c, "var", XMLNS_VERSION );
- xt_add_child( reply, c );
-
- c = xt_new_node( "feature", NULL, NULL );
- xt_add_attr( c, "var", XMLNS_TIME );
- xt_add_child( reply, c );
-
- c = xt_new_node( "feature", NULL, NULL );
- xt_add_attr( c, "var", XMLNS_CHATSTATES );
- xt_add_child( reply, c );
-
- /* Later this can be useful to announce things like
- MUC support. */
+ for( f = features; *f; f ++ )
+ {
+ c = xt_new_node( "feature", NULL, NULL );
+ xt_add_attr( c, "var", *f );
+ xt_add_child( reply, c );
+ }
}
else
{
@@ -372,15 +371,13 @@ static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node *
imcb_add_buddy( ic, jid, ( group && group->text_len ) ?
group->text : NULL );
- imcb_rename_buddy( ic, jid, name );
+ if( name )
+ imcb_rename_buddy( ic, jid, name );
}
else if( strcmp( sub, "remove" ) == 0 )
{
- /* Don't have any API call for this yet! So let's
- just try to handle this as well as we can. */
jabber_buddy_remove_bare( ic, jid );
- imcb_buddy_status( ic, jid, 0, NULL, NULL );
- /* FIXME! */
+ imcb_remove_buddy( ic, jid, NULL );
}
}
diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c
index edad5dbd..e7be63fd 100644
--- a/protocols/jabber/jabber.c
+++ b/protocols/jabber/jabber.c
@@ -54,6 +54,9 @@ static void jabber_init( account_t *acc )
s = set_add( &acc->set, "tls", "try", set_eval_tls, acc );
s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add( &acc->set, "xmlconsole", "false", set_eval_bool, acc );
+ s->flags |= ACC_SET_OFFLINE_ONLY;
}
static void jabber_login( account_t *acc )
@@ -188,6 +191,14 @@ static void jabber_login( account_t *acc )
imcb_error( ic, "Could not connect to server" );
imc_logout( ic, TRUE );
}
+
+ if( set_getbool( &acc->set, "xmlconsole" ) )
+ {
+ jd->flags |= JFLAG_XMLCONSOLE;
+ /* Shouldn't really do this at this stage already, maybe. But
+ I think this shouldn't break anything. */
+ imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
+ }
}
static void jabber_logout( struct im_connection *ic )
@@ -196,6 +207,9 @@ static void jabber_logout( struct im_connection *ic )
jabber_end_stream( ic );
+ while( ic->groupchats )
+ jabber_chat_free( ic->groupchats );
+
if( jd->r_inpa >= 0 )
b_event_remove( jd->r_inpa );
if( jd->w_inpa >= 0 )
@@ -223,9 +237,16 @@ static int jabber_buddy_msg( struct im_connection *ic, char *who, char *message,
struct jabber_data *jd = ic->proto_data;
struct jabber_buddy *bud;
struct xt_node *node;
+ char *s;
int st;
- bud = jabber_buddy_by_jid( ic, who, 0 );
+ if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
+ return jabber_write( ic, message, strlen( message ) );
+
+ if( ( s = strchr( who, '=' ) ) && jabber_chat_by_name( ic, s + 1 ) )
+ bud = jabber_buddy_by_ext_jid( ic, who, 0 );
+ else
+ bud = jabber_buddy_by_jid( ic, who, 0 );
node = xt_new_node( "body", message, NULL );
node = jabber_make_packet( "message", "chat", bud ? bud->full_jid : who, node );
@@ -310,12 +331,34 @@ static void jabber_set_away( struct im_connection *ic, char *state_txt, char *me
static void jabber_add_buddy( struct im_connection *ic, char *who, char *group )
{
+ struct jabber_data *jd = ic->proto_data;
+
+ if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
+ {
+ jd->flags |= JFLAG_XMLCONSOLE;
+ imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
+ return;
+ }
+
if( jabber_add_to_roster( ic, who, NULL ) )
presence_send_request( ic, who, "subscribe" );
}
static void jabber_remove_buddy( struct im_connection *ic, char *who, char *group )
{
+ struct jabber_data *jd = ic->proto_data;
+
+ if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
+ {
+ jd->flags &= ~JFLAG_XMLCONSOLE;
+ /* Not necessary for now. And for now the code isn't too
+ happy if the buddy is completely gone right after calling
+ this function already.
+ imcb_remove_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
+ */
+ return;
+ }
+
/* We should always do this part. Clean up our administration a little bit. */
jabber_buddy_remove_bare( ic, who );
@@ -323,6 +366,36 @@ static void jabber_remove_buddy( struct im_connection *ic, char *who, char *grou
presence_send_request( ic, who, "unsubscribe" );
}
+static struct groupchat *jabber_chat_join_( struct im_connection *ic, char *room, char *nick, char *password )
+{
+ if( strchr( room, '@' ) == NULL )
+ imcb_error( ic, "Invalid room name: %s", room );
+ else if( jabber_chat_by_name( ic, room ) )
+ imcb_error( ic, "Already present in chat `%s'", room );
+ else
+ return jabber_chat_join( ic, room, nick, password );
+
+ return NULL;
+}
+
+static void jabber_chat_msg_( struct groupchat *c, char *message, int flags )
+{
+ if( c && message )
+ jabber_chat_msg( c, message, flags );
+}
+
+static void jabber_chat_topic_( struct groupchat *c, char *topic )
+{
+ if( c && topic )
+ jabber_chat_topic( c, topic );
+}
+
+static void jabber_chat_leave_( struct groupchat *c )
+{
+ if( c )
+ jabber_chat_leave( c, NULL );
+}
+
static void jabber_keepalive( struct im_connection *ic )
{
/* Just any whitespace character is enough as a keepalive for XMPP sessions. */
@@ -387,16 +460,16 @@ void jabber_initmodule()
ret->logout = jabber_logout;
ret->buddy_msg = jabber_buddy_msg;
ret->away_states = jabber_away_states;
-// ret->get_status_string = jabber_get_status_string;
ret->set_away = jabber_set_away;
// ret->set_info = jabber_set_info;
ret->get_info = jabber_get_info;
ret->add_buddy = jabber_add_buddy;
ret->remove_buddy = jabber_remove_buddy;
-// ret->chat_msg = jabber_chat_msg;
+ ret->chat_msg = jabber_chat_msg_;
+ ret->chat_topic = jabber_chat_topic_;
// ret->chat_invite = jabber_chat_invite;
-// ret->chat_leave = jabber_chat_leave;
-// ret->chat_open = jabber_chat_open;
+ ret->chat_leave = jabber_chat_leave_;
+ ret->chat_join = jabber_chat_join_;
ret->keepalive = jabber_keepalive;
ret->send_typing = jabber_send_typing;
ret->handle_cmp = g_strcasecmp;
diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h
index 42f57ae1..e26c3899 100644
--- a/protocols/jabber/jabber.h
+++ b/protocols/jabber/jabber.h
@@ -31,16 +31,17 @@
typedef enum
{
- JFLAG_STREAM_STARTED = 1, /* Set when we detected the beginning of the stream
+ JFLAG_STREAM_STARTED = 1, /* Set when we detected the beginning of the stream
and want to do auth. */
- JFLAG_AUTHENTICATED = 2, /* Set when we're successfully authenticatd. */
- JFLAG_STREAM_RESTART = 4, /* Set when we want to restart the stream (after
+ JFLAG_AUTHENTICATED = 2, /* Set when we're successfully authenticatd. */
+ JFLAG_STREAM_RESTART = 4, /* Set when we want to restart the stream (after
SASL or TLS). */
- JFLAG_WAIT_SESSION = 8, /* Set if we sent a <session> tag and need a reply
+ JFLAG_WAIT_SESSION = 8, /* Set if we sent a <session> tag and need a reply
before we continue. */
- JFLAG_WAIT_BIND = 16, /* ... for <bind> tag. */
- JFLAG_WANT_TYPING = 32, /* Set if we ever sent a typing notification, this
+ JFLAG_WAIT_BIND = 16, /* ... for <bind> tag. */
+ JFLAG_WANT_TYPING = 32, /* Set if we ever sent a typing notification, this
activates all XEP-85 related code. */
+ JFLAG_XMLCONSOLE = 64, /* If the user added an xmlconsole buddy. */
} jabber_flags_t;
typedef enum
@@ -49,12 +50,12 @@ typedef enum
sure it gets sent only once. */
JBFLAG_DOES_XEP85 = 2, /* Set this when the resource seems to support
XEP85 (typing notification shite). */
+ JBFLAG_IS_CHATROOM = 4, /* It's convenient to use this JID thingy for
+ groupchat state info too. */
+ JBFLAG_IS_ANONYMOUS = 8, /* For anonymous chatrooms, when we don't have
+ have a real JID. */
} jabber_buddy_flags_t;
-#define JABBER_PORT_DEFAULT "5222"
-#define JABBER_PORT_MIN 5220
-#define JABBER_PORT_MAX 5229
-
struct jabber_data
{
struct im_connection *ic;
@@ -100,6 +101,9 @@ struct jabber_buddy
char *full_jid;
char *resource;
+ char *ext_jid; /* The JID to use in BitlBee. The real JID if possible, */
+ /* otherwise something similar to the conference JID. */
+
int priority;
struct jabber_away_state *away_state;
char *away_message;
@@ -110,6 +114,20 @@ struct jabber_buddy
struct jabber_buddy *next;
};
+struct jabber_chat
+{
+ int flags;
+ char *name;
+ char *my_full_jid; /* Separate copy because of case sensitivity. */
+ struct jabber_buddy *me;
+};
+
+#define JABBER_XMLCONSOLE_HANDLE "xmlconsole"
+
+#define JABBER_PORT_DEFAULT "5222"
+#define JABBER_PORT_MIN 5220
+#define JABBER_PORT_MAX 5229
+
/* Prefixes to use for packet IDs (mainly for IQ packets ATM). Usually the
first one should be used, but when storing a packet in the cache, a
"special" kind of ID is assigned to make it easier later to figure out
@@ -131,8 +149,11 @@ struct jabber_buddy
#define XMLNS_VERSION "jabber:iq:version" /* XEP-0092 */
#define XMLNS_TIME "jabber:iq:time" /* XEP-0090 */
#define XMLNS_VCARD "vcard-temp" /* XEP-0054 */
+#define XMLNS_DELAY "jabber:x:delay" /* XEP-0091 */
#define XMLNS_CHATSTATES "http://jabber.org/protocol/chatstates" /* 0085 */
#define XMLNS_DISCOVER "http://jabber.org/protocol/disco#info" /* 0030 */
+#define XMLNS_MUC "http://jabber.org/protocol/muc" /* XEP-0045 */
+#define XMLNS_MUC_USER "http://jabber.org/protocol/muc#user"/* XEP-0045 */
/* iq.c */
xt_status jabber_pkt_iq( struct xt_node *node, gpointer data );
@@ -163,18 +184,29 @@ void jabber_cache_clean( struct im_connection *ic );
const struct jabber_away_state *jabber_away_state_by_code( char *code );
const struct jabber_away_state *jabber_away_state_by_name( char *name );
void jabber_buddy_ask( struct im_connection *ic, char *handle );
-char *jabber_normalize( char *orig );
+char *jabber_normalize( const char *orig );
typedef enum
{
GET_BUDDY_CREAT = 1, /* Try to create it, if necessary. */
- GET_BUDDY_EXACT = 2, /* Get an exact message (only makes sense with bare JIDs). */
+ GET_BUDDY_EXACT = 2, /* Get an exact match (only makes sense with bare JIDs). */
+ GET_BUDDY_FIRST = 4, /* No selection, simply get the first resource for this JID. */
} get_buddy_flags_t;
+struct jabber_error
+{
+ char *code, *text, *type;
+};
+
struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid );
struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid, get_buddy_flags_t flags );
+struct jabber_buddy *jabber_buddy_by_ext_jid( struct im_connection *ic, char *jid, get_buddy_flags_t flags );
int jabber_buddy_remove( struct im_connection *ic, char *full_jid );
int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid );
+struct groupchat *jabber_chat_by_name( struct im_connection *ic, const char *name );
+time_t jabber_get_timestamp( struct xt_node *xt );
+struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns );
+void jabber_error_free( struct jabber_error *err );
extern const struct jabber_away_state jabber_away_state_list[];
@@ -192,4 +224,13 @@ xt_status sasl_pkt_challenge( struct xt_node *node, gpointer data );
xt_status sasl_pkt_result( struct xt_node *node, gpointer data );
gboolean sasl_supported( struct im_connection *ic );
+/* conference.c */
+struct groupchat *jabber_chat_join( struct im_connection *ic, char *room, char *nick, char *password );
+void jabber_chat_free( struct groupchat *c );
+int jabber_chat_msg( struct groupchat *ic, char *message, int flags );
+int jabber_chat_topic( struct groupchat *c, char *topic );
+int jabber_chat_leave( struct groupchat *c, const char *reason );
+void jabber_chat_pkt_presence( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node );
+void jabber_chat_pkt_message( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node );
+
#endif
diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c
index 3c0e71f4..56491c4f 100644
--- a/protocols/jabber/jabber_util.c
+++ b/protocols/jabber/jabber_util.c
@@ -47,7 +47,7 @@ char *set_eval_priority( set_t *set, char *value )
convenient, they have one disadvantage: If I would just
call p_s_u() now to send the new prio setting, it would
send the old setting because the set->value gets changed
- when the eval returns a non-NULL value.
+ after the (this) eval returns a non-NULL value.
So now I can choose between implementing post-set
functions next to evals, or just do this little hack: */
@@ -128,7 +128,7 @@ struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond,
/* Cache a node/packet for later use. Mainly useful for IQ packets if you need
them when you receive the response. Use this BEFORE sending the packet so
- it'll get a new id= tag, and do NOT free() the packet after writing it! */
+ it'll get a new id= tag, and do NOT free() the packet after sending it! */
void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func )
{
struct jabber_data *jd = ic->proto_data;
@@ -251,7 +251,7 @@ void jabber_buddy_ask( struct im_connection *ic, char *handle )
}
/* Returns a new string. Don't leak it! */
-char *jabber_normalize( char *orig )
+char *jabber_normalize( const char *orig )
{
int len, i;
char *new;
@@ -319,6 +319,8 @@ struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid_
}
else
{
+ /* Keep in mind that full_jid currently isn't really
+ a full JID... */
new->bare_jid = g_strdup( full_jid );
g_hash_table_insert( jd->buddies, new->bare_jid, new );
}
@@ -332,7 +334,8 @@ struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid_
else
{
/* Let's waste some more bytes of RAM instead of to make
- memory management a total disaster here.. */
+ memory management a total disaster here. And it saves
+ me one g_free() call in this function. :-P */
new->full_jid = full_jid;
}
@@ -352,6 +355,8 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_,
if( ( s = strchr( jid, '/' ) ) )
{
+ int none_found = 0;
+
*s = 0;
if( ( bud = g_hash_table_lookup( jd->buddies, jid ) ) )
{
@@ -369,8 +374,16 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_,
break;
}
}
+ else
+ {
+ /* This hack is there to make sure that O_CREAT will
+ work if there's already another resouce present
+ for this JID, even if it's an unknown buddy. This
+ is done to handle conferences properly. */
+ none_found = 1;
+ }
- if( bud == NULL && ( flags & GET_BUDDY_CREAT ) && imcb_find_buddy( ic, jid ) )
+ if( bud == NULL && ( flags & GET_BUDDY_CREAT ) && ( imcb_find_buddy( ic, jid ) || !none_found ) )
{
*s = '/';
bud = jabber_buddy_add( ic, jid );
@@ -417,6 +430,38 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_,
}
}
+/* I'm keeping a separate ext_jid attribute to save a JID that makes sense
+ to export to BitlBee. This is mainly for groupchats right now. It's
+ a bit of a hack, but I just think having the user nickname in the hostname
+ part of the hostmask doesn't look nice on IRC. Normally you can convert
+ a normal JID to ext_jid by swapping the part before and after the / and
+ replacing the / with a =. But there should be some stripping (@s are
+ allowed in Jabber nicks...). */
+struct jabber_buddy *jabber_buddy_by_ext_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags )
+{
+ struct jabber_buddy *bud;
+ char *s, *jid;
+
+ jid = jabber_normalize( jid_ );
+
+ if( ( s = strchr( jid, '=' ) ) == NULL )
+ return NULL;
+
+ for( bud = jabber_buddy_by_jid( ic, s + 1, GET_BUDDY_FIRST ); bud; bud = bud->next )
+ {
+ /* Hmmm, could happen if not all people in the chat are anonymized? */
+ if( bud->ext_jid == NULL )
+ continue;
+
+ if( strcmp( bud->ext_jid, jid ) == 0 )
+ break;
+ }
+
+ g_free( jid );
+
+ return bud;
+}
+
/* Remove one specific full JID from our list. Use this when a buddy goes
off-line (because (s)he can still be online from a different location.
XXX: See above, we should accept bare JIDs too... */
@@ -440,6 +485,7 @@ int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )
{
g_hash_table_remove( jd->buddies, bud->bare_jid );
g_free( bud->bare_jid );
+ g_free( bud->ext_jid );
g_free( bud->full_jid );
g_free( bud->away_message );
g_free( bud );
@@ -472,6 +518,7 @@ int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )
item, because we're removing the first. */
g_hash_table_replace( jd->buddies, bi->bare_jid, bi->next );
+ g_free( bi->ext_jid );
g_free( bi->full_jid );
g_free( bi->away_message );
g_free( bi );
@@ -494,39 +541,144 @@ int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )
/* Remove a buddy completely; removes all resources that belong to the
specified bare JID. Use this when removing someone from the contact
list, for example. */
-int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid_ )
+int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid )
{
struct jabber_data *jd = ic->proto_data;
struct jabber_buddy *bud, *next;
- char *bare_jid;
- if( strchr( bare_jid_, '/' ) )
+ if( strchr( bare_jid, '/' ) )
return 0;
- bare_jid = jabber_normalize( bare_jid_ );
-
- if( ( bud = g_hash_table_lookup( jd->buddies, bare_jid ) ) )
+ if( ( bud = jabber_buddy_by_jid( ic, bare_jid, GET_BUDDY_FIRST ) ) )
{
/* Most important: Remove the hash reference. We don't know
this buddy anymore. */
g_hash_table_remove( jd->buddies, bud->bare_jid );
+ g_free( bud->bare_jid );
/* Deallocate the linked list of resources. */
while( bud )
{
+ /* ext_jid && anonymous means that this buddy is
+ specific to one groupchat (the one we're
+ currently cleaning up) so it can be deleted
+ completely. */
+ if( bud->ext_jid && bud->flags & JBFLAG_IS_ANONYMOUS )
+ imcb_remove_buddy( ic, bud->ext_jid, NULL );
+
next = bud->next;
+ g_free( bud->ext_jid );
g_free( bud->full_jid );
g_free( bud->away_message );
g_free( bud );
bud = next;
}
- g_free( bare_jid );
return 1;
}
else
{
- g_free( bare_jid );
return 0;
}
}
+
+struct groupchat *jabber_chat_by_name( struct im_connection *ic, const char *name )
+{
+ char *normalized = jabber_normalize( name );
+ struct groupchat *ret;
+ struct jabber_chat *jc;
+
+ for( ret = ic->groupchats; ret; ret = ret->next )
+ {
+ jc = ret->data;
+ if( strcmp( normalized, jc->name ) == 0 )
+ break;
+ }
+ g_free( normalized );
+
+ return ret;
+}
+
+time_t jabber_get_timestamp( struct xt_node *xt )
+{
+ struct tm tp, utc;
+ struct xt_node *c;
+ time_t res, tres;
+ char *s = NULL;
+
+ for( c = xt->children; ( c = xt_find_node( c, "x" ) ); c = c->next )
+ {
+ if( ( s = xt_find_attr( c, "xmlns" ) ) && strcmp( s, XMLNS_DELAY ) == 0 )
+ break;
+ }
+
+ if( !c || !( s = xt_find_attr( c, "stamp" ) ) )
+ return 0;
+
+ memset( &tp, 0, sizeof( tp ) );
+ if( sscanf( s, "%4d%2d%2dT%2d:%2d:%2d", &tp.tm_year, &tp.tm_mon, &tp.tm_mday,
+ &tp.tm_hour, &tp.tm_min, &tp.tm_sec ) != 6 )
+ return 0;
+
+ tp.tm_year -= 1900;
+ tp.tm_mon --;
+ tp.tm_isdst = -1; /* GRRRRRRRRRRR */
+
+ res = mktime( &tp );
+ /* Problem is, mktime() just gave us the GMT timestamp for the
+ given local time... While the given time WAS NOT local. So
+ we should fix this now.
+
+ Now I could choose between messing with environment variables
+ (kludgy) or using timegm() (not portable)... Or doing the
+ following, which I actually prefer... */
+ gmtime_r( &res, &utc );
+ utc.tm_isdst = -1; /* Once more: GRRRRRRRRRRRRRRRRRR!!! */
+ if( utc.tm_hour == tp.tm_hour && utc.tm_min == tp.tm_min )
+ /* Sweet! We're in UTC right now... */
+ return res;
+
+ tres = mktime( &utc );
+ res += res - tres;
+
+ /* Yes, this is a hack. And it will go wrong around DST changes.
+ BUT this is more likely to be threadsafe than messing with
+ environment variables, and possibly more portable... */
+
+ return res;
+}
+
+struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns )
+{
+ struct jabber_error *err = g_new0( struct jabber_error, 1 );
+ struct xt_node *c;
+ char *s;
+
+ err->type = xt_find_attr( node, "type" );
+
+ for( c = node->children; c; c = c->next )
+ {
+ if( !( s = xt_find_attr( c, "xmlns" ) ) ||
+ strcmp( s, xmlns ) != 0 )
+ continue;
+
+ if( strcmp( c->name, "text" ) != 0 )
+ {
+ err->code = c->name;
+ }
+ /* Only use the text if it doesn't have an xml:lang attribute,
+ if it's empty or if it's set to something English. */
+ else if( !( s = xt_find_attr( c, "xml:lang" ) ) ||
+ !*s || strncmp( s, "en", 2 ) == 0 )
+ {
+ err->text = c->text;
+ }
+ }
+
+ return err;
+}
+
+void jabber_error_free( struct jabber_error *err )
+{
+ g_free( err );
+}
diff --git a/protocols/jabber/message.c b/protocols/jabber/message.c
index 19edbdfd..fab62a91 100644
--- a/protocols/jabber/message.c
+++ b/protocols/jabber/message.c
@@ -29,25 +29,33 @@ xt_status jabber_pkt_message( struct xt_node *node, gpointer data )
char *from = xt_find_attr( node, "from" );
char *type = xt_find_attr( node, "type" );
struct xt_node *body = xt_find_node( node->children, "body" ), *c;
+ struct jabber_buddy *bud = NULL;
char *s;
+ if( !from )
+ return XT_HANDLED; /* Consider this packet corrupted. */
+
+ bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT );
+
if( type && strcmp( type, "error" ) == 0 )
{
/* Handle type=error packet. */
}
- else if( type && strcmp( type, "groupchat" ) == 0 )
+ else if( type && from && strcmp( type, "groupchat" ) == 0 )
{
- /* TODO! */
+ jabber_chat_pkt_message( ic, bud, node );
}
else /* "chat", "normal", "headline", no-type or whatever. Should all be pretty similar. */
{
- struct jabber_buddy *bud = NULL;
GString *fullmsg = g_string_new( "" );
if( ( s = strchr( from, '/' ) ) )
{
- if( ( bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT ) ) )
+ if( bud )
+ {
bud->last_act = time( NULL );
+ from = bud->ext_jid ? : bud->bare_jid;
+ }
else
*s = 0; /* We need to generate a bare JID now. */
}
@@ -75,26 +83,31 @@ xt_status jabber_pkt_message( struct xt_node *node, gpointer data )
fullmsg = g_string_append( fullmsg, body->text );
if( fullmsg->len > 0 )
- imcb_buddy_msg( ic, bud ? bud->bare_jid : from, fullmsg->str, 0, 0 );
+ imcb_buddy_msg( ic, from, fullmsg->str,
+ 0, jabber_get_timestamp( node ) );
g_string_free( fullmsg, TRUE );
/* Handling of incoming typing notifications. */
- if( xt_find_node( node->children, "composing" ) )
+ if( bud == NULL )
+ {
+ /* Can't handle these for unknown buddies. */
+ }
+ else if( xt_find_node( node->children, "composing" ) )
{
bud->flags |= JBFLAG_DOES_XEP85;
- imcb_buddy_typing( ic, bud ? bud->bare_jid : from, OPT_TYPING );
+ imcb_buddy_typing( ic, from, OPT_TYPING );
}
/* No need to send a "stopped typing" signal when there's a message. */
else if( xt_find_node( node->children, "active" ) && ( body == NULL ) )
{
bud->flags |= JBFLAG_DOES_XEP85;
- imcb_buddy_typing( ic, bud ? bud->bare_jid : from, 0 );
+ imcb_buddy_typing( ic, from, 0 );
}
else if( xt_find_node( node->children, "paused" ) )
{
bud->flags |= JBFLAG_DOES_XEP85;
- imcb_buddy_typing( ic, bud ? bud->bare_jid : from, OPT_THINKING );
+ imcb_buddy_typing( ic, from, OPT_THINKING );
}
if( s )
diff --git a/protocols/jabber/presence.c b/protocols/jabber/presence.c
index 49c66a3d..71a044b5 100644
--- a/protocols/jabber/presence.c
+++ b/protocols/jabber/presence.c
@@ -30,15 +30,22 @@ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )
char *type = xt_find_attr( node, "type" ); /* NULL should mean the person is online. */
struct xt_node *c;
struct jabber_buddy *bud;
+ int is_chat = 0, is_away = 0;
char *s;
if( !from )
return XT_HANDLED;
+ if( ( s = strchr( from, '/' ) ) )
+ {
+ *s = 0;
+ if( jabber_chat_by_name( ic, from ) )
+ is_chat = 1;
+ *s = '/';
+ }
+
if( type == NULL )
{
- int is_away = 0;
-
if( !( bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT | GET_BUDDY_CREAT ) ) )
{
if( set_getbool( &ic->irc->set, "debug" ) )
@@ -71,30 +78,55 @@ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )
else
bud->priority = 0;
- if( bud == jabber_buddy_by_jid( ic, bud->bare_jid, 0 ) )
+ if( is_chat )
+ jabber_chat_pkt_presence( ic, bud, node );
+ else if( bud == jabber_buddy_by_jid( ic, bud->bare_jid, 0 ) )
imcb_buddy_status( ic, bud->bare_jid, OPT_LOGGED_IN | is_away,
( is_away && bud->away_state ) ? bud->away_state->full_name : NULL,
bud->away_message );
}
else if( strcmp( type, "unavailable" ) == 0 )
{
- if( jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT ) == NULL )
+ if( ( bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT ) ) == NULL )
{
if( set_getbool( &ic->irc->set, "debug" ) )
imcb_log( ic, "WARNING: Received presence information from unknown JID: %s", from );
return XT_HANDLED;
}
+ /* Handle this before we delete the JID. */
+ if( is_chat )
+ {
+ jabber_chat_pkt_presence( ic, bud, node );
+ }
+
jabber_buddy_remove( ic, from );
- if( ( s = strchr( from, '/' ) ) )
+ if( is_chat )
+ {
+ /* Nothing else to do for now? */
+ }
+ else if( ( s = strchr( from, '/' ) ) )
{
*s = 0;
- /* Only count this as offline if there's no other resource
- available anymore. */
- if( jabber_buddy_by_jid( ic, from, 0 ) == NULL )
+ /* If another resource is still available, send its presence
+ information. */
+ if( ( bud = jabber_buddy_by_jid( ic, from, 0 ) ) )
+ {
+ if( bud->away_state && ( *bud->away_state->code == 0 ||
+ strcmp( bud->away_state->code, "chat" ) == 0 ) )
+ is_away = OPT_AWAY;
+
+ imcb_buddy_status( ic, bud->bare_jid, OPT_LOGGED_IN | is_away,
+ ( is_away && bud->away_state ) ? bud->away_state->full_name : NULL,
+ bud->away_message );
+ }
+ else
+ {
+ /* Otherwise, count him/her as offline now. */
imcb_buddy_status( ic, from, 0, NULL, NULL );
+ }
*s = '/';
}
@@ -125,7 +157,17 @@ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )
}
else if( strcmp( type, "error" ) == 0 )
{
- /* What to do with it? */
+ struct jabber_error *err;
+
+ if( ( c = xt_find_node( node->children, "error" ) ) )
+ {
+ err = jabber_error_parse( c, XMLNS_STANZA_ERROR );
+ imcb_error( ic, "Stanza (%s) error: %s%s%s", node->name,
+ err->code, err->text ? ": " : "",
+ err->text ? err->text : "" );
+ jabber_error_free( err );
+ }
+ /* What else to do with it? */
}
return XT_HANDLED;
@@ -139,6 +181,7 @@ int presence_send_update( struct im_connection *ic )
struct xt_node *node;
char *show = jd->away_state->code;
char *status = jd->away_message;
+ struct groupchat *c;
int st;
node = jabber_make_packet( "presence", NULL, NULL, NULL );
@@ -150,6 +193,16 @@ int presence_send_update( struct im_connection *ic )
st = jabber_write_packet( ic, node );
+ /* Have to send this update to all groupchats too, the server won't
+ do this automatically. */
+ for( c = ic->groupchats; c && st; c = c->next )
+ {
+ struct jabber_chat *jc = c->data;
+
+ xt_add_attr( node, "to", jc->my_full_jid );
+ st = jabber_write_packet( ic, node );
+ }
+
xt_free_node( node );
return st;
}
diff --git a/protocols/jabber/sasl.c b/protocols/jabber/sasl.c
index 6eee37b3..87059051 100644
--- a/protocols/jabber/sasl.c
+++ b/protocols/jabber/sasl.c
@@ -331,5 +331,5 @@ gboolean sasl_supported( struct im_connection *ic )
{
struct jabber_data *jd = ic->proto_data;
- return ( (void*) ( jd->xt && jd->xt->root && xt_find_attr( jd->xt->root, "version" ) ) ) != NULL;
+ return ( jd->xt && jd->xt->root && xt_find_attr( jd->xt->root, "version" ) ) != 0;
}
diff --git a/protocols/jabber/xmltree.c b/protocols/jabber/xmltree.c
index 7e74cccb..62549eb5 100644
--- a/protocols/jabber/xmltree.c
+++ b/protocols/jabber/xmltree.c
@@ -443,7 +443,7 @@ void xt_free( struct xt_parser *xt )
/* To find a node's child with a specific name, pass the node's children
list, not the node itself! The reason you have to do this by hand: So
that you can also use this function as a find-next. */
-struct xt_node *xt_find_node( struct xt_node *node, char *name )
+struct xt_node *xt_find_node( struct xt_node *node, const char *name )
{
while( node )
{
@@ -456,7 +456,7 @@ struct xt_node *xt_find_node( struct xt_node *node, char *name )
return node;
}
-char *xt_find_attr( struct xt_node *node, char *key )
+char *xt_find_attr( struct xt_node *node, const char *key )
{
int i;
@@ -525,7 +525,7 @@ void xt_add_child( struct xt_node *parent, struct xt_node *child )
}
}
-void xt_add_attr( struct xt_node *node, char *key, char *value )
+void xt_add_attr( struct xt_node *node, const char *key, const char *value )
{
int i;
@@ -552,7 +552,7 @@ void xt_add_attr( struct xt_node *node, char *key, char *value )
node->attr[i].value = g_strdup( value );
}
-int xt_remove_attr( struct xt_node *node, char *key )
+int xt_remove_attr( struct xt_node *node, const char *key )
{
int i, last;
diff --git a/protocols/jabber/xmltree.h b/protocols/jabber/xmltree.h
index 70850c1d..b8b61641 100644
--- a/protocols/jabber/xmltree.h
+++ b/protocols/jabber/xmltree.h
@@ -86,12 +86,12 @@ void xt_print( struct xt_node *node );
struct xt_node *xt_dup( struct xt_node *node );
void xt_free_node( struct xt_node *node );
void xt_free( struct xt_parser *xt );
-struct xt_node *xt_find_node( struct xt_node *node, char *name );
-char *xt_find_attr( struct xt_node *node, char *key );
+struct xt_node *xt_find_node( struct xt_node *node, const char *name );
+char *xt_find_attr( struct xt_node *node, const char *key );
struct xt_node *xt_new_node( char *name, char *text, struct xt_node *children );
void xt_add_child( struct xt_node *parent, struct xt_node *child );
-void xt_add_attr( struct xt_node *node, char *key, char *value );
-int xt_remove_attr( struct xt_node *node, char *key );
+void xt_add_attr( struct xt_node *node, const char *key, const char *value );
+int xt_remove_attr( struct xt_node *node, const char *key );
#endif
diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c
index 1693cb95..cb9e2cab 100644
--- a/protocols/msn/sb.c
+++ b/protocols/msn/sb.c
@@ -231,7 +231,7 @@ void msn_sb_destroy( struct msn_switchboard *sb )
if( sb->chat )
{
- imcb_chat_removed( sb->chat );
+ imcb_chat_free( sb->chat );
}
if( sb->handler )
diff --git a/protocols/nogaim.c b/protocols/nogaim.c
index d0395fa9..e0f04c0b 100644
--- a/protocols/nogaim.c
+++ b/protocols/nogaim.c
@@ -98,7 +98,6 @@ void register_protocol (struct prpl *p)
protocols = g_list_append(protocols, p);
}
-
struct prpl *find_protocol(const char *name)
{
GList *gl;
@@ -288,7 +287,7 @@ void cancel_auto_reconnect( account_t *a )
void imc_logout( struct im_connection *ic, int allow_reconnect )
{
irc_t *irc = ic->irc;
- user_t *t, *u = irc->users;
+ user_t *t, *u;
account_t *a;
/* Nested calls might happen sometimes, this is probably the best
@@ -305,6 +304,7 @@ void imc_logout( struct im_connection *ic, int allow_reconnect )
ic->acc->prpl->logout( ic );
b_event_remove( ic->inpa );
+ u = irc->users;
while( u )
{
if( u->ic == ic )
@@ -427,7 +427,6 @@ struct buddy *imcb_find_buddy( struct im_connection *ic, char *handle )
void imcb_rename_buddy( struct im_connection *ic, char *handle, char *realname )
{
user_t *u = user_findhandle( ic, handle );
- char *s, newnick[MAX_NICK_LENGTH+1];
if( !u || !realname ) return;
@@ -439,31 +438,54 @@ void imcb_rename_buddy( struct im_connection *ic, char *handle, char *realname )
if( ( ic->flags & OPT_LOGGED_IN ) && set_getbool( &ic->irc->set, "display_namechanges" ) )
imcb_log( ic, "User `%s' changed name to `%s'", u->nick, u->realname );
+ }
+}
+
+void imcb_remove_buddy( struct im_connection *ic, char *handle, char *group )
+{
+ user_t *u;
+
+ if( ( u = user_findhandle( ic, handle ) ) )
+ user_del( ic->irc, u->nick );
+}
+
+/* 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, char *handle, char *nick )
+{
+ user_t *u = user_findhandle( ic, handle );
+ char newnick[MAX_NICK_LENGTH+1], *orig_nick;
+
+ if( u && !u->online && !nick_saved( ic->acc, handle ) )
+ {
+ /* Only do this if the person isn't online yet (which should
+ be the case if we just added it) and if the user hasn't
+ assigned a nickname to this buddy already. */
+
+ strncpy( newnick, nick, MAX_NICK_LENGTH );
+ newnick[MAX_NICK_LENGTH] = 0;
- if( !u->online && !nick_saved( ic->acc, handle ) )
+ /* Some processing to make sure this string is a valid IRC nickname. */
+ nick_strip( newnick );
+ if( set_getbool( &ic->irc->set, "lcnicks" ) )
+ nick_lc( newnick );
+
+ if( strcmp( u->nick, newnick ) != 0 )
{
- /* Detect numeric handles: */
- for( s = u->user; isdigit( *s ); s++ );
+ /* Only do this if newnick is different from the current one.
+ If rejoining a channel, maybe we got this nick already
+ (and dedupe would only add an underscore. */
+ nick_dedupe( ic->acc, handle, newnick );
- if( *s == 0 )
- {
- /* If we reached the end of the string, it only contained numbers.
- Seems to be an ICQ# then, so hopefully realname contains
- something more useful. */
- strcpy( newnick, realname );
-
- /* Some processing to make sure this string is a valid IRC nickname. */
- nick_strip( newnick );
- if( set_getbool( &ic->irc->set, "lcnicks" ) )
- nick_lc( newnick );
-
- u->nick = g_strdup( newnick );
- }
+ /* u->nick will be freed halfway the process, so it can't be
+ passed as an argument. */
+ orig_nick = g_strdup( u->nick );
+ user_rename( ic->irc, orig_nick, newnick );
+ g_free( orig_nick );
}
}
}
-
/* prpl.c */
struct show_got_added_data
@@ -553,8 +575,8 @@ void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags,
irc_kill( ic->irc, u );
u->online = 0;
- /* Remove him/her from the conversations to prevent PART messages after he/she QUIT already */
- for( c = ic->conversations; c; c = c->next )
+ /* Remove him/her from the groupchats to prevent PART messages after he/she QUIT already */
+ for( c = ic->groupchats; c; c = c->next )
remove_chat_buddy_silent( c, handle );
}
@@ -669,10 +691,10 @@ void imcb_buddy_typing( struct im_connection *ic, char *handle, u_int32_t flags
}
}
-void imcb_chat_removed( struct groupchat *c )
+void imcb_chat_free( struct groupchat *c )
{
struct im_connection *ic = c->ic;
- struct groupchat *l = NULL;
+ struct groupchat *l;
GList *ir;
if( set_getbool( &ic->irc->set, "debug" ) )
@@ -692,10 +714,13 @@ void imcb_chat_removed( struct groupchat *c )
/* irc_part( ic->irc, u, c->channel ); */
}
+ /* Find the previous chat in the linked list. */
+ for( l = ic->groupchats; l && l->next != c; l = l->next );
+
if( l )
l->next = c->next;
else
- ic->conversations = c->next;
+ ic->groupchats = c->next;
for( ir = c->in_room; ir; ir = ir->next )
g_free( ir->data );
@@ -734,23 +759,47 @@ void imcb_chat_msg( struct groupchat *c, char *who, char *msg, u_int32_t flags,
g_free( wrapped );
}
+void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at )
+{
+ struct im_connection *ic = c->ic;
+ user_t *u = NULL;
+
+ if( who == NULL)
+ u = user_find( ic->irc, ic->irc->mynick );
+ else if( g_strcasecmp( who, ic->acc->user ) == 0 )
+ u = user_find( ic->irc, ic->irc->nick );
+ else
+ u = user_findhandle( ic, who );
+
+ if( ( g_strcasecmp( set_getstr( &ic->irc->set, "strip_html" ), "always" ) == 0 ) ||
+ ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->irc->set, "strip_html" ) ) )
+ strip_html( topic );
+
+ g_free( c->topic );
+ c->topic = g_strdup( topic );
+
+ if( c->joined && u )
+ irc_write( ic->irc, ":%s!%s@%s TOPIC %s :%s", u->nick, u->user, u->host, c->channel, topic );
+}
+
struct groupchat *imcb_chat_new( struct im_connection *ic, char *handle )
{
struct groupchat *c;
/* This one just creates the conversation structure, user won't see anything yet */
- if( ic->conversations )
+ if( ic->groupchats )
{
- for( c = ic->conversations; c->next; c = c->next );
+ for( c = ic->groupchats; c->next; c = c->next );
c = c->next = g_new0( struct groupchat, 1 );
}
else
- ic->conversations = c = g_new0( struct groupchat, 1 );
+ ic->groupchats = c = g_new0( struct groupchat, 1 );
c->ic = ic;
c->title = g_strdup( handle );
c->channel = g_strdup_printf( "&chat_%03d", ic->irc->c_id++ );
+ c->topic = g_strdup_printf( "%s :BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", c->channel, c->title );
if( set_getbool( &ic->irc->set, "debug" ) )
imcb_log( ic, "Creating new conversation: (id=0x%x,handle=%s)", (int) c, handle );
@@ -795,6 +844,7 @@ void imcb_chat_add_buddy( struct groupchat *b, char *handle )
}
}
+/* This function is one BIG hack... :-( EREWRITE */
void imcb_chat_remove_buddy( struct groupchat *b, char *handle, char *reason )
{
user_t *u;
@@ -806,6 +856,9 @@ void imcb_chat_remove_buddy( struct groupchat *b, char *handle, char *reason )
/* It might be yourself! */
if( g_strcasecmp( handle, b->ic->acc->user ) == 0 )
{
+ if( b->joined == 0 )
+ return;
+
u = user_find( b->ic->irc, b->ic->irc->nick );
b->joined = 0;
me = 1;
@@ -815,9 +868,8 @@ void imcb_chat_remove_buddy( struct groupchat *b, char *handle, char *reason )
u = user_findhandle( b->ic, handle );
}
- if( remove_chat_buddy_silent( b, handle ) )
- if( ( b->joined || me ) && u )
- irc_part( b->ic->irc, u, b->channel );
+ if( me || ( remove_chat_buddy_silent( b, handle ) && b->joined && u ) )
+ irc_part( b->ic->irc, u, b->channel );
}
static int remove_chat_buddy_silent( struct groupchat *b, const char *handle )
@@ -844,24 +896,6 @@ static int remove_chat_buddy_silent( struct groupchat *b, const char *handle )
/* Misc. BitlBee stuff which shouldn't really be here */
-struct groupchat *chat_by_channel( char *channel )
-{
- struct im_connection *ic;
- struct groupchat *c;
- GSList *l;
-
- /* This finds the connection which has a conversation which belongs to this channel */
- for( l = connections; l; l = l->next )
- {
- ic = l->data;
- for( c = ic->conversations; c && g_strcasecmp( c->channel, channel ) != 0; c = c->next );
- if( c )
- return c;
- }
-
- return NULL;
-}
-
char *set_eval_away_devoice( set_t *set, char *value )
{
irc_t *irc = set->data;
@@ -1094,3 +1128,35 @@ void imc_rem_block( struct im_connection *ic, char *handle )
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 );
+}
diff --git a/protocols/nogaim.h b/protocols/nogaim.h
index ff4cdb96..adee5e33 100644
--- a/protocols/nogaim.h
+++ b/protocols/nogaim.h
@@ -60,6 +60,7 @@
#define OPT_LOGGING_OUT 0x00000002
#define OPT_AWAY 0x00000004
#define OPT_DOES_HTML 0x00000010
+#define OPT_LOCALBUDDY 0x00000020 /* For nicks local to one groupchat */
#define OPT_TYPING 0x00000100 /* Some pieces of code make assumptions */
#define OPT_THINKING 0x00000200 /* about these values... Stupid me! */
@@ -90,7 +91,7 @@ struct im_connection
/* BitlBee */
irc_t *irc;
- struct groupchat *conversations;
+ struct groupchat *groupchats;
};
struct groupchat {
@@ -103,12 +104,14 @@ struct groupchat {
GList *in_room;
GList *ignored;
- /* BitlBee */
struct groupchat *next;
char *channel;
/* The title variable contains the ID you gave when you created the
* chat using imcb_chat_new(). */
char *title;
+ /* Use imcb_chat_topic() to change this variable otherwise the user
+ * won't notice the topic change. */
+ char *topic;
char joined;
/* This is for you, you can add your own structure here to extend this
* structure for your protocol's needs. */
@@ -211,6 +214,11 @@ struct prpl {
* not implement this. */
struct groupchat *
(* chat_join) (struct im_connection *, char *room, char *nick, char *password);
+ /* Change the topic, if supported. Note that BitlBee expects the IM
+ server to confirm the topic change with a regular topic change
+ event. If it doesn't do that, you have to fake it to make it
+ visible to the user. */
+ void (* chat_topic) (struct groupchat *, char *topic);
/* You can tell what away states your protocol supports, so that
* BitlBee will try to map the IRC away reasons to them, or use
@@ -264,6 +272,7 @@ G_MODULE_EXPORT void imcb_add_buddy( struct im_connection *ic, char *handle, cha
G_MODULE_EXPORT void imcb_remove_buddy( struct im_connection *ic, char *handle, char *group );
G_MODULE_EXPORT struct buddy *imcb_find_buddy( struct im_connection *ic, char *handle );
G_MODULE_EXPORT void imcb_rename_buddy( struct im_connection *ic, char *handle, char *realname );
+G_MODULE_EXPORT void imcb_buddy_nick_hint( struct im_connection *ic, char *handle, char *nick );
/* Buddy activity */
/* To manipulate the status of a handle.
@@ -275,6 +284,7 @@ G_MODULE_EXPORT void imcb_buddy_status( struct im_connection *ic, const char *ha
/* Call when a handle says something. 'flags' and 'sent_at may be just 0. */
G_MODULE_EXPORT void imcb_buddy_msg( struct im_connection *ic, char *handle, char *msg, u_int32_t flags, time_t sent_at );
G_MODULE_EXPORT void imcb_buddy_typing( struct im_connection *ic, char *handle, u_int32_t flags );
+G_MODULE_EXPORT void imcb_clean_handle( struct im_connection *ic, char *handle );
/* Groupchats */
G_MODULE_EXPORT void imcb_chat_invited( struct im_connection *ic, char *handle, char *who, char *msg, GList *data );
@@ -290,8 +300,9 @@ G_MODULE_EXPORT void imcb_chat_add_buddy( struct groupchat *b, char *handle );
G_MODULE_EXPORT void imcb_chat_remove_buddy( struct groupchat *b, char *handle, char *reason );
/* To tell BitlBee 'who' said 'msg' in 'c'. 'flags' and 'sent_at' can be 0. */
G_MODULE_EXPORT void imcb_chat_msg( struct groupchat *c, char *who, char *msg, u_int32_t flags, time_t sent_at );
-G_MODULE_EXPORT void imcb_chat_removed( struct groupchat *c );
-struct groupchat *chat_by_channel( char *channel );
+/* To tell BitlBee 'who' changed the topic of 'c' to 'topic'. */
+G_MODULE_EXPORT void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at );
+G_MODULE_EXPORT void imcb_chat_free( struct groupchat *c );
/* Actions, or whatever. */
int imc_set_away( struct im_connection *ic, char *away );
diff --git a/protocols/oscar/oscar.c b/protocols/oscar/oscar.c
index 2b8b4853..96983738 100644
--- a/protocols/oscar/oscar.c
+++ b/protocols/oscar/oscar.c
@@ -2030,6 +2030,7 @@ static int gaim_ssi_parselist(aim_session_t *sess, aim_frame_t *fr, ...) {
imcb_add_buddy(ic, curitem->name, NULL);
if (realname) {
+ imcb_buddy_nick_hint(ic, curitem->name, realname);
imcb_rename_buddy(ic, curitem->name, realname);
g_free(realname);
}
@@ -2522,7 +2523,7 @@ void oscar_chat_kill(struct im_connection *ic, struct chat_connection *cc)
struct oscar_data *od = (struct oscar_data *)ic->proto_data;
/* Notify the conversation window that we've left the chat */
- imcb_chat_removed(cc->cnv);
+ imcb_chat_free(cc->cnv);
/* Destroy the chat_connection */
od->oscar_chats = g_slist_remove(od->oscar_chats, cc);
diff --git a/protocols/yahoo/yahoo.c b/protocols/yahoo/yahoo.c
index 69fc29bb..28a72877 100644
--- a/protocols/yahoo/yahoo.c
+++ b/protocols/yahoo/yahoo.c
@@ -144,8 +144,8 @@ static void byahoo_logout( struct im_connection *ic )
struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data;
GSList *l;
- while( ic->conversations )
- imcb_chat_removed( ic->conversations );
+ while( ic->groupchats )
+ imcb_chat_free( ic->groupchats );
for( l = yd->buddygroups; l; l = l->next )
{
@@ -317,7 +317,7 @@ static void byahoo_chat_leave( struct groupchat *c )
struct byahoo_data *yd = (struct byahoo_data *) c->ic->proto_data;
yahoo_conference_logoff( yd->y2_id, NULL, c->data, c->title );
- imcb_chat_removed( c );
+ imcb_chat_free( c );
}
static struct groupchat *byahoo_chat_with( struct im_connection *ic, char *who )
@@ -797,7 +797,7 @@ static void byahoo_accept_conf( gpointer w, struct byahoo_conf_invitation *inv )
static void byahoo_reject_conf( gpointer w, struct byahoo_conf_invitation *inv )
{
yahoo_conference_decline( inv->yid, NULL, inv->members, inv->name, "User rejected groupchat" );
- imcb_chat_removed( inv->c );
+ imcb_chat_free( inv->c );
g_free( inv->name );
g_free( inv );
}
@@ -840,7 +840,7 @@ void ext_yahoo_conf_userjoin( int id, const char *ignored, const char *who, cons
struct im_connection *ic = byahoo_get_ic_by_id( id );
struct groupchat *c;
- for( c = ic->conversations; c && strcmp( c->title, room ) != 0; c = c->next );
+ for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next );
if( c )
imcb_chat_add_buddy( c, (char*) who );
@@ -852,7 +852,7 @@ void ext_yahoo_conf_userleave( int id, const char *ignored, const char *who, con
struct im_connection *ic = byahoo_get_ic_by_id( id );
struct groupchat *c;
- for( c = ic->conversations; c && strcmp( c->title, room ) != 0; c = c->next );
+ for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next );
if( c )
imcb_chat_remove_buddy( c, (char*) who, "" );
@@ -864,7 +864,7 @@ void ext_yahoo_conf_message( int id, const char *ignored, const char *who, const
char *m = byahoo_strip( msg );
struct groupchat *c;
- for( c = ic->conversations; c && strcmp( c->title, room ) != 0; c = c->next );
+ for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next );
if( c )
imcb_chat_msg( c, (char*) who, (char*) m, 0, 0 );
diff --git a/root_commands.c b/root_commands.c
index 59852de6..e9c71f04 100644
--- a/root_commands.c
+++ b/root_commands.c
@@ -929,24 +929,49 @@ static void cmd_join_chat( irc_t *irc, char **cmd )
chat = cmd[2];
if( cmd[3] )
{
- channel = g_strdup( cmd[3] );
+ if( cmd[3][0] != '#' && cmd[3][0] != '&' )
+ channel = g_strdup_printf( "&%s", cmd[3] );
+ else
+ channel = g_strdup( cmd[3] );
}
else
{
char *s;
- channel = g_strdup( chat );
+ channel = g_strdup_printf( "&%s", chat );
if( ( s = strchr( channel, '@' ) ) )
*s = 0;
}
if( cmd[3] && cmd[4] )
nick = cmd[4];
+ else
+ nick = irc->nick;
if( cmd[3] && cmd[4] && cmd[5] )
password = cmd[5];
- c = a->prpl->chat_join( ic, chat, nick, password );
+ if( !nick_ok( channel + 1 ) )
+ {
+ irc_usermsg( irc, "Invalid channel name: %s", channel );
+ g_free( channel );
+ return;
+ }
+ else if( g_strcasecmp( channel, irc->channel ) == 0 || irc_chat_by_channel( irc, channel ) )
+ {
+ irc_usermsg( irc, "Channel already exists: %s", channel );
+ g_free( channel );
+ return;
+ }
- g_free( channel );
+ if( ( c = a->prpl->chat_join( ic, chat, nick, password ) ) )
+ {
+ g_free( c->channel );
+ c->channel = channel;
+ }
+ else
+ {
+ irc_usermsg( irc, "Tried to join chat, not sure if this was successful" );
+ g_free( channel );
+ }
}
const command_t commands[] = {
diff --git a/user.c b/user.c
index 232e3a58..26676dd4 100644
--- a/user.c
+++ b/user.c
@@ -160,6 +160,7 @@ user_t *user_findhandle( struct im_connection *ic, char *handle )
return NULL;
}
+/* DO NOT PASS u->nick FOR oldnick !!! */
void user_rename( irc_t *irc, char *oldnick, char *newnick )
{
user_t *u = user_find( irc, oldnick );