source: protocols/twitter/twitter.c @ cfbecc9

Last change on this file since cfbecc9 was cfbecc9, checked in by Wilmer van der Gaast <wilmer@…>, at 2013-01-06T12:09:35Z

Fixed NULL pointer dereference bug when newly connecting to identi.ca.
Twitter sends us our real username on OAuth completion, which is then used
instead of the username supplied by the user. Identi.ca doesn't supply
this info so BitlBee would instead replace td->user with a NULL pointer.

  • Property mode set to 100644
File size: 21.3 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Simple module to facilitate twitter functionality.                       *
5*                                                                           *
6*  Copyright 2009-2010 Geert Mulders <g.c.w.m.mulders@gmail.com>            *
7*  Copyright 2010-2012 Wilmer van der Gaast <wilmer@gaast.net>              *
8*                                                                           *
9*  This library is free software; you can redistribute it and/or            *
10*  modify it under the terms of the GNU Lesser General Public               *
11*  License as published by the Free Software Foundation, version            *
12*  2.1.                                                                     *
13*                                                                           *
14*  This library is distributed in the hope that it will be useful,          *
15*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
16*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU        *
17*  Lesser General Public License for more details.                          *
18*                                                                           *
19*  You should have received a copy of the GNU Lesser General Public License *
20*  along with this library; if not, write to the Free Software Foundation,  *
21*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA           *
22*                                                                           *
23****************************************************************************/
24
25#include "nogaim.h"
26#include "oauth.h"
27#include "twitter.h"
28#include "twitter_http.h"
29#include "twitter_lib.h"
30#include "url.h"
31
32GSList *twitter_connections = NULL;
33/**
34 * Main loop function
35 */
36gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond)
37{
38        struct im_connection *ic = data;
39
40        // Check if we are still logged in...
41        if (!g_slist_find(twitter_connections, ic))
42                return 0;
43
44        // Do stuff..
45        twitter_get_timeline(ic, -1);
46
47        // If we are still logged in run this function again after timeout.
48        return (ic->flags & OPT_LOGGED_IN) == OPT_LOGGED_IN;
49}
50
51static void twitter_main_loop_start(struct im_connection *ic)
52{
53        struct twitter_data *td = ic->proto_data;
54
55        /* Create the room now that we "logged in". */
56        if (td->flags & TWITTER_MODE_CHAT)
57                twitter_groupchat_init(ic);
58
59        imcb_log(ic, "Getting initial statuses");
60
61        // Run this once. After this queue the main loop function (or open the
62        // stream if available).
63        twitter_main_loop(ic, -1, 0);
64       
65        if (set_getbool(&ic->acc->set, "stream")) {
66                /* That fetch was just to get backlog, the stream will give
67                   us the rest. \o/ */
68                twitter_open_stream(ic);
69               
70                /* Stream sends keepalives (empty lines) or actual data at
71                   least twice a minute. Disconnect if this stops. */
72                ic->flags |= OPT_PONGS;
73        } else {
74                /* Not using the streaming API, so keep polling the old-
75                   fashioned way. :-( */
76                td->main_loop_id =
77                    b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000,
78                                  twitter_main_loop, ic);
79        }
80}
81
82struct groupchat *twitter_groupchat_init(struct im_connection *ic)
83{
84        char *name_hint;
85        struct groupchat *gc;
86        struct twitter_data *td = ic->proto_data;
87        GSList *l;
88
89        if (td->timeline_gc)
90                return td->timeline_gc;
91
92        td->timeline_gc = gc = imcb_chat_new(ic, "twitter/timeline");
93
94        name_hint = g_strdup_printf("%s_%s", td->prefix, ic->acc->user);
95        imcb_chat_name_hint(gc, name_hint);
96        g_free(name_hint);
97
98        for (l = ic->bee->users; l; l = l->next) {
99                bee_user_t *bu = l->data;
100                if (bu->ic == ic)
101                        imcb_chat_add_buddy(gc, bu->handle);
102        }
103        imcb_chat_add_buddy(gc, ic->acc->user);
104       
105        return gc;
106}
107
108static void twitter_oauth_start(struct im_connection *ic);
109
110void twitter_login_finish(struct im_connection *ic)
111{
112        struct twitter_data *td = ic->proto_data;
113
114        td->flags &= ~TWITTER_DOING_TIMELINE;
115
116        if (set_getbool(&ic->acc->set, "oauth") && !td->oauth_info)
117                twitter_oauth_start(ic);
118        else if (!(td->flags & TWITTER_MODE_ONE) &&
119                 !(td->flags & TWITTER_HAVE_FRIENDS)) {
120                imcb_log(ic, "Getting contact list");
121                twitter_get_friends_ids(ic, -1);
122        } else
123                twitter_main_loop_start(ic);
124}
125
126static const struct oauth_service twitter_oauth = {
127        "https://api.twitter.com/oauth/request_token",
128        "https://api.twitter.com/oauth/access_token",
129        "https://api.twitter.com/oauth/authorize",
130        .consumer_key = "xsDNKJuNZYkZyMcu914uEA",
131        .consumer_secret = "FCxqcr0pXKzsF9ajmP57S3VQ8V6Drk4o2QYtqMcOszo",
132};
133
134static const struct oauth_service identica_oauth = {
135        "https://identi.ca/api/oauth/request_token",
136        "https://identi.ca/api/oauth/access_token",
137        "https://identi.ca/api/oauth/authorize",
138        .consumer_key = "e147ff789fcbd8a5a07963afbb43f9da",
139        .consumer_secret = "c596267f277457ec0ce1ab7bb788d828",
140};
141
142static gboolean twitter_oauth_callback(struct oauth_info *info);
143
144static const struct oauth_service *get_oauth_service(struct im_connection *ic)
145{
146        struct twitter_data *td = ic->proto_data;
147
148        if (strstr(td->url_host, "identi.ca"))
149                return &identica_oauth;
150        else
151                return &twitter_oauth;
152
153        /* Could add more services, or allow configuring your own base URL +
154           API keys. */
155}
156
157static void twitter_oauth_start(struct im_connection *ic)
158{
159        struct twitter_data *td = ic->proto_data;
160
161        imcb_log(ic, "Requesting OAuth request token");
162
163        td->oauth_info = oauth_request_token(get_oauth_service(ic), twitter_oauth_callback, ic);
164
165        /* We need help from the user to complete OAuth login, so don't time
166           out on this login. */
167        ic->flags |= OPT_SLOW_LOGIN;
168}
169
170static gboolean twitter_oauth_callback(struct oauth_info *info)
171{
172        struct im_connection *ic = info->data;
173        struct twitter_data *td;
174
175        if (!g_slist_find(twitter_connections, ic))
176                return FALSE;
177
178        td = ic->proto_data;
179        if (info->stage == OAUTH_REQUEST_TOKEN) {
180                char name[strlen(ic->acc->user) + 9], *msg;
181
182                if (info->request_token == NULL) {
183                        imcb_error(ic, "OAuth error: %s", twitter_parse_error(info->http));
184                        imc_logout(ic, TRUE);
185                        return FALSE;
186                }
187
188                sprintf(name, "%s_%s", td->prefix, ic->acc->user);
189                msg = g_strdup_printf("To finish OAuth authentication, please visit "
190                                      "%s and respond with the resulting PIN code.",
191                                      info->auth_url);
192                imcb_buddy_msg(ic, name, msg, 0, 0);
193                g_free(msg);
194        } else if (info->stage == OAUTH_ACCESS_TOKEN) {
195                const char *sn;
196               
197                if (info->token == NULL || info->token_secret == NULL) {
198                        imcb_error(ic, "OAuth error: %s", twitter_parse_error(info->http));
199                        imc_logout(ic, TRUE);
200                        return FALSE;
201                }
202               
203                if ((sn = oauth_params_get(&info->params, "screen_name"))) {
204                        if (ic->acc->prpl->handle_cmp(sn, ic->acc->user) != 0)
205                                imcb_log(ic, "Warning: You logged in via OAuth as %s "
206                                         "instead of %s.", sn, ic->acc->user);
207                        g_free(td->user);
208                        td->user = g_strdup(sn);
209                }
210
211                /* IM mods didn't do this so far and it's ugly but I should
212                   be able to get away with it... */
213                g_free(ic->acc->pass);
214                ic->acc->pass = oauth_to_string(info);
215
216                twitter_login_finish(ic);
217        }
218
219        return TRUE;
220}
221
222int twitter_url_len_diff(gchar *msg, unsigned int target_len)
223{
224        int url_len_diff = 0;
225
226        static GRegex *regex = NULL;
227        GMatchInfo *match_info;
228
229        if (regex == NULL)
230                regex = g_regex_new("(^|\\s)(http(s)?://[^\\s$]+)", 0, 0, NULL);
231       
232        g_regex_match(regex, msg, 0, &match_info);
233        while (g_match_info_matches(match_info)) {
234                gchar *url = g_match_info_fetch(match_info, 2);
235                url_len_diff += target_len - g_utf8_strlen(url, -1);
236                if (g_match_info_fetch(match_info, 3) != NULL)
237                        url_len_diff += 1;
238                g_free(url);
239                g_match_info_next(match_info, NULL);
240        }
241        g_match_info_free(match_info);
242
243        return url_len_diff;
244}
245
246static gboolean twitter_length_check(struct im_connection *ic, gchar * msg)
247{
248        int max = set_getint(&ic->acc->set, "message_length"), len;
249        int target_len = set_getint(&ic->acc->set, "target_url_length");
250        int url_len_diff = 0;
251   
252        if (target_len > 0)
253                url_len_diff = twitter_url_len_diff(msg, target_len);
254
255        if (max == 0 || (len = g_utf8_strlen(msg, -1) + url_len_diff) <= max)
256                return TRUE;
257
258        twitter_log(ic, "Maximum message length exceeded: %d > %d", len, max);
259
260        return FALSE;
261}
262
263static char *set_eval_commands(set_t * set, char *value)
264{
265        if (g_strcasecmp(value, "strict") == 0 )
266                return value;
267        else
268                return set_eval_bool(set, value);
269}
270
271static char *set_eval_mode(set_t * set, char *value)
272{
273        if (g_strcasecmp(value, "one") == 0 ||
274            g_strcasecmp(value, "many") == 0 || g_strcasecmp(value, "chat") == 0)
275                return value;
276        else
277                return NULL;
278}
279
280static void twitter_init(account_t * acc)
281{
282        set_t *s;
283        char *def_url;
284        char *def_tul;
285
286        if (strcmp(acc->prpl->name, "twitter") == 0) {
287                def_url = TWITTER_API_URL;
288                def_tul = "20";
289        } else {                /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */
290                def_url = IDENTICA_API_URL;
291                def_tul = "0";
292        }
293
294        s = set_add(&acc->set, "auto_reply_timeout", "10800", set_eval_int, acc);
295
296        s = set_add(&acc->set, "base_url", def_url, NULL, acc);
297        s->flags |= ACC_SET_OFFLINE_ONLY;
298
299        s = set_add(&acc->set, "commands", "true", set_eval_commands, acc);
300
301        s = set_add(&acc->set, "fetch_interval", "60", set_eval_int, acc);
302        s->flags |= ACC_SET_OFFLINE_ONLY;
303
304        s = set_add(&acc->set, "fetch_mentions", "true", set_eval_bool, acc);
305
306        s = set_add(&acc->set, "message_length", "140", set_eval_int, acc);
307
308        s = set_add(&acc->set, "target_url_length", def_tul, set_eval_int, acc);
309
310        s = set_add(&acc->set, "mode", "chat", set_eval_mode, acc);
311        s->flags |= ACC_SET_OFFLINE_ONLY;
312
313        s = set_add(&acc->set, "oauth", "true", set_eval_oauth, acc);
314
315        s = set_add(&acc->set, "show_ids", "true", set_eval_bool, acc);
316
317        s = set_add(&acc->set, "show_old_mentions", "20", set_eval_int, acc);
318
319        s = set_add(&acc->set, "strip_newlines", "false", set_eval_bool, acc);
320       
321        if (strcmp(acc->prpl->name, "twitter") == 0) {
322                s = set_add(&acc->set, "stream", "true", set_eval_bool, acc);
323                s->flags |= ACC_SET_OFFLINE_ONLY;
324        }
325}
326
327/**
328 * Login method. Since the twitter API works with separate HTTP request we
329 * only save the user and pass to the twitter_data object.
330 */
331static void twitter_login(account_t * acc)
332{
333        struct im_connection *ic = imcb_new(acc);
334        struct twitter_data *td;
335        char name[strlen(acc->user) + 9];
336        url_t url;
337        char *s;
338       
339        if (!url_set(&url, set_getstr(&ic->acc->set, "base_url")) ||
340            (url.proto != PROTO_HTTP && url.proto != PROTO_HTTPS)) {
341                imcb_error(ic, "Incorrect API base URL: %s", set_getstr(&ic->acc->set, "base_url"));
342                imc_logout(ic, FALSE);
343                return;
344        }
345
346        if (!strstr(url.host, "twitter.com") &&
347            set_getbool(&ic->acc->set, "stream")) {
348                imcb_error(ic, "Warning: The streaming API is only supported by Twitter, "
349                               "and you seem to be connecting to a different service.");
350        }
351
352        imcb_log(ic, "Connecting");
353
354        twitter_connections = g_slist_append(twitter_connections, ic);
355        td = g_new0(struct twitter_data, 1);
356        ic->proto_data = td;
357        td->user = g_strdup(acc->user);
358
359        td->url_ssl = url.proto == PROTO_HTTPS;
360        td->url_port = url.port;
361        td->url_host = g_strdup(url.host);
362        if (strcmp(url.file, "/") != 0)
363                td->url_path = g_strdup(url.file);
364        else {
365                td->url_path = g_strdup("");
366                if (g_str_has_suffix(url.host, "twitter.com"))
367                        /* May fire for people who turned on HTTPS. */
368                        imcb_error(ic, "Warning: Twitter requires a version number in API calls "
369                                       "now. Try resetting the base_url account setting.");
370        }
371       
372        /* Hacky string mangling: Turn identi.ca into identi.ca and api.twitter.com
373           into twitter, and try to be sensible if we get anything else. */
374        td->prefix = g_strdup(url.host);
375        if (g_str_has_suffix(td->prefix, ".com"))
376                td->prefix[strlen(url.host) - 4] = '\0';
377        if ((s = strrchr(td->prefix, '.')) && strlen(s) > 4) {
378                /* If we have at least 3 chars after the last dot, cut off the rest.
379                   (mostly a www/api prefix or sth) */
380                s = g_strdup(s + 1);
381                g_free(td->prefix);
382                td->prefix = s;
383        }
384       
385        if (strstr(acc->pass, "oauth_token="))
386                td->oauth_info = oauth_from_string(acc->pass, get_oauth_service(ic));
387
388        sprintf(name, "%s_%s", td->prefix, acc->user);
389        imcb_add_buddy(ic, name, NULL);
390        imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL);
391
392        td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH);
393        td->log_id = -1;
394       
395        s = set_getstr(&ic->acc->set, "mode");
396        if (g_strcasecmp(s, "one") == 0)
397                td->flags |= TWITTER_MODE_ONE;
398        else if (g_strcasecmp(s, "many") == 0)
399                td->flags |= TWITTER_MODE_MANY;
400        else
401                td->flags |= TWITTER_MODE_CHAT;
402
403        twitter_login_finish(ic);
404}
405
406/**
407 * Logout method. Just free the twitter_data.
408 */
409static void twitter_logout(struct im_connection *ic)
410{
411        struct twitter_data *td = ic->proto_data;
412
413        // Set the status to logged out.
414        ic->flags &= ~OPT_LOGGED_IN;
415
416        // Remove the main_loop function from the function queue.
417        b_event_remove(td->main_loop_id);
418
419        if (td->timeline_gc)
420                imcb_chat_free(td->timeline_gc);
421
422        if (td) {
423                http_close(td->stream);
424                oauth_info_free(td->oauth_info);
425                g_free(td->user);
426                g_free(td->prefix);
427                g_free(td->url_host);
428                g_free(td->url_path);
429                g_free(td->log);
430                g_free(td);
431        }
432
433        twitter_connections = g_slist_remove(twitter_connections, ic);
434}
435
436static void twitter_handle_command(struct im_connection *ic, char *message);
437
438/**
439 *
440 */
441static int twitter_buddy_msg(struct im_connection *ic, char *who, char *message, int away)
442{
443        struct twitter_data *td = ic->proto_data;
444        int plen = strlen(td->prefix);
445
446        if (g_strncasecmp(who, td->prefix, plen) == 0 && who[plen] == '_' &&
447            g_strcasecmp(who + plen + 1, ic->acc->user) == 0) {
448                if (set_getbool(&ic->acc->set, "oauth") &&
449                    td->oauth_info && td->oauth_info->token == NULL) {
450                        char pin[strlen(message) + 1], *s;
451
452                        strcpy(pin, message);
453                        for (s = pin + sizeof(pin) - 2; s > pin && isspace(*s); s--)
454                                *s = '\0';
455                        for (s = pin; *s && isspace(*s); s++) {
456                        }
457
458                        if (!oauth_access_token(s, td->oauth_info)) {
459                                imcb_error(ic, "OAuth error: %s",
460                                           "Failed to send access token request");
461                                imc_logout(ic, TRUE);
462                                return FALSE;
463                        }
464                } else
465                        twitter_handle_command(ic, message);
466        } else {
467                twitter_direct_messages_new(ic, who, message);
468        }
469        return (0);
470}
471
472/**
473 *
474 */
475static void twitter_set_my_name(struct im_connection *ic, char *info)
476{
477}
478
479static void twitter_get_info(struct im_connection *ic, char *who)
480{
481}
482
483static void twitter_add_buddy(struct im_connection *ic, char *who, char *group)
484{
485        twitter_friendships_create_destroy(ic, who, 1);
486}
487
488static void twitter_remove_buddy(struct im_connection *ic, char *who, char *group)
489{
490        twitter_friendships_create_destroy(ic, who, 0);
491}
492
493static void twitter_chat_msg(struct groupchat *c, char *message, int flags)
494{
495        if (c && message)
496                twitter_handle_command(c->ic, message);
497}
498
499static void twitter_chat_invite(struct groupchat *c, char *who, char *message)
500{
501}
502
503static void twitter_chat_leave(struct groupchat *c)
504{
505        struct twitter_data *td = c->ic->proto_data;
506
507        if (c != td->timeline_gc)
508                return;         /* WTF? */
509
510        /* If the user leaves the channel: Fine. Rejoin him/her once new
511           tweets come in. */
512        imcb_chat_free(td->timeline_gc);
513        td->timeline_gc = NULL;
514}
515
516static void twitter_keepalive(struct im_connection *ic)
517{
518}
519
520static void twitter_add_permit(struct im_connection *ic, char *who)
521{
522}
523
524static void twitter_rem_permit(struct im_connection *ic, char *who)
525{
526}
527
528static void twitter_add_deny(struct im_connection *ic, char *who)
529{
530}
531
532static void twitter_rem_deny(struct im_connection *ic, char *who)
533{
534}
535
536//static char *twitter_set_display_name( set_t *set, char *value )
537//{
538//      return value;
539//}
540
541static void twitter_buddy_data_add(struct bee_user *bu)
542{
543        bu->data = g_new0(struct twitter_user_data, 1);
544}
545
546static void twitter_buddy_data_free(struct bee_user *bu)
547{
548        g_free(bu->data);
549}
550
551/** Convert the given bitlbee tweet ID, bitlbee username, or twitter tweet ID
552 *  into a twitter tweet ID.
553 *
554 *  Returns 0 if the user provides garbage.
555 */
556static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, char *arg, bee_user_t **bu_) {
557        struct twitter_data *td = ic->proto_data;
558        struct twitter_user_data *tud;
559        bee_user_t *bu = NULL;
560        guint64 id = 0;
561       
562        if (bu_)
563                *bu_ = NULL;
564        if (!arg || !arg[0])
565                return 0;
566       
567        if (arg[0] != '#' && (bu = bee_user_by_handle(ic->bee, ic, arg))) {
568                if ((tud = bu->data))
569                        id = tud->last_id;
570        } else {
571                if (arg[0] == '#')
572                        arg++;
573                if (sscanf(arg, "%" G_GINT64_MODIFIER "x", &id) == 1 &&
574                    id < TWITTER_LOG_LENGTH) {
575                        bu = td->log[id].bu;
576                        id = td->log[id].id;
577                        /* Beware of dangling pointers! */
578                        if (!g_slist_find(ic->bee->users, bu))
579                                bu = NULL;
580                } else if (sscanf(arg, "%" G_GINT64_MODIFIER "d", &id) == 1) {
581                        /* Allow normal tweet IDs as well; not a very useful
582                           feature but it's always been there. Just ignore
583                           very low IDs to avoid accidents. */
584                        if (id < 1000000)
585                                id = 0;
586                }
587        }
588        if (bu_)
589                *bu_ = bu;
590        return id;
591}
592
593static void twitter_handle_command(struct im_connection *ic, char *message)
594{
595        struct twitter_data *td = ic->proto_data;
596        char *cmds, **cmd, *new = NULL;
597        guint64 in_reply_to = 0, id;
598        gboolean allow_post =
599                g_strcasecmp(set_getstr(&ic->acc->set, "commands"), "strict") != 0;
600        bee_user_t *bu = NULL;
601
602        cmds = g_strdup(message);
603        cmd = split_command_parts(cmds);
604
605        if (cmd[0] == NULL) {
606                goto eof;
607        } else if (!set_getbool(&ic->acc->set, "commands") && allow_post) {
608                /* Not supporting commands if "commands" is set to true/strict. */
609        } else if (g_strcasecmp(cmd[0], "undo") == 0) {
610                if (cmd[1] == NULL)
611                        twitter_status_destroy(ic, td->last_status_id);
612                else if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL)))
613                        twitter_status_destroy(ic, id);
614                else
615                        twitter_log(ic, "Could not undo last action");
616
617                goto eof;
618        } else if (g_strcasecmp(cmd[0], "favourite") == 0 && cmd[1]) {
619                if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL))) {
620                        twitter_favourite_tweet(ic, id);
621                } else {
622                        twitter_log(ic, "Please provide a message ID or username.");
623                }
624                goto eof;
625        } else if (g_strcasecmp(cmd[0], "follow") == 0 && cmd[1]) {
626                twitter_add_buddy(ic, cmd[1], NULL);
627                goto eof;
628        } else if (g_strcasecmp(cmd[0], "unfollow") == 0 && cmd[1]) {
629                twitter_remove_buddy(ic, cmd[1], NULL);
630                goto eof;
631        } else if ((g_strcasecmp(cmd[0], "report") == 0 ||
632                    g_strcasecmp(cmd[0], "spam") == 0) && cmd[1]) {
633                char *screen_name;
634               
635                /* Report nominally works on users but look up the user who
636                   posted the given ID if the user wants to do it that way */
637                twitter_message_id_from_command_arg(ic, cmd[1], &bu);
638                if (bu)
639                        screen_name = bu->handle;
640                else
641                        screen_name = cmd[1];
642               
643                twitter_report_spam(ic, screen_name);
644                goto eof;
645        } else if (g_strcasecmp(cmd[0], "rt") == 0 && cmd[1]) {
646                id = twitter_message_id_from_command_arg(ic, cmd[1], NULL);
647
648                td->last_status_id = 0;
649                if (id)
650                        twitter_status_retweet(ic, id);
651                else
652                        twitter_log(ic, "User `%s' does not exist or didn't "
653                                    "post any statuses recently", cmd[1]);
654
655                goto eof;
656        } else if (g_strcasecmp(cmd[0], "reply") == 0 && cmd[1] && cmd[2]) {
657                id = twitter_message_id_from_command_arg(ic, cmd[1], &bu);
658                if (!id || !bu) {
659                        twitter_log(ic, "User `%s' does not exist or didn't "
660                                    "post any statuses recently", cmd[1]);
661                        goto eof;
662                }
663                message = new = g_strdup_printf("@%s %s", bu->handle, message + (cmd[2] - cmd[0]));
664                in_reply_to = id;
665                allow_post = TRUE;
666        } else if (g_strcasecmp(cmd[0], "post") == 0) {
667                message += 5;
668                allow_post = TRUE;
669        }
670
671        if (allow_post) {
672                char *s;
673
674                if (!twitter_length_check(ic, message))
675                        goto eof;
676
677                s = cmd[0] + strlen(cmd[0]) - 1;
678                if (!new && s > cmd[0] && (*s == ':' || *s == ',')) {
679                        *s = '\0';
680
681                        if ((bu = bee_user_by_handle(ic->bee, ic, cmd[0]))) {
682                                struct twitter_user_data *tud = bu->data;
683
684                                new = g_strdup_printf("@%s %s", bu->handle,
685                                                      message + (s - cmd[0]) + 2);
686                                message = new;
687
688                                if (time(NULL) < tud->last_time +
689                                    set_getint(&ic->acc->set, "auto_reply_timeout"))
690                                        in_reply_to = tud->last_id;
691                        }
692                }
693
694                /* If the user runs undo between this request and its response
695                   this would delete the second-last Tweet. Prevent that. */
696                td->last_status_id = 0;
697                twitter_post_status(ic, message, in_reply_to);
698        } else {
699                twitter_log(ic, "Unknown command: %s", cmd[0]);
700        }
701eof:
702        g_free(new);
703        g_free(cmds);
704}
705
706void twitter_log(struct im_connection *ic, char *format, ... )
707{
708        struct twitter_data *td = ic->proto_data;
709        va_list params;
710        char *text;
711       
712        va_start(params, format);
713        text = g_strdup_vprintf(format, params);
714        va_end(params);
715       
716        if (td->timeline_gc)
717                imcb_chat_log(td->timeline_gc, "%s", text);
718        else
719                imcb_log(ic, "%s", text);
720       
721        g_free(text);
722}
723
724
725void twitter_initmodule()
726{
727        struct prpl *ret = g_new0(struct prpl, 1);
728
729        ret->options = OPT_NOOTR;
730        ret->name = "twitter";
731        ret->login = twitter_login;
732        ret->init = twitter_init;
733        ret->logout = twitter_logout;
734        ret->buddy_msg = twitter_buddy_msg;
735        ret->get_info = twitter_get_info;
736        ret->set_my_name = twitter_set_my_name;
737        ret->add_buddy = twitter_add_buddy;
738        ret->remove_buddy = twitter_remove_buddy;
739        ret->chat_msg = twitter_chat_msg;
740        ret->chat_invite = twitter_chat_invite;
741        ret->chat_leave = twitter_chat_leave;
742        ret->keepalive = twitter_keepalive;
743        ret->add_permit = twitter_add_permit;
744        ret->rem_permit = twitter_rem_permit;
745        ret->add_deny = twitter_add_deny;
746        ret->rem_deny = twitter_rem_deny;
747        ret->buddy_data_add = twitter_buddy_data_add;
748        ret->buddy_data_free = twitter_buddy_data_free;
749        ret->handle_cmp = g_strcasecmp;
750
751        register_protocol(ret);
752
753        /* And an identi.ca variant: */
754        ret = g_memdup(ret, sizeof(struct prpl));
755        ret->name = "identica";
756        register_protocol(ret);
757}
Note: See TracBrowser for help on using the repository browser.