Allow / in the regexes for search/groups
[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 SessionAsync session_async;
101         private SessionSync session_sync;
102         
103         public RestAPIAbstract(Account? _account) {
104                 urls = new RestUrls(ServiceType.UNKNOWN);
105                 set_auth(_account);
106
107                 session_async = new SessionAsync();
108                 session_sync = new SessionSync();
109
110                 session_async.timeout = 30; //seconds
111                 session_sync.timeout = 30; //seconds
112
113                 //Basic HTTP authorization
114         session_async.authenticate.connect(http_auth);
115         session_sync.authenticate.connect(http_auth);
116         }
117
118         private void http_auth(Message msg, Soup.Auth auth, bool retrying) {
119                 if(retrying)
120                                 return;
121
122                 auth.authenticate(account.login, account.password);
123         }
124         
125         private void select_urls() {
126                 switch(account.service) {
127                         case "twitter.com":
128                                 urls.set_prefix(ServiceType.TWITTER);
129                                 break;
130                         
131                         case "identi.ca":
132                                 urls.set_prefix(ServiceType.IDENTICA);
133                                 break;
134                         
135                         case "other":
136                                 string proxy = "http://api.twitter.com/";
137                                 if(account.proxy != "")
138                                         proxy = account.proxy;
139                                 
140                                 urls.set_prefix(ServiceType.UNKNOWN, proxy);
141                                 break;
142                         
143                         default:
144                                 urls.set_prefix(ServiceType.TWITTER);
145                                 break;
146                 }
147         }
148         
149         public void set_auth(Account? _account) {
150                 account = _account;
151                 
152                 if(account != null)
153                         select_urls();
154         }
155         
156         public signal void request(string req);
157         
158         public virtual ArrayList<Status>? get_timeline(int count = 0, FullStatus? fstatus = null,
159                 string since_id = "", string max_id = "", bool sync = true) throws RestError, ParseError {
160                 
161                 return null;
162         }
163         
164         public virtual Status get_status(string id) throws RestError, ParseError {
165                 return new Status();
166         }
167         
168         public virtual void favorite_create(string id) throws RestError {}
169         public virtual void favorite_destroy(string id) throws RestError {}
170         
171         public virtual void follow_create(string screen_name) throws RestError {}
172         public virtual void follow_destroy(string screen_name) throws RestError {}
173         
174         public virtual void destroy_status(string id) throws RestError {}
175         
176         protected void reply_tracking(int status_code) throws RestError {
177                 switch(status_code) {
178                         case 2:
179                                 throw new RestError.CODE("Connection problems: can't connect to the server.");
180                         
181                         case 4:
182                                 throw new RestError.CODE("Connection problems: timeout.");
183                         
184                         case 400:
185                                 throw new RestError.CODE("%d Rate limiting: you have reached the limit requests.".printf(status_code));
186                         
187                         case 401:
188                                 throw new RestError.CODE("%d Unauthorized: the request requires user authentication.".printf(status_code));
189                         
190                         case 403:
191                                 throw new RestError.CODE("%d Forbidden: the server understood the request, but is refusing to fulfill it.".printf(status_code));
192                         
193                         case 404:
194                                 throw new RestError.CODE_404("%d Not Found: The server has not found anything matching the Request-URI.".printf(status_code));
195                         
196                         case 407:
197                                 throw new RestError.CODE("%d Proxy Authentication Required: the request requires user authentication.".printf(status_code));
198                         
199                         default:
200                                 throw new RestError.CODE("%d Unknown Error".printf(status_code));
201                 }
202         }
203         
204         protected void no_account() throws RestError {
205                 throw new RestError.CODE("Account is not found");
206         }
207         
208         public string make_request(owned string req_url, string method,
209                 HashTable<string, string> params = new HashTable<string, string>(str_hash, str_equal),
210                 bool async = true, int retry = 3) throws RestError {
211                 
212                 if(account == null)
213                         no_account();
214                 
215                 //send signal about all requests
216         request("%s: %s".printf(method, req_url));
217         
218         Session session;
219         
220                 if(async)
221                         session = session_async;
222                 else
223                         session = session_sync;
224                 
225                 Message message = form_request_new_from_hash(method, req_url, params);
226                 message.request_headers.append("User-Agent", "%s/%s".printf(Config.APPNAME, Config.APP_VERSION));
227                 
228                 debug("and one more");
229                 int status_code = 0;
230                 for(int i = 0; i < retry; i++) {
231                         debug("go into loop");
232                         try {
233                                 status_code = (int)session.send_message(message);
234                         } catch(GLib.Error e) {
235                                 debug("we got some error: %s", e.message);
236                                 break;
237                         }
238                         debug("something receive %d", status_code);
239                         if(status_code == 200 || status_code == 401 || status_code == 4)
240                                 break;
241                 }
242                 debug("...");
243                 if(status_code != 200)
244                         reply_tracking(status_code);
245
246                 debug("end of make_request");
247                 
248                 return (string)message.response_body.data;
249         }
250         
251         /* check user for DM availability */
252         public bool check_friendship(string screen_name,
253                 bool just_friend_check = false) throws RestError, ParseError {
254                 
255                 string req_url = urls.friendship();
256                 
257                 var map = new HashTable<string, string>(str_hash, str_equal);
258                 map.insert("source_screen_name", account.login);
259                 map.insert("target_screen_name", screen_name);
260                 debug(req_url);
261                 string data = make_request(req_url, "GET", map);
262
263                 bool result = parse_friendship(data, just_friend_check);
264
265                 return result;
266         }
267         
268         private bool parse_friendship(string data,
269                 bool just_friend_check = false) throws ParseError {
270
271                 bool followed_by = false;
272                 bool following = false;
273                 
274                 Xml.Doc* xmlDoc = Xml.Parser.parse_memory(data, (int)data.size());
275                 if(xmlDoc == null)
276                         throw new ParseError.CODE("Invalid XML data");
277                 
278                 Xml.Node* rootNode = xmlDoc->get_root_element();
279                 
280                 Xml.Node* iter;
281                 for(iter = rootNode->children; iter != null; iter = iter->next) {
282                         if (iter->type != ElementType.ELEMENT_NODE)
283                                 continue;
284                         
285                         if(iter->name == "target") {
286                                 
287                                 Xml.Node* iter_in;
288                                 for(iter_in = iter->children; iter_in != null; iter_in = iter_in->next) {
289                                         switch(iter_in->name) {
290                                                 case "followed_by":
291                                                         followed_by = iter_in->get_content().to_bool();
292                                                         break;
293                                         
294                                                 case "following":
295                                                         following = iter_in->get_content().to_bool();
296                                                         break;
297                                         }
298                                 }
299                                 delete iter_in;
300                                 break;
301                         }
302                         
303                 } delete iter;
304                 
305                 if(just_friend_check && followed_by)
306                         return true;
307                 
308                 if(followed_by && following)
309                         return true;
310                 
311                 return false;
312         }
313 }
314
315 }