aboutsummaryrefslogtreecommitdiffstats
path: root/protocols
diff options
context:
space:
mode:
Diffstat (limited to 'protocols')
-rw-r--r--protocols/account.c14
-rw-r--r--protocols/bee_chat.c15
-rw-r--r--protocols/jabber/iq.c4
-rw-r--r--protocols/jabber/jabber.c21
-rw-r--r--protocols/jabber/jabber.h1
-rw-r--r--protocols/jabber/jabber_util.c4
-rw-r--r--protocols/nogaim.c11
-rw-r--r--protocols/nogaim.h3
-rw-r--r--protocols/oscar/chat.c5
-rw-r--r--protocols/oscar/tlv.c6
-rw-r--r--protocols/twitter/twitter.c276
-rw-r--r--protocols/twitter/twitter.h18
-rw-r--r--protocols/twitter/twitter_lib.c287
-rw-r--r--protocols/twitter/twitter_lib.h3
14 files changed, 620 insertions, 48 deletions
diff --git a/protocols/account.c b/protocols/account.c
index 188e362e..234b9de2 100644
--- a/protocols/account.c
+++ b/protocols/account.c
@@ -28,7 +28,7 @@
#include "account.h"
static const char* account_protocols_local[] = {
- "gg", NULL
+ "gg", "whatsapp", NULL
};
static char *set_eval_nick_source( set_t *set, char *value );
@@ -350,9 +350,6 @@ static gboolean account_on_timeout( gpointer d, gint fd, b_input_condition cond
void account_on( bee_t *bee, account_t *a )
{
- GHashTableIter nicks;
- gpointer k, v;
-
if( a->ic )
{
/* Trying to enable an already-enabled account */
@@ -366,15 +363,6 @@ void account_on( bee_t *bee, account_t *a )
if( a->ic && !( a->ic->flags & ( OPT_SLOW_LOGIN | OPT_LOGGED_IN ) ) )
a->ic->keepalive = b_timeout_add( 120000, account_on_timeout, a->ic );
-
- if( a->flags & ACC_FLAG_LOCAL )
- {
- g_hash_table_iter_init(&nicks, a->nicks);
- while( g_hash_table_iter_next( &nicks, &k, &v ) )
- {
- a->prpl->add_buddy( a->ic, (char*) k, NULL );
- }
- }
}
void account_off( bee_t *bee, account_t *a )
diff --git a/protocols/bee_chat.c b/protocols/bee_chat.c
index 39110a10..e1d07925 100644
--- a/protocols/bee_chat.c
+++ b/protocols/bee_chat.c
@@ -79,6 +79,13 @@ void imcb_chat_free( struct groupchat *c )
g_free( c );
}
+static gboolean handle_is_self( struct im_connection *ic, const char *handle )
+{
+ return ( ic->acc->prpl->handle_is_self ) ?
+ ic->acc->prpl->handle_is_self( ic, handle ) :
+ ( ic->acc->prpl->handle_cmp( ic->acc->user, handle ) == 0 );
+}
+
void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, uint32_t flags, time_t sent_at )
{
struct im_connection *ic = c->ic;
@@ -88,7 +95,7 @@ void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, uint32_t fl
char *s;
/* Gaim sends own messages through this too. IRC doesn't want this, so kill them */
- if( g_strcasecmp( who, ic->acc->user ) == 0 )
+ if( handle_is_self( ic, who ) )
return;
bu = bee_user_by_handle( bee, ic, who );
@@ -138,7 +145,7 @@ void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at
if( who == NULL)
bu = NULL;
- else if( g_strcasecmp( who, ic->acc->user ) == 0 )
+ else if( handle_is_self( ic, who ) )
bu = bee->user;
else
bu = bee_user_by_handle( bee, ic, who );
@@ -160,7 +167,7 @@ void imcb_chat_add_buddy( struct groupchat *c, const char *handle )
if( set_getbool( &c->ic->bee->set, "debug" ) )
imcb_log( c->ic, "User %s added to conversation %p", handle, c );
- me = ic->acc->prpl->handle_cmp( handle, ic->acc->user ) == 0;
+ me = handle_is_self( ic, handle );
/* Most protocols allow people to join, even when they're not in
your contact list. Try to handle that here */
@@ -188,7 +195,7 @@ void imcb_chat_remove_buddy( struct groupchat *c, const char *handle, const char
imcb_log( ic, "User %s removed from conversation %p (%s)", handle, c, reason ? reason : "" );
/* It might be yourself! */
- if( g_strcasecmp( handle, ic->acc->user ) == 0 )
+ if( handle_is_self( ic, handle ) )
{
if( c->joined == 0 )
return;
diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c
index 61417bcc..cf1ff298 100644
--- a/protocols/jabber/iq.c
+++ b/protocols/jabber/iq.c
@@ -357,10 +357,6 @@ xt_status jabber_pkt_bind_sess( struct im_connection *ic, struct xt_node *node,
if( s )
*s = '\0';
jabber_set_me( ic, c->text );
- imcb_log( ic, "Server claims your JID is `%s' instead of `%s'. "
- "This mismatch may cause problems with groupchats "
- "and possibly other things.",
- c->text, ic->acc->user );
if( s )
*s = '/';
}
diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c
index 4b5cb3a1..30e55159 100644
--- a/protocols/jabber/jabber.c
+++ b/protocols/jabber/jabber.c
@@ -62,6 +62,8 @@ static void jabber_init( account_t *acc )
s = set_add( &acc->set, "oauth", "false", set_eval_oauth, acc );
+ s = set_add( &acc->set, "display_name", NULL, NULL, acc );
+
g_snprintf( str, sizeof( str ), "%d", jabber_port_list[0] );
s = set_add( &acc->set, "port", str, set_eval_int, acc );
s->flags |= ACC_SET_OFFLINE_ONLY;
@@ -317,6 +319,7 @@ static void jabber_logout( struct im_connection *ic )
g_free( jd->oauth2_access_token );
g_free( jd->away_message );
+ g_free( jd->internal_jid );
g_free( jd->username );
g_free( jd->me );
g_free( jd );
@@ -472,14 +475,22 @@ static void jabber_remove_buddy( struct im_connection *ic, char *who, char *grou
static struct groupchat *jabber_chat_join_( struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets )
{
struct jabber_data *jd = ic->proto_data;
+ char *final_nick;
+ /* Ignore the passed nick parameter if we have our own default */
+ if ( !( final_nick = set_getstr( sets, "nick" ) ) &&
+ !( final_nick = set_getstr( &ic->acc->set, "display_name" ) ) ) {
+ /* Well, whatever, actually use the provided default, then */
+ final_nick = (char *) nick;
+ }
+
if( strchr( room, '@' ) == NULL )
imcb_error( ic, "%s is not a valid Jabber room name. Maybe you mean %s@conference.%s?",
room, room, jd->server );
else if( jabber_chat_by_jid( ic, room ) )
imcb_error( ic, "Already present in chat `%s'", room );
else
- return jabber_chat_join( ic, room, nick, set_getstr( sets, "password" ) );
+ return jabber_chat_join( ic, room, final_nick, set_getstr( sets, "password" ) );
return NULL;
}
@@ -620,6 +631,13 @@ void *jabber_buddy_action( struct bee_user *bu, const char *action, char * const
return NULL;
}
+gboolean jabber_handle_is_self( struct im_connection *ic, const char *who ) {
+ struct jabber_data *jd = ic->proto_data;
+ return ( ( g_strcasecmp( who, ic->acc->user ) == 0 ) ||
+ ( jd->internal_jid &&
+ g_strcasecmp( who, jd->internal_jid ) == 0 ) );
+}
+
void jabber_initmodule()
{
struct prpl *ret = g_new0( struct prpl, 1 );
@@ -647,6 +665,7 @@ void jabber_initmodule()
ret->keepalive = jabber_keepalive;
ret->send_typing = jabber_send_typing;
ret->handle_cmp = g_strcasecmp;
+ ret->handle_is_self = jabber_handle_is_self;
ret->transfer_request = jabber_si_transfer_request;
ret->buddy_action_list = jabber_buddy_action_list;
ret->buddy_action = jabber_buddy_action;
diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h
index eb99f9ca..a5882767 100644
--- a/protocols/jabber/jabber.h
+++ b/protocols/jabber/jabber.h
@@ -96,6 +96,7 @@ struct jabber_data
char *username; /* USERNAME@server */
char *server; /* username@SERVER -=> server/domain, not hostname */
char *me; /* bare jid */
+ char *internal_jid;
const struct oauth2_service *oauth2_service;
char *oauth2_access_token;
diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c
index d6396802..1a3d9fd4 100644
--- a/protocols/jabber/jabber_util.c
+++ b/protocols/jabber/jabber_util.c
@@ -823,6 +823,10 @@ gboolean jabber_set_me( struct im_connection *ic, const char *me )
jd->server = strchr( jd->me, '@' );
jd->username = g_strndup( jd->me, jd->server - jd->me );
jd->server ++;
+
+ /* Set the "internal" account username, for groupchats */
+ g_free( jd->internal_jid );
+ jd->internal_jid = g_strdup( jd->me );
return TRUE;
}
diff --git a/protocols/nogaim.c b/protocols/nogaim.c
index ff1c9a85..0a674b40 100644
--- a/protocols/nogaim.c
+++ b/protocols/nogaim.c
@@ -293,6 +293,17 @@ void imcb_connected( struct im_connection *ic )
function should be handled correctly. (IOW, ignored) */
if( ic->flags & OPT_LOGGED_IN )
return;
+
+ if( ic->acc->flags & ACC_FLAG_LOCAL )
+ {
+ GHashTableIter nicks;
+ gpointer k, v;
+ g_hash_table_iter_init( &nicks, ic->acc->nicks );
+ while( g_hash_table_iter_next( &nicks, &k, &v ) )
+ {
+ ic->acc->prpl->add_buddy( ic, (char*) k, NULL );
+ }
+ }
imcb_log( ic, "Logged in" );
diff --git a/protocols/nogaim.h b/protocols/nogaim.h
index c236a0b5..d1711cde 100644
--- a/protocols/nogaim.h
+++ b/protocols/nogaim.h
@@ -262,6 +262,9 @@ struct prpl {
GList *(* buddy_action_list) (struct bee_user *bu);
void *(* buddy_action) (struct bee_user *bu, const char *action, char * const args[], void *data);
+ /* If null, equivalent to handle_cmp( ic->acc->user, who ) */
+ gboolean (* handle_is_self) (struct im_connection *, const char *who);
+
/* Some placeholders so eventually older plugins may cooperate with newer BitlBees. */
void *resv1;
void *resv2;
diff --git a/protocols/oscar/chat.c b/protocols/oscar/chat.c
index d8399563..6c8d8998 100644
--- a/protocols/oscar/chat.c
+++ b/protocols/oscar/chat.c
@@ -66,7 +66,8 @@ int aim_chat_send_im(aim_session_t *sess, aim_conn_t *conn, guint16 flags, const
*
*/
for (i = 0; i < sizeof(ckstr); i++)
- aimutil_put8(ckstr+i, (guint8) rand());
+ (void) aimutil_put8(ckstr+i, (guint8) rand());
+
cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_CHAT, NULL);
cookie->data = NULL; /* XXX store something useful here */
@@ -227,7 +228,7 @@ int aim_chat_invite(aim_session_t *sess, aim_conn_t *conn, const char *sn, const
* Cookie
*/
for (i = 0; i < sizeof(ckstr); i++)
- aimutil_put8(ckstr, (guint8) rand());
+ (void) aimutil_put8(ckstr, (guint8) rand());
/* XXX should be uncached by an unwritten 'invite accept' handler */
if ((priv = g_malloc(sizeof(struct aim_invite_priv)))) {
diff --git a/protocols/oscar/tlv.c b/protocols/oscar/tlv.c
index 89ef6f26..e7e3a7be 100644
--- a/protocols/oscar/tlv.c
+++ b/protocols/oscar/tlv.c
@@ -180,7 +180,7 @@ int aim_addtlvtochain8(aim_tlvlist_t **list, const guint16 t, const guint8 v)
{
guint8 v8[1];
- aimutil_put8(v8, v);
+ (void) aimutil_put8(v8, v);
return aim_addtlvtochain_raw(list, t, 1, v8);
}
@@ -198,7 +198,7 @@ int aim_addtlvtochain16(aim_tlvlist_t **list, const guint16 t, const guint16 v)
{
guint8 v16[2];
- aimutil_put16(v16, v);
+ (void) aimutil_put16(v16, v);
return aim_addtlvtochain_raw(list, t, 2, v16);
}
@@ -216,7 +216,7 @@ int aim_addtlvtochain32(aim_tlvlist_t **list, const guint16 t, const guint32 v)
{
guint8 v32[4];
- aimutil_put32(v32, v);
+ (void) aimutil_put32(v32, v);
return aim_addtlvtochain_raw(list, t, 4, v32);
}
diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c
index d2aafcb4..8f5dc168 100644
--- a/protocols/twitter/twitter.c
+++ b/protocols/twitter/twitter.c
@@ -30,6 +30,215 @@
#include "url.h"
GSList *twitter_connections = NULL;
+
+static int twitter_filter_cmp(struct twitter_filter *tf1,
+ struct twitter_filter *tf2)
+{
+ int i1 = 0;
+ int i2 = 0;
+ int i;
+
+ static const twitter_filter_type_t types[] = {
+ /* Order of the types */
+ TWITTER_FILTER_TYPE_FOLLOW,
+ TWITTER_FILTER_TYPE_TRACK
+ };
+
+ for (i = 0; i < G_N_ELEMENTS(types); i++) {
+ if (types[i] == tf1->type) {
+ i1 = i + 1;
+ break;
+ }
+ }
+
+ for (i = 0; i < G_N_ELEMENTS(types); i++) {
+ if (types[i] == tf2->type) {
+ i2 = i + 1;
+ break;
+ }
+ }
+
+ if (i1 != i2) {
+ /* With different types, return their difference */
+ return i1 - i2;
+ }
+
+ /* With the same type, return the text comparison */
+ return g_strcasecmp(tf1->text, tf2->text);
+}
+
+static gboolean twitter_filter_update(gpointer data, gint fd,
+ b_input_condition cond)
+{
+ struct im_connection *ic = data;
+ struct twitter_data *td = ic->proto_data;
+
+ if (td->filters) {
+ twitter_open_filter_stream(ic);
+ } else if (td->filter_stream) {
+ http_close(td->filter_stream);
+ td->filter_stream = NULL;
+ }
+
+ td->filter_update_id = 0;
+ return FALSE;
+}
+
+static struct twitter_filter *twitter_filter_get(struct groupchat *c,
+ twitter_filter_type_t type,
+ const char *text)
+{
+ struct twitter_data *td = c->ic->proto_data;
+ struct twitter_filter *tf = NULL;
+ struct twitter_filter tfc = {type, (char*) text};
+ GSList *l;
+
+ for (l = td->filters; l; l = g_slist_next(l)) {
+ tf = l->data;
+
+ if (twitter_filter_cmp(tf, &tfc) == 0)
+ break;
+
+ tf = NULL;
+ }
+
+ if (!tf) {
+ tf = g_new0(struct twitter_filter, 1);
+ tf->type = type;
+ tf->text = g_strdup(text);
+ td->filters = g_slist_prepend(td->filters, tf);
+ }
+
+ if (!g_slist_find(tf->groupchats, c))
+ tf->groupchats = g_slist_prepend(tf->groupchats, c);
+
+ if (td->filter_update_id > 0)
+ b_event_remove(td->filter_update_id);
+
+ /* Wait for other possible filter changes to avoid request spam */
+ td->filter_update_id = b_timeout_add(TWITTER_FILTER_UPDATE_WAIT,
+ twitter_filter_update, c->ic);
+ return tf;
+}
+
+static void twitter_filter_free(struct twitter_filter *tf)
+{
+ g_slist_free(tf->groupchats);
+ g_free(tf->text);
+ g_free(tf);
+}
+
+static void twitter_filter_remove(struct groupchat *c)
+{
+ struct twitter_data *td = c->ic->proto_data;
+ struct twitter_filter *tf;
+ GSList *l = td->filters;
+ GSList *p;
+
+ while (l != NULL) {
+ tf = l->data;
+ tf->groupchats = g_slist_remove(tf->groupchats, c);
+
+ p = l;
+ l = g_slist_next(l);
+
+ if (!tf->groupchats) {
+ twitter_filter_free(tf);
+ td->filters = g_slist_delete_link(td->filters, p);
+ }
+ }
+
+ if (td->filter_update_id > 0)
+ b_event_remove(td->filter_update_id);
+
+ /* Wait for other possible filter changes to avoid request spam */
+ td->filter_update_id = b_timeout_add(TWITTER_FILTER_UPDATE_WAIT,
+ twitter_filter_update, c->ic);}
+
+static void twitter_filter_remove_all(struct im_connection *ic)
+{
+ struct twitter_data *td = ic->proto_data;
+ GSList *chats = NULL;
+ struct twitter_filter *tf;
+ GSList *l = td->filters;
+ GSList *p;
+
+ while (l != NULL) {
+ tf = l->data;
+
+ /* Build up a list of groupchats to be freed */
+ for (p = tf->groupchats; p; p = g_slist_next(p)) {
+ if (!g_slist_find(chats, p->data))
+ chats = g_slist_prepend(chats, p->data);
+ }
+
+ p = l;
+ l = g_slist_next(l);
+ twitter_filter_free(p->data);
+ td->filters = g_slist_delete_link(td->filters, p);
+ }
+
+ l = chats;
+
+ while (l != NULL) {
+ p = l;
+ l = g_slist_next(l);
+
+ /* Freed each remaining groupchat */
+ imcb_chat_free(p->data);
+ chats = g_slist_delete_link(chats, p);
+ }
+
+ if (td->filter_stream) {
+ http_close(td->filter_stream);
+ td->filter_stream = NULL;
+ }
+}
+
+static GSList *twitter_filter_parse(struct groupchat *c, const char *text)
+{
+ char **fs = g_strsplit(text, ";", 0);
+ GSList *ret = NULL;
+ struct twitter_filter *tf;
+ char **f;
+ char *v;
+ int i;
+ int t;
+
+ static const twitter_filter_type_t types[] = {
+ TWITTER_FILTER_TYPE_FOLLOW,
+ TWITTER_FILTER_TYPE_TRACK
+ };
+
+ static const char *typestrs[] = {
+ "follow",
+ "track"
+ };
+
+ for (f = fs; *f; f++) {
+ if ((v = strchr(*f, ':')) == NULL)
+ continue;
+
+ *(v++) = 0;
+
+ for (t = -1, i = 0; i < G_N_ELEMENTS(types); i++) {
+ if (g_strcasecmp(typestrs[i], *f) == 0) {
+ t = i;
+ break;
+ }
+ }
+
+ if (t < 0 || strlen(v) == 0)
+ continue;
+
+ tf = twitter_filter_get(c, types[t], v);
+ ret = g_slist_prepend(ret, tf);
+ }
+
+ g_strfreev(fs);
+ return ret;
+}
+
/**
* Main loop function
*/
@@ -329,6 +538,10 @@ static void twitter_init(account_t * acc)
s = set_add(&acc->set, "show_old_mentions", "0", set_eval_int, acc);
s = set_add(&acc->set, "strip_newlines", "false", set_eval_bool, acc);
+
+ s = set_add(&acc->set, "format_string", "\002[\002%i\002]\002 %c", NULL, acc);
+ s = set_add(&acc->set, "retweet_format_string", "\002[\002%i\002]\002 RT @%a: %c", NULL, acc);
+ s = set_add(&acc->set, "reply_format_string", "\002[\002%i->%r\002]\002 %c", NULL, acc);
s = set_add(&acc->set, "_last_tweet", "0", NULL, acc);
s->flags |= SET_HIDDEN | SET_NOSAVE;
@@ -435,7 +648,11 @@ static void twitter_logout(struct im_connection *ic)
imcb_chat_free(td->timeline_gc);
if (td) {
+ if (td->filter_update_id > 0)
+ b_event_remove(td->filter_update_id);
+
http_close(td->stream);
+ twitter_filter_remove_all(ic);
oauth_info_free(td->oauth_info);
g_free(td->user);
g_free(td->prefix);
@@ -508,12 +725,57 @@ static void twitter_chat_invite(struct groupchat *c, char *who, char *message)
{
}
+static struct groupchat *twitter_chat_join(struct im_connection *ic,
+ const char *room, const char *nick,
+ const char *password, set_t **sets)
+{
+ struct groupchat *c = imcb_chat_new(ic, room);
+ GSList *fs = twitter_filter_parse(c, room);
+ GString *topic = g_string_new("");
+ struct twitter_filter *tf;
+ GSList *l;
+
+ fs = g_slist_sort(fs, (GCompareFunc) twitter_filter_cmp);
+
+ for (l = fs; l; l = g_slist_next(l)) {
+ tf = l->data;
+
+ if (topic->len > 0)
+ g_string_append(topic, ", ");
+
+ if (tf->type == TWITTER_FILTER_TYPE_FOLLOW)
+ g_string_append_c(topic, '@');
+
+ g_string_append(topic, tf->text);
+ }
+
+ if (topic->len > 0)
+ g_string_prepend(topic, "Twitter Filter: ");
+
+ imcb_chat_topic(c, NULL, topic->str, 0);
+ imcb_chat_add_buddy(c, ic->acc->user);
+
+ if (topic->len == 0) {
+ imcb_error(ic, "Failed to handle any filters");
+ imcb_chat_free(c);
+ c = NULL;
+ }
+
+ g_string_free(topic, TRUE);
+ g_slist_free(fs);
+
+ return c;
+}
+
static void twitter_chat_leave(struct groupchat *c)
{
struct twitter_data *td = c->ic->proto_data;
- if (c != td->timeline_gc)
- return; /* WTF? */
+ if (c != td->timeline_gc) {
+ twitter_filter_remove(c);
+ imcb_chat_free(c);
+ return;
+ }
/* If the user leaves the channel: Fine. Rejoin him/her once new
tweets come in. */
@@ -673,6 +935,15 @@ static void twitter_handle_command(struct im_connection *ic, char *message)
message = new = g_strdup_printf("@%s %s", bu->handle, cmd[2]);
in_reply_to = id;
allow_post = TRUE;
+ } else if (g_strcasecmp(cmd[0], "rawreply") == 0 && cmd[1] && cmd[2]) {
+ id = twitter_message_id_from_command_arg(ic, cmd[1], NULL);
+ if (!id) {
+ twitter_log(ic, "Tweet `%s' does not exist", cmd[1]);
+ goto eof;
+ }
+ message = cmd[2];
+ in_reply_to = id;
+ allow_post = TRUE;
} else if (g_strcasecmp(cmd[0], "post") == 0) {
message += 5;
allow_post = TRUE;
@@ -747,6 +1018,7 @@ void twitter_initmodule()
ret->remove_buddy = twitter_remove_buddy;
ret->chat_msg = twitter_chat_msg;
ret->chat_invite = twitter_chat_invite;
+ ret->chat_join = twitter_chat_join;
ret->chat_leave = twitter_chat_leave;
ret->keepalive = twitter_keepalive;
ret->add_permit = twitter_add_permit;
diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h
index 8792b7c9..00230cc0 100644
--- a/protocols/twitter/twitter.h
+++ b/protocols/twitter/twitter.h
@@ -44,6 +44,12 @@ typedef enum
TWITTER_GOT_MENTIONS = 0x40000,
} twitter_flags_t;
+typedef enum
+{
+ TWITTER_FILTER_TYPE_FOLLOW = 0,
+ TWITTER_FILTER_TYPE_TRACK
+} twitter_filter_type_t;
+
struct twitter_log_data;
struct twitter_data
@@ -57,10 +63,13 @@ struct twitter_data
guint64 timeline_id;
GSList *follow_ids;
+ GSList *filters;
guint64 last_status_id; /* For undo */
gint main_loop_id;
+ gint filter_update_id;
struct http_request *stream;
+ struct http_request *filter_stream;
struct groupchat *timeline_gc;
gint http_fails;
twitter_flags_t flags;
@@ -78,6 +87,15 @@ struct twitter_data
int log_id;
};
+#define TWITTER_FILTER_UPDATE_WAIT 3000
+struct twitter_filter
+{
+ twitter_filter_type_t type;
+ char *text;
+ guint64 uid;
+ GSList *groupchats;
+};
+
struct twitter_user_data
{
guint64 last_id;
diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c
index 718867a7..b827a139 100644
--- a/protocols/twitter/twitter_lib.c
+++ b/protocols/twitter/twitter_lib.c
@@ -50,6 +50,7 @@ struct twitter_xml_list {
};
struct twitter_xml_user {
+ guint64 uid;
char *name;
char *screen_name;
};
@@ -60,6 +61,8 @@ struct twitter_xml_status {
struct twitter_xml_user *user;
guint64 id, rt_id; /* Usually equal, with RTs id == *original* id */
guint64 reply_to;
+ gboolean from_filter;
+ struct twitter_xml_status *rt;
};
/**
@@ -85,6 +88,7 @@ static void txs_free(struct twitter_xml_status *txs)
g_free(txs->text);
txu_free(txs->user);
+ txs_free(txs->rt);
g_free(txs);
}
@@ -391,11 +395,15 @@ static void twitter_http_get_users_lookup(struct http_request *req)
struct twitter_xml_user *twitter_xt_get_user(const json_value *node)
{
struct twitter_xml_user *txu;
+ json_value *jv;
txu = g_new0(struct twitter_xml_user, 1);
txu->name = g_strdup(json_o_str(node, "name"));
txu->screen_name = g_strdup(json_o_str(node, "screen_name"));
+ jv = json_o_get(node, "id");
+ txu->uid = jv->u.integer;
+
return txu;
}
@@ -482,9 +490,9 @@ static struct twitter_xml_status *twitter_xt_get_status(const json_value *node)
struct twitter_xml_status *rtxs = twitter_xt_get_status(rt);
if (rtxs) {
g_free(txs->text);
- txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
+ txs->text = g_strdup(rtxs->text);
txs->id = rtxs->id;
- txs_free(rtxs);
+ txs->rt = rtxs;
}
} else if (entities) {
txs->text = expand_entities(txs->text, entities);
@@ -602,6 +610,49 @@ static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_
return TRUE;
}
+/**
+ * Function to properly format a tweet as per the users configuration.
+ */
+static char *twitter_msg_get_text(struct im_connection *ic, int log_id, int reply_to,
+ struct twitter_xml_status *txs, const char *prefix) {
+ gchar * format = set_getstr(&ic->acc->set, "format_string");
+ GString * text = g_string_new(NULL);
+
+ gchar *c;
+ if (reply_to != -1)
+ format = set_getstr(&ic->acc->set, "reply_format_string");
+ if (txs->rt)
+ format = set_getstr(&ic->acc->set, "retweet_format_string");
+
+ for (c = format; *c ; c++) {
+ if (!(*c == '%' && *(c+1))) {
+ text = g_string_append_c(text, *c);
+ continue;
+ }
+ c++; // Move past the %
+ switch (*c) {
+ case 'i':
+ g_string_append_printf(text, "%02x", log_id);
+ break;
+ case 'r':
+ if (reply_to != -1) // In case someone does put %r in the wrong format_string
+ g_string_append_printf(text, "%02x", reply_to);
+ break;
+ case 'a':
+ if (txs->rt) // In case someone does put %a in the wrong format_string
+ text = g_string_append(text, txs->rt->user->screen_name);
+ break;
+ case 'c':
+ text = g_string_append(text, txs->text);
+ break;
+ default:
+ text = g_string_append_c(text, *c);
+ }
+ }
+ text = g_string_prepend(text, prefix);
+ return g_string_free(text, FALSE);
+}
+
/* Will log messages either way. Need to keep track of IDs for stream deduping.
Plus, show_ids is on by default and I don't see why anyone would disable it. */
static char *twitter_msg_add_id(struct im_connection *ic,
@@ -640,19 +691,45 @@ static char *twitter_msg_add_id(struct im_connection *ic,
if (g_strcasecmp(txs->user->screen_name, td->user) == 0)
td->log[td->log_id].id = txs->rt_id;
- if (set_getbool(&ic->acc->set, "show_ids")) {
- if (reply_to != -1)
- return g_strdup_printf("\002[\002%02x->%02x\002]\002 %s%s",
- td->log_id, reply_to, prefix, txs->text);
- else
- return g_strdup_printf("\002[\002%02x\002]\002 %s%s",
- td->log_id, prefix, txs->text);
- } else {
- if (*prefix)
- return g_strconcat(prefix, txs->text, NULL);
- else
- return NULL;
+ return twitter_msg_get_text(ic, td->log_id, reply_to, txs, prefix);
+}
+
+/**
+ * Function that is called to see the filter statuses in groupchat windows.
+ */
+static void twitter_status_show_filter(struct im_connection *ic, struct twitter_xml_status *status)
+{
+ struct twitter_data *td = ic->proto_data;
+ char *msg = twitter_msg_add_id(ic, status, "");
+ struct twitter_filter *tf;
+ GSList *f;
+ GSList *l;
+
+ for (f = td->filters; f; f = g_slist_next(f)) {
+ tf = f->data;
+
+ switch (tf->type) {
+ case TWITTER_FILTER_TYPE_FOLLOW:
+ if (status->user->uid != tf->uid)
+ continue;
+ break;
+
+ case TWITTER_FILTER_TYPE_TRACK:
+ if (strcasestr(status->text, tf->text) == NULL)
+ continue;
+ break;
+
+ default:
+ continue;
+ }
+
+ for (l = tf->groupchats; l; l = g_slist_next(l)) {
+ imcb_chat_msg(l->data, status->user->screen_name,
+ msg ? msg : status->text, 0, 0);
+ }
}
+
+ g_free(msg);
}
/**
@@ -730,7 +807,9 @@ static void twitter_status_show(struct im_connection *ic, struct twitter_xml_sta
if (set_getbool(&ic->acc->set, "strip_newlines"))
strip_newlines(status->text);
- if (td->flags & TWITTER_MODE_CHAT)
+ if (status->from_filter)
+ twitter_status_show_filter(ic, status);
+ else if (td->flags & TWITTER_MODE_CHAT)
twitter_status_show_chat(ic, status);
else
twitter_status_show_msg(ic, status);
@@ -744,7 +823,7 @@ static void twitter_status_show(struct im_connection *ic, struct twitter_xml_sta
g_free(last_id_str);
}
-static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o);
+static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter);
static void twitter_http_stream(struct http_request *req)
{
@@ -753,6 +832,7 @@ static void twitter_http_stream(struct http_request *req)
json_value *parsed;
int len = 0;
char c, *nl;
+ gboolean from_filter;
if (!g_slist_find(twitter_connections, ic))
return;
@@ -761,7 +841,11 @@ static void twitter_http_stream(struct http_request *req)
td = ic->proto_data;
if ((req->flags & HTTPC_EOF) || !req->reply_body) {
- td->stream = NULL;
+ if (req == td->stream)
+ td->stream = NULL;
+ else if (req == td->filter_stream)
+ td->filter_stream = NULL;
+
imcb_error(ic, "Stream closed (%s)", req->status_string);
imc_logout(ic, TRUE);
return;
@@ -778,7 +862,8 @@ static void twitter_http_stream(struct http_request *req)
req->reply_body[len] = '\0';
if ((parsed = json_parse(req->reply_body, req->body_size))) {
- twitter_stream_handle_object(ic, parsed);
+ from_filter = (req == td->filter_stream);
+ twitter_stream_handle_object(ic, parsed, from_filter);
}
json_value_free(parsed);
req->reply_body[len] = c;
@@ -794,13 +879,14 @@ static void twitter_http_stream(struct http_request *req)
static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o);
static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs);
-static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o)
+static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter)
{
struct twitter_data *td = ic->proto_data;
struct twitter_xml_status *txs;
json_value *c;
if ((txs = twitter_xt_get_status(o))) {
+ txs->from_filter = from_filter;
gboolean ret = twitter_stream_handle_status(ic, txs);
txs_free(txs);
return ret;
@@ -898,6 +984,169 @@ gboolean twitter_open_stream(struct im_connection *ic)
return FALSE;
}
+static gboolean twitter_filter_stream(struct im_connection *ic)
+{
+ struct twitter_data *td = ic->proto_data;
+ char *args[4] = {"follow", NULL, "track", NULL};
+ GString *followstr = g_string_new("");
+ GString *trackstr = g_string_new("");
+ gboolean ret = FALSE;
+ struct twitter_filter *tf;
+ GSList *l;
+
+ for (l = td->filters; l; l = g_slist_next(l)) {
+ tf = l->data;
+
+ switch (tf->type) {
+ case TWITTER_FILTER_TYPE_FOLLOW:
+ if (followstr->len > 0)
+ g_string_append_c(followstr, ',');
+
+ g_string_append_printf(followstr, "%" G_GUINT64_FORMAT,
+ tf->uid);
+ break;
+
+ case TWITTER_FILTER_TYPE_TRACK:
+ if (trackstr->len > 0)
+ g_string_append_c(trackstr, ',');
+
+ g_string_append(trackstr, tf->text);
+ break;
+
+ default:
+ continue;
+ }
+ }
+
+ args[1] = followstr->str;
+ args[3] = trackstr->str;
+
+ if (td->filter_stream)
+ http_close(td->filter_stream);
+
+ if ((td->filter_stream = twitter_http(ic, TWITTER_FILTER_STREAM_URL,
+ twitter_http_stream, ic, 0,
+ args, 4))) {
+ /* This flag must be enabled or we'll get no data until EOF
+ (which err, kind of, defeats the purpose of a streaming API). */
+ td->filter_stream->flags |= HTTPC_STREAMING;
+ ret = TRUE;
+ }
+
+ g_string_free(followstr, TRUE);
+ g_string_free(trackstr, TRUE);
+
+ return ret;
+}
+
+static void twitter_filter_users_post(struct http_request *req)
+{
+ struct im_connection *ic = req->data;
+ struct twitter_data *td;
+ struct twitter_filter *tf;
+ GList *users = NULL;
+ json_value *parsed;
+ json_value *id;
+ const char *name;
+ GString *fstr;
+ GSList *l;
+ GList *u;
+ int i;
+
+ // Check if the connection is still active.
+ if (!g_slist_find(twitter_connections, ic))
+ return;
+
+ td = ic->proto_data;
+
+ if (!(parsed = twitter_parse_response(ic, req)))
+ return;
+
+ for (l = td->filters; l; l = g_slist_next(l)) {
+ tf = l->data;
+
+ if (tf->type == TWITTER_FILTER_TYPE_FOLLOW)
+ users = g_list_prepend(users, tf);
+ }
+
+ if (parsed->type != json_array)
+ goto finish;
+
+ for (i = 0; i < parsed->u.array.length; i++) {
+ id = json_o_get(parsed->u.array.values[i], "id");
+ name = json_o_str(parsed->u.array.values[i], "screen_name");
+
+ if (!name || !id || id->type != json_integer)
+ continue;
+
+ for (u = users; u; u = g_list_next(u)) {
+ tf = u->data;
+
+ if (g_strcasecmp(tf->text, name) == 0) {
+ tf->uid = id->u.integer;
+ users = g_list_delete_link(users, u);
+ break;
+ }
+ }
+ }
+
+finish:
+ json_value_free(parsed);
+ twitter_filter_stream(ic);
+
+ if (!users)
+ return;
+
+ fstr = g_string_new("");
+
+ for (u = users; u; u = g_list_next(u)) {
+ if (fstr->len > 0)
+ g_string_append(fstr, ", ");
+
+ g_string_append(fstr, tf->text);
+ }
+
+ imcb_error(ic, "Failed UID acquisitions: %s", fstr->str);
+
+ g_string_free(fstr, TRUE);
+ g_list_free(users);
+}
+
+gboolean twitter_open_filter_stream(struct im_connection *ic)
+{
+ struct twitter_data *td = ic->proto_data;
+ char *args[2] = {"screen_name", NULL};
+ GString *ustr = g_string_new("");
+ struct twitter_filter *tf;
+ struct http_request *req;
+ GSList *l;
+
+ for (l = td->filters; l; l = g_slist_next(l)) {
+ tf = l->data;
+
+ if (tf->type != TWITTER_FILTER_TYPE_FOLLOW || tf->uid != 0)
+ continue;
+
+ if (ustr->len > 0)
+ g_string_append_c(ustr, ',');
+
+ g_string_append(ustr, tf->text);
+ }
+
+ if (ustr->len == 0) {
+ g_string_free(ustr, TRUE);
+ return twitter_filter_stream(ic);
+ }
+
+ args[1] = ustr->str;
+ req = twitter_http(ic, TWITTER_USERS_LOOKUP_URL,
+ twitter_filter_users_post,
+ ic, 0, args, 2);
+
+ g_string_free(ustr, TRUE);
+ return req != NULL;
+}
+
static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);
diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h
index 7fd3b808..ee103100 100644
--- a/protocols/twitter/twitter_lib.h
+++ b/protocols/twitter/twitter_lib.h
@@ -78,9 +78,12 @@
/* Report spam */
#define TWITTER_REPORT_SPAM_URL "/users/report_spam.json"
+/* Stream URLs */
#define TWITTER_USER_STREAM_URL "https://userstream.twitter.com/1.1/user.json"
+#define TWITTER_FILTER_STREAM_URL "https://stream.twitter.com/1.1/statuses/filter.json"
gboolean twitter_open_stream(struct im_connection *ic);
+gboolean twitter_open_filter_stream(struct im_connection *ic);
gboolean twitter_get_timeline(struct im_connection *ic, gint64 next_cursor);
void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor);
void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor);