/* * Copyright 2012-2013 Coronastreet Networks * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License */ package org.coronastreet.gpxconverter; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.swing.JTextArea; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.CookieStore; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.content.InputStreamBody; import org.apache.http.entity.mime.content.StringBody; import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import org.apache.xml.serialize.OutputFormat; import org.apache.xml.serialize.XMLSerializer; import org.json.JSONArray; import org.json.JSONObject; import org.json.JSONException; import org.jsoup.Jsoup; import org.jsoup.select.Elements; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; @SuppressWarnings("deprecation") public class StravaForm { private String loginURL = "https://www.strava.com/login"; private String sessionURL = "https://www.strava.com/session"; private String uploadFormURL = "http://app.strava.com/upload/select"; private String uploadURL = "http://app.strava.com/upload/files"; private CloseableHttpClient httpClient; private HttpContext localContext; private CookieStore cookieStore; private String email; private String password; private String tripName; private String activityType; private Document outDoc; private String rideStartTime; private String deviceType; private boolean hasAltimeter = false; private String outFile = "C:\\Temp\\temp.tcx"; private JTextArea statusTextArea; private List<Trkpt> trackPoints; public StravaForm() { } @SuppressWarnings("unused") private String convertDoc() { OutputFormat format = new OutputFormat(outDoc); format.setIndenting(true); StringWriter stringOut = new StringWriter (); XMLSerializer serializer = new XMLSerializer(stringOut, format); try { serializer.serialize(outDoc); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return stringOut.toString(); } public boolean processData() { boolean success = false; //load the output template loadTCXTemplate(); setIdAndStartTime(); setDeviceType(); // Add the track data we imported to the output document if (addTrackData()) { success = true; } // Spit out the TCX file // printOutFile(); success = true; return success; } @SuppressWarnings("unused") private void printOutFile(){ try { OutputFormat format = new OutputFormat(outDoc); format.setIndenting(true); XMLSerializer serializer = new XMLSerializer(new FileOutputStream(new File(outFile)), format); log("Writing out TCX file."); serializer.serialize(outDoc); } catch(IOException ie) { ie.printStackTrace(); } } private void setIdAndStartTime() { NodeList nl = outDoc.getElementsByTagName("Activity"); NodeList nl2 = ((Element) nl.item(0)).getElementsByTagName("Id"); if(nl2 != null && nl2.getLength() > 0) { Element el = (Element)nl2.item(0); el.appendChild(outDoc.createTextNode(rideStartTime)); } NodeList nl3 = ((Element) nl.item(0)).getElementsByTagName("Lap"); if(nl3 != null && nl3.getLength() > 0) { Element el = (Element)nl3.item(0); el.setAttribute("StartTime", rideStartTime); } } private Element createTrackPointElement(Trkpt tp){ Element eTrackpoint = outDoc.createElement("Trackpoint"); //create time element and time text node and attach it to the trackpoint Element eTime = outDoc.createElement("Time"); eTime.appendChild(outDoc.createTextNode(tp.getTime())); eTrackpoint.appendChild(eTime); //create elevation element and elevation text node and attach it to the trackpoint Element eElevation = outDoc.createElement("AltitudeMeters"); eElevation.appendChild(outDoc.createTextNode(tp.getElevation())); eTrackpoint.appendChild(eElevation); //create Speed Sensor element and attach it to the trackpoint Element eSensorState = outDoc.createElement("SensorState"); eSensorState.appendChild(outDoc.createTextNode("Absent")); eTrackpoint.appendChild(eSensorState); // Create Lat/Long and add them to Position Element ePosition = outDoc.createElement("Position"); Element eLatitudeDegrees = outDoc.createElement("LatitudeDegrees"); eLatitudeDegrees.appendChild(outDoc.createTextNode(tp.getLat())); Element eLongitudeDegrees = outDoc.createElement("LongitudeDegrees"); eLongitudeDegrees.appendChild(outDoc.createTextNode(tp.getLon())); ePosition.appendChild(eLongitudeDegrees); ePosition.appendChild(eLatitudeDegrees); eTrackpoint.appendChild(ePosition); //create HeartRate and add it to the trackpoint Element eHR = outDoc.createElement("HeartRateBpm"); eHR.setAttribute("xsi:type", "HeartRateInBeatsPerMinute_t"); Element eHRValue = outDoc.createElement("Value"); eHRValue.appendChild(outDoc.createTextNode(tp.getHr())); eHR.appendChild(eHRValue); eTrackpoint.appendChild(eHR); //create Cadence element text node and add it to the trackpoint Element eCad = outDoc.createElement("Cadence"); eCad.appendChild(outDoc.createTextNode(tp.getCad())); eTrackpoint.appendChild(eCad); //create Temperature element text node and add it to the trackpoint Element eTemp = outDoc.createElement("Temperature"); eTemp.appendChild(outDoc.createTextNode(tp.getTemp())); eTrackpoint.appendChild(eTemp); return eTrackpoint; } private void setDeviceType() { // Strava only recognizes Garmin devices for Altimeter stuff // Everything else shows up as "Mobile" if (hasAltimeter) { deviceType = "Garmin Edge 800"; } else { deviceType = "Garmin Edge 200"; } NodeList nl = outDoc.getElementsByTagName("Activity"); NodeList nl1 = ((Element) nl.item(0)).getElementsByTagName("Creator"); NodeList nl2 = ((Element) nl1.item(0)).getElementsByTagName("Name"); if(nl2 != null && nl2.getLength() > 0) { Element el = (Element)nl2.item(0); el.appendChild(outDoc.createTextNode(deviceType)); } } private boolean addTrackData() { boolean success = false; // Get the Track element Element track = null; Element docEle = outDoc.getDocumentElement(); // GRabbing the track node. in theory, the node list should always return 1 NodeList nl = docEle.getElementsByTagName("Track"); if(nl != null && nl.getLength() > 0) { track = (Element)nl.item(0); } int trkCounter = 0; Iterator<Trkpt> it = trackPoints.iterator(); while(it.hasNext()) { Trkpt t = (Trkpt)it.next(); Element tp = createTrackPointElement(t); //dumpNode(tp); track.appendChild(tp); trkCounter++; } log("Added " + trkCounter + " trackpoints to the template."); if (trkCounter >= 1) { success = true; } return success; } public void upload() { //httpClient = new DefaultHttpClient(); httpClient = HttpClientBuilder.create().build(); localContext = new BasicHttpContext(); cookieStore = new BasicCookieStore(); localContext.setAttribute(HttpClientContext.COOKIE_STORE, cookieStore); //httpClient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY); if(doLogin()) { //log("Ok....logged in..."); try { // Have to fetch the form to get the CSRF Token HttpGet get = new HttpGet(uploadFormURL); HttpResponse formResponse = httpClient.execute(get, localContext); //log("Fetched the upload form...: " + formResponse.getStatusLine()); org.jsoup.nodes.Document doc = Jsoup.parse(EntityUtils.toString(formResponse.getEntity())); String csrftoken, csrfparam; Elements metalinksParam = doc.select("meta[name=csrf-param]"); if (!metalinksParam.isEmpty()) { csrfparam = metalinksParam.first().attr("content"); } else { csrfparam = null; log("Missing csrf-param?"); } Elements metalinksToken = doc.select("meta[name=csrf-token]"); if (!metalinksToken.isEmpty()) { csrftoken = metalinksToken.first().attr("content"); } else { csrftoken = null; log("Missing csrf-token?"); } HttpPost request = new HttpPost(uploadURL); request.setHeader("X-CSRF-Token", csrftoken); MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); entity.addPart("method", new StringBody("post")); entity.addPart("new_uploader", new StringBody("1")); entity.addPart(csrfparam, new StringBody(csrftoken)); entity.addPart("files[]", new InputStreamBody(document2InputStream(outDoc), "application/octet-stream", "temp.tcx")); // Need to do this bit because without it you can't disable chunked encoding, and Strava doesn't support chunked. ByteArrayOutputStream bArrOS = new ByteArrayOutputStream(); entity.writeTo(bArrOS); bArrOS.flush(); ByteArrayEntity bArrEntity = new ByteArrayEntity(bArrOS.toByteArray()); bArrOS.close(); bArrEntity.setChunked(false); bArrEntity.setContentEncoding(entity.getContentEncoding()); bArrEntity.setContentType(entity.getContentType()); request.setEntity(bArrEntity); HttpResponse response = httpClient.execute(request, localContext); if (response.getStatusLine().getStatusCode() != 200) { log("Failed to Upload"); HttpEntity en = response.getEntity(); if (en != null) { String output = EntityUtils.toString(en); log(output); } } else { HttpEntity ent = response.getEntity(); if (ent != null) { String output = EntityUtils.toString(ent); //log(output); JSONObject userInfo = new JSONArray(output).getJSONObject(0); //log("Object: " + userInfo.toString()); if (userInfo.get("workflow").equals("Error")) { log("Upload Error: " + userInfo.get("error")); } else { log("Successful Uploaded. ID is " + userInfo.get("id")); } } } httpClient.close(); }catch (Exception ex) { log("Exception? " + ex.getMessage()); ex.printStackTrace(); // handle exception here } } else { log("Failed to upload!"); } } protected InputStream document2InputStream(Document document) throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); OutputFormat outputFormat = new OutputFormat(document); XMLSerializer serializer = new XMLSerializer(outputStream, outputFormat); serializer.serialize(document); return new ByteArrayInputStream(outputStream.toByteArray()); } protected boolean doLogin() { boolean ret = false; log("Authenticating athlete..."); try { HttpGet get = new HttpGet(loginURL); HttpResponse response = httpClient.execute(get, localContext); //log("Fetched the login form...: " + response.getStatusLine()); org.jsoup.nodes.Document doc = Jsoup.parse(EntityUtils.toString(response.getEntity())); String csrftoken, csrfparam; Elements metalinksParam = doc.select("meta[name=csrf-param]"); if (!metalinksParam.isEmpty()) { csrfparam = metalinksParam.first().attr("content"); log("Setting csrf-param to " + csrfparam); } else { csrfparam = null; log("Missing csrf-param?"); } Elements metalinksToken = doc.select("meta[name=csrf-token]"); if (!metalinksToken.isEmpty()) { csrftoken = metalinksToken.first().attr("content"); log("Setting csrf-token to " + csrftoken); } else { csrftoken = null; log("Missing csrf-token?"); } HttpPost post = new HttpPost(sessionURL); post.setHeader("Referer", "https://www.strava.com/login"); List <NameValuePair> nvps = new ArrayList <NameValuePair>(); nvps.add(new BasicNameValuePair(csrfparam, csrftoken)); nvps.add(new BasicNameValuePair("plan", "")); nvps.add(new BasicNameValuePair("email", email)); nvps.add(new BasicNameValuePair("password", password)); post.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8)); HttpResponse sessionResponse = httpClient.execute(post, localContext); if (sessionResponse.getStatusLine().getStatusCode() != 302) { log("Failed to Login. " + sessionResponse.getStatusLine().getStatusCode()); String output = EntityUtils.toString(sessionResponse.getEntity()); log(output); ret = false; } else { ret = true; } HttpEntity entity = sessionResponse.getEntity(); EntityUtils.consume(entity); }catch (Exception ex) { // handle exception here ex.printStackTrace(); } return ret; } private void loadTCXTemplate(){ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); try { DocumentBuilder db = dbf.newDocumentBuilder(); log("Loading TCX template file."); outDoc = db.parse(this.getClass().getResourceAsStream("/org/coronastreet/gpxconverter/tcxtemplate.xml")); }catch(ParserConfigurationException pce) { pce.printStackTrace(); }catch(SAXException se) { se.printStackTrace(); }catch(IOException ioe) { ioe.printStackTrace(); } } @SuppressWarnings("unused") private void dumpNode(JSONObject o) throws JSONException { log(o.toString(2)); } private void log(String s) { this.statusTextArea.append("STRAVA: " + s + "\n"); this.statusTextArea.repaint(1); } @SuppressWarnings("unused") private void log(InputStream is) { try { BufferedReader rd = new BufferedReader(new InputStreamReader(is)); String line = ""; while ((line = rd.readLine()) != null) { log(line); } } catch (Exception e) { e.printStackTrace(); } } public List<Trkpt> getTrackPoints() { return trackPoints; } public void setTrackPoints(List<Trkpt> trackPoints) { this.trackPoints = trackPoints; } public JTextArea getStatusTextArea() { return statusTextArea; } public void setStatusTextArea(JTextArea statusTextArea) { this.statusTextArea = statusTextArea; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getTripName() { return tripName; } public void setTripName(String tripName) { this.tripName = tripName; } public String getActivityType() { return activityType; } public void setActivityType(String at) { this.activityType = at; } public String getRideStartTime() { return rideStartTime; } public void setRideStartTime(String rideStartTime) { this.rideStartTime = rideStartTime; } public void setHasAltimeter(boolean hasAltimeter) { this.hasAltimeter = hasAltimeter; } }