X-Git-Url: http://git.maemo.org/git/?a=blobdiff_plain;f=src%2Fadac-mitfahrclub.vala;h=810a8616d6f100d914f74bbb938b2730458492c7;hb=f3b79c5e6298baf4fd76e5ce8e60a3fb8c296f64;hp=b192408de00e648f493520ef69e2f82ffa7b51cb;hpb=ce0912fd78541a6bc8601df3de4013380ce05233;p=beifahrer diff --git a/src/adac-mitfahrclub.vala b/src/adac-mitfahrclub.vala index b192408..810a861 100644 --- a/src/adac-mitfahrclub.vala +++ b/src/adac-mitfahrclub.vala @@ -43,7 +43,8 @@ public enum LiftFlags { SMOKER = 1, NON_SMOKER = 2, ADAC_MEMBER = 4, - WOMEN_ONLY = 8 + WOMEN_ONLY = 8, + ACTIVE = 16, } public class Lift : Object { @@ -62,6 +63,7 @@ public class Lift : Object { public string phone; public string phone2; public string email; + public string email_image_uri; public string description; public string modified; @@ -70,40 +72,203 @@ public class Lift : Object { } } -public class CallbackMessage : Soup.Message { - public SourceFunc callback; - - public CallbackMessage (string url, SourceFunc? _callback) { - method = "GET"; - callback = _callback; - set_uri (new Soup.URI (url)); +public class MyInformation { + public enum Gender { + MALE, + FEMALE } + + public Gender gender; + public string title; + public string first_name; + public string last_name; + public Date birthday; + public Date registered_since; + + // Address + public string street; + public string number; + public string zip_code; + public string city; + public string country; + + // Contact + public string phone1; + public string phone2; + public string phone3; + public string cell; + public string email1; + public string email2; + + public bool smoker; + public bool adac_member; } public class AdacMitfahrclub { - const string BASE_URI = "http://mitfahrclub.adac.de"; + const string HTTP_BASE_URI = "http://mitfahrclub.adac.de"; + const string HTTPS_BASE_URI = "https://mitfahrclub.adac.de"; - Soup.SessionAsync session; + Curl.EasyHandle curl; List city_list = null; public AdacMitfahrclub () { - session = new Soup.SessionAsync (); + curl = new Curl.EasyHandle (); + // FIXME: Fremantle SDK doesn't come with certs + curl.setopt (Curl.Option.SSL_VERIFYPEER, 0); + // curl.setopt (Curl.Option.VERBOSE, 1); } - private void message_finished (Soup.Session session, Soup.Message message) { - Idle.add (((CallbackMessage) message).callback); + private string _url = null; + private SourceFunc callback = null; + void* download_thread () { + curl.setopt (Curl.Option.WRITEFUNCTION, write_callback); + curl.setopt (Curl.Option.WRITEDATA, this); + curl.setopt (Curl.Option.URL, _url); + if (aeolus_cookie != null) + curl.setopt (Curl.Option.COOKIE, "MIKINIMEDIA=%s; Quirinus[adacAeolus]=%s;".printf (mikini_cookie, aeolus_cookie)); + else if (mikini_cookie != null) + curl.setopt (Curl.Option.COOKIE, "MIKINIMEDIA=%s".printf (mikini_cookie)); + var res = curl.perform (); + + if (callback != null) + Idle.add (callback); + callback = null; + + return null; + } + + StringBuilder result; + + [CCode (instance_pos = -1)] + size_t write_callback (void *buffer, size_t size, size_t nmemb) { + // if (cancellable != null && cancellable.is_cancelled ()) + // return 0; + + result.append_len ((string) buffer, (ssize_t) (size * nmemb)); + + return size * nmemb; } private async Html.Doc* get_html_document (string url) { - var message = new CallbackMessage (url, get_html_document.callback); - session.queue_message (message, message_finished); + _url = url; + callback = get_html_document.callback; + result = new StringBuilder (); + try { + Thread.create(download_thread, false); + } catch (ThreadError e) { + critical ("Failed to create download thread\n"); + return null; + } yield; - return Html.Doc.read_memory ((char[]) message.response_body.data, (int) message.response_body.length, + return Html.Doc.read_memory ((char[]) result.str, (int) result.len, url, null, Html.ParserOption.NOERROR | Html.ParserOption.NOWARNING); } + private string username; + private string password; + private string mikini_cookie; + private string aeolus_cookie; + + public void set_cookie (string value) { + aeolus_cookie = value; + } + + public void set_credentials (string _username, string _password) { + username = _username; + password = _password; + } + + public void login (string? _username, string? _password) { + set_credentials (_username, _password); + if (logged_in) + return; + if (login_callback != null) + return; + login_thread (); + } + + public bool logged_in = false; + private SourceFunc login_callback = null; + public async bool login_async () { + if (logged_in) + return true; + if (login_callback != null || username == null || password == null) + return false; + login_callback = login_async.callback; + result = new StringBuilder (); + try { + Thread.create(login_thread, false); + } catch (ThreadError e) { + critical ("Failed to create login thread\n"); + return false; + } + + yield; + login_callback = null; + + return logged_in; + } + + void *login_thread () { + result = new StringBuilder (); + curl.setopt (Curl.Option.URL, HTTP_BASE_URI); + curl.setopt (Curl.Option.WRITEFUNCTION, write_callback); + curl.setopt (Curl.Option.WRITEDATA, this); + curl.setopt (Curl.Option.COOKIEFILE, ""); + var res = curl.perform (); + + Curl.SList cookies; + curl.getinfo (Curl.Info.COOKIELIST, out cookies); + unowned Curl.SList cookie = cookies; + while (cookie != null) { + if (cookie.data != null) { + var c = cookie.data.split ("\t"); + if (c.length > 5) + print ("%s=%s\n", c[5], c[6]); + if (c[5] == "MIKINIMEDIA") + mikini_cookie = c[6]; + } + cookie = cookie.next; + } + + result = new StringBuilder (); + string postdata = "data[User][continue]=/&data[User][js_allowed]=0&data[User][cookie_allowed]=1&data[User][username]=%s&data[User][password]=%s&data[User][remember_me]=1".printf (username, password); + curl.setopt (Curl.Option.POSTFIELDS, postdata); + curl.setopt (Curl.Option.URL, HTTPS_BASE_URI + "/users/login/"); + curl.setopt (Curl.Option.SSL_VERIFYPEER, 0); + res = curl.perform (); + // print ("%s\n", result.str); + + cookies = null; + curl.getinfo (Curl.Info.COOKIELIST, out cookies); + cookie = cookies; + while (cookie != null) { + if (cookie.data != null) { + var c = cookie.data.split ("\t"); + if (c.length > 5) + print ("%s=%s\n", c[5], c[6]); + // "Quirinus[adacAeolus]" + if (c[5] == "Quirinus[adacAeolus]") { + aeolus_cookie = c[6]; + logged_in = true; + } + } + cookie = cookie.next; + } + + if (result.str.contains ("
Die eingegebenen Zugangsdaten konnten nicht gefunden werden. Bitte versuchen Sie es erneut.
")) { + print ("LOGIN FAILED\n"); + aeolus_cookie = null; + logged_in = false; + } + + if (login_callback != null) + Idle.add (login_callback); + return null; + } + private void save_city_list () { FileStream list_file = FileStream.open ("/home/user/.cache/beifahrer/city_list", "w"); if (list_file == null) @@ -165,7 +330,7 @@ public class AdacMitfahrclub { } public async unowned List? download_city_list () { - var doc = yield get_html_document (BASE_URI); + var doc = yield get_html_document (HTTP_BASE_URI); if (doc == null) { stderr.printf ("Error: parsing failed\n"); return null; @@ -236,30 +401,28 @@ public class AdacMitfahrclub { return result; } - public async List? get_lift_list (string city_from, string city_to, Date date, int tolerance = 0) { + public string? get_lift_list_url (string city_from, int radius_from, string city_to, int radius_to, Date date, int tolerance = 0) { if (city_list == null) get_city_list (); int num_from = get_city_number (city_from); - if (num_from == 0) { - stderr.printf ("Unknown city: %s\n", city_to); + if (num_from == 0) return null; - } int num_to = get_city_number (city_to); - if (num_to == 0) { - stderr.printf ("Unknown city: %s\n", city_to); + if (num_to == 0) return null; - } - string url = BASE_URI + "/mitfahrclub/%s/%s/b.html".printf ( - city_from, + string url = HTTP_BASE_URI + "/mitfahrclub/%s/%s/b.html".printf ( + city_from.replace ("/", "_"), city_to ); - url += "?type=b&city_from=%d&radius_from=0&city_to=%d&radius_to=0".printf ( + url += "?type=b&city_from=%d&radius_from=%d&city_to=%d&radius_to=%d".printf ( num_from, - num_to + radius_from, + num_to, + radius_to ); url += "&date=date&day=%d&month=%d&year=%d&tolerance=%d&smoking=&avg_speed=&".printf ( @@ -269,7 +432,11 @@ public class AdacMitfahrclub { tolerance ); - var doc = yield get_html_document (url); + return url; + } + + public async List? get_lift_list (string city_from, int radius_from, string city_to, int radius_to, Date date, int tolerance = 0) { + var doc = yield get_html_document (get_lift_list_url (city_from, radius_from, city_to, radius_to, date, tolerance)); if (doc == null) { stderr.printf ("Error: parsing failed\n"); return null; @@ -335,7 +502,7 @@ public class AdacMitfahrclub { lift.places = n3->content.to_int (); break; case 5: - lift.price = n3->content; + lift.price = n3->content.replace (" EUR", " €"); break; default: print ("TEXT:%s\n", n3->content); @@ -365,10 +532,12 @@ public class AdacMitfahrclub { return lift; } - public async bool update_lift_details (Lift lift) { - string url = BASE_URI + lift.href; + public string get_lift_details_url (Lift lift) { + return HTTP_BASE_URI + lift.href; + } - var doc = yield get_html_document (url); + public async bool update_lift_details (Lift lift) { + var doc = yield get_html_document (get_lift_details_url (lift)); if (doc == null) { stderr.printf ("Error: parsing failed\n"); return false; @@ -415,7 +584,7 @@ public class AdacMitfahrclub { if (n2->children->name == "img") { // FIXME: email image - // n2->children->get_prop ("src")) + lift.email_image_uri = n2->children->get_prop ("src"); continue; } @@ -489,7 +658,7 @@ public class AdacMitfahrclub { else if (text1 == "Raucher") print ("Raucher: %s\n", text2); else if (text1 == "Fahrpreis") - lift.price = text2; + lift.price = text2.replace (" EUR", " €"); else if (text1 == "ADAC-Mitglied" && text2 != "nein") lift.flags |= LiftFlags.ADAC_MEMBER; } @@ -509,6 +678,271 @@ public class AdacMitfahrclub { return true; } + public string get_my_information_url () { + return HTTPS_BASE_URI + "/users/view"; + } + + public async MyInformation get_my_information () { + var doc = yield get_html_document (get_my_information_url ()); + if (doc == null) { + stderr.printf ("Error: parsing failed\n"); + return null; + } + + var table = search_tag_by_class (doc->children, "table", "user"); + if (table == null) { + stderr.printf ("Error: does not contain user table\n"); + return null; + } + + var my_info = new MyInformation (); + + Xml.Node* n; + for (n = table->children; n != null; n = n->next) { + if (n->name == "tr") { + var n2 = n->children; + if (n2 == null || n2->name != "td" || + n2->children == null || n2->children->name != "text") + continue; + + string text = n2->children->content; + + n2 = n2->next; + if (n2 != null && n2->name == "text") + n2 = n2->next; + if (n2 == null || n2->name != "td" || + n2->children == null || n2->children->name != "text") + continue; + + string content = n2->children->content; + + switch (text) { + case "Anrede": + my_info.gender = (content == "Herr") ? MyInformation.Gender.MALE : MyInformation.Gender.FEMALE; + continue; + case "Titel": + my_info.title = content; + continue; + case "Vorname": + my_info.first_name = content; + continue; + case "Name": + my_info.last_name = content; + continue; + case "Geburtsdatum": + // my_info.birthday = ... + continue; + case "registriert seit": + // my_info.registered_since = content; + continue; + // default: + // print ("\t%s=%s\n", text, content); + // break; + } + + text = content; + + n2 = n2->next; + if (n2 != null && n2->name == "text") + n2 = n2->next; + if (n2 == null || n2->name != "td") + continue; + + if (n2->children != null && n2->children->name != "text") + content = n2->children->content; + else + content = ""; + + switch (text) { + case "Straße, Nr.": + my_info.street = content; + my_info.number = ""; + continue; + case "PLZ, Ort": + my_info.zip_code = ""; + my_info.city = content; + continue; + case "Land": + my_info.country = content; + continue; + case "Telefon 1": + my_info.phone1 = content; + continue; + case "Telefon 2": + my_info.phone2 = content; + continue; + case "Telefon 3": + my_info.phone3 = content; + continue; + case "Handy": + my_info.cell = content; + continue; + case "Email 1": + my_info.email1 = content; + continue; + case "Email 2": + my_info.email2 = content; + continue; + case "Raucher": + // FIXME + my_info.smoker = false; + continue; + case "ADAC-Mitglied": + // FIXME + my_info.adac_member = false; + continue; + // default: + // print ("\"%s\"=\"%s\"\n", text, content); + // break; + } + } + } + +/* + + Anrede + Herr + + + Titel + + -- + + ... +*/ + return my_info; + } + + public string get_my_offers_url () { + return HTTP_BASE_URI + "/lifts/mysinglelifts"; + } + + public async List? get_my_offers () { + var doc = yield get_html_document (get_my_offers_url ()); + if (doc == null) { + stderr.printf ("Error: parsing failed\n"); + return null; + } + + var table = search_tag_by_class (doc->children, "table", "list"); + if (table == null) { + stderr.printf ("Error: does not contain user table\n"); + return null; + } + + var list = new List (); + for (var n = table->children; n != null; n = n->next) { + if (n->name == "tr") { + var lift = parse_offer_row (n); + if (lift != null) // Skip the title row + list.append ((owned) lift); + } + } + + if (table->next != null && table->next->name == "div") { + var text = get_child_text_content (table->next); + if (text != null) { + print ("\"%s\"\n", text); + if (text == "Sie haben derzeit keine einmaligen Fahrten eingetragen") { + print ("NO ENTRIES\n"); + } + } + } + + return list; + } + + Lift? parse_offer_row (Xml.Node *tr) { + var lift = new Lift (); + + // checkbox + var td = get_next_td (tr->children); + if (td == null) + return null; + + // action + td = get_next_td (td->next); + if (td == null) + return null; + // FIXME: get uri + + // type + td = get_next_td (td->next); + if (td == null) + return null; + var text = get_child_text_content (td); + if (text == null) + return null; + // FIXME == + if (text != "Mitfahrer") + return null; + + // point of departure + td = get_next_td (td->next); + if (td == null) + return null; + text = get_child_text_content (td); + if (text == null) + return null; + lift.city_from = text; + + // point of arrival + td = get_next_td (td->next); + if (td == null) + return null; + text = get_child_text_content (td); + if (text == null) + return null; + lift.city_to = text; + + // date + td = get_next_td (td->next); + if (td == null) + return null; + text = get_child_text_content (td); + if (text == null) + return null; + parse_date (text, out lift.time); + + // time + td = get_next_td (td->next); + if (td == null) + return null; + text = get_child_text_content (td); + if (text == null) + return null; + parse_time (text, out lift.time); + + // active? + td = get_next_td (td->next); + if (td == null) + return null; + var a = td->children; + if (a == null || a->name != "a") + return null; + text = a->get_prop ("class"); + if (text == "status icon icon_ajax_active") + lift.flags |= LiftFlags.ACTIVE; + + return lift; + } + + Xml.Node* get_next_td (Xml.Node *n) { + while (n != null) { + if (n->name == "td") + return n; + n = n->next; + } + return null; + } + + unowned string get_child_text_content (Xml.Node *n) { + if (n->children != null && n->children->name == "text") + return n->children->content; + else + return null; + } + Xml.Node* search_tag_by_property (Xml.Node* node, string tag, string prop, string val) requires (node != null) { for (var n = node; n != null; n = n->next) { if (n->name == tag && n->get_prop (prop) == val) @@ -536,9 +970,11 @@ public class AdacMitfahrclub { void parse_date (string date, out Time time) { int year; - if (date.length == 11) + if (date.length == 12) // "Mo, 01.02.03" + date = date.offset (4); + else if (date.length == 11) // "Mo 01.02.03" date = date.offset (3); - if (date.length != 8) + if (date.length != 8) // "01.02.03" return; var res = date.scanf ("%02d.%02d.%02d", out time.day, out time.month, out year); time.year = year + 2000;