source: protocols/twitter/twitter.c @ b201c68

Last change on this file since b201c68 was 0e788f5, checked in by Wilmer van der Gaast <wilmer@…>, at 2013-02-21T19:15:59Z

I'm still bored on a long flight. Wrote a script to automatically update
my copyright mentions since some were getting pretty stale. Left files not
touched since before 2012 alone so that this change doesn't touch almost
EVERY source file.

  • Property mode set to 100644
File size: 21.1 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-2013 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
472static void twitter_get_info(struct im_connection *ic, char *who)
473{
474}
475
476static void twitter_add_buddy(struct im_connection *ic, char *who, char *group)
477{
478        twitter_friendships_create_destroy(ic, who, 1);
479}
480
481static void twitter_remove_buddy(struct im_connection *ic, char *who, char *group)
482{
483        twitter_friendships_create_destroy(ic, who, 0);
484}
485
486static void twitter_chat_msg(struct groupchat *c, char *message, int flags)
487{
488        if (c && message)
489                twitter_handle_command(c->ic, message);
490}
491
492static void twitter_chat_invite(struct groupchat *c, char *who, char *message)
493{
494}
495
496static void twitter_chat_leave(struct groupchat *c)
497{
498        struct twitter_data *td = c->ic->proto_data;
499
500        if (c != td->timeline_gc)
501                return;         /* WTF? */
502
503        /* If the user leaves the channel: Fine. Rejoin him/her once new
504           tweets come in. */
505        imcb_chat_free(td->timeline_gc);
506        td->timeline_gc = NULL;
507}
508
509static void twitter_keepalive(struct im_connection *ic)
510{
511}
512
513static void twitter_add_permit(struct im_connection *ic, char *who)
514{
515}
516
517static void twitter_rem_permit(struct im_connection *ic, char *who)
518{
519}
520
521static void twitter_add_deny(struct im_connection *ic, char *who)
522{
523}
524
525static void twitter_rem_deny(struct im_connection *ic, char *who)
526{
527}
528
529//static char *twitter_set_display_name( set_t *set, char *value )
530//{
531//      return value;
532//}
533
534static void twitter_buddy_data_add(struct bee_user *bu)
535{
536        bu->data = g_new0(struct twitter_user_data, 1);
537}
538
539static void twitter_buddy_data_free(struct bee_user *bu)
540{
541        g_free(bu->data);
542}
543
544/** Convert the given bitlbee tweet ID, bitlbee username, or twitter tweet ID
545 *  into a twitter tweet ID.
546 *
547 *  Returns 0 if the user provides garbage.
548 */
549static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, char *arg, bee_user_t **bu_) {
550        struct twitter_data *td = ic->proto_data;
551        struct twitter_user_data *tud;
552        bee_user_t *bu = NULL;
553        guint64 id = 0;
554       
555        if (bu_)
556                *bu_ = NULL;
557        if (!arg || !arg[0])
558                return 0;
559       
560        if (arg[0] != '#' && (bu = bee_user_by_handle(ic->bee, ic, arg))) {
561                if ((tud = bu->data))
562                        id = tud->last_id;
563        } else {
564                if (arg[0] == '#')
565                        arg++;
566                if (sscanf(arg, "%" G_GINT64_MODIFIER "x", &id) == 1 &&
567                    id < TWITTER_LOG_LENGTH) {
568                        bu = td->log[id].bu;
569                        id = td->log[id].id;
570                        /* Beware of dangling pointers! */
571                        if (!g_slist_find(ic->bee->users, bu))
572                                bu = NULL;
573                } else if (sscanf(arg, "%" G_GINT64_MODIFIER "d", &id) == 1) {
574                        /* Allow normal tweet IDs as well; not a very useful
575                           feature but it's always been there. Just ignore
576                           very low IDs to avoid accidents. */
577                        if (id < 1000000)
578                                id = 0;
579                }
580        }
581        if (bu_)
582                *bu_ = bu;
583        return id;
584}
585
586static void twitter_handle_command(struct im_connection *ic, char *message)
587{
588        struct twitter_data *td = ic->proto_data;
589        char *cmds, **cmd, *new = NULL;
590        guint64 in_reply_to = 0, id;
591        gboolean allow_post =
592                g_strcasecmp(set_getstr(&ic->acc->set, "commands"), "strict") != 0;
593        bee_user_t *bu = NULL;
594
595        cmds = g_strdup(message);
596        cmd = split_command_parts(cmds);
597
598        if (cmd[0] == NULL) {
599                goto eof;
600        } else if (!set_getbool(&ic->acc->set, "commands") && allow_post) {
601                /* Not supporting commands if "commands" is set to true/strict. */
602        } else if (g_strcasecmp(cmd[0], "undo") == 0) {
603                if (cmd[1] == NULL)
604                        twitter_status_destroy(ic, td->last_status_id);
605                else if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL)))
606                        twitter_status_destroy(ic, id);
607                else
608                        twitter_log(ic, "Could not undo last action");
609
610                goto eof;
611        } else if (g_strcasecmp(cmd[0], "favourite") == 0 && cmd[1]) {
612                if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL))) {
613                        twitter_favourite_tweet(ic, id);
614                } else {
615                        twitter_log(ic, "Please provide a message ID or username.");
616                }
617                goto eof;
618        } else if (g_strcasecmp(cmd[0], "follow") == 0 && cmd[1]) {
619                twitter_add_buddy(ic, cmd[1], NULL);
620                goto eof;
621        } else if (g_strcasecmp(cmd[0], "unfollow") == 0 && cmd[1]) {
622                twitter_remove_buddy(ic, cmd[1], NULL);
623                goto eof;
624        } else if ((g_strcasecmp(cmd[0], "report") == 0 ||
625                    g_strcasecmp(cmd[0], "spam") == 0) && cmd[1]) {
626                char *screen_name;
627               
628                /* Report nominally works on users but look up the user who
629                   posted the given ID if the user wants to do it that way */
630                twitter_message_id_from_command_arg(ic, cmd[1], &bu);
631                if (bu)
632                        screen_name = bu->handle;
633                else
634                        screen_name = cmd[1];
635               
636                twitter_report_spam(ic, screen_name);
637                goto eof;
638        } else if (g_strcasecmp(cmd[0], "rt") == 0 && cmd[1]) {
639                id = twitter_message_id_from_command_arg(ic, cmd[1], NULL);
640
641                td->last_status_id = 0;
642                if (id)
643                        twitter_status_retweet(ic, id);
644                else
645                        twitter_log(ic, "User `%s' does not exist or didn't "
646                                    "post any statuses recently", cmd[1]);
647
648                goto eof;
649        } else if (g_strcasecmp(cmd[0], "reply") == 0 && cmd[1] && cmd[2]) {
650                id = twitter_message_id_from_command_arg(ic, cmd[1], &bu);
651                if (!id || !bu) {
652                        twitter_log(ic, "User `%s' does not exist or didn't "
653                                    "post any statuses recently", cmd[1]);
654                        goto eof;
655                }
656                message = new = g_strdup_printf("@%s %s", bu->handle, message + (cmd[2] - cmd[0]));
657                in_reply_to = id;
658                allow_post = TRUE;
659        } else if (g_strcasecmp(cmd[0], "post") == 0) {
660                message += 5;
661                allow_post = TRUE;
662        }
663
664        if (allow_post) {
665                char *s;
666
667                if (!twitter_length_check(ic, message))
668                        goto eof;
669
670                s = cmd[0] + strlen(cmd[0]) - 1;
671                if (!new && s > cmd[0] && (*s == ':' || *s == ',')) {
672                        *s = '\0';
673
674                        if ((bu = bee_user_by_handle(ic->bee, ic, cmd[0]))) {
675                                struct twitter_user_data *tud = bu->data;
676
677                                new = g_strdup_printf("@%s %s", bu->handle,
678                                                      message + (s - cmd[0]) + 2);
679                                message = new;
680
681                                if (time(NULL) < tud->last_time +
682                                    set_getint(&ic->acc->set, "auto_reply_timeout"))
683                                        in_reply_to = tud->last_id;
684                        }
685                }
686
687                /* If the user runs undo between this request and its response
688                   this would delete the second-last Tweet. Prevent that. */
689                td->last_status_id = 0;
690                twitter_post_status(ic, message, in_reply_to);
691        } else {
692                twitter_log(ic, "Unknown command: %s", cmd[0]);
693        }
694eof:
695        g_free(new);
696        g_free(cmds);
697}
698
699void twitter_log(struct im_connection *ic, char *format, ... )
700{
701        struct twitter_data *td = ic->proto_data;
702        va_list params;
703        char *text;
704       
705        va_start(params, format);
706        text = g_strdup_vprintf(format, params);
707        va_end(params);
708       
709        if (td->timeline_gc)
710                imcb_chat_log(td->timeline_gc, "%s", text);
711        else
712                imcb_log(ic, "%s", text);
713       
714        g_free(text);
715}
716
717
718void twitter_initmodule()
719{
720        struct prpl *ret = g_new0(struct prpl, 1);
721
722        ret->options = OPT_NOOTR;
723        ret->name = "twitter";
724        ret->login = twitter_login;
725        ret->init = twitter_init;
726        ret->logout = twitter_logout;
727        ret->buddy_msg = twitter_buddy_msg;
728        ret->get_info = twitter_get_info;
729        ret->add_buddy = twitter_add_buddy;
730        ret->remove_buddy = twitter_remove_buddy;
731        ret->chat_msg = twitter_chat_msg;
732        ret->chat_invite = twitter_chat_invite;
733        ret->chat_leave = twitter_chat_leave;
734        ret->keepalive = twitter_keepalive;
735        ret->add_permit = twitter_add_permit;
736        ret->rem_permit = twitter_rem_permit;
737        ret->add_deny = twitter_add_deny;
738        ret->rem_deny = twitter_rem_deny;
739        ret->buddy_data_add = twitter_buddy_data_add;
740        ret->buddy_data_free = twitter_buddy_data_free;
741        ret->handle_cmp = g_strcasecmp;
742
743        register_protocol(ret);
744
745        /* And an identi.ca variant: */
746        ret = g_memdup(ret, sizeof(struct prpl));
747        ret->name = "identica";
748        register_protocol(ret);
749}
Note: See TracBrowser for help on using the repository browser.