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