Index: .travis.yml
===================================================================
--- .travis.yml (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ .travis.yml (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -1,7 +1,7 @@
language: c
-script: dpkg-buildpackage -uc -us
+script: ./configure && make check && dpkg-buildpackage -uc -us
before_install:
- sudo apt-get update -qq
- - sudo apt-get install --no-install-recommends -qq asciidoc xsltproc xmlto lynx check libevent-dev libpurple-dev
+ - sudo apt-get install --no-install-recommends -qq asciidoc xsltproc xmlto lynx check libevent-dev libpurple-dev check
- wget http://dump.dequis.org/indexed/bitlbee-travis-libs/libotr5{,-dev}_4.1.0-2~bpo70+1_amd64.deb
- sudo dpkg -i *.deb
Index: bitlbee.c
===================================================================
--- bitlbee.c (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ bitlbee.c (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -329,8 +329,4 @@
irc_t *irc;
- /* Since we're fork()ing here, let's make sure we won't
- get the same random numbers as the parent/siblings. */
- srand( time( NULL ) ^ getpid() );
-
b_main_init();
Index: doc/user-guide/commands.xml
===================================================================
--- doc/user-guide/commands.xml (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ doc/user-guide/commands.xml (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -847,4 +847,5 @@
rt <screenname|#id>Retweet someone's last Tweet (or one with the given ID)
reply <screenname|#id>Reply to a Tweet (with a reply-to reference)
+ rawreply <screenname|#id>Reply to a Tweet (with no reply-to reference)
report <screenname|#id>Report the given user (or the user who posted the tweet with the given ID) for sending spam. This will also block them.
follow <screenname>Start following a person
@@ -888,5 +889,11 @@
- Currently only available for MSN connections. This setting allows you to read and change your "friendly name" for this connection. Since this is a server-side setting, it can't be changed when the account is off-line.
+ Currently only available for MSN connections, and for jabber groupchats.
+
+
+ For MSN: This setting allows you to read and change your "friendly name" for this connection. Since this is a server-side setting, it can't be changed when the account is off-line.
+
+
+ For jabber groupchats: this sets the default value of 'nick' for newly created groupchats. There is no way to set an account-wide nick like MSN.
@@ -1080,4 +1087,66 @@
With modes "many" and "one", you can post tweets by /msg'ing the twitter_yourusername contact. In mode "chat", messages posted in the Twitter channel will also be posted as tweets.
+
+
+
+
+
+ ^B[^B%i^B]^B %c
+
+
+
+ This setting controls how tweets are displayed. Below are listed the two replacement attributes you may use in the string. If you wish to use a percent symbol in your string, ensure you escape it with a second percent symbol (i.e. "%%").
+
+
+
+ The default setting contains ^B as a control code for toggling of the bold property. Other similar properties (including color codes) are also functional in this setting. See the documentation for your IRC client for details on such control codes.
+
+
+
+ %iThe ID of the tweet
+ %cThe text of the tweet
+
+
+
+
+
+
+ ^B[^B%i->%t^B]^B %c
+
+
+
+ This setting controls how replies to tweets are displayed. Below are listed the three replacement attributes you may use in the string. If you wish to use a percent symbol in your string, ensure you escape it with a second percent symbol (i.e. "%%").
+
+
+
+ The default setting contains ^B as a control code for toggling of the bold property. Other similar properties (including color codes) are also functional in this setting. See the documentation for your IRC client for details on such control codes.
+
+
+
+ %iThe ID of this tweet
+ %rThe ID of the tweet being replied to
+ %cThe text of the tweet
+
+
+
+
+
+
+ ^B[^B%i^B]^B RT @%a: %c
+
+
+
+ This setting controls how retweets are displayed. Below are listed the three replacement attributes you may use in the string. If you wish to use a percent symbol in your string, ensure you escape it with a second percent symbol (i.e. "%%").
+
+
+
+ The default setting contains ^B as a control code for toggling of the bold property. Other similar properties (including color codes) are also functional in this setting. See the documentation for your IRC client for details on such control codes.
+
+
+
+ %iThe ID of the tweet
+ %aThe author of the original tweet
+ %cThe text of the tweet
+
Index: lib/misc.c
===================================================================
--- lib/misc.c (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ lib/misc.c (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -414,67 +414,18 @@
}
-/* A pretty reliable random number generator. Tries to use the /dev/random
- devices first, and falls back to the random number generator from libc
- when it fails. Opens randomizer devices with O_NONBLOCK to make sure a
- lack of entropy won't halt BitlBee. */
+/* A wrapper for /dev/urandom.
+ * If /dev/urandom is not present or not usable, it calls abort()
+ * to prevent bitlbee from working without a decent entropy source */
void random_bytes( unsigned char *buf, int count )
{
- static int use_dev = -1;
-
- /* Actually this probing code isn't really necessary, is it? */
- if( use_dev == -1 )
- {
- if( access( "/dev/random", R_OK ) == 0 || access( "/dev/urandom", R_OK ) == 0 )
- use_dev = 1;
- else
- {
- use_dev = 0;
- srand( ( getpid() << 16 ) ^ time( NULL ) );
- }
- }
-
- if( use_dev )
- {
- int fd;
-
- /* At least on Linux, /dev/random can block if there's not
- enough entropy. We really don't want that, so if it can't
- give anything, use /dev/urandom instead. */
- if( ( fd = open( "/dev/random", O_RDONLY | O_NONBLOCK ) ) >= 0 )
- if( read( fd, buf, count ) == count )
- {
- close( fd );
- return;
- }
- close( fd );
-
- /* urandom isn't supposed to block at all, but just to be
- sure. If it blocks, we'll disable use_dev and use the libc
- randomizer instead. */
- if( ( fd = open( "/dev/urandom", O_RDONLY | O_NONBLOCK ) ) >= 0 )
- if( read( fd, buf, count ) == count )
- {
- close( fd );
- return;
- }
- close( fd );
-
- /* If /dev/random blocks once, we'll still try to use it
- again next time. If /dev/urandom also fails for some
- reason, stick with libc during this session. */
-
- use_dev = 0;
- srand( ( getpid() << 16 ) ^ time( NULL ) );
- }
-
- if( !use_dev )
- {
- int i;
-
- /* Possibly the LSB of rand() isn't very random on some
- platforms. Seems okay on at least Linux and OSX though. */
- for( i = 0; i < count; i ++ )
- buf[i] = rand() & 0xff;
- }
+ int fd;
+ if( ( ( fd = open( "/dev/urandom", O_RDONLY ) ) == -1 ) ||
+ ( read( fd, buf, count ) == -1 ) )
+ {
+ log_message( LOGLVL_ERROR, "/dev/urandom not present - aborting" );
+ abort();
+ }
+
+ close( fd );
}
Index: protocols/account.c
===================================================================
--- protocols/account.c (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ protocols/account.c (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -29,5 +29,5 @@
static const char* account_protocols_local[] = {
- "gg", NULL
+ "gg", "whatsapp", NULL
};
@@ -351,7 +351,4 @@
void account_on( bee_t *bee, account_t *a )
{
- GHashTableIter nicks;
- gpointer k, v;
-
if( a->ic )
{
@@ -367,13 +364,4 @@
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 );
- }
- }
}
Index: protocols/bee_chat.c
===================================================================
--- protocols/bee_chat.c (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ protocols/bee_chat.c (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -80,4 +80,11 @@
}
+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 )
{
@@ -89,5 +96,5 @@
/* 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;
@@ -139,5 +146,5 @@
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
@@ -161,5 +168,5 @@
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
@@ -189,5 +196,5 @@
/* It might be yourself! */
- if( g_strcasecmp( handle, ic->acc->user ) == 0 )
+ if( handle_is_self( ic, handle ) )
{
if( c->joined == 0 )
Index: protocols/jabber/iq.c
===================================================================
--- protocols/jabber/iq.c (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ protocols/jabber/iq.c (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -358,8 +358,4 @@
*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 = '/';
Index: protocols/jabber/jabber.c
===================================================================
--- protocols/jabber/jabber.c (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ protocols/jabber/jabber.c (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -63,4 +63,6 @@
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 );
@@ -318,4 +320,5 @@
g_free( jd->oauth2_access_token );
g_free( jd->away_message );
+ g_free( jd->internal_jid );
g_free( jd->username );
g_free( jd->me );
@@ -473,5 +476,13 @@
{
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?",
@@ -480,5 +491,5 @@
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;
@@ -619,4 +630,11 @@
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 ) );
}
@@ -648,4 +666,5 @@
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;
Index: protocols/jabber/jabber.h
===================================================================
--- protocols/jabber/jabber.h (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ protocols/jabber/jabber.h (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -97,4 +97,5 @@
char *server; /* username@SERVER -=> server/domain, not hostname */
char *me; /* bare jid */
+ char *internal_jid;
const struct oauth2_service *oauth2_service;
Index: protocols/jabber/jabber_util.c
===================================================================
--- protocols/jabber/jabber_util.c (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ protocols/jabber/jabber_util.c (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -824,4 +824,8 @@
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;
Index: protocols/nogaim.c
===================================================================
--- protocols/nogaim.c (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ protocols/nogaim.c (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -294,4 +294,15 @@
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" );
Index: protocols/nogaim.h
===================================================================
--- protocols/nogaim.h (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ protocols/nogaim.h (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -263,4 +263,7 @@
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;
Index: protocols/oscar/chat.c
===================================================================
--- protocols/oscar/chat.c (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ protocols/oscar/chat.c (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -67,5 +67,6 @@
*/
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);
@@ -228,5 +229,5 @@
*/
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 */
Index: protocols/oscar/tlv.c
===================================================================
--- protocols/oscar/tlv.c (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ protocols/oscar/tlv.c (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -181,5 +181,5 @@
guint8 v8[1];
- aimutil_put8(v8, v);
+ (void) aimutil_put8(v8, v);
return aim_addtlvtochain_raw(list, t, 1, v8);
@@ -199,5 +199,5 @@
guint8 v16[2];
- aimutil_put16(v16, v);
+ (void) aimutil_put16(v16, v);
return aim_addtlvtochain_raw(list, t, 2, v16);
@@ -217,5 +217,5 @@
guint8 v32[4];
- aimutil_put32(v32, v);
+ (void) aimutil_put32(v32, v);
return aim_addtlvtochain_raw(list, t, 4, v32);
Index: protocols/twitter/twitter.c
===================================================================
--- protocols/twitter/twitter.c (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ protocols/twitter/twitter.c (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -31,4 +31,213 @@
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
@@ -330,4 +539,8 @@
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);
@@ -436,5 +649,9 @@
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);
@@ -509,10 +726,55 @@
}
+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
@@ -674,4 +936,13 @@
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;
@@ -748,4 +1019,5 @@
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;
Index: protocols/twitter/twitter.h
===================================================================
--- protocols/twitter/twitter.h (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ protocols/twitter/twitter.h (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -45,4 +45,10 @@
} twitter_flags_t;
+typedef enum
+{
+ TWITTER_FILTER_TYPE_FOLLOW = 0,
+ TWITTER_FILTER_TYPE_TRACK
+} twitter_filter_type_t;
+
struct twitter_log_data;
@@ -58,8 +64,11 @@
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;
@@ -77,4 +86,13 @@
struct twitter_log_data *log;
int log_id;
+};
+
+#define TWITTER_FILTER_UPDATE_WAIT 3000
+struct twitter_filter
+{
+ twitter_filter_type_t type;
+ char *text;
+ guint64 uid;
+ GSList *groupchats;
};
Index: protocols/twitter/twitter_lib.c
===================================================================
--- protocols/twitter/twitter_lib.c (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ protocols/twitter/twitter_lib.c (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -51,4 +51,5 @@
struct twitter_xml_user {
+ guint64 uid;
char *name;
char *screen_name;
@@ -61,4 +62,6 @@
guint64 id, rt_id; /* Usually equal, with RTs id == *original* id */
guint64 reply_to;
+ gboolean from_filter;
+ struct twitter_xml_status *rt;
};
@@ -86,4 +89,5 @@
g_free(txs->text);
txu_free(txs->user);
+ txs_free(txs->rt);
g_free(txs);
}
@@ -392,8 +396,12 @@
{
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;
@@ -483,7 +491,7 @@
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) {
@@ -603,4 +611,47 @@
}
+/**
+ * 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. */
@@ -641,17 +692,43 @@
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);
}
@@ -731,5 +808,7 @@
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
@@ -745,5 +824,5 @@
}
-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)
@@ -754,4 +833,5 @@
int len = 0;
char c, *nl;
+ gboolean from_filter;
if (!g_slist_find(twitter_connections, ic))
@@ -762,5 +842,9 @@
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);
@@ -779,5 +863,6 @@
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);
@@ -795,5 +880,5 @@
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;
@@ -802,4 +887,5 @@
if ((txs = twitter_xt_get_status(o))) {
+ txs->from_filter = from_filter;
gboolean ret = twitter_stream_handle_status(ic, txs);
txs_free(txs);
@@ -899,4 +985,167 @@
}
+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);
Index: protocols/twitter/twitter_lib.h
===================================================================
--- protocols/twitter/twitter_lib.h (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ protocols/twitter/twitter_lib.h (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -79,7 +79,10 @@
#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);
Index: unix.c
===================================================================
--- unix.c (revision fd213feecb33f1bbb46af3199916a001c6ace1a0)
+++ unix.c (revision 12fe5ea5ee1fb6efc73213e715a44b94239c71b9)
@@ -87,6 +87,4 @@
#endif
- srand( time( NULL ) ^ getpid() );
-
global.helpfile = g_strdup( HELP_FILE );
if( help_init( &global.help, global.helpfile ) == NULL )