SMOKER = 1,
NON_SMOKER = 2,
ADAC_MEMBER = 4,
- WOMEN_ONLY = 8
+ WOMEN_ONLY = 8,
+ ACTIVE = 16,
}
public class Lift : Object {
public string phone;
public string phone2;
public string email;
+ public string email_image_uri;
public string description;
public string modified;
}
}
-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;
- Soup.Message message;
+ Curl.EasyHandle curl;
List<City> 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) {
- 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 ("<div id=\"flashMessage\" class=\"message\">Die eingegebenen Zugangsdaten konnten nicht gefunden werden. Bitte versuchen Sie es erneut.</div>")) {
+ 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)
}
public async unowned List<City>? download_city_list () {
- var doc = yield get_html_document (BASE_URI);
+ var doc = yield get_html_document (HTTP_BASE_URI);
if (doc == null) {
- print ("Error: parsing failed");
- print ("%s\n", (string) message.response_body.data);
+ stderr.printf ("Error: parsing failed\n");
return null;
}
var form = search_tag_by_id (doc->children, "form", "search_national_form");
if (form == null) {
- print ("Error: does not contain search_national_form");
- print ("%s\n", (string) message.response_body.data);
+ stderr.printf ("Error: does not contain search_national_form\n");
return null;
}
var select = search_tag_by_name (form->children, "select", "city_from");
if (select == null) {
- print ("Error: does not contain city_from");
- print ("%s\n", (string) message.response_body.data);
+ stderr.printf ("Error: does not contain city_from\n");
return null;
}
return result;
}
- public async List<Lift>? 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 (
tolerance
);
- var doc = yield get_html_document (url);
+ return url;
+ }
+
+ public async List<Lift>? 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) {
- print ("Error: parsing failed");
- print ("%s\n", (string) message.response_body.data);
+ stderr.printf ("Error: parsing failed\n");
return null;
}
var table = search_tag_by_class (doc->children, "table", "list p_15");
if (table == null) {
- print ("Error: does not contain list p_15 table");
- print ("%s\n", (string) message.response_body.data);
+ stderr.printf ("Error: does not contain list p_15 table\n");
return null;
}
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);
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) {
- print ("Error: parsing failed");
- print ("%s\n", (string) message.response_body.data);
+ stderr.printf ("Error: parsing failed\n");
return false;
}
var table = search_tag_by_class (doc->children, "table", "lift");
if (table == null) {
- print ("Error: does not contain lift table");
- print ("%s\n", (string) message.response_body.data);
+ stderr.printf ("Error: does not contain lift table\n");
return false;
}
if (n2->children->name == "img") {
// FIXME: email image
- // n2->children->get_prop ("src"))
+ lift.email_image_uri = n2->children->get_prop ("src");
continue;
}
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;
}
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;
+ }
+ }
+ }
+
+/*
+ <tr class="head top">
+ <td width="150" class="label">Anrede</td>
+ <td width="400">Herr</td>
+ </tr>
+ <tr class="head">
+ <td class="label">Titel</td>
+
+ <td>--</td>
+ </tr>
+ ...
+*/
+ return my_info;
+ }
+
+ public string get_my_offers_url () {
+ return HTTP_BASE_URI + "/lifts/mysinglelifts";
+ }
+
+ public async List<Lift>? 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<Lift> ();
+ 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)
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;