Home timeline and basic status update fully working
[mokosuite2:micromoko.git] / src / twitter / twitter.c
1 /*
2  * Micromoko Twitter library
3  * Library entry point
4  * Copyright (C) 2009-2010 Daniele Ricci <daniele.athome@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include <mokosuite/utils/utils.h>
22 #include <mokosuite/utils/remote-config-service.h>
23 #include <mokosuite/utils/dbus.h>
24 #include <glib.h>
25 #include <rest/rest-proxy.h>
26 #include <rest/oauth-proxy.h>
27 #include <rest/rest-xml-parser.h>
28 #include <stdarg.h>
29
30 #include "twitter.h"
31 #include "private.h"
32
33 // default log domain
34 int _micromoko_twitter_utils_log_dom = -1;
35
36 // global
37 RemoteConfigService* config = NULL;
38
39 static void _status_update(twitter_session* session, twitter_call* call, const char* payload, goffset length, void* userdata)
40 {
41     callback_pack* data = userdata;
42
43     DEBUG("got status update response! (%llu bytes)", length);
44
45     twitter_status* e = NULL;
46     RestXmlParser* parse = rest_xml_parser_new();
47     RestXmlNode* root = rest_xml_parser_parse_from_data(parse, payload, length);
48     if (!root || strcmp(root->name, "status")) {
49         DEBUG("%s", payload);
50         WARN("no status data!!");
51     }
52     else {
53         e = twitter_parse_status(root);
54     }
55
56     // user callback
57     TwitterStatusUpdateCallback cb = data->callback;
58     if (cb)
59         (cb)(session, call, e, data->userdata);
60     free(data);
61 }
62
63 /**
64  * Update the status.
65  * @param session
66  * @param status
67  * @param reply_to_id
68  * @param callback
69  * @param userdata
70  * @return the call on success
71  */
72 twitter_call* twitter_update_status(twitter_session* session, const char* text, const char* reply_to_id, TwitterStatusUpdateCallback callback, void* userdata)
73 {
74     callback_pack* data = malloc(sizeof(callback_pack));
75     data->callback = callback;
76     data->userdata = userdata;
77
78     return twitter_session_call_new(session, "statuses/update", "POST", _status_update, data, "status", text, "in_reply_to_status_id", reply_to_id, NULL);
79 }
80
81
82 static void oauth_token_copy(oauth_token* dest, oauth_token* src)
83 {
84     if (dest == NULL || src == NULL) return;
85
86     dest->key = strdup(src->key);
87     dest->secret = strdup(src->secret);
88 }
89
90 static void oauth_token_free_keys(oauth_token* token)
91 {
92     free(token->key);
93     free(token->secret);
94 }
95
96 static void oauth_proxy(twitter_session* session, const char* key, const char* secret)
97 {
98     if (!session->oauth_proxy) {
99         if (key != NULL && secret != NULL)
100             session->oauth_proxy = oauth_proxy_new_with_token(session->consumer.key, session->consumer.secret, key, secret, TWITTER_BASE_URI, FALSE);
101         else
102             session->oauth_proxy = oauth_proxy_new(session->consumer.key, session->consumer.secret, TWITTER_BASE_URI, FALSE);
103     }
104 }
105
106 static RestProxyCall* call_proxy(twitter_session* session, const char* function, const char* method)
107 {
108     char* req = g_strdup_printf("%s.%s", function, DEFAULT_CALL_TYPE);
109     DEBUG("creating call for request \"%s\" (oauth_proxy=%p)", req, session->oauth_proxy);
110     RestProxyCall* call = rest_proxy_new_call(session->oauth_proxy);
111     rest_proxy_call_set_function(call, req);
112
113     rest_proxy_call_set_method(call, method);
114
115     g_free(req);
116     return call;
117 }
118
119 static void _access_token(OAuthProxy* proxy, GError* error, GObject* weak_object, void* userdata)
120 {
121     DEBUG("got access token %s", oauth_proxy_get_token(proxy));
122     twitter_session* sess = userdata;
123     sess->access.key = strdup(oauth_proxy_get_token(proxy));
124     sess->access.secret = strdup(oauth_proxy_get_token_secret(proxy));
125
126     // user callback
127     OAuthTokenCallback cb = sess->access_token_cb;
128     if (cb)
129         (cb)(sess, sess->access_token_data);
130 }
131
132 static void _request_token(OAuthProxy* proxy, GError* error, GObject* weak_object, void* userdata)
133 {
134     DEBUG("got request token %s", oauth_proxy_get_token(proxy));
135     twitter_session* sess = userdata;
136     sess->request.key = strdup(oauth_proxy_get_token(proxy));
137     sess->request.secret = strdup(oauth_proxy_get_token_secret(proxy));
138
139     // user callback
140     OAuthTokenCallback cb = sess->request_token_cb;
141     if (cb)
142         (cb)(sess, sess->request_token_data);
143 }
144
145 static void _call_response(RestProxyCall *call_proxy, GError* error, GObject* weak_object, void* userdata)
146 {
147     twitter_call* call = userdata;
148
149     const char* payload = rest_proxy_call_get_payload(call->call_proxy);
150     goffset len = rest_proxy_call_get_payload_length(call->call_proxy);
151     //DEBUG("response:\n%s", payload);
152
153     TwitterCallCallback cb = call->callback;
154     if (cb)
155         (cb)(call->session, call, payload, len, call->userdata);
156
157     // destroy call
158     twitter_call_destroy(call);
159 }
160
161 /**
162  * Destroys a Twitter command call.
163  * @param call
164  */
165 void twitter_call_destroy(twitter_call* call)
166 {
167     free(call->function);
168     free(call->method);
169     g_object_unref(call->call_proxy);
170     free(call);
171 }
172
173 /**
174  * Creates a new Twitter command request and immediately starts it.
175  * @param session
176  * @param function
177  * @param method
178  * @param ... arguments
179  * @return the new call
180  */
181 twitter_call* twitter_session_call_new(twitter_session* session,
182             const char* function, const char* method,
183             TwitterCallCallback callback, void* userdata, ...)
184 {
185     twitter_call* call = calloc(1, sizeof(twitter_call));
186     va_list ap;
187     int i = 0;
188     const char* arg;
189     const char* arg_name = NULL;
190
191     call->session = session;
192     call->function = strdup(function);
193     call->method = strdup(method);
194     call->callback = callback;
195     call->userdata = userdata;
196     call->call_proxy = call_proxy(session, function, method);
197
198     va_start(ap, userdata);
199     while ((arg = va_arg(ap, const char*))) {
200         if (i % 2) {
201             DEBUG("argument: name=\"%s\", value=\"%s\"", arg_name, arg);
202             rest_proxy_call_add_param(call->call_proxy, arg_name, arg);
203         }
204         else
205             arg_name = arg;
206
207         i++;
208     }
209     va_end(ap);
210
211     rest_proxy_call_async (call->call_proxy, _call_response, NULL, call, NULL);
212     return call;
213 }
214
215 /**
216  * Requests an OAuth access token to Twitter.
217  * @param session
218  * @param callback callback to be called when the access token has been requested
219  * @return TRUE on success
220  */
221 bool twitter_session_oauth_access_token(twitter_session* session, const char* pin, OAuthTokenCallback callback, void* userdata)
222 {
223     session->access_token_cb = callback;
224     session->access_token_data = userdata;
225
226     oauth_proxy(session, NULL, NULL);
227     return oauth_proxy_access_token_async(OAUTH_PROXY(session->oauth_proxy),
228         TWITTER_ACCESS_TOKEN_FUNC, pin, _access_token, NULL, session, NULL);
229 }
230
231 /**
232  * Requests an OAuth request token to Twitter.
233  * @param session
234  * @param callback callback to be called when the request token has been requested
235  * @return TRUE on success
236  */
237 bool twitter_session_oauth_request_token(twitter_session* session, OAuthTokenCallback callback, void* userdata)
238 {
239     session->request_token_cb = callback;
240     session->request_token_data = userdata;
241
242     oauth_proxy(session, NULL, NULL);
243     return oauth_proxy_request_token_async(OAUTH_PROXY(session->oauth_proxy),
244         TWITTER_REQUEST_TOKEN_FUNC, "oob", _request_token, NULL, session, NULL);
245 }
246
247 /**
248  * Sets an access token for this session.
249  * @param session
250  * @param access
251  */
252 void twitter_session_set_access_token(twitter_session* session, oauth_token* access)
253 {
254     oauth_token_copy(&session->access, access);
255     if (session->oauth_proxy) {
256         oauth_proxy_set_token(OAUTH_PROXY(session->oauth_proxy), access->key);
257         oauth_proxy_set_token_secret(OAUTH_PROXY(session->oauth_proxy), access->secret);
258     }
259     else {
260         oauth_proxy(session, access->key, access->secret);
261     }
262 }
263
264 void twitter_session_destroy(twitter_session* session)
265 {
266     free(session->username);
267     oauth_token_free_keys(&session->consumer);
268     oauth_token_free_keys(&session->request);
269     oauth_token_free_keys(&session->access);
270
271     g_object_unref(session->oauth_proxy);
272 }
273
274 /**
275  * Creates a new Twitter session.
276  * @param consumer API consumer token
277  * @return a new session ready for use
278  */
279 twitter_session* twitter_session_new(oauth_token* consumer)
280 {
281     twitter_session* s = calloc(1, sizeof(twitter_session));
282     s->consumer.key = strdup(consumer->key);
283     s->consumer.secret = strdup(consumer->secret);
284
285     return s;
286 }
287
288 void twitter_init(RemoteConfigService* config)
289 {
290     // initialize log
291     _micromoko_twitter_utils_log_dom = eina_log_domain_register(MICROMOKO_TWITTER_LOG_NAME, MICROMOKO_TWITTER_LOG_COLOR);
292     if (_micromoko_twitter_utils_log_dom < 0)
293         printf("Cannot create log domain.\n");
294
295     eina_log_domain_level_set(MICROMOKO_TWITTER_LOG_NAME, TWITTER_LOG_LEVEL);
296     INFO("%s Twitter library version %s", PACKAGE_NAME, VERSION);
297 }