Merge commit 'upstream/0.2.5'
[debian/pino.git] / src / rest_api_abstract.vala
1 /* rest_api_abstract.vala
2  *
3  * Copyright (C) 2009-2010  troorl
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Author:
19  *      troorl <troorl@gmail.com>
20  */
21
22 using Gee;
23 using Soup;
24 using Auth;
25 using TimeUtils;
26 using Xml;
27
28 namespace RestAPI {
29
30 public class Status : Object {
31
32         public string id;
33         public string text;
34         public Time created_at = Time();
35         public string created_at_s = "";
36         public string user_name;
37         public string user_screen_name;
38         public string user_avatar;
39         
40         public string re_text = "";
41         public string re_user_name;
42         public string re_user_screen_name;
43         public string re_user_avatar;
44         
45         public string to_user = "";
46         public string to_status_id = "";
47         
48         public bool is_retweet = false;
49         public bool is_favorite = false;
50         
51         public bool tmp = false;
52 }
53
54 public class FullStatus : Status {
55         
56         public string followers = "";
57         public string friends = "";
58         public string statuses = "";
59         public string url = "";
60         public string desc = "";
61         public bool following = false;
62         
63         public bool done = false;
64         
65 }
66
67 public struct AuthData {
68         public string login;
69         public string password;
70         public string service;
71
72
73 public static enum ServiceType {
74         TWITTER,
75         IDENTICA,
76         UNKNOWN
77 }
78
79 errordomain RestError {
80         CODE,
81         CODE_404
82 }
83
84 errordomain ParseError {
85         CODE
86 }
87
88 public static enum TimelineType {
89         HOME,
90         MENTIONS,
91         USER,
92         FAVORITES
93 }
94
95 public abstract class RestAPIAbstract : Object {
96         
97         protected RestUrls urls;
98         public Account? account;
99         
100         private Session? session = null;
101         
102         public RestAPIAbstract(Account? _account) {
103                 urls = new RestUrls(ServiceType.UNKNOWN);
104                 set_auth(_account);
105         }
106         
107         private void select_urls() {
108                 switch(account.service) {
109                         case "twitter.com":
110                                 urls.set_prefix(ServiceType.TWITTER);
111                                 break;
112                         
113                         case "identi.ca":
114                                 urls.set_prefix(ServiceType.IDENTICA);
115                                 break;
116                         
117                         case "other":
118                                 string proxy = "http://api.twitter.com/";
119                                 if(account.proxy != "")
120                                         proxy = account.proxy;
121                                 
122                                 urls.set_prefix(ServiceType.UNKNOWN, proxy);
123                                 break;
124                         
125                         default:
126                                 urls.set_prefix(ServiceType.TWITTER);
127                                 break;
128                 }
129         }
130         
131         public void set_auth(Account? _account) {
132                 account = _account;
133                 
134                 if(account != null)
135                         select_urls();
136         }
137         
138         public signal void request(string req);
139         
140         public virtual ArrayList<Status>? get_timeline(int count = 0, FullStatus? fstatus = null,
141                 string since_id = "", string max_id = "", bool sync = true) throws RestError, ParseError {
142                 
143                 return null;
144         }
145         
146         public virtual Status get_status(string id) throws RestError, ParseError {
147                 return new Status();
148         }
149         
150         public virtual void favorite_create(string id) throws RestError {}
151         public virtual void favorite_destroy(string id) throws RestError {}
152         
153         public virtual void follow_create(string screen_name) throws RestError {}
154         public virtual void follow_destroy(string screen_name) throws RestError {}
155         
156         public virtual void destroy_status(string id) throws RestError {}
157         
158         protected void reply_tracking(int status_code) throws RestError {
159                 switch(status_code) {
160                         case 2:
161                                 throw new RestError.CODE("Connection problems: can't connect to the server.");
162                         
163                         case 400:
164                                 throw new RestError.CODE("%d Rate limiting: you have reached the limit requests.".printf(status_code));
165                         
166                         case 401:
167                                 throw new RestError.CODE("%d Unauthorized: the request requires user authentication.".printf(status_code));
168                         
169                         case 403:
170                                 throw new RestError.CODE("%d Forbidden: the server understood the request, but is refusing to fulfill it.".printf(status_code));
171                         
172                         case 404:
173                                 throw new RestError.CODE_404("%d Not Found: The server has not found anything matching the Request-URI.".printf(status_code));
174                         
175                         case 407:
176                                 throw new RestError.CODE("%d Proxy Authentication Required: the request requires user authentication.".printf(status_code));
177                         
178                         default:
179                                 throw new RestError.CODE("%d Unknown Error".printf(status_code));
180                 }
181         }
182         
183         protected void no_account() throws RestError {
184                 throw new RestError.CODE("Account is not found");
185         }
186         
187         public void stop() {
188                 if(session != null) {
189                         session.abort();
190                 }
191         }
192         
193         public string make_request(owned string req_url, string method,
194                 HashTable<string, string> params = new HashTable<string, string>(str_hash, str_equal),
195                 bool async = true, int retry = 3) throws RestError {
196                 
197                 if(account == null)
198                         no_account();
199                 
200                 if(method == "GET") { //set get-parameters
201                         string query = "";
202                         warning(params.size().to_string());
203                         if(params.size() > 0) {
204                                 query = "?";
205                                 
206                                 int tmp_iter = 0;
207                                 foreach(string key in params.get_keys()) {
208                                         query += Soup.form_encode(key, params.lookup(key));
209                                         
210                                         if(tmp_iter < params.size() - 1)
211                                                 query += "&";
212                                         
213                                         tmp_iter++;
214                                 }
215                         }
216                         req_url += query;
217                 }
218                 
219                 //send signal about all requests
220         request("%s: %s".printf(method, req_url));
221         
222         //Session session;
223         
224                 if(async)
225                         session = new SessionAsync();
226                 else
227                         session = new SessionSync();
228                 
229         Message message = new Message(method, req_url);
230         message.set_http_version(HTTPVersion.1_1);
231         
232         MessageHeaders headers = new MessageHeaders(MessageHeadersType.MULTIPART);
233         headers.append("User-Agent", "%s/%s".printf(Config.APPNAME, Config.APP_VERSION));
234         
235         message.request_headers = headers;
236         
237         if(method != "GET") { //set post/delete-parameters
238                 string body = form_encode_hash(params);
239                         message.set_request("application/x-www-form-urlencoded",
240                                 MemoryUse.COPY, body, (int)body.size());
241                 }
242                 
243                 //Basic HTTP authorization
244         session.authenticate += (sess, msg, auth, retrying) => {
245                         if (retrying) return;
246                         auth.authenticate(account.login, account.password);
247                 };
248                 
249                 int status_code = 0;
250                 for(int i = 0; i < retry; i++) {
251                         status_code = (int)session.send_message(message);
252                         if(status_code == 200 || status_code == 401)
253                                 break;
254                 }
255                 
256                 if(status_code != 200)
257                         reply_tracking(status_code);
258                 
259                 return (string)message.response_body.flatten().data;
260         }
261         
262         /* check user for DM availability */
263         public bool check_friendship(string screen_name,
264                 bool just_friend_check = false) throws RestError {
265                 
266                 string req_url = urls.friendship();
267                 
268                 var map = new HashTable<string, string>(str_hash, str_equal);
269                 map.insert("source_screen_name", account.login);
270                 map.insert("target_screen_name", screen_name);
271                 warning(req_url);
272                 string data = make_request(req_url, "GET", map);
273                 
274                 return parse_friendship(data, just_friend_check);
275         }
276         
277         private bool parse_friendship(string data, bool just_friend_check = false) {
278                 bool followed_by = false;
279                 bool following = false;
280                 
281                 Xml.Doc* xmlDoc = Xml.Parser.parse_memory(data, (int)data.size());
282                 Xml.Node* rootNode = xmlDoc->get_root_element();
283                 
284                 Xml.Node* iter;
285                 for(iter = rootNode->children; iter != null; iter = iter->next) {
286                         if (iter->type != ElementType.ELEMENT_NODE)
287                                 continue;
288                         
289                         if(iter->name == "target") {
290                                 
291                                 Xml.Node* iter_in;
292                                 for(iter_in = iter->children; iter_in != null; iter_in = iter_in->next) {
293                                         switch(iter_in->name) {
294                                                 case "followed_by":
295                                                         followed_by = iter_in->get_content().to_bool();
296                                                         break;
297                                         
298                                                 case "following":
299                                                         following = iter_in->get_content().to_bool();
300                                                         break;
301                                         }
302                                 }
303                                 delete iter_in;
304                                 break;
305                         }
306                         
307                 } delete iter;
308                 
309                 if(just_friend_check && followed_by)
310                         return true;
311                 
312                 if(followed_by && following)
313                         return true;
314                 
315                 return false;
316         }
317 }
318
319 }