diff options
Diffstat (limited to 'protocols')
-rw-r--r-- | protocols/account.c | 14 | ||||
-rw-r--r-- | protocols/bee_chat.c | 15 | ||||
-rw-r--r-- | protocols/jabber/iq.c | 4 | ||||
-rw-r--r-- | protocols/jabber/jabber.c | 21 | ||||
-rw-r--r-- | protocols/jabber/jabber.h | 1 | ||||
-rw-r--r-- | protocols/jabber/jabber_util.c | 4 | ||||
-rw-r--r-- | protocols/nogaim.c | 11 | ||||
-rw-r--r-- | protocols/nogaim.h | 3 | ||||
-rw-r--r-- | protocols/oscar/chat.c | 5 | ||||
-rw-r--r-- | protocols/oscar/tlv.c | 6 | ||||
-rw-r--r-- | protocols/twitter/twitter.c | 276 | ||||
-rw-r--r-- | protocols/twitter/twitter.h | 18 | ||||
-rw-r--r-- | protocols/twitter/twitter_lib.c | 287 | ||||
-rw-r--r-- | protocols/twitter/twitter_lib.h | 3 |
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); |