aboutsummaryrefslogtreecommitdiffstats
path: root/protocols/ft.h
blob: 159f16f2ecd9c056413e5c7e31fa7985095f888d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/********************************************************************\
* BitlBee -- An IRC to other IM-networks gateway                     *
*                                                                    *
* Copyright 2006 Marijn Kruisselbrink and others                     *
\********************************************************************/

/* Generic file transfer header                                     */

/*
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License with
  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
  Suite 330, Boston, MA  02111-1307  USA
*/

#ifndef _FT_H
#define _FT_H

/*
 * One buffer is needed for each transfer. The receiver stores a message
 * in it and gives it to the sender. The sender will stall the receiver
 * till the buffer has been sent out.
 */
#define FT_BUFFER_SIZE 2048

typedef enum {
	FT_STATUS_LISTENING	= 1,
	FT_STATUS_TRANSFERRING	= 2,
	FT_STATUS_FINISHED	= 4,
	FT_STATUS_CANCELED	= 8,
	FT_STATUS_CONNECTING	= 16
} file_status_t;

/*
 * This structure holds all irc specific information regarding an incoming (from the point of view of
 * the irc client) file transfer. New instances of this struct should only be created by calling the
 * imcb_file_send_start() method, which will initialize most of the fields. The data field and the various
 * methods are zero-initialized. Instances will automatically be deleted once the transfer is completed,
 * canceled, or the connection to the irc client has been lost (note that also if only the irc connection
 * and not the file transfer connection is lost, the file transfer will still be canceled and freed).
 *
 * The following (poor ascii-art) diagram illustrates what methods are called for which status-changes:
 *
 *	                        /-----------\                    /----------\
 *	               -------> | LISTENING | -----------------> | CANCELED |
 *	                        \-----------/  [canceled,]free   \----------/
 *	                              |
 *	                              | accept
 *	                              V
 *	               /------ /-------------\                    /------------------------\
 *	   out_of_data |       | TRANSFERING | -----------------> | TRANSFERING | CANCELED |
 *	               \-----> \-------------/  [canceled,]free   \------------------------/
 *	                              |
 *	                              | finished,free
 *	                              V
 *	                 /------------------------\
 *	                 | TRANSFERING | FINISHED |
 *	                 \------------------------/
 */
typedef struct file_transfer {

	/* Are we sending something? */
	int sending;

	/*
	 * The current status of this file transfer.
	 */ 
	file_status_t status;
	
	/*
	 * file size
	 */
	size_t file_size;
	
	/*
	 * Number of bytes that have been successfully transferred.
	 */
	size_t bytes_transferred;

	/*
	 * Time started. Used to calculate kb/s.
	 */
	time_t started;

	/*
	 * file name
	 */
	char *file_name;

	/*
	 * A unique local ID for this file transfer.
	 */
	unsigned int local_id;

	/*
	 * IM-protocol specific data associated with this file transfer.
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/***************************************************************************\
*                                                                           *
*  BitlBee - An IRC to IM gateway                                           *
*  Simple module to facilitate twitter functionality.                       *
*                                                                           *
*  Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com>                 *
*                                                                           *
*  This library is free software; you can redistribute it and/or            *
*  modify it under the terms of the GNU Lesser General Public               *
*  License as published by the Free Software Foundation, version            *
*  2.1.                                                                     *
*                                                                           *
*  This library is distributed in the hope that it will be useful,          *
*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU        *
*  Lesser General Public License for more details.                          *
*                                                                           *
*  You should have received a copy of the GNU Lesser General Public License *
*  along with this library; if not, write to the Free Software Foundation,  *
*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA           *
*                                                                           *
****************************************************************************/

#include "nogaim.h"
#include "oauth.h"
#include "twitter.h"
#include "twitter_http.h"
#include "twitter_lib.h"
#include "url.h"

/**
 * Main loop function
 */
gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond)
{
	struct im_connection *ic = data;
	
twitter_connections, ic )) return 0; // Do stuff.. twitter_get_home_timeline(ic, -1); // If we are still logged in run this function again after timeout. return (ic->flags & OPT_LOGGED_IN) == OPT_LOGGED_IN; } static void twitter_main_loop_start( struct im_connection *ic ) { struct twitter_data *td = ic->proto_data; imcb_log( ic, "Getting initial statuses" ); // Run this once. After this queue the main loop function. twitter_main_loop(ic, -1, 0); // Queue the main_loop // Save the return value, so we can remove the timeout on logout. td->main_loop_id = b_timeout_add(60000, twitter_main_loop, ic); } static void twitter_oauth_start( struct im_connection *ic ); void twitter_login_finish( struct im_connection *ic ) { struct twitter_data *td = ic->proto_data; 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 ) ) { imcb_log( ic, "Getting contact list" ); twitter_get_statuses_friends( ic, -1 ); } else twitter_main_loop_start( ic ); } static const struct oauth_service twitter_oauth = { "http://api.twitter.com/oauth/request_token", "http://api.twitter.com/oauth/access_token", "https://api.twitter.com/oauth/authorize", .consumer_key = "xsDNKJuNZYkZyMcu914uEA", .consumer_secret = "FCxqcr0pXKzsF9ajmP57S3VQ8V6Drk4o2QYtqMcOszo", }; static gboolean twitter_oauth_callback( struct oauth_info *info ); static void twitter_oauth_start( struct im_connection *ic ) { struct twitter_data *td = ic->proto_data; imcb_log( ic, "Requesting OAuth request token" ); td->oauth_info = oauth_request_token( &twitter_oauth, twitter_oauth_callback, ic ); } static gboolean twitter_oauth_callback( struct oauth_info *info ) { struct im_connection *ic = info->data; struct twitter_data *td; if( !g_slist_find( twitter_connections, ic ) ) return FALSE; td = ic->proto_data; if( info->stage == OAUTH_REQUEST_TOKEN ) { char name[strlen(ic->acc->user)+9], *msg; if( info->request_token == NULL ) { imcb_error( ic, "OAuth error: %s", info->http->status_string ); imc_logout( ic, TRUE ); return FALSE; } sprintf( name, "%s_%s", td->prefix, ic->acc->user ); msg = g_strdup_printf( "To finish OAuth authentication, please visit " "%s and respond with the resulting PIN code.", info->auth_url ); imcb_buddy_msg( ic, name, msg, 0, 0 ); g_free( msg ); } else if( info->stage == OAUTH_ACCESS_TOKEN ) { if( info->token == NULL || info->token_secret == NULL ) { imcb_error( ic, "OAuth error: %s", info->http->status_string ); imc_logout( ic, TRUE ); return FALSE; } /* IM mods didn't do this so far and it's ugly but I should be able to get away with it... */ g_free( ic->acc->pass ); ic->acc->pass = oauth_to_string( info ); twitter_login_finish( ic ); } 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; } static gboolean twitter_length_check( struct im_connection *ic, gchar *msg ) { int max = set_getint( &ic->acc->set, "message_length" ), len; if( max == 0 || ( len = g_utf8_strlen( msg, -1 ) ) <= max ) return TRUE; imcb_error( ic, "Maximum message length exceeded: %d > %d", len, max ); return FALSE; } static void twitter_init( account_t *acc ) { set_t *s; char *def_url; char *def_oauth; if( strcmp( acc->prpl->name, "twitter" ) == 0 ) { def_url = TWITTER_API_URL; def_oauth = "true"; } else /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */ { def_url = IDENTICA_API_URL; def_oauth = "false"; } s = set_add( &acc->set, "auto_reply_timeout", "10800", set_eval_int, acc ); s = set_add( &acc->set, "base_url", def_url, NULL, acc ); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add( &acc->set, "commands", "true", set_eval_bool, acc ); s = set_add( &acc->set, "message_length", "140", set_eval_int, acc ); s = set_add( &acc->set, "mode", "chat", set_eval_mode, acc ); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add( &acc->set, "oauth", def_oauth, set_eval_bool, acc ); } /** * Login method. Since the twitter API works with seperate HTTP request we * only save the user and pass to the twitter_data object. */ static void twitter_login( account_t *acc ) { struct im_connection *ic = imcb_new( acc ); struct twitter_data *td; char name[strlen(acc->user)+9]; url_t url; if( !url_set( &url, set_getstr( &ic->acc->set, "base_url" ) ) || ( url.proto != PROTO_HTTP && url.proto != PROTO_HTTPS ) ) { imcb_error( ic, "Incorrect API base URL: %s", set_getstr( &ic->acc->set, "base_url" ) ); imc_logout( ic, FALSE ); return; } twitter_connections = g_slist_append( twitter_connections, ic ); td = g_new0( struct twitter_data, 1 ); ic->proto_data = td; td->url_ssl = url.proto == PROTO_HTTPS; td->url_port = url.port; td->url_host = g_strdup( url.host ); if( strcmp( url.file, "/" ) != 0 ) td->url_path = g_strdup( url.file ); else td->url_path = g_strdup( "" ); if( g_str_has_suffix( url.host, ".com" ) ) td->prefix = g_strndup( url.host, strlen( url.host ) - 4 ); else td->prefix = g_strdup( url.host ); td->user = acc->user; if( strstr( acc->pass, "oauth_token=" ) ) td->oauth_info = oauth_from_string( acc->pass, &twitter_oauth ); sprintf( name, "%s_%s", td->prefix, acc->user ); imcb_add_buddy( ic, name, NULL ); imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL ); imcb_log( ic, "Connecting" ); twitter_login_finish( ic ); } /** * Logout method. Just free the twitter_data. */ static void twitter_logout( struct im_connection *ic ) { struct twitter_data *td = ic->proto_data; // Set the status to logged out. ic->flags &= ~ OPT_LOGGED_IN; // Remove the main_loop function from the function queue. b_event_remove(td->main_loop_id); if(td->home_timeline_gc) imcb_chat_free(td->home_timeline_gc); if( td ) { oauth_info_free( td->oauth_info ); g_free( td->prefix ); g_free( td->url_host ); g_free( td->url_path ); g_free( td->pass ); g_free( td ); } twitter_connections = g_slist_remove( twitter_connections, ic ); } static void twitter_handle_command( struct im_connection *ic, char *message ); /** * */ static int twitter_buddy_msg( struct im_connection *ic, char *who, char *message, int away ) { struct twitter_data *td = ic->proto_data; int plen = strlen( td->prefix ); if (g_strncasecmp(who, td->prefix, plen) == 0 && who[plen] == '_' && g_strcasecmp(who + plen + 1, ic->acc->user) == 0) { if( set_getbool( &ic->acc->set, "oauth" ) && td->oauth_info && td->oauth_info->token == NULL ) { char pin[strlen(message)+1], *s; strcpy( pin, message ); for( s = pin + sizeof( pin ) - 2; s > pin && isspace( *s ); s -- ) *s = '\0'; for( s = pin; *s && isspace( *s ); s ++ ) {} if( !oauth_access_token( s, td->oauth_info ) ) { imcb_error( ic, "OAuth error: %s", "Failed to send access token request" ); imc_logout( ic, TRUE ); return FALSE; } } else twitter_handle_command(ic, message); } else { twitter_direct_messages_new(ic, who, 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) { } static void twitter_add_buddy( struct im_connection *ic, char *who, char *group ) { twitter_friendships_create_destroy(ic, who, 1); } static void twitter_remove_buddy( struct im_connection *ic, char *who, char *group ) { twitter_friendships_create_destroy(ic, who, 0); } static void twitter_chat_msg( struct groupchat *c, char *message, int flags ) { if( c && message ) twitter_handle_command( c->ic, message ); } static void twitter_chat_invite( struct groupchat *c, char *who, char *message ) { } static void twitter_chat_leave( struct groupchat *c ) { struct twitter_data *td = c->ic->proto_data; if( c != td->home_timeline_gc ) return; /* WTF? */ /* If the user leaves the channel: Fine. Rejoin him/her once new tweets come in. */ imcb_chat_free(td->home_timeline_gc); td->home_timeline_gc = NULL; } static struct groupchat *twitter_chat_with( struct im_connection *ic, char *who ) { return NULL; } static void twitter_keepalive( struct im_connection *ic ) { } static void twitter_add_permit( struct im_connection *ic, char *who ) { } static void twitter_rem_permit( struct im_connection *ic, char *who ) { } static void twitter_add_deny( struct im_connection *ic, char *who ) { } static void twitter_rem_deny( struct im_connection *ic, char *who ) { } static int twitter_send_typing( struct im_connection *ic, char *who, int typing ) { return( 1 ); } //static char *twitter_set_display_name( set_t *set, char *value ) //{ // return value; //} static void twitter_buddy_data_add( struct bee_user *bu ) { bu->data = g_new0( struct twitter_user_data, 1 ); } static void twitter_buddy_data_free( struct bee_user *bu ) { g_free( bu->data ); } static void twitter_handle_command( struct im_connection *ic, char *message ) { struct twitter_data *td = ic->proto_data; char *cmds, **cmd; cmds = g_strdup( message ); cmd = split_command_parts( cmds ); if( cmd[0] == NULL ) { g_free( cmds ); return; } else if( !set_getbool( &ic->acc->set, "commands" ) ) { /* Not supporting commands. */ } else if( g_strcasecmp( cmd[0], "undo" ) == 0 ) { guint64 id; if( cmd[1] ) id = g_ascii_strtoull( cmd[1], NULL, 10 ); else id = td->last_status_id; /* TODO: User feedback. */ if( id ) twitter_status_destroy( ic, id ); g_free( cmds ); return; } else if( g_strcasecmp( cmd[0], "follow" ) == 0 && cmd[1] ) { twitter_add_buddy( ic, cmd[1], NULL ); g_free( cmds ); return; } else if( g_strcasecmp( cmd[0], "unfollow" ) == 0 && cmd[1] ) { twitter_remove_buddy( ic, cmd[1], NULL ); g_free( cmds ); return; } else if( g_strcasecmp( cmd[0], "rt" ) == 0 && cmd[1] ) { struct twitter_user_data *tud; bee_user_t *bu; guint64 id; if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[1] ) ) && ( tud = bu->data ) && tud->last_id ) id = tud->last_id; else id = g_ascii_strtoull( cmd[1], NULL, 10 ); td->last_status_id = 0; if( id ) twitter_status_retweet( ic, id ); g_free( cmds ); return; } else if( g_strcasecmp( cmd[0], "post" ) == 0 ) { message += 5; } { guint64 in_reply_to = 0; char *s, *new = NULL; bee_user_t *bu; if( !twitter_length_check( ic, message ) ) { g_free( cmds ); return; } s = cmd[0] + strlen( cmd[0] ) - 1; if( s > cmd[0] && ( *s == ':' || *s == ',' ) ) { *s = '\0'; if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[0] ) ) ) { struct twitter_user_data *tud = bu->data; new = g_strdup_printf( "@%s %s", bu->handle, message + ( s - cmd[0] ) + 2 ); message = new; if( time( NULL ) < tud->last_time + set_getint( &ic->acc->set, "auto_reply_timeout" ) ) in_reply_to = tud->last_id; } } /* If the user runs undo between this request and its response this would delete the second-last Tweet. Prevent that. */ td->last_status_id = 0; twitter_post_status( ic, message, in_reply_to ); g_free( new ); } g_free( cmds ); } void twitter_initmodule() { struct prpl *ret = g_new0(struct prpl, 1); ret->options = OPT_NOOTR; ret->name = "twitter"; ret->login = twitter_login; ret->init = twitter_init; 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; ret->chat_invite = twitter_chat_invite; ret->chat_leave = twitter_chat_leave; ret->chat_with = twitter_chat_with; ret->keepalive = twitter_keepalive; ret->add_permit = twitter_add_permit; ret->rem_permit = twitter_rem_permit; ret->add_deny = twitter_add_deny; ret->rem_deny = twitter_rem_deny; ret->send_typing = twitter_send_typing; ret->buddy_data_add = twitter_buddy_data_add; ret->buddy_data_free = twitter_buddy_data_free; ret->handle_cmp = g_strcasecmp; register_protocol(ret); /* And an identi.ca variant: */ ret = g_memdup(ret, sizeof(struct prpl)); ret->name = "identica"; register_protocol(ret); // Initialise the twitter_connections GSList. twitter_connections = NULL; }