aboutsummaryrefslogtreecommitdiffstats
path: root/protocols/twitter
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/twitter')
-rw-r--r--protocols/twitter/Makefile6
-rw-r--r--protocols/twitter/twitter.c173
-rw-r--r--protocols/twitter/twitter.h9
-rw-r--r--protocols/twitter/twitter_lib.c132
-rw-r--r--protocols/twitter/twitter_lib.h6
5 files changed, 297 insertions, 29 deletions
diff --git a/protocols/twitter/Makefile b/protocols/twitter/Makefile
index ca1e4695..3fa9b61e 100644
--- a/protocols/twitter/Makefile
+++ b/protocols/twitter/Makefile
@@ -7,11 +7,13 @@
### DEFINITIONS
-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/twitter/
+endif
# [SH] Program variables
objects = twitter.o twitter_http.o twitter_lib.o
-CFLAGS += -Wall
LFLAGS += -r
# [SH] Phony targets
@@ -32,7 +34,7 @@ distclean: clean
$(objects): ../../Makefile.settings Makefile
-$(objects): %.o: %.c
+$(objects): %.o: $(SRCDIR)%.c
@echo '*' Compiling $<
@$(CC) -c $(CFLAGS) $< -o $@
diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c
index fca619c3..16b069ee 100644
--- a/protocols/twitter/twitter.c
+++ b/protocols/twitter/twitter.c
@@ -118,7 +118,7 @@ static gboolean twitter_oauth_callback( struct oauth_info *info )
return FALSE;
}
- sprintf( name, "twitter_%s", ic->acc->user );
+ sprintf( name, "%s_%s", td->prefix, ic->acc->user );
msg = g_strdup_printf( "To finish OAuth authentication, please visit "
"%s and respond with the resulting PIN code.",
info->auth_url );
@@ -171,16 +171,33 @@ static gboolean twitter_length_check( struct im_connection *ic, gchar *msg )
static void twitter_init( account_t *acc )
{
set_t *s;
+ char *def_url;
+ char *def_oauth;
- s = set_add( &acc->set, "base_url", TWITTER_API_URL, NULL, acc );
+ if( strcmp( acc->prpl->name, "twitter" ) == 0 )
+ {
+ def_url = TWITTER_API_URL;
+ def_oauth = "true";
+ }
+ else /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */
+ {
+ def_url = IDENTICA_API_URL;
+ def_oauth = "false";
+ }
+
+ s = set_add( &acc->set, "auto_reply_timeout", "10800", set_eval_int, acc );
+
+ s = set_add( &acc->set, "base_url", def_url, NULL, acc );
s->flags |= ACC_SET_OFFLINE_ONLY;
+ s = set_add( &acc->set, "commands", "true", set_eval_bool, acc );
+
s = set_add( &acc->set, "message_length", "140", set_eval_int, acc );
s = set_add( &acc->set, "mode", "one", set_eval_mode, acc );
s->flags |= ACC_SET_OFFLINE_ONLY;
- s = set_add( &acc->set, "oauth", "true", set_eval_bool, acc );
+ s = set_add( &acc->set, "oauth", def_oauth, set_eval_bool, acc );
}
/**
@@ -213,12 +230,16 @@ static void twitter_login( account_t *acc )
td->url_path = g_strdup( url.file );
else
td->url_path = g_strdup( "" );
+ if( g_str_has_suffix( url.host, ".com" ) )
+ td->prefix = g_strndup( url.host, strlen( url.host ) - 4 );
+ else
+ td->prefix = g_strdup( url.host );
td->user = acc->user;
if( strstr( acc->pass, "oauth_token=" ) )
td->oauth_info = oauth_from_string( acc->pass, &twitter_oauth );
- sprintf( name, "twitter_%s", acc->user );
+ sprintf( name, "%s_%s", td->prefix, acc->user );
imcb_add_buddy( ic, name, NULL );
imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL );
@@ -235,7 +256,7 @@ static void twitter_logout( struct im_connection *ic )
struct twitter_data *td = ic->proto_data;
// Set the status to logged out.
- ic->flags = 0;
+ ic->flags &= ~ OPT_LOGGED_IN;
// Remove the main_loop function from the function queue.
b_event_remove(td->main_loop_id);
@@ -246,6 +267,7 @@ static void twitter_logout( struct im_connection *ic )
if( td )
{
oauth_info_free( td->oauth_info );
+ g_free( td->prefix );
g_free( td->url_host );
g_free( td->url_path );
g_free( td->pass );
@@ -255,15 +277,18 @@ static void twitter_logout( struct im_connection *ic )
twitter_connections = g_slist_remove( twitter_connections, ic );
}
+static void twitter_handle_command( struct im_connection *ic, char *message );
+
/**
*
*/
static int twitter_buddy_msg( struct im_connection *ic, char *who, char *message, int away )
{
struct twitter_data *td = ic->proto_data;
+ int plen = strlen( td->prefix );
- if (g_strncasecmp(who, "twitter_", 8) == 0 &&
- g_strcasecmp(who + 8, ic->acc->user) == 0)
+ if (g_strncasecmp(who, td->prefix, plen) == 0 && who[plen] == '_' &&
+ g_strcasecmp(who + plen + 1, ic->acc->user) == 0)
{
if( set_getbool( &ic->acc->set, "oauth" ) &&
td->oauth_info && td->oauth_info->token == NULL )
@@ -282,8 +307,8 @@ static int twitter_buddy_msg( struct im_connection *ic, char *who, char *message
return FALSE;
}
}
- else if( twitter_length_check(ic, message) )
- twitter_post_status(ic, message);
+ else
+ twitter_handle_command(ic, message);
}
else
{
@@ -315,8 +340,8 @@ static void twitter_remove_buddy( struct im_connection *ic, char *who, char *gro
static void twitter_chat_msg( struct groupchat *c, char *message, int flags )
{
- if( c && message && twitter_length_check(c->ic, message))
- twitter_post_status(c->ic, message);
+ if( c && message )
+ twitter_handle_command( c->ic, message );
}
static void twitter_chat_invite( struct groupchat *c, char *who, char *message )
@@ -371,6 +396,124 @@ static int twitter_send_typing( struct im_connection *ic, char *who, int typing
// return value;
//}
+static void twitter_buddy_data_add( struct bee_user *bu )
+{
+ bu->data = g_new0( struct twitter_user_data, 1 );
+}
+
+static void twitter_buddy_data_free( struct bee_user *bu )
+{
+ g_free( bu->data );
+}
+
+static void twitter_handle_command( struct im_connection *ic, char *message )
+{
+ struct twitter_data *td = ic->proto_data;
+ char *cmds, **cmd;
+
+ cmds = g_strdup( message );
+ cmd = split_command_parts( cmds );
+
+ if( cmd[0] == NULL )
+ {
+ g_free( cmds );
+ return;
+ }
+ else if( !set_getbool( &ic->acc->set, "commands" ) )
+ {
+ /* Not supporting commands. */
+ }
+ else if( g_strcasecmp( cmd[0], "undo" ) == 0 )
+ {
+ guint64 id;
+
+ if( cmd[1] )
+ id = g_ascii_strtoull( cmd[1], NULL, 10 );
+ else
+ id = td->last_status_id;
+
+ /* TODO: User feedback. */
+ if( id )
+ twitter_status_destroy( ic, id );
+
+ g_free( cmds );
+ return;
+ }
+ else if( g_strcasecmp( cmd[0], "follow" ) == 0 && cmd[1] )
+ {
+ twitter_add_buddy( ic, cmd[1], NULL );
+ g_free( cmds );
+ return;
+ }
+ else if( g_strcasecmp( cmd[0], "unfollow" ) == 0 && cmd[1] )
+ {
+ twitter_remove_buddy( ic, cmd[1], NULL );
+ g_free( cmds );
+ return;
+ }
+ else if( g_strcasecmp( cmd[0], "rt" ) == 0 && cmd[1] )
+ {
+ struct twitter_user_data *tud;
+ bee_user_t *bu;
+ guint64 id;
+
+ if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[1] ) ) &&
+ ( tud = bu->data ) && tud->last_id )
+ id = tud->last_id;
+ else
+ id = g_ascii_strtoull( cmd[1], NULL, 10 );
+
+ td->last_status_id = 0;
+ if( id )
+ twitter_status_retweet( ic, id );
+
+ g_free( cmds );
+ return;
+ }
+ else if( g_strcasecmp( cmd[0], "post" ) == 0 )
+ {
+ message += 5;
+ }
+
+ {
+ guint64 in_reply_to = 0;
+ char *s, *new = NULL;
+ bee_user_t *bu;
+
+ if( !twitter_length_check( ic, message ) )
+ {
+ g_free( cmds );
+ return;
+ }
+
+ s = cmd[0] + strlen( cmd[0] ) - 1;
+ if( s > cmd[0] && ( *s == ':' || *s == ',' ) )
+ {
+ *s = '\0';
+
+ if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[0] ) ) )
+ {
+ struct twitter_user_data *tud = bu->data;
+
+ new = g_strdup_printf( "@%s %s", bu->handle,
+ message + ( s - cmd[0] ) + 2 );
+ message = new;
+
+ if( time( NULL ) < tud->last_time +
+ set_getint( &ic->acc->set, "auto_reply_timeout" ) )
+ in_reply_to = tud->last_id;
+ }
+ }
+
+ /* If the user runs undo between this request and its response
+ this would delete the second-last Tweet. Prevent that. */
+ td->last_status_id = 0;
+ twitter_post_status( ic, message, in_reply_to );
+ g_free( new );
+ }
+ g_free( cmds );
+}
+
void twitter_initmodule()
{
struct prpl *ret = g_new0(struct prpl, 1);
@@ -395,11 +538,17 @@ void twitter_initmodule()
ret->add_deny = twitter_add_deny;
ret->rem_deny = twitter_rem_deny;
ret->send_typing = twitter_send_typing;
+ ret->buddy_data_add = twitter_buddy_data_add;
+ ret->buddy_data_free = twitter_buddy_data_free;
ret->handle_cmp = g_strcasecmp;
+
+ register_protocol(ret);
+ /* And an identi.ca variant: */
+ ret = g_memdup(ret, sizeof(struct prpl));
+ ret->name = "identica";
register_protocol(ret);
// Initialise the twitter_connections GSList.
twitter_connections = NULL;
}
-
diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h
index e61d32be..0acb3b4d 100644
--- a/protocols/twitter/twitter.h
+++ b/protocols/twitter/twitter.h
@@ -43,6 +43,7 @@ struct twitter_data
char* pass;
struct oauth_info *oauth_info;
guint64 home_timeline_id;
+ guint64 last_status_id; /* For undo */
gint main_loop_id;
struct groupchat *home_timeline_gc;
gint http_fails;
@@ -52,6 +53,14 @@ struct twitter_data
int url_port;
char *url_host;
char *url_path;
+
+ char *prefix; /* Used to generate contact + channel name. */
+};
+
+struct twitter_user_data
+{
+ guint64 last_id;
+ time_t last_time;
};
/**
diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c
index d1b65c26..f86e1f15 100644
--- a/protocols/twitter/twitter_lib.c
+++ b/protocols/twitter/twitter_lib.c
@@ -22,7 +22,10 @@
****************************************************************************/
/* For strptime(): */
+#if(__sun)
+#else
#define _XOPEN_SOURCE
+#endif
#include "twitter_http.h"
#include "twitter.h"
@@ -35,6 +38,14 @@
#include <ctype.h>
#include <errno.h>
+/* GLib < 2.12.0 doesn't have g_ascii_strtoll(), work around using system strtoll(). */
+/* GLib < 2.12.4 can be buggy: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=488013 */
+#if !GLIB_CHECK_VERSION(2,12,5)
+#include <stdlib.h>
+#include <limits.h>
+#define g_ascii_strtoll strtoll
+#endif
+
#define TXL_STATUS 1
#define TXL_USER 2
#define TXL_ID 3
@@ -65,6 +76,8 @@ static void twitter_groupchat_init(struct im_connection *ic);
*/
static void txu_free(struct twitter_xml_user *txu)
{
+ if (txu == NULL)
+ return;
g_free(txu->name);
g_free(txu->screen_name);
g_free(txu);
@@ -88,6 +101,8 @@ static void txs_free(struct twitter_xml_status *txs)
static void txl_free(struct twitter_xml_list *txl)
{
GSList *l;
+ if (txl == NULL)
+ return;
for ( l = txl->list; l ; l = g_slist_next(l) )
if (txl->type == TXL_STATUS)
txs_free((struct twitter_xml_status *)l->data);
@@ -104,7 +119,7 @@ static void twitter_add_buddy(struct im_connection *ic, char *name, const char *
struct twitter_data *td = ic->proto_data;
// Check if the buddy is allready in the buddy list.
- if (!imcb_find_buddy( ic, name ))
+ if (!bee_user_by_handle( ic->bee, ic, name ))
{
char *mode = set_getstr(&ic->acc->set, "mode");
@@ -112,7 +127,12 @@ static void twitter_add_buddy(struct im_connection *ic, char *name, const char *
imcb_add_buddy( ic, name, NULL );
imcb_rename_buddy( ic, name, fullname );
if (g_strcasecmp(mode, "chat") == 0)
+ {
+ /* Necessary so that nicks always get translated to the
+ exact Twitter username. */
+ imcb_buddy_nick_hint( ic, name, name );
imcb_chat_add_buddy( td->home_timeline_gc, name );
+ }
else if (g_strcasecmp(mode, "many") == 0)
imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL );
}
@@ -336,6 +356,11 @@ static xt_status twitter_xt_get_user_list( struct xt_node *node, struct twitter_
return XT_HANDLED;
}
+#ifdef __GLIBC__
+#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S %z %Y"
+#else
+#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y"
+#endif
/**
* Function to fill a twitter_xml_status struct.
@@ -347,7 +372,8 @@ static xt_status twitter_xt_get_user_list( struct xt_node *node, struct twitter_
*/
static xt_status twitter_xt_get_status( struct xt_node *node, struct twitter_xml_status *txs )
{
- struct xt_node *child;
+ struct xt_node *child, *rt = NULL;
+ gboolean truncated = FALSE;
// Walk over the nodes children.
for( child = node->children ; child ; child = child->next )
@@ -356,6 +382,14 @@ static xt_status twitter_xt_get_status( struct xt_node *node, struct twitter_xml
{
txs->text = g_memdup( child->text, child->text_len + 1 );
}
+ else if (g_strcasecmp( "truncated", child->name ) == 0 && child->text)
+ {
+ truncated = bool2int(child->text);
+ }
+ else if (g_strcasecmp( "retweeted_status", child->name ) == 0)
+ {
+ rt = child;
+ }
else if (g_strcasecmp( "created_at", child->name ) == 0)
{
struct tm parsed;
@@ -363,7 +397,7 @@ static xt_status twitter_xt_get_status( struct xt_node *node, struct twitter_xml
/* Very sensitive to changes to the formatting of
this field. :-( Also assumes the timezone used
is UTC since C time handling functions suck. */
- if( strptime( child->text, "%a %b %d %H:%M:%S %z %Y", &parsed ) != NULL )
+ if( strptime( child->text, TWITTER_TIME_FORMAT, &parsed ) != NULL )
txs->created_at = mktime_utc( &parsed );
}
else if (g_strcasecmp( "user", child->name ) == 0)
@@ -376,6 +410,22 @@ static xt_status twitter_xt_get_status( struct xt_node *node, struct twitter_xml
txs->id = g_ascii_strtoull (child->text, NULL, 10);
}
}
+
+ /* If it's a truncated retweet, get the original because dots suck. */
+ if (truncated && rt)
+ {
+ struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1);
+ if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED)
+ {
+ txs_free(rtxs);
+ return XT_HANDLED;
+ }
+
+ g_free(txs->text);
+ txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
+ txs_free(rtxs);
+ }
+
return XT_HANDLED;
}
@@ -385,10 +435,11 @@ static xt_status twitter_xt_get_status( struct xt_node *node, struct twitter_xml
* - all <status>es within the <status> element and
* - the next_cursor.
*/
-static xt_status twitter_xt_get_status_list( struct xt_node *node, struct twitter_xml_list *txl )
+static xt_status twitter_xt_get_status_list( struct im_connection *ic, struct xt_node *node, struct twitter_xml_list *txl )
{
struct twitter_xml_status *txs;
struct xt_node *child;
+ bee_user_t *bu;
// Set the type of the list.
txl->type = TXL_STATUS;
@@ -403,6 +454,18 @@ static xt_status twitter_xt_get_status_list( struct xt_node *node, struct twitte
twitter_xt_get_status(child, txs);
// Put the item in the front of the list.
txl->list = g_slist_prepend (txl->list, txs);
+
+ if (txs->user && txs->user->screen_name &&
+ (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name)))
+ {
+ struct twitter_user_data *tud = bu->data;
+
+ if (txs->id > tud->last_id)
+ {
+ tud->last_id = txs->id;
+ tud->last_time = txs->created_at;
+ }
+ }
}
else if ( g_strcasecmp( "next_cursor", child->name ) == 0)
{
@@ -446,7 +509,7 @@ static void twitter_groupchat_init(struct im_connection *ic)
td->home_timeline_gc = gc = imcb_chat_new( ic, "home/timeline" );
- name_hint = g_strdup_printf( "Twitter_%s", ic->acc->user );
+ name_hint = g_strdup_printf( "%s_%s", td->prefix, ic->acc->user );
imcb_chat_name_hint( gc, name_hint );
g_free( name_hint );
}
@@ -472,6 +535,9 @@ static void twitter_groupchat(struct im_connection *ic, GSList *list)
for ( l = list; l ; l = g_slist_next(l) )
{
status = l->data;
+ if (status->user == NULL || status->text == NULL)
+ continue;
+
twitter_add_buddy(ic, status->user->screen_name, status->user->name);
strip_html(status->text);
@@ -503,7 +569,7 @@ static void twitter_private_message_chat(struct im_connection *ic, GSList *list)
if( mode_one )
{
- g_snprintf( from, sizeof( from ) - 1, "twitter_%s", ic->acc->user );
+ g_snprintf( from, sizeof( from ) - 1, "%s_%s", td->prefix, ic->acc->user );
from[MAX_STRING-1] = '\0';
}
@@ -578,7 +644,7 @@ static void twitter_http_get_home_timeline(struct http_request *req)
parser = xt_new( NULL, txl );
xt_feed( parser, req->reply_body, req->body_size );
// The root <statuses> node should hold the list of statuses <status>
- twitter_xt_get_status_list(parser->root, txl);
+ twitter_xt_get_status_list(ic, parser->root, txl);
xt_free( parser );
// See if the user wants to see the messages in a groupchat window or as private messages.
@@ -687,29 +753,51 @@ void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor)
static void twitter_http_post(struct http_request *req)
{
struct im_connection *ic = req->data;
+ struct twitter_data *td;
// Check if the connection is still active.
if( !g_slist_find( twitter_connections, ic ) )
return;
+ td = ic->proto_data;
+ td->last_status_id = 0;
+
// Check if the HTTP request went well.
if (req->status_code != 200) {
// It didn't go well, output the error and return.
imcb_error(ic, "HTTP error: %s", twitter_parse_error(req));
return;
}
+
+ if (req->body_size > 0)
+ {
+ struct xt_parser *xp = NULL;
+ struct xt_node *node;
+
+ xp = xt_new(NULL, NULL);
+ xt_feed(xp, req->reply_body, req->body_size);
+
+ if ((node = xt_find_node(xp->root, "status")) &&
+ (node = xt_find_node(node->children, "id")) && node->text)
+ td->last_status_id = g_ascii_strtoull( node->text, NULL, 10 );
+
+ xt_free(xp);
+ }
}
/**
* Function to POST a new status to twitter.
*/
-void twitter_post_status(struct im_connection *ic, char* msg)
+void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to)
{
- char* args[2];
- args[0] = "status";
- args[1] = msg;
- twitter_http(ic, TWITTER_STATUS_UPDATE_URL, twitter_http_post, ic, 1, args, 2);
-// g_free(args[1]);
+ char* args[4] = {
+ "status", msg,
+ "in_reply_to_status_id",
+ g_strdup_printf("%llu", (unsigned long long) in_reply_to)
+ };
+ twitter_http(ic, TWITTER_STATUS_UPDATE_URL, twitter_http_post, ic, 1,
+ args, in_reply_to ? 4 : 2);
+ g_free(args[3]);
}
@@ -735,4 +823,20 @@ void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int
args[0] = "screen_name";
args[1] = who;
twitter_http(ic, create ? TWITTER_FRIENDSHIPS_CREATE_URL : TWITTER_FRIENDSHIPS_DESTROY_URL, twitter_http_post, ic, 1, args, 2);
-} \ No newline at end of file
+}
+
+void twitter_status_destroy(struct im_connection *ic, guint64 id)
+{
+ char *url;
+ url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL, (unsigned long long) id, ".xml");
+ twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
+ g_free(url);
+}
+
+void twitter_status_retweet(struct im_connection *ic, guint64 id)
+{
+ char *url;
+ url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL, (unsigned long long) id, ".xml");
+ twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
+ g_free(url);
+}
diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h
index 6b90f9bb..24b4a089 100644
--- a/protocols/twitter/twitter_lib.h
+++ b/protocols/twitter/twitter_lib.h
@@ -29,11 +29,13 @@
#include "twitter_http.h"
#define TWITTER_API_URL "http://twitter.com"
+#define IDENTICA_API_URL "http://identi.ca/api"
/* Status URLs */
#define TWITTER_STATUS_UPDATE_URL "/statuses/update.xml"
#define TWITTER_STATUS_SHOW_URL "/statuses/show/"
#define TWITTER_STATUS_DESTROY_URL "/statuses/destroy/"
+#define TWITTER_STATUS_RETWEET_URL "/statuses/retweet/"
/* Timeline URLs */
#define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline.xml"
@@ -79,9 +81,11 @@ void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor);
void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor);
-void twitter_post_status(struct im_connection *ic, char *msg);
+void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to);
void twitter_direct_messages_new(struct im_connection *ic, char *who, char *message);
void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create);
+void twitter_status_destroy(struct im_connection *ic, guint64 id);
+void twitter_status_retweet(struct im_connection *ic, guint64 id);
#endif //_TWITTER_LIB_H