Merge branch 'upstream' for new release 2.1
[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 user_name;
36         public string user_screen_name;
37         public string user_avatar;
38         
39         public string re_text = "";
40         public string re_user_name;
41         public string re_user_screen_name;
42         public string re_user_avatar;
43         
44         public string to_user = "";
45         public string to_status_id = "";
46         
47         public bool is_retweet = false;
48 }
49
50 public class FullStatus : Status {
51         
52         public string followers = "";
53         public string friends = "";
54         public string statuses = "";
55         public string url = "";
56         public string desc = "";
57         public bool following = false;
58         
59         public bool done = false;
60         
61 }
62
63 public struct AuthData {
64         public string login = "";
65         public string password = "";
66         public string service = "";
67
68
69 public static enum ServiceType {
70         TWITTER,
71         IDENTICA,
72         UNKNOWN
73 }
74
75 errordomain RestError {
76         CODE,
77         CODE_404
78 }
79
80 errordomain ParseError {
81         CODE
82 }
83
84 public static enum TimelineType {
85         HOME,
86         MENTIONS,
87         USER
88 }
89
90 public abstract class RestAPIAbstract : Object {
91         
92         protected RestUrls urls;
93         public Account? account;
94         
95         public RestAPIAbstract(Account? _account) {
96                 urls = new RestUrls(ServiceType.UNKNOWN);
97                 set_auth(_account);
98         }
99         
100         private void select_urls() {
101                 switch(account.service) {
102                         case "twitter.com":
103                                 urls.set_prefix(ServiceType.TWITTER);
104                                 break;
105                         
106                         case "identi.ca":
107                                 urls.set_prefix(ServiceType.IDENTICA);
108                                 break;
109                         
110                         case "other":
111                                 string proxy = "http://api.twitter.com/";
112                                 if(account.proxy != "")
113                                         proxy = account.proxy;
114                                 
115                                 urls.set_prefix(ServiceType.UNKNOWN, proxy);
116                                 break;
117                         
118                         default:
119                                 urls.set_prefix(ServiceType.TWITTER);
120                                 break;
121                 }
122         }
123         
124         public void set_auth(Account? _account) {
125                 account = _account;
126                 
127                 if(account != null)
128                         select_urls();
129         }
130         
131         public signal void request(string req);
132         
133         public virtual ArrayList<Status>? get_timeline(int count = 0, FullStatus? fstatus = null,
134                 string since_id = "", string max_id = "") throws RestError, ParseError {
135                 
136                 return null;
137         }
138         
139         public virtual Status get_status(string id) throws RestError, ParseError {
140                 return new Status();
141         }
142         
143         public virtual void follow_create(string screen_name) throws RestError {}
144         public virtual void follow_destroy(string screen_name) throws RestError {}
145         
146         public virtual void destroy_status(string id) throws RestError {}
147         
148         protected void reply_tracking(int status_code) throws RestError {
149                 switch(status_code) {
150                         case 2:
151                                 throw new RestError.CODE("Connection problems: can't connect to the server.");
152                         
153                         case 401:
154                                 throw new RestError.CODE("%d Unauthorized: the request requires user authentication.".printf(status_code));
155                         
156                         case 403:
157                                 throw new RestError.CODE("%d Forbidden: the server understood the request, but is refusing to fulfill it.".printf(status_code));
158                         
159                         case 404:
160                                 throw new RestError.CODE_404("%d Not Found: The server has not found anything matching the Request-URI.".printf(status_code));
161                         
162                         case 407:
163                                 throw new RestError.CODE("%d Proxy Authentication Required: the request requires user authentication.".printf(status_code));
164                         
165                         default:
166                                 throw new RestError.CODE("%d Unknown Error".printf(status_code));
167                 }
168         }
169         
170         protected void no_account() throws RestError {
171                 throw new RestError.CODE("Account is not found");
172         }
173         
174         public string make_request(owned string req_url, string method,
175                 HashTable<string, string> params = new HashTable<string, string>(null, null),
176                 bool async = true, int retry = 3) throws RestError {
177                 
178                 if(account == null)
179                         no_account();
180                 
181                 if(method == "GET") { //set get-parameters
182                         string query = "";
183                 
184                         if(params.size() > 0) {
185                                 query = "?";
186                                 
187                                 //Very dirty. HashTable.loockup() doesn't work. Bug?
188                                 int tmp_iter = 0;
189                                 foreach(string key in params.get_keys()) {
190                                         int tmp_iter2 = 0;
191                                         foreach(string val in params.get_values()) {
192                                                 if(tmp_iter2 == tmp_iter) {
193                                                         query += Soup.form_encode(key, val);
194                                                         if(tmp_iter < params.size() - 1)
195                                                                 query += "&";
196                                                         break;
197                                                 }
198                                                 tmp_iter2++;
199                                         }
200                                         tmp_iter++;
201                                 }
202                         }
203                         req_url += query;
204                 }
205                 
206                 //send signal about all requests
207         request("%s: %s".printf(method, req_url));
208         
209         Session session;
210         
211                 if(async)
212                         session = new SessionAsync();
213                 else
214                         session = new SessionSync();
215                 
216         Message message = new Message(method, req_url);
217         message.set_http_version (HTTPVersion.1_1);
218         
219         MessageHeaders headers = new MessageHeaders(MessageHeadersType.MULTIPART);
220         headers.append("User-Agent", "%s/%s".printf(Config.APPNAME, Config.APP_VERSION));
221         
222         message.request_headers = headers;
223         
224         if(method != "GET") { //set post/delete-parameters
225                 string body = form_encode_hash(params);
226                         message.set_request("application/x-www-form-urlencoded",
227                                 MemoryUse.COPY, body, (int)body.size());
228                 }
229                 
230                 //Basic HTTP authorization
231         session.authenticate += (sess, msg, auth, retrying) => {
232                         if (retrying) return;
233                         auth.authenticate(account.login, account.password);
234                 };
235                 
236                 int status_code = 0;
237                 for(int i = 0; i < retry; i++) {
238                         status_code = (int)session.send_message(message);
239                         if(status_code == 200 || status_code == 401)
240                                 break;
241                 }
242                 
243                 if(status_code != 200)
244                         reply_tracking(status_code);
245                 
246                 return (string)message.response_body.flatten().data;
247         }
248         
249         /* check user for DM availability */
250         public bool check_friendship(string screen_name,
251                 bool just_friend_check = false) throws RestError {
252                 
253                 string req_url = urls.friendship();
254                 
255                 HashTable map = new HashTable<string, string>(null, null);
256                 map.insert("source_screen_name", account.login);
257                 map.insert("target_screen_name", screen_name);
258                 warning(req_url);
259                 string data = make_request(req_url, "GET", map);
260                 
261                 return parse_friendship(data, just_friend_check);
262         }
263         
264         private bool parse_friendship(string data, bool just_friend_check = false) {
265                 bool followed_by = false;
266                 bool following = false;
267                 
268                 Xml.Doc* xmlDoc = Xml.Parser.parse_memory(data, (int)data.size());
269                 Xml.Node* rootNode = xmlDoc->get_root_element();
270                 
271                 Xml.Node* iter;
272                 for(iter = rootNode->children; iter != null; iter = iter->next) {
273                         if (iter->type != ElementType.ELEMENT_NODE)
274                                 continue;
275                         
276                         if(iter->name == "target") {
277                                 
278                                 Xml.Node* iter_in;
279                                 for(iter_in = iter->children; iter_in != null; iter_in = iter_in->next) {
280                                         switch(iter_in->name) {
281                                                 case "followed_by":
282                                                         followed_by = iter_in->get_content().to_bool();
283                                                         break;
284                                         
285                                                 case "following":
286                                                         following = iter_in->get_content().to_bool();
287                                                         break;
288                                         }
289                                 }
290                                 delete iter_in;
291                                 break;
292                         }
293                         
294                 } delete iter;
295                 
296                 if(just_friend_check && followed_by)
297                         return true;
298                 
299                 if(followed_by && following)
300                         return true;
301                 
302                 return false;
303         }
304 }
305
306 }