a539cc6a80769ff8b95f9c9de512167434f433fc
[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 4:
164                                 throw new RestError.CODE("Connection problems: timeout.");
165                         
166                         case 400:
167                                 throw new RestError.CODE("%d Rate limiting: you have reached the limit requests.".printf(status_code));
168                         
169                         case 401:
170                                 throw new RestError.CODE("%d Unauthorized: the request requires user authentication.".printf(status_code));
171                         
172                         case 403:
173                                 throw new RestError.CODE("%d Forbidden: the server understood the request, but is refusing to fulfill it.".printf(status_code));
174                         
175                         case 404:
176                                 throw new RestError.CODE_404("%d Not Found: The server has not found anything matching the Request-URI.".printf(status_code));
177                         
178                         case 407:
179                                 throw new RestError.CODE("%d Proxy Authentication Required: the request requires user authentication.".printf(status_code));
180                         
181                         default:
182                                 throw new RestError.CODE("%d Unknown Error".printf(status_code));
183                 }
184         }
185         
186         protected void no_account() throws RestError {
187                 throw new RestError.CODE("Account is not found");
188         }
189         
190         public string make_request(owned string req_url, string method,
191                 HashTable<string, string> params = new HashTable<string, string>(str_hash, str_equal),
192                 bool async = true, int retry = 3) throws RestError {
193                 
194                 if(account == null)
195                         no_account();
196                 
197                 if(method == "GET") { //set get-parameters
198                         string query = "";
199                         debug(params.size().to_string());
200                         if(params.size() > 0) {
201                                 query = "?";
202                                 
203                                 int tmp_iter = 0;
204                                 foreach(string key in params.get_keys()) {
205                                         query += Soup.form_encode(key, params.lookup(key));
206                                         
207                                         if(tmp_iter < params.size() - 1)
208                                                 query += "&";
209                                         
210                                         tmp_iter++;
211                                 }
212                         }
213                         req_url += query;
214                 }
215                 debug("end of GET parameters setting");
216                 //send signal about all requests
217         request("%s: %s".printf(method, req_url));
218         
219         Session session;
220         
221                 if(async)
222                         session = new SessionAsync();
223                 else
224                         session = new SessionSync();
225
226                 session.timeout = 30; //seconds
227                 
228         Message message = new Message(method, req_url);
229         message.set_http_version(HTTPVersion.1_1);
230         
231         MessageHeaders headers = new MessageHeaders(MessageHeadersType.MULTIPART);
232         headers.append("User-Agent", "%s/%s".printf(Config.APPNAME, Config.APP_VERSION));
233         
234         message.request_headers = headers;
235         debug("just a control point");
236         if(method != "GET") { //set post/delete-parameters
237                 string body = form_encode_hash(params);
238                         message.set_request("application/x-www-form-urlencoded",
239                                 MemoryUse.COPY, body, (int)body.size());
240                 }
241                 debug("another control point");
242                 //Basic HTTP authorization
243         session.authenticate += (sess, msg, auth, retrying) => {
244                         if (retrying) return;
245                         auth.authenticate(account.login, account.password);
246                 };
247                 debug("and one more");
248                 int status_code = 0;
249                 for(int i = 0; i < retry; i++) {
250                         debug("go into loop");
251                         try {
252                                 status_code = (int)session.send_message(message);
253                         } catch(GLib.Error e) {
254                                 debug("we got some error: %s", e.message);
255                                 break;
256                         }
257                         debug("something recieve %d", status_code);
258                         if(status_code == 200 || status_code == 401 || status_code == 4)
259                                 break;
260                 }
261                 debug("...");
262                 if(status_code != 200)
263                         reply_tracking(status_code);
264
265                 debug("end of make_request");
266                 
267                 return (string)message.response_body.flatten().data;
268         }
269         
270         /* check user for DM availability */
271         public bool check_friendship(string screen_name,
272                 bool just_friend_check = false) throws RestError, ParseError {
273                 
274                 string req_url = urls.friendship();
275                 
276                 var map = new HashTable<string, string>(str_hash, str_equal);
277                 map.insert("source_screen_name", account.login);
278                 map.insert("target_screen_name", screen_name);
279                 debug(req_url);
280                 string data = make_request(req_url, "GET", map);
281
282                 Parser.init();
283                 bool result = parse_friendship(data, just_friend_check);
284                 Parser.cleanup();
285
286                 return result;
287         }
288         
289         private bool parse_friendship(string data,
290                 bool just_friend_check = false) throws ParseError {
291
292                 bool followed_by = false;
293                 bool following = false;
294                 
295                 Xml.Doc* xmlDoc = Xml.Parser.parse_memory(data, (int)data.size());
296                 if(xmlDoc == null)
297                         throw new ParseError.CODE("Invalid XML data");
298                 
299                 Xml.Node* rootNode = xmlDoc->get_root_element();
300                 
301                 Xml.Node* iter;
302                 for(iter = rootNode->children; iter != null; iter = iter->next) {
303                         if (iter->type != ElementType.ELEMENT_NODE)
304                                 continue;
305                         
306                         if(iter->name == "target") {
307                                 
308                                 Xml.Node* iter_in;
309                                 for(iter_in = iter->children; iter_in != null; iter_in = iter_in->next) {
310                                         switch(iter_in->name) {
311                                                 case "followed_by":
312                                                         followed_by = iter_in->get_content().to_bool();
313                                                         break;
314                                         
315                                                 case "following":
316                                                         following = iter_in->get_content().to_bool();
317                                                         break;
318                                         }
319                                 }
320                                 delete iter_in;
321                                 break;
322                         }
323                         
324                 } delete iter;
325                 
326                 if(just_friend_check && followed_by)
327                         return true;
328                 
329                 if(followed_by && following)
330                         return true;
331                 
332                 return false;
333         }
334 }
335
336 }