aboutsummaryrefslogtreecommitdiffstats
path: root/protocols/twitter/twitter.c
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/twitter/twitter.c')
-rw-r--r--protocols/twitter/twitter.c343
1 files changed, 206 insertions, 137 deletions
diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c
index f7e1ec76..db61ba7c 100644
--- a/protocols/twitter/twitter.c
+++ b/protocols/twitter/twitter.c
@@ -3,7 +3,8 @@
* BitlBee - An IRC to IM gateway *
* Simple module to facilitate twitter functionality. *
* *
-* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> *
+* Copyright 2009-2010 Geert Mulders <g.c.w.m.mulders@gmail.com> *
+* Copyright 2010-2013 Wilmer van der Gaast <wilmer@gaast.net> *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
@@ -28,17 +29,7 @@
#include "twitter_lib.h"
#include "url.h"
-#define twitter_msg( ic, fmt... ) \
- do { \
- struct twitter_data *td = ic->proto_data; \
- if( td->timeline_gc ) \
- imcb_chat_log( td->timeline_gc, fmt ); \
- else \
- imcb_log( ic, fmt ); \
- } while( 0 );
-
GSList *twitter_connections = NULL;
-
/**
* Main loop function
*/
@@ -61,15 +52,57 @@ static void twitter_main_loop_start(struct im_connection *ic)
{
struct twitter_data *td = ic->proto_data;
+ /* Create the room now that we "logged in". */
+ if (td->flags & TWITTER_MODE_CHAT)
+ twitter_groupchat_init(ic);
+
imcb_log(ic, "Getting initial statuses");
- // Run this once. After this queue the main loop function.
+ // Run this once. After this queue the main loop function (or open the
+ // stream if available).
twitter_main_loop(ic, -1, 0);
+
+ if (set_getbool(&ic->acc->set, "stream")) {
+ /* That fetch was just to get backlog, the stream will give
+ us the rest. \o/ */
+ twitter_open_stream(ic);
+
+ /* Stream sends keepalives (empty lines) or actual data at
+ least twice a minute. Disconnect if this stops. */
+ ic->flags |= OPT_PONGS;
+ } else {
+ /* Not using the streaming API, so keep polling the old-
+ fashioned way. :-( */
+ td->main_loop_id =
+ b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000,
+ twitter_main_loop, ic);
+ }
+}
+
+struct groupchat *twitter_groupchat_init(struct im_connection *ic)
+{
+ char *name_hint;
+ struct groupchat *gc;
+ struct twitter_data *td = ic->proto_data;
+ GSList *l;
+
+ if (td->timeline_gc)
+ return td->timeline_gc;
+
+ td->timeline_gc = gc = imcb_chat_new(ic, "twitter/timeline");
+
+ name_hint = g_strdup_printf("%s_%s", td->prefix, ic->acc->user);
+ imcb_chat_name_hint(gc, name_hint);
+ g_free(name_hint);
- // Queue the main_loop
- // Save the return value, so we can remove the timeout on logout.
- td->main_loop_id =
- b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, twitter_main_loop, ic);
+ for (l = ic->bee->users; l; l = l->next) {
+ bee_user_t *bu = l->data;
+ if (bu->ic == ic)
+ imcb_chat_add_buddy(gc, bu->handle);
+ }
+ imcb_chat_add_buddy(gc, ic->acc->user);
+
+ return gc;
}
static void twitter_oauth_start(struct im_connection *ic);
@@ -82,11 +115,10 @@ void twitter_login_finish(struct im_connection *ic)
if (set_getbool(&ic->acc->set, "oauth") && !td->oauth_info)
twitter_oauth_start(ic);
- else if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") != 0 &&
- !(td->flags & TWITTER_HAVE_FRIENDS)) {
+ else if (!(td->flags & TWITTER_MODE_ONE) &&
+ !(td->flags & TWITTER_HAVE_FRIENDS)) {
imcb_log(ic, "Getting contact list");
twitter_get_friends_ids(ic, -1);
- //twitter_get_statuses_friends(ic, -1);
} else
twitter_main_loop_start(ic);
}
@@ -160,17 +192,18 @@ static gboolean twitter_oauth_callback(struct oauth_info *info)
imcb_buddy_msg(ic, name, msg, 0, 0);
g_free(msg);
} else if (info->stage == OAUTH_ACCESS_TOKEN) {
+ const char *sn;
+
if (info->token == NULL || info->token_secret == NULL) {
imcb_error(ic, "OAuth error: %s", twitter_parse_error(info->http));
imc_logout(ic, TRUE);
return FALSE;
- } else {
- const char *sn = oauth_params_get(&info->params, "screen_name");
-
- if (sn != NULL && ic->acc->prpl->handle_cmp(sn, ic->acc->user) != 0) {
+ }
+
+ if ((sn = oauth_params_get(&info->params, "screen_name"))) {
+ if (ic->acc->prpl->handle_cmp(sn, ic->acc->user) != 0)
imcb_log(ic, "Warning: You logged in via OAuth as %s "
- "instead of %s.", sn, ic->acc->user);
- }
+ "instead of %s.", sn, ic->acc->user);
g_free(td->user);
td->user = g_strdup(sn);
}
@@ -186,16 +219,6 @@ static gboolean twitter_oauth_callback(struct oauth_info *info)
return TRUE;
}
-
-static char *set_eval_mode(set_t * set, char *value)
-{
- if (g_strcasecmp(value, "one") == 0 ||
- g_strcasecmp(value, "many") == 0 || g_strcasecmp(value, "chat") == 0)
- return value;
- else
- return NULL;
-}
-
int twitter_url_len_diff(gchar *msg, unsigned int target_len)
{
int url_len_diff = 0;
@@ -232,25 +255,39 @@ static gboolean twitter_length_check(struct im_connection *ic, gchar * msg)
if (max == 0 || (len = g_utf8_strlen(msg, -1) + url_len_diff) <= max)
return TRUE;
- imcb_error(ic, "Maximum message length exceeded: %d > %d", len, max);
+ twitter_log(ic, "Maximum message length exceeded: %d > %d", len, max);
return FALSE;
}
+static char *set_eval_commands(set_t * set, char *value)
+{
+ if (g_strcasecmp(value, "strict") == 0 )
+ return value;
+ else
+ return set_eval_bool(set, value);
+}
+
+static char *set_eval_mode(set_t * set, char *value)
+{
+ if (g_strcasecmp(value, "one") == 0 ||
+ g_strcasecmp(value, "many") == 0 || g_strcasecmp(value, "chat") == 0)
+ return value;
+ else
+ return NULL;
+}
+
static void twitter_init(account_t * acc)
{
set_t *s;
char *def_url;
- char *def_oauth;
char *def_tul;
if (strcmp(acc->prpl->name, "twitter") == 0) {
def_url = TWITTER_API_URL;
- def_oauth = "true";
def_tul = "20";
} else { /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */
def_url = IDENTICA_API_URL;
- def_oauth = "true";
def_tul = "0";
}
@@ -259,7 +296,7 @@ static void twitter_init(account_t * 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, "commands", "true", set_eval_commands, acc);
s = set_add(&acc->set, "fetch_interval", "60", set_eval_int, acc);
s->flags |= ACC_SET_OFFLINE_ONLY;
@@ -273,18 +310,22 @@ static void twitter_init(account_t * acc)
s = set_add(&acc->set, "mode", "chat", set_eval_mode, acc);
s->flags |= ACC_SET_OFFLINE_ONLY;
- s = set_add(&acc->set, "oauth", def_oauth, set_eval_oauth, acc);
+ s = set_add(&acc->set, "oauth", "true", set_eval_oauth, acc);
s = set_add(&acc->set, "show_ids", "true", set_eval_bool, acc);
- s->flags |= ACC_SET_OFFLINE_ONLY;
s = set_add(&acc->set, "show_old_mentions", "20", set_eval_int, acc);
s = set_add(&acc->set, "strip_newlines", "false", set_eval_bool, acc);
+
+ if (strcmp(acc->prpl->name, "twitter") == 0) {
+ s = set_add(&acc->set, "stream", "true", set_eval_bool, acc);
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+ }
}
/**
- * Login method. Since the twitter API works with seperate HTTP request we
+ * Login method. Since the twitter API works with separate HTTP request we
* only save the user and pass to the twitter_data object.
*/
static void twitter_login(account_t * acc)
@@ -302,6 +343,12 @@ static void twitter_login(account_t * acc)
return;
}
+ if (!strstr(url.host, "twitter.com") &&
+ set_getbool(&ic->acc->set, "stream")) {
+ imcb_error(ic, "Warning: The streaming API is only supported by Twitter, "
+ "and you seem to be connecting to a different service.");
+ }
+
imcb_log(ic, "Connecting");
twitter_connections = g_slist_append(twitter_connections, ic);
@@ -342,8 +389,16 @@ static void twitter_login(account_t * acc)
imcb_add_buddy(ic, name, NULL);
imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL);
- if (set_getbool(&acc->set, "show_ids"))
- td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH);
+ td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH);
+ td->log_id = -1;
+
+ s = set_getstr(&ic->acc->set, "mode");
+ if (g_strcasecmp(s, "one") == 0)
+ td->flags |= TWITTER_MODE_ONE;
+ else if (g_strcasecmp(s, "many") == 0)
+ td->flags |= TWITTER_MODE_MANY;
+ else
+ td->flags |= TWITTER_MODE_CHAT;
twitter_login_finish(ic);
}
@@ -365,6 +420,7 @@ static void twitter_logout(struct im_connection *ic)
imcb_chat_free(td->timeline_gc);
if (td) {
+ http_close(td->stream);
oauth_info_free(td->oauth_info);
g_free(td->user);
g_free(td->prefix);
@@ -413,13 +469,6 @@ static int twitter_buddy_msg(struct im_connection *ic, char *who, char *message,
return (0);
}
-/**
- *
- */
-static void twitter_set_my_name(struct im_connection *ic, char *info)
-{
-}
-
static void twitter_get_info(struct im_connection *ic, char *who)
{
}
@@ -492,132 +541,131 @@ static void twitter_buddy_data_free(struct bee_user *bu)
g_free(bu->data);
}
+/** Convert the given bitlbee tweet ID, bitlbee username, or twitter tweet ID
+ * into a twitter tweet ID.
+ *
+ * Returns 0 if the user provides garbage.
+ */
+static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, char *arg, bee_user_t **bu_) {
+ struct twitter_data *td = ic->proto_data;
+ struct twitter_user_data *tud;
+ bee_user_t *bu = NULL;
+ guint64 id = 0;
+
+ if (bu_)
+ *bu_ = NULL;
+ if (!arg || !arg[0])
+ return 0;
+
+ if (arg[0] != '#' && (bu = bee_user_by_handle(ic->bee, ic, arg))) {
+ if ((tud = bu->data))
+ id = tud->last_id;
+ } else {
+ if (arg[0] == '#')
+ arg++;
+ if (sscanf(arg, "%" G_GINT64_MODIFIER "x", &id) == 1 &&
+ id < TWITTER_LOG_LENGTH) {
+ bu = td->log[id].bu;
+ id = td->log[id].id;
+ /* Beware of dangling pointers! */
+ if (!g_slist_find(ic->bee->users, bu))
+ bu = NULL;
+ } else if (sscanf(arg, "%" G_GINT64_MODIFIER "d", &id) == 1) {
+ /* Allow normal tweet IDs as well; not a very useful
+ feature but it's always been there. Just ignore
+ very low IDs to avoid accidents. */
+ if (id < 1000000)
+ id = 0;
+ }
+ }
+ if (bu_)
+ *bu_ = bu;
+ return id;
+}
+
static void twitter_handle_command(struct im_connection *ic, char *message)
{
struct twitter_data *td = ic->proto_data;
char *cmds, **cmd, *new = NULL;
- guint64 in_reply_to = 0;
+ guint64 in_reply_to = 0, id;
+ gboolean allow_post =
+ g_strcasecmp(set_getstr(&ic->acc->set, "commands"), "strict") != 0;
+ bee_user_t *bu = NULL;
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. */
+ goto eof;
+ } else if (!set_getbool(&ic->acc->set, "commands") && allow_post) {
+ /* Not supporting commands if "commands" is set to true/strict. */
} else if (g_strcasecmp(cmd[0], "undo") == 0) {
- guint64 id;
-
if (cmd[1] == NULL)
twitter_status_destroy(ic, td->last_status_id);
- else if (sscanf(cmd[1], "%" G_GUINT64_FORMAT, &id) == 1) {
- if (id < TWITTER_LOG_LENGTH && td->log)
- id = td->log[id].id;
-
+ else if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL)))
twitter_status_destroy(ic, id);
- } else
- twitter_msg(ic, "Could not undo last action");
+ else
+ twitter_log(ic, "Could not undo last action");
- g_free(cmds);
- return;
+ goto eof;
+ } else if (g_strcasecmp(cmd[0], "favourite") == 0 && cmd[1]) {
+ if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL))) {
+ twitter_favourite_tweet(ic, id);
+ } else {
+ twitter_log(ic, "Please provide a message ID or username.");
+ }
+ goto eof;
} else if (g_strcasecmp(cmd[0], "follow") == 0 && cmd[1]) {
twitter_add_buddy(ic, cmd[1], NULL);
- g_free(cmds);
- return;
+ goto eof;
} else if (g_strcasecmp(cmd[0], "unfollow") == 0 && cmd[1]) {
twitter_remove_buddy(ic, cmd[1], NULL);
- g_free(cmds);
- return;
+ goto eof;
} else if ((g_strcasecmp(cmd[0], "report") == 0 ||
g_strcasecmp(cmd[0], "spam") == 0) && cmd[1]) {
- char * screen_name;
- guint64 id;
- screen_name = cmd[1];
+ char *screen_name;
+
/* Report nominally works on users but look up the user who
posted the given ID if the user wants to do it that way */
- if (g_str_has_prefix(cmd[1], "#") &&
- sscanf(cmd[1] + 1, "%" G_GUINT64_FORMAT, &id) == 1) {
- if (id < TWITTER_LOG_LENGTH && td->log) {
- if (g_slist_find(ic->bee->users, td->log[id].bu)) {
- screen_name = td->log[id].bu->handle;
- }
- }
- }
+ twitter_message_id_from_command_arg(ic, cmd[1], &bu);
+ if (bu)
+ screen_name = bu->handle;
+ else
+ screen_name = cmd[1];
+
twitter_report_spam(ic, screen_name);
- g_free(cmds);
- return;
+ goto eof;
} else if (g_strcasecmp(cmd[0], "rt") == 0 && cmd[1]) {
- struct twitter_user_data *tud;
- bee_user_t *bu;
- guint64 id;
-
- if (g_str_has_prefix(cmd[1], "#") &&
- sscanf(cmd[1] + 1, "%" G_GUINT64_FORMAT, &id) == 1) {
- if (id < TWITTER_LOG_LENGTH && td->log)
- id = td->log[id].id;
- } else if ((bu = bee_user_by_handle(ic->bee, ic, cmd[1])) &&
- (tud = bu->data) && tud->last_id)
- id = tud->last_id;
- else if (sscanf(cmd[1], "%" G_GUINT64_FORMAT, &id) == 1){
- if (id < TWITTER_LOG_LENGTH && td->log)
- id = td->log[id].id;
- }
+ id = twitter_message_id_from_command_arg(ic, cmd[1], NULL);
td->last_status_id = 0;
if (id)
twitter_status_retweet(ic, id);
else
- twitter_msg(ic, "User `%s' does not exist or didn't "
+ twitter_log(ic, "User `%s' does not exist or didn't "
"post any statuses recently", cmd[1]);
- g_free(cmds);
- return;
+ goto eof;
} else if (g_strcasecmp(cmd[0], "reply") == 0 && cmd[1] && cmd[2]) {
- struct twitter_user_data *tud;
- bee_user_t *bu = NULL;
- guint64 id = 0;
-
- if (g_str_has_prefix(cmd[1], "#") &&
- sscanf(cmd[1] + 1, "%" G_GUINT64_FORMAT, &id) == 1 &&
- (id < TWITTER_LOG_LENGTH) && td->log) {
- bu = td->log[id].bu;
- if (g_slist_find(ic->bee->users, bu))
- id = td->log[id].id;
- else
- bu = NULL;
- } else if ((bu = bee_user_by_handle(ic->bee, ic, cmd[1])) &&
- (tud = bu->data) && tud->last_id) {
- id = tud->last_id;
- } else if (sscanf(cmd[1], "%" G_GUINT64_FORMAT, &id) == 1 &&
- (id < TWITTER_LOG_LENGTH) && td->log) {
- bu = td->log[id].bu;
- if (g_slist_find(ic->bee->users, bu))
- id = td->log[id].id;
- else
- bu = NULL;
- }
-
+ id = twitter_message_id_from_command_arg(ic, cmd[1], &bu);
if (!id || !bu) {
- twitter_msg(ic, "User `%s' does not exist or didn't "
+ twitter_log(ic, "User `%s' does not exist or didn't "
"post any statuses recently", cmd[1]);
- return;
+ goto eof;
}
message = new = g_strdup_printf("@%s %s", bu->handle, message + (cmd[2] - cmd[0]));
in_reply_to = id;
+ allow_post = TRUE;
} else if (g_strcasecmp(cmd[0], "post") == 0) {
message += 5;
+ allow_post = TRUE;
}
- {
+ if (allow_post) {
char *s;
- bee_user_t *bu;
- if (!twitter_length_check(ic, message)) {
- g_free(new);
- g_free(cmds);
- return;
- }
+ if (!twitter_length_check(ic, message))
+ goto eof;
s = cmd[0] + strlen(cmd[0]) - 1;
if (!new && s > cmd[0] && (*s == ':' || *s == ',')) {
@@ -640,11 +688,33 @@ static void twitter_handle_command(struct im_connection *ic, char *message)
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);
+ } else {
+ twitter_log(ic, "Unknown command: %s", cmd[0]);
}
+eof:
+ g_free(new);
g_free(cmds);
}
+void twitter_log(struct im_connection *ic, char *format, ... )
+{
+ struct twitter_data *td = ic->proto_data;
+ va_list params;
+ char *text;
+
+ va_start(params, format);
+ text = g_strdup_vprintf(format, params);
+ va_end(params);
+
+ if (td->timeline_gc)
+ imcb_chat_log(td->timeline_gc, "%s", text);
+ else
+ imcb_log(ic, "%s", text);
+
+ g_free(text);
+}
+
+
void twitter_initmodule()
{
struct prpl *ret = g_new0(struct prpl, 1);
@@ -656,7 +726,6 @@ void twitter_initmodule()
ret->logout = twitter_logout;
ret->buddy_msg = twitter_buddy_msg;
ret->get_info = twitter_get_info;
- ret->set_my_name = twitter_set_my_name;
ret->add_buddy = twitter_add_buddy;
ret->remove_buddy = twitter_remove_buddy;
ret->chat_msg = twitter_chat_msg;