/* rest_api_abstract.vala * * Copyright (C) 2009-2010 troorl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Author: * troorl */ using Gee; using Soup; using Auth; using TimeUtils; using Xml; namespace RestAPI { public class Status : Object { public string id; public string text; public Time created_at = Time(); public string created_at_s = ""; public string user_name; public string user_screen_name; public string user_avatar; public string re_text = ""; public string re_user_name; public string re_user_screen_name; public string re_user_avatar; public string to_user = ""; public string to_status_id = ""; public bool is_retweet = false; public bool is_favorite = false; public bool tmp = false; } public class FullStatus : Status { public string followers = ""; public string friends = ""; public string statuses = ""; public string url = ""; public string desc = ""; public bool following = false; public bool done = false; } public struct AuthData { public string login; public string password; public string service; } public static enum ServiceType { TWITTER, IDENTICA, UNKNOWN } errordomain RestError { CODE, CODE_404 } errordomain ParseError { CODE } public static enum TimelineType { HOME, MENTIONS, USER, FAVORITES } public abstract class RestAPIAbstract : Object { protected RestUrls urls; public Account? account; private SessionAsync session_async; private SessionSync session_sync; public RestAPIAbstract(Account? _account) { urls = new RestUrls(ServiceType.UNKNOWN); set_auth(_account); session_async = new SessionAsync(); session_sync = new SessionSync(); session_async.timeout = 30; //seconds session_sync.timeout = 30; //seconds //Basic HTTP authorization session_async.authenticate.connect(http_auth); session_sync.authenticate.connect(http_auth); } private void http_auth(Message msg, Soup.Auth auth, bool retrying) { if(retrying) return; auth.authenticate(account.login, account.password); } private void select_urls() { switch(account.service) { case "twitter.com": urls.set_prefix(ServiceType.TWITTER); break; case "identi.ca": urls.set_prefix(ServiceType.IDENTICA); break; case "other": string proxy = "http://api.twitter.com/"; if(account.proxy != "") proxy = account.proxy; urls.set_prefix(ServiceType.UNKNOWN, proxy); break; default: urls.set_prefix(ServiceType.TWITTER); break; } } public void set_auth(Account? _account) { account = _account; if(account != null) select_urls(); } public signal void request(string req); public virtual ArrayList? get_timeline(int count = 0, FullStatus? fstatus = null, string since_id = "", string max_id = "", bool sync = true) throws RestError, ParseError { return null; } public virtual Status get_status(string id) throws RestError, ParseError { return new Status(); } public virtual void favorite_create(string id) throws RestError {} public virtual void favorite_destroy(string id) throws RestError {} public virtual void follow_create(string screen_name) throws RestError {} public virtual void follow_destroy(string screen_name) throws RestError {} public virtual void destroy_status(string id) throws RestError {} protected void reply_tracking(int status_code) throws RestError { switch(status_code) { case 2: throw new RestError.CODE("Connection problems: can't connect to the server."); case 4: throw new RestError.CODE("Connection problems: timeout."); case 400: throw new RestError.CODE("%d Rate limiting: you have reached the limit requests.".printf(status_code)); case 401: throw new RestError.CODE("%d Unauthorized: the request requires user authentication.".printf(status_code)); case 403: throw new RestError.CODE("%d Forbidden: the server understood the request, but is refusing to fulfill it.".printf(status_code)); case 404: throw new RestError.CODE_404("%d Not Found: The server has not found anything matching the Request-URI.".printf(status_code)); case 407: throw new RestError.CODE("%d Proxy Authentication Required: the request requires user authentication.".printf(status_code)); default: throw new RestError.CODE("%d Unknown Error".printf(status_code)); } } protected void no_account() throws RestError { throw new RestError.CODE("Account is not found"); } public string make_request(owned string req_url, string method, HashTable params = new HashTable(str_hash, str_equal), bool async = true, int retry = 3) throws RestError { if(account == null) no_account(); //send signal about all requests request("%s: %s".printf(method, req_url)); Session session; if(async) session = session_async; else session = session_sync; Message message = form_request_new_from_hash(method, req_url, params); message.request_headers.append("User-Agent", "%s/%s".printf(Config.APPNAME, Config.APP_VERSION)); debug("and one more"); int status_code = 0; for(int i = 0; i < retry; i++) { debug("go into loop"); try { status_code = (int)session.send_message(message); } catch(GLib.Error e) { debug("we got some error: %s", e.message); break; } debug("something receive %d", status_code); if(status_code == 200 || status_code == 401 || status_code == 4) break; } debug("..."); if(status_code != 200) reply_tracking(status_code); debug("end of make_request"); return (string)message.response_body.data; } /* check user for DM availability */ public bool check_friendship(string screen_name, bool just_friend_check = false) throws RestError, ParseError { string req_url = urls.friendship(); var map = new HashTable(str_hash, str_equal); map.insert("source_screen_name", account.login); map.insert("target_screen_name", screen_name); debug(req_url); string data = make_request(req_url, "GET", map); Parser.init(); bool result = parse_friendship(data, just_friend_check); Parser.cleanup(); return result; } private bool parse_friendship(string data, bool just_friend_check = false) throws ParseError { bool followed_by = false; bool following = false; Xml.Doc* xmlDoc = Xml.Parser.parse_memory(data, (int)data.size()); if(xmlDoc == null) throw new ParseError.CODE("Invalid XML data"); Xml.Node* rootNode = xmlDoc->get_root_element(); Xml.Node* iter; for(iter = rootNode->children; iter != null; iter = iter->next) { if (iter->type != ElementType.ELEMENT_NODE) continue; if(iter->name == "target") { Xml.Node* iter_in; for(iter_in = iter->children; iter_in != null; iter_in = iter_in->next) { switch(iter_in->name) { case "followed_by": followed_by = iter_in->get_content().to_bool(); break; case "following": following = iter_in->get_content().to_bool(); break; } } delete iter_in; break; } } delete iter; if(just_friend_check && followed_by) return true; if(followed_by && following) return true; return false; } } }