source: protocols/twitter/twitter.c @ 5983eca

Last change on this file since 5983eca was 5983eca, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-06-11T11:28:01Z

Re-indent Twitter code. It just lacks *any* kind of consistency. Flags used:
-i8 -kr -ts8 -ut -l100 Because K&R isn't so bad after all but spaces are
definitely evil. (Not that GNU indent understands how to use tabs, oh well.)

  • Property mode set to 100644
File size: 16.2 KB
RevLine 
[1b221e0]1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Simple module to facilitate twitter functionality.                       *
5*                                                                           *
6*  Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com>                 *
7*                                                                           *
8*  This library is free software; you can redistribute it and/or            *
9*  modify it under the terms of the GNU Lesser General Public               *
10*  License as published by the Free Software Foundation, version            *
11*  2.1.                                                                     *
12*                                                                           *
13*  This library is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU        *
16*  Lesser General Public License for more details.                          *
17*                                                                           *
18*  You should have received a copy of the GNU Lesser General Public License *
19*  along with this library; if not, write to the Free Software Foundation,  *
20*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA           *
21*                                                                           *
22****************************************************************************/
23
24#include "nogaim.h"
[713d611]25#include "oauth.h"
[1b221e0]26#include "twitter.h"
27#include "twitter_http.h"
28#include "twitter_lib.h"
[bb5ce4d1]29#include "url.h"
[1b221e0]30
[665c24f]31#define twitter_msg( ic, fmt... ) \
32        do {                                                        \
33                struct twitter_data *td = ic->proto_data;           \
34                if( td->home_timeline_gc )                          \
35                        imcb_chat_log( td->home_timeline_gc, fmt ); \
36                else                                                \
37                        imcb_log( ic, fmt );                        \
38        } while( 0 );
[5983eca]39
[9c9a29c]40GSList *twitter_connections = NULL;
[665c24f]41
[1b221e0]42/**
[62d2cfb]43 * Main loop function
44 */
[1b221e0]45gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond)
46{
47        struct im_connection *ic = data;
[5983eca]48
[1b221e0]49        // Check if we are still logged in...
[5983eca]50        if (!g_slist_find(twitter_connections, ic))
[1b221e0]51                return 0;
52
53        // Do stuff..
54        twitter_get_home_timeline(ic, -1);
55
56        // If we are still logged in run this function again after timeout.
57        return (ic->flags & OPT_LOGGED_IN) == OPT_LOGGED_IN;
58}
59
[5983eca]60static void twitter_main_loop_start(struct im_connection *ic)
[713d611]61{
62        struct twitter_data *td = ic->proto_data;
[5983eca]63
64        imcb_log(ic, "Getting initial statuses");
[713d611]65
66        // Run this once. After this queue the main loop function.
67        twitter_main_loop(ic, -1, 0);
68
69        // Queue the main_loop
70        // Save the return value, so we can remove the timeout on logout.
71        td->main_loop_id = b_timeout_add(60000, twitter_main_loop, ic);
72}
73
[5983eca]74static void twitter_oauth_start(struct im_connection *ic);
[d6aa6dd]75
[5983eca]76void twitter_login_finish(struct im_connection *ic)
[d6aa6dd]77{
78        struct twitter_data *td = ic->proto_data;
[5983eca]79
80        if (set_getbool(&ic->acc->set, "oauth") && !td->oauth_info)
81                twitter_oauth_start(ic);
82        else if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") != 0 &&
83                 !(td->flags & TWITTER_HAVE_FRIENDS)) {
84                imcb_log(ic, "Getting contact list");
85                twitter_get_statuses_friends(ic, -1);
86        } else
87                twitter_main_loop_start(ic);
[d6aa6dd]88}
[c2ecadc]89
[5983eca]90static const struct oauth_service twitter_oauth = {
[c2ecadc]91        "http://api.twitter.com/oauth/request_token",
92        "http://api.twitter.com/oauth/access_token",
[c01bbd1]93        "https://api.twitter.com/oauth/authorize",
[c2ecadc]94        .consumer_key = "xsDNKJuNZYkZyMcu914uEA",
95        .consumer_secret = "FCxqcr0pXKzsF9ajmP57S3VQ8V6Drk4o2QYtqMcOszo",
96};
97
[5983eca]98static const struct oauth_service identica_oauth = {
[ce617f0]99        "http://identi.ca/api/oauth/request_token",
100        "http://identi.ca/api/oauth/access_token",
101        "https://identi.ca/api/oauth/authorize",
102        .consumer_key = "e147ff789fcbd8a5a07963afbb43f9da",
103        .consumer_secret = "c596267f277457ec0ce1ab7bb788d828",
104};
105
[5983eca]106static gboolean twitter_oauth_callback(struct oauth_info *info);
[713d611]107
[5983eca]108static const struct oauth_service *get_oauth_service(struct im_connection *ic)
[ce617f0]109{
110        struct twitter_data *td = ic->proto_data;
[5983eca]111
112        if (strstr(td->url_host, "identi.ca"))
[ce617f0]113                return &identica_oauth;
114        else
115                return &twitter_oauth;
[5983eca]116
[ce617f0]117        /* Could add more services, or allow configuring your own base URL +
118           API keys. */
119}
120
[5983eca]121static void twitter_oauth_start(struct im_connection *ic)
[713d611]122{
[18dbb20]123        struct twitter_data *td = ic->proto_data;
[c42e8b9]124
[5983eca]125        imcb_log(ic, "Requesting OAuth request token");
126
127        td->oauth_info = oauth_request_token(get_oauth_service(ic), twitter_oauth_callback, ic);
128
[748bcdd]129        /* We need help from the user to complete OAuth login, so don't time
130           out on this login. */
131        ic->flags |= OPT_SLOW_LOGIN;
[713d611]132}
133
[5983eca]134static gboolean twitter_oauth_callback(struct oauth_info *info)
[713d611]135{
136        struct im_connection *ic = info->data;
[18dbb20]137        struct twitter_data *td;
[5983eca]138
139        if (!g_slist_find(twitter_connections, ic))
[18dbb20]140                return FALSE;
[5983eca]141
[18dbb20]142        td = ic->proto_data;
[5983eca]143        if (info->stage == OAUTH_REQUEST_TOKEN) {
144                char name[strlen(ic->acc->user) + 9], *msg;
145
146                if (info->request_token == NULL) {
147                        imcb_error(ic, "OAuth error: %s", twitter_parse_error(info->http));
148                        imc_logout(ic, TRUE);
[18dbb20]149                        return FALSE;
[c42e8b9]150                }
[5983eca]151
152                sprintf(name, "%s_%s", td->prefix, ic->acc->user);
153                msg = g_strdup_printf("To finish OAuth authentication, please visit "
154                                      "%s and respond with the resulting PIN code.",
155                                      info->auth_url);
156                imcb_buddy_msg(ic, name, msg, 0, 0);
157                g_free(msg);
158        } else if (info->stage == OAUTH_ACCESS_TOKEN) {
159                if (info->token == NULL || info->token_secret == NULL) {
160                        imcb_error(ic, "OAuth error: %s", twitter_parse_error(info->http));
161                        imc_logout(ic, TRUE);
[18dbb20]162                        return FALSE;
[5983eca]163                } else {
164                        const char *sn = oauth_params_get(&info->params, "screen_name");
165
166                        if (sn != NULL && ic->acc->prpl->handle_cmp(sn, ic->acc->user) != 0) {
167                                imcb_log(ic, "Warning: You logged in via OAuth as %s "
168                                         "instead of %s.", sn, ic->acc->user);
[93cc86f]169                        }
170                }
[5983eca]171
[288b215]172                /* IM mods didn't do this so far and it's ugly but I should
173                   be able to get away with it... */
[5983eca]174                g_free(ic->acc->pass);
175                ic->acc->pass = oauth_to_string(info);
176
177                twitter_login_finish(ic);
[c42e8b9]178        }
[5983eca]179
[18dbb20]180        return TRUE;
[713d611]181}
182
[c2ecadc]183
[5983eca]184static char *set_eval_mode(set_t * set, char *value)
[e88fbe27]185{
[5983eca]186        if (g_strcasecmp(value, "one") == 0 ||
187            g_strcasecmp(value, "many") == 0 || g_strcasecmp(value, "chat") == 0)
[e88fbe27]188                return value;
189        else
190                return NULL;
191}
[1b221e0]192
[5983eca]193static gboolean twitter_length_check(struct im_connection *ic, gchar * msg)
[9997691]194{
[5983eca]195        int max = set_getint(&ic->acc->set, "message_length"), len;
196
197        if (max == 0 || (len = g_utf8_strlen(msg, -1)) <= max)
[9997691]198                return TRUE;
[5983eca]199
200        imcb_error(ic, "Maximum message length exceeded: %d > %d", len, max);
201
[9997691]202        return FALSE;
203}
204
[5983eca]205static void twitter_init(account_t * acc)
[1b221e0]206{
[62d2cfb]207        set_t *s;
[ffcdf13]208        char *def_url;
209        char *def_oauth;
[5983eca]210
211        if (strcmp(acc->prpl->name, "twitter") == 0) {
[ffcdf13]212                def_url = TWITTER_API_URL;
213                def_oauth = "true";
[5983eca]214        } else {                /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */
215
[ffcdf13]216                def_url = IDENTICA_API_URL;
217                def_oauth = "false";
218        }
[5983eca]219
220        s = set_add(&acc->set, "auto_reply_timeout", "10800", set_eval_int, acc);
221
222        s = set_add(&acc->set, "base_url", def_url, NULL, acc);
[bb5ce4d1]223        s->flags |= ACC_SET_OFFLINE_ONLY;
[5983eca]224
225        s = set_add(&acc->set, "commands", "true", set_eval_bool, acc);
226
227        s = set_add(&acc->set, "message_length", "140", set_eval_int, acc);
228
229        s = set_add(&acc->set, "mode", "chat", set_eval_mode, acc);
[2abceca]230        s->flags |= ACC_SET_OFFLINE_ONLY;
[5983eca]231
232        s = set_add(&acc->set, "show_ids", "false", set_eval_bool, acc);
[ce81acd]233        s->flags |= ACC_SET_OFFLINE_ONLY;
[5983eca]234
235        s = set_add(&acc->set, "oauth", def_oauth, set_eval_bool, acc);
[1b221e0]236}
237
238/**
239 * Login method. Since the twitter API works with seperate HTTP request we
240 * only save the user and pass to the twitter_data object.
241 */
[5983eca]242static void twitter_login(account_t * acc)
[1b221e0]243{
[5983eca]244        struct im_connection *ic = imcb_new(acc);
[bb5ce4d1]245        struct twitter_data *td;
[5983eca]246        char name[strlen(acc->user) + 9];
[bb5ce4d1]247        url_t url;
[62d2cfb]248
[5983eca]249        if (!url_set(&url, set_getstr(&ic->acc->set, "base_url")) ||
250            (url.proto != PROTO_HTTP && url.proto != PROTO_HTTPS)) {
251                imcb_error(ic, "Incorrect API base URL: %s", set_getstr(&ic->acc->set, "base_url"));
252                imc_logout(ic, FALSE);
[bb5ce4d1]253                return;
254        }
[5983eca]255
256        twitter_connections = g_slist_append(twitter_connections, ic);
257        td = g_new0(struct twitter_data, 1);
[713d611]258        ic->proto_data = td;
[5983eca]259
[bb5ce4d1]260        td->url_ssl = url.proto == PROTO_HTTPS;
261        td->url_port = url.port;
[5983eca]262        td->url_host = g_strdup(url.host);
263        if (strcmp(url.file, "/") != 0)
264                td->url_path = g_strdup(url.file);
[bb5ce4d1]265        else
[5983eca]266                td->url_path = g_strdup("");
267        if (g_str_has_suffix(url.host, ".com"))
268                td->prefix = g_strndup(url.host, strlen(url.host) - 4);
[ffcdf13]269        else
[5983eca]270                td->prefix = g_strdup(url.host);
271
[27ad50b]272        td->flags |= TWITTER_HAVE_FRIENDS;
[1b221e0]273        td->user = acc->user;
[5983eca]274        if (strstr(acc->pass, "oauth_token="))
275                td->oauth_info = oauth_from_string(acc->pass, get_oauth_service(ic));
276
277        sprintf(name, "%s_%s", td->prefix, acc->user);
278        imcb_add_buddy(ic, name, NULL);
279        imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL);
280
281        if (set_getbool(&acc->set, "show_ids"))
282                td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH);
283
284        imcb_log(ic, "Connecting");
285
286        twitter_login_finish(ic);
[1b221e0]287}
288
289/**
290 * Logout method. Just free the twitter_data.
291 */
[5983eca]292static void twitter_logout(struct im_connection *ic)
[1b221e0]293{
294        struct twitter_data *td = ic->proto_data;
[5983eca]295
[1b221e0]296        // Set the status to logged out.
[5983eca]297        ic->flags &= ~OPT_LOGGED_IN;
[1b221e0]298
[2abceca]299        // Remove the main_loop function from the function queue.
300        b_event_remove(td->main_loop_id);
301
[5983eca]302        if (td->home_timeline_gc)
[37d84b3]303                imcb_chat_free(td->home_timeline_gc);
[1014cab]304
[5983eca]305        if (td) {
306                oauth_info_free(td->oauth_info);
307                g_free(td->prefix);
308                g_free(td->url_host);
309                g_free(td->url_path);
310                g_free(td->pass);
311                g_free(td->log);
312                g_free(td);
[1b221e0]313        }
[62d2cfb]314
[5983eca]315        twitter_connections = g_slist_remove(twitter_connections, ic);
[1b221e0]316}
317
[5983eca]318static void twitter_handle_command(struct im_connection *ic, char *message);
[7b87539]319
[1b221e0]320/**
321 *
322 */
[5983eca]323static int twitter_buddy_msg(struct im_connection *ic, char *who, char *message, int away)
[1b221e0]324{
[c42e8b9]325        struct twitter_data *td = ic->proto_data;
[5983eca]326        int plen = strlen(td->prefix);
327
[ffcdf13]328        if (g_strncasecmp(who, td->prefix, plen) == 0 && who[plen] == '_' &&
[5983eca]329            g_strcasecmp(who + plen + 1, ic->acc->user) == 0) {
330                if (set_getbool(&ic->acc->set, "oauth") &&
331                    td->oauth_info && td->oauth_info->token == NULL) {
332                        char pin[strlen(message) + 1], *s;
333
334                        strcpy(pin, message);
335                        for (s = pin + sizeof(pin) - 2; s > pin && isspace(*s); s--)
[64f8c425]336                                *s = '\0';
[5983eca]337                        for (s = pin; *s && isspace(*s); s++) {
338                        }
339
340                        if (!oauth_access_token(s, td->oauth_info)) {
341                                imcb_error(ic, "OAuth error: %s",
342                                           "Failed to send access token request");
343                                imc_logout(ic, TRUE);
[c2ecadc]344                                return FALSE;
345                        }
[5983eca]346                } else
[7b87539]347                        twitter_handle_command(ic, message);
[5983eca]348        } else {
[e88fbe27]349                twitter_direct_messages_new(ic, who, message);
[c42e8b9]350        }
[5983eca]351        return (0);
[1b221e0]352}
353
354/**
355 *
356 */
[5983eca]357static void twitter_set_my_name(struct im_connection *ic, char *info)
[1b221e0]358{
359}
360
[5983eca]361static void twitter_get_info(struct im_connection *ic, char *who)
[1b221e0]362{
363}
364
[5983eca]365static void twitter_add_buddy(struct im_connection *ic, char *who, char *group)
[1b221e0]366{
[7d53efb]367        twitter_friendships_create_destroy(ic, who, 1);
[1b221e0]368}
369
[5983eca]370static void twitter_remove_buddy(struct im_connection *ic, char *who, char *group)
[1b221e0]371{
[7d53efb]372        twitter_friendships_create_destroy(ic, who, 0);
[1b221e0]373}
374
[5983eca]375static void twitter_chat_msg(struct groupchat *c, char *message, int flags)
[1b221e0]376{
[5983eca]377        if (c && message)
378                twitter_handle_command(c->ic, message);
[1b221e0]379}
380
[5983eca]381static void twitter_chat_invite(struct groupchat *c, char *who, char *message)
[1b221e0]382{
383}
384
[5983eca]385static void twitter_chat_leave(struct groupchat *c)
[1b221e0]386{
[16592d8]387        struct twitter_data *td = c->ic->proto_data;
[5983eca]388
389        if (c != td->home_timeline_gc)
390                return;         /* WTF? */
391
[16592d8]392        /* If the user leaves the channel: Fine. Rejoin him/her once new
393           tweets come in. */
394        imcb_chat_free(td->home_timeline_gc);
395        td->home_timeline_gc = NULL;
[1b221e0]396}
397
[5983eca]398static void twitter_keepalive(struct im_connection *ic)
[1b221e0]399{
400}
401
[5983eca]402static void twitter_add_permit(struct im_connection *ic, char *who)
[1b221e0]403{
404}
405
[5983eca]406static void twitter_rem_permit(struct im_connection *ic, char *who)
[1b221e0]407{
408}
409
[5983eca]410static void twitter_add_deny(struct im_connection *ic, char *who)
[1b221e0]411{
412}
413
[5983eca]414static void twitter_rem_deny(struct im_connection *ic, char *who)
[1b221e0]415{
416}
417
418//static char *twitter_set_display_name( set_t *set, char *value )
419//{
[5983eca]420//      return value;
[1b221e0]421//}
[7b87539]422
[5983eca]423static void twitter_buddy_data_add(struct bee_user *bu)
[203a2d2]424{
[5983eca]425        bu->data = g_new0(struct twitter_user_data, 1);
[203a2d2]426}
427
[5983eca]428static void twitter_buddy_data_free(struct bee_user *bu)
[203a2d2]429{
[5983eca]430        g_free(bu->data);
[203a2d2]431}
432
[5983eca]433static void twitter_handle_command(struct im_connection *ic, char *message)
[7b87539]434{
435        struct twitter_data *td = ic->proto_data;
[15bc063]436        char *cmds, **cmd, *new = NULL;
437        guint64 in_reply_to = 0;
[5983eca]438
439        cmds = g_strdup(message);
440        cmd = split_command_parts(cmds);
441
442        if (cmd[0] == NULL) {
443                g_free(cmds);
[7b87539]444                return;
[5983eca]445        } else if (!set_getbool(&ic->acc->set, "commands")) {
[7b87539]446                /* Not supporting commands. */
[5983eca]447        } else if (g_strcasecmp(cmd[0], "undo") == 0) {
[7b87539]448                guint64 id;
[5983eca]449
450                if (cmd[1])
451                        id = g_ascii_strtoull(cmd[1], NULL, 10);
[7b87539]452                else
453                        id = td->last_status_id;
[5983eca]454
[7b87539]455                /* TODO: User feedback. */
[5983eca]456                if (id)
457                        twitter_status_destroy(ic, id);
[665c24f]458                else
[5983eca]459                        twitter_msg(ic, "Could not undo last action");
460
461                g_free(cmds);
[7b87539]462                return;
[5983eca]463        } else if (g_strcasecmp(cmd[0], "follow") == 0 && cmd[1]) {
464                twitter_add_buddy(ic, cmd[1], NULL);
465                g_free(cmds);
[b890626]466                return;
[5983eca]467        } else if (g_strcasecmp(cmd[0], "unfollow") == 0 && cmd[1]) {
468                twitter_remove_buddy(ic, cmd[1], NULL);
469                g_free(cmds);
[b890626]470                return;
[5983eca]471        } else if (g_strcasecmp(cmd[0], "rt") == 0 && cmd[1]) {
[b890626]472                struct twitter_user_data *tud;
473                bee_user_t *bu;
474                guint64 id;
[5983eca]475
476                if ((bu = bee_user_by_handle(ic->bee, ic, cmd[1])) &&
477                    (tud = bu->data) && tud->last_id)
[b890626]478                        id = tud->last_id;
[5983eca]479                else {
480                        id = g_ascii_strtoull(cmd[1], NULL, 10);
481                        if (id < TWITTER_LOG_LENGTH && td->log)
[4f50ea5]482                                id = td->log[id].id;
483                }
[5983eca]484
[b890626]485                td->last_status_id = 0;
[5983eca]486                if (id)
487                        twitter_status_retweet(ic, id);
[665c24f]488                else
[5983eca]489                        twitter_msg(ic, "User `%s' does not exist or didn't "
490                                    "post any statuses recently", cmd[1]);
491
492                g_free(cmds);
[b890626]493                return;
[5983eca]494        } else if (g_strcasecmp(cmd[0], "reply") == 0 && cmd[1] && cmd[2]) {
[15bc063]495                struct twitter_user_data *tud;
496                bee_user_t *bu = NULL;
497                guint64 id = 0;
[5983eca]498
499                if ((bu = bee_user_by_handle(ic->bee, ic, cmd[1])) &&
500                    (tud = bu->data) && tud->last_id) {
[15bc063]501                        id = tud->last_id;
[5983eca]502                } else if ((id = g_ascii_strtoull(cmd[1], NULL, 10)) &&
503                           (id < TWITTER_LOG_LENGTH) && td->log) {
[15bc063]504                        bu = td->log[id].bu;
[5983eca]505                        if (g_slist_find(ic->bee->users, bu))
[15bc063]506                                id = td->log[id].id;
507                        else
508                                bu = NULL;
509                }
[5983eca]510                if (!id || !bu) {
511                        twitter_msg(ic, "User `%s' does not exist or didn't "
512                                    "post any statuses recently", cmd[1]);
[15bc063]513                        return;
514                }
[5983eca]515                message = new = g_strdup_printf("@%s %s", bu->handle, message + (cmd[2] - cmd[0]));
[15bc063]516                in_reply_to = id;
[5983eca]517        } else if (g_strcasecmp(cmd[0], "post") == 0) {
[7b87539]518                message += 5;
519        }
[5983eca]520
[7b87539]521        {
[15bc063]522                char *s;
[7b87539]523                bee_user_t *bu;
[5983eca]524
525                if (!twitter_length_check(ic, message)) {
526                        g_free(new);
527                        g_free(cmds);
528                        return;
[7b87539]529                }
[5983eca]530
531                s = cmd[0] + strlen(cmd[0]) - 1;
532                if (!new && s > cmd[0] && (*s == ':' || *s == ',')) {
[7b87539]533                        *s = '\0';
[5983eca]534
535                        if ((bu = bee_user_by_handle(ic->bee, ic, cmd[0]))) {
[b890626]536                                struct twitter_user_data *tud = bu->data;
[5983eca]537
538                                new = g_strdup_printf("@%s %s", bu->handle,
539                                                      message + (s - cmd[0]) + 2);
[7b87539]540                                message = new;
[5983eca]541
542                                if (time(NULL) < tud->last_time +
543                                    set_getint(&ic->acc->set, "auto_reply_timeout"))
[b890626]544                                        in_reply_to = tud->last_id;
[7b87539]545                        }
546                }
[5983eca]547
[b890626]548                /* If the user runs undo between this request and its response
549                   this would delete the second-last Tweet. Prevent that. */
550                td->last_status_id = 0;
[5983eca]551                twitter_post_status(ic, message, in_reply_to);
552                g_free(new);
[7b87539]553        }
[5983eca]554        g_free(cmds);
[7b87539]555}
[1b221e0]556
557void twitter_initmodule()
558{
559        struct prpl *ret = g_new0(struct prpl, 1);
[5983eca]560
[1dd3470]561        ret->options = OPT_NOOTR;
[1b221e0]562        ret->name = "twitter";
563        ret->login = twitter_login;
564        ret->init = twitter_init;
565        ret->logout = twitter_logout;
566        ret->buddy_msg = twitter_buddy_msg;
567        ret->get_info = twitter_get_info;
568        ret->set_my_name = twitter_set_my_name;
569        ret->add_buddy = twitter_add_buddy;
570        ret->remove_buddy = twitter_remove_buddy;
571        ret->chat_msg = twitter_chat_msg;
572        ret->chat_invite = twitter_chat_invite;
573        ret->chat_leave = twitter_chat_leave;
574        ret->keepalive = twitter_keepalive;
575        ret->add_permit = twitter_add_permit;
576        ret->rem_permit = twitter_rem_permit;
577        ret->add_deny = twitter_add_deny;
578        ret->rem_deny = twitter_rem_deny;
[203a2d2]579        ret->buddy_data_add = twitter_buddy_data_add;
580        ret->buddy_data_free = twitter_buddy_data_free;
[1b221e0]581        ret->handle_cmp = g_strcasecmp;
[5983eca]582
[ffcdf13]583        register_protocol(ret);
[1b221e0]584
[ffcdf13]585        /* And an identi.ca variant: */
586        ret = g_memdup(ret, sizeof(struct prpl));
587        ret->name = "identica";
[1b221e0]588        register_protocol(ret);
589}
Note: See TracBrowser for help on using the repository browser.