Code now expects refresh tokens and requests its own access tokens (not tested)
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
/bin/
|
||||
/target/
|
||||
/data.zip
|
||||
|
||||
+274
-123
@@ -11,10 +11,12 @@ import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
@@ -30,19 +32,20 @@ import org.json.simple.parser.ParseException;
|
||||
public class Main
|
||||
{
|
||||
public static final String baseUrl = "https://www.strava.com/api/v3/";
|
||||
public static final String tokenUrl = "https://www.strava.com/api/v3/oauth/token";
|
||||
private static int athleteId = 0;
|
||||
private static JSONParser parser = new JSONParser();
|
||||
private static String testRequest;
|
||||
private static File errorFile;
|
||||
|
||||
|
||||
private static void writeError(String text)
|
||||
{
|
||||
if(errorFile==null)
|
||||
if (errorFile == null)
|
||||
{
|
||||
errorFile = new File("error_"+System.currentTimeMillis()+".txt");
|
||||
errorFile = new File("error_" + System.currentTimeMillis() + ".txt");
|
||||
}
|
||||
|
||||
try(BufferedWriter bout = new BufferedWriter(new FileWriter(errorFile, true)))
|
||||
|
||||
try (BufferedWriter bout = new BufferedWriter(new FileWriter(errorFile, true)))
|
||||
{
|
||||
bout.write(text);
|
||||
bout.newLine();
|
||||
@@ -51,18 +54,20 @@ public class Main
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* For testing: ensures the test requests are used
|
||||
*
|
||||
* @param testrequest Request to be used
|
||||
*/
|
||||
static void setTest(String testrequest)
|
||||
{
|
||||
testRequest=testrequest;
|
||||
testRequest = testrequest;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves the data of one athlete into a temp file and returns it.
|
||||
*
|
||||
* @param token Identifier / authorization token of the athlete
|
||||
* @return created temp file
|
||||
*/
|
||||
@@ -74,22 +79,22 @@ public class Main
|
||||
|
||||
// for each activity: save streams
|
||||
JSONArray allActivities = new JSONArray();
|
||||
int simpleActivityId=0;
|
||||
for (String id: activities.keySet())
|
||||
int simpleActivityId = 0;
|
||||
for (String id : activities.keySet())
|
||||
{
|
||||
JSONObject data = addStreams(id, activities.get(id), token);
|
||||
data.put("activity_id", simpleActivityId);
|
||||
allActivities.add(data);
|
||||
simpleActivityId++;
|
||||
}
|
||||
|
||||
// get general information
|
||||
|
||||
// get general information
|
||||
JSONObject athleteInfo = saveGeneralInformation(token);
|
||||
athleteInfo.put("activities", allActivities);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
File temp = File.createTempFile("Athlete_" +athleteId , ".json");
|
||||
File temp = File.createTempFile("Athlete_" + athleteId, ".json");
|
||||
temp.deleteOnExit();
|
||||
BufferedWriter bw = new BufferedWriter(new FileWriter(temp));
|
||||
bw.write(athleteInfo.toString());
|
||||
@@ -98,101 +103,106 @@ public class Main
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
writeError("Athlete "+athleteId+": Error writing temp file: "+e.toString());
|
||||
writeError("Athlete " + athleteId + ": Error writing temp file: " + e.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds the streams to the given activity
|
||||
* @param id Strava id of the activity
|
||||
* @param data general information of the activity
|
||||
*
|
||||
* @param id Strava id of the activity
|
||||
* @param data general information of the activity
|
||||
* @param token Identifier / authorization token of the athlete
|
||||
* @return The data with the added streams
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static JSONObject addStreams(String id, JSONObject data, String token)
|
||||
{
|
||||
//TODO: Array representation is not tested
|
||||
String requestUrlExtension = "activities/"+id+"/streams?keys=[time, distance, latlng, altitude, "
|
||||
// TODO: Array representation is not tested
|
||||
String requestUrlExtension = "activities/" + id + "/streams?keys=[time, distance, latlng, altitude, "
|
||||
+ "velocity_smooth, heartrate, cadence, watts, temp, moving, grade_smooth]&key_by_type=";
|
||||
String json = makeOneRequest(requestUrlExtension, token);
|
||||
String json = makeOneGetRequest(requestUrlExtension, token);
|
||||
String type = "type";
|
||||
|
||||
|
||||
Object obj;
|
||||
try
|
||||
{
|
||||
obj = parser.parse(json);
|
||||
JSONArray listOfStreams = (JSONArray) obj;
|
||||
for (int i=0; i<listOfStreams.size(); i++)
|
||||
for (int i = 0; i < listOfStreams.size(); i++)
|
||||
{
|
||||
JSONObject oneStream = (JSONObject) listOfStreams.get(i);
|
||||
data.put("stream_"+oneStream.get(type).toString(), oneStream);
|
||||
data.put("stream_" + oneStream.get(type).toString(), oneStream);
|
||||
}
|
||||
}
|
||||
catch (ParseException | NumberFormatException e)
|
||||
{
|
||||
writeError("Athlete "+athleteId+": Error parsing json (Streams): "+e.toString());
|
||||
writeError("Athlete " + athleteId + ": Error parsing json (Streams): " + e.toString());
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gathers all activities of a user, extracts the general information and the ids.
|
||||
* Gathers all activities of a user, extracts the general information and the
|
||||
* ids.
|
||||
*
|
||||
* @param token Identifier / authorization token of the athlete
|
||||
* @return A Map with key = Strava Id of an activity and value = JSONObject with the general information of the activity
|
||||
* @return A Map with key = Strava Id of an activity and value = JSONObject with
|
||||
* the general information of the activity
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static Map<String, JSONObject> getActivities(String token)
|
||||
{
|
||||
Map<String, JSONObject> result = new HashMap<>();
|
||||
/*
|
||||
* Possible values = AlpineSki, BackcountrySki, Canoeing, Crossfit, EBikeRide, Elliptical, Golf,
|
||||
* Handcycle, Hike, IceSkate, InlineSkate, Kayaking, Kitesurf, NordicSki, Ride, RockClimbing,
|
||||
* RollerSki, Rowing, Run, Sail, Skateboard, Snowboard, Snowshoe, Soccer, StairStepper,
|
||||
* StandUpPaddling, Surfing, Swim, Velomobile, VirtualRide, VirtualRun, Walk, WeightTraining,
|
||||
* Wheelchair, Windsurf, Workout, Yoga
|
||||
* Possible values = AlpineSki, BackcountrySki, Canoeing, Crossfit, EBikeRide,
|
||||
* Elliptical, Golf, Handcycle, Hike, IceSkate, InlineSkate, Kayaking, Kitesurf,
|
||||
* NordicSki, Ride, RockClimbing, RollerSki, Rowing, Run, Sail, Skateboard,
|
||||
* Snowboard, Snowshoe, Soccer, StairStepper, StandUpPaddling, Surfing, Swim,
|
||||
* Velomobile, VirtualRide, VirtualRun, Walk, WeightTraining, Wheelchair,
|
||||
* Windsurf, Workout, Yoga
|
||||
*/
|
||||
String type = "type";
|
||||
String type = "type";
|
||||
String timezone = "timezone";
|
||||
String start = "start_date_local"; //Start date measured in users time zone
|
||||
String start = "start_date_local"; // Start date measured in users time zone
|
||||
String id = "id";
|
||||
|
||||
|
||||
int pageIndex = 1;
|
||||
while(true)
|
||||
while (true)
|
||||
{
|
||||
String requestExtension = "athlete/activities?before=&after=&page="+pageIndex+"&per_page=";
|
||||
String json = makeOneRequest(requestExtension, token);
|
||||
if (json.isEmpty()||json.isBlank()||json.equals("")) //don't know where the last page is...
|
||||
String requestExtension = "athlete/activities?before=&after=&page=" + pageIndex + "&per_page=";
|
||||
String json = makeOneGetRequest(requestExtension, token);
|
||||
if (json.isEmpty() || json.isBlank() || json.equals("")) // don't know where the last page is...
|
||||
{
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
Object obj;
|
||||
try
|
||||
{
|
||||
obj = parser.parse(json);
|
||||
JSONArray listOfActivites = (JSONArray) obj;
|
||||
for (int i=0; i<listOfActivites.size(); i++)
|
||||
for (int i = 0; i < listOfActivites.size(); i++)
|
||||
{
|
||||
JSONObject oneActivity = (JSONObject) listOfActivites.get(i);
|
||||
|
||||
JSONObject toSave = new JSONObject();
|
||||
toSave.put(type, oneActivity.get(type));
|
||||
toSave.put(type, oneActivity.get(type));
|
||||
toSave.put(timezone, oneActivity.get(timezone));
|
||||
toSave.put(start, oneActivity.get(start));
|
||||
toSave.put("athlete_id", athleteId);
|
||||
|
||||
|
||||
Object idObj = oneActivity.get(id);
|
||||
if(idObj != null)
|
||||
if (idObj != null)
|
||||
{
|
||||
result.put(oneActivity.get(id).toString(), toSave);
|
||||
result.put(oneActivity.get(id).toString(), toSave);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ParseException | NumberFormatException e)
|
||||
{
|
||||
writeError("Athlete "+athleteId+": Error parsing json (Activities): "+e.toString());
|
||||
writeError("Athlete " + athleteId + ": Error parsing json (Activities): " + e.toString());
|
||||
}
|
||||
pageIndex++;
|
||||
}
|
||||
@@ -201,22 +211,23 @@ public class Main
|
||||
|
||||
/**
|
||||
* Extracts an athletes general information.
|
||||
*
|
||||
* @param token Identifier / authorization token of the athlete
|
||||
* @return extracted data or null if there was an error
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static JSONObject saveGeneralInformation(String token)
|
||||
{
|
||||
String sex = "sex"; //Possible values = M, F
|
||||
String sex = "sex"; // Possible values = M, F
|
||||
String country = "country";
|
||||
String date_pref = "date_preference";
|
||||
String meas_pref = "measurement_preference"; //Possible values = feet, meters
|
||||
String meas_pref = "measurement_preference"; // Possible values = feet, meters
|
||||
String weight = "weight";
|
||||
|
||||
String json = makeOneRequest("athlete", token);
|
||||
|
||||
|
||||
String json = makeOneGetRequest("athlete", token);
|
||||
|
||||
JSONObject toSave = new JSONObject();
|
||||
try
|
||||
try
|
||||
{
|
||||
Object obj = parser.parse(json);
|
||||
JSONObject data = (JSONObject) obj;
|
||||
@@ -226,108 +237,210 @@ public class Main
|
||||
toSave.put(meas_pref, data.get(meas_pref));
|
||||
toSave.put(weight, data.get(weight));
|
||||
toSave.put("id", athleteId);
|
||||
|
||||
return toSave;
|
||||
|
||||
return toSave;
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
writeError("Athlete "+athleteId+": Error parsing general information.");
|
||||
return null;
|
||||
writeError("Athlete " + athleteId + ": Error parsing general information.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Zip a list of files in one .zip file.
|
||||
*
|
||||
* @param files HasMap of <intended Filename, File> which should be zipped
|
||||
* @throws IOException If there was an error zipping
|
||||
*/
|
||||
static void zipAllFiles(Map<String,File> files) throws IOException
|
||||
static void zipAllFiles(Map<String, File> files) throws IOException
|
||||
{
|
||||
FileOutputStream fos = new FileOutputStream("data.zip");
|
||||
ZipOutputStream zipOut = new ZipOutputStream(fos);
|
||||
for (String key : files.keySet())
|
||||
{
|
||||
File fileToZip = files.get(key);
|
||||
FileInputStream fis = new FileInputStream(fileToZip);
|
||||
ZipEntry zipEntry = new ZipEntry(key);
|
||||
zipOut.putNextEntry(zipEntry);
|
||||
FileOutputStream fos = new FileOutputStream("data.zip");
|
||||
ZipOutputStream zipOut = new ZipOutputStream(fos);
|
||||
for (String key : files.keySet())
|
||||
{
|
||||
File fileToZip = files.get(key);
|
||||
FileInputStream fis = new FileInputStream(fileToZip);
|
||||
ZipEntry zipEntry = new ZipEntry(key);
|
||||
zipOut.putNextEntry(zipEntry);
|
||||
|
||||
byte[] bytes = new byte[1024];
|
||||
int length;
|
||||
while((length = fis.read(bytes)) >= 0) {
|
||||
zipOut.write(bytes, 0, length);
|
||||
}
|
||||
fis.close();
|
||||
}
|
||||
zipOut.close();
|
||||
fos.close();
|
||||
byte[] bytes = new byte[1024];
|
||||
int length;
|
||||
while ((length = fis.read(bytes)) >= 0)
|
||||
{
|
||||
zipOut.write(bytes, 0, length);
|
||||
}
|
||||
fis.close();
|
||||
}
|
||||
zipOut.close();
|
||||
fos.close();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles one request to the API
|
||||
* Handles one GET request to the API
|
||||
*
|
||||
* @param requestUrlExtension Extension for the baseUrl (without '/')
|
||||
* @param token Identification / authorization token of the athlete
|
||||
* @param token Identification / authorization token of the
|
||||
* athlete
|
||||
* @return The response as a String, an empty String in case of error.
|
||||
*/
|
||||
static String makeOneRequest(String requestUrlExtension, String token)
|
||||
static String makeOneGetRequest(String requestUrlExtension, String token)
|
||||
{
|
||||
//TODO: http requests are not tested
|
||||
if(testRequest!= null)
|
||||
// TODO: http requests are not tested
|
||||
if (testRequest != null)
|
||||
{
|
||||
String varTestRequest = testRequest.replaceAll("\\:\\s*([0-9]{15,})\\,", ":\"$1\",");
|
||||
testRequest=null;
|
||||
testRequest = null;
|
||||
return varTestRequest;
|
||||
}
|
||||
HttpURLConnection connection = null;
|
||||
StringBuilder result = new StringBuilder();
|
||||
try
|
||||
HttpsURLConnection connection = null;
|
||||
try
|
||||
{
|
||||
//Create connection
|
||||
URL url = new URL(baseUrl+requestUrlExtension);
|
||||
connection = (HttpsURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setRequestProperty("Authorization", "Bearer [["+token+"]]");
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
|
||||
if (responseCode != HttpURLConnection.HTTP_OK)
|
||||
{
|
||||
writeError("Wrong response code: "+responseCode);
|
||||
return "";
|
||||
}
|
||||
try (Reader reader = new BufferedReader(
|
||||
new InputStreamReader(connection.getInputStream(), Charset.forName(StandardCharsets.UTF_8.name()))))
|
||||
{
|
||||
int c = reader.read();
|
||||
while (c != -1)
|
||||
{
|
||||
result.append((char) c);
|
||||
c = reader.read();
|
||||
}
|
||||
}
|
||||
// Create connection
|
||||
URL url = new URL(baseUrl + requestUrlExtension);
|
||||
connection = (HttpsURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setRequestProperty("Authorization", "Bearer " + token);
|
||||
|
||||
return getResponse(connection);
|
||||
}
|
||||
catch(IOException e)
|
||||
catch (IOException e)
|
||||
{
|
||||
writeError("Error while handling request: "+e.toString());
|
||||
writeError("Error while handling GET request: " + e.toString());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the response from the connection and returns it as a String
|
||||
*
|
||||
* @param connection Connection to the site
|
||||
* @return Response as a String
|
||||
* @throws IOException in case of error
|
||||
*/
|
||||
static String getResponse(HttpsURLConnection connection) throws IOException
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode != HttpURLConnection.HTTP_OK)
|
||||
{
|
||||
writeError("Wrong response code: " + responseCode);
|
||||
return "";
|
||||
}
|
||||
//Numbers too long for int or long are turned into Strings
|
||||
return result.toString().replaceAll("\\:\\s*([0-9]{15,})\\,", ":\"$1\",");
|
||||
|
||||
try (Reader reader = new BufferedReader(
|
||||
new InputStreamReader(connection.getInputStream(), Charset.forName(StandardCharsets.UTF_8.name()))))
|
||||
{
|
||||
int c = reader.read();
|
||||
while (c != -1)
|
||||
{
|
||||
result.append((char) c);
|
||||
c = reader.read();
|
||||
}
|
||||
}
|
||||
|
||||
// Numbers too long for int or long are turned into Strings
|
||||
return result.toString().replaceAll("\\:\\s*([0-9]{15,})\\,", ":\"$1\",");
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for generating the accessToken
|
||||
*
|
||||
* @param data Triplet containing: a the client_id, b the client_secret, c the
|
||||
* refresh_token
|
||||
* @return The response as a String, an empty String in case of error.
|
||||
*/
|
||||
static String makeOnePostRequest(Triplet data)
|
||||
{
|
||||
// TODO: http requests are not tested
|
||||
HttpsURLConnection connection = null;
|
||||
try
|
||||
{
|
||||
// Create connection
|
||||
URL url = new URL(tokenUrl);
|
||||
connection = (HttpsURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
Map<String, String> params = new LinkedHashMap<>();
|
||||
params.put("client_id", data.getA());
|
||||
params.put("client_secret", data.getB());
|
||||
params.put("grant_type", "refresh_token");
|
||||
params.put("refresh_token", data.getC());
|
||||
|
||||
StringBuilder postData = new StringBuilder();
|
||||
for (String key : params.keySet())
|
||||
{
|
||||
if (postData.length() != 0)
|
||||
{
|
||||
postData.append('&');
|
||||
}
|
||||
postData.append(URLEncoder.encode(key, "UTF-8"));
|
||||
postData.append('=');
|
||||
postData.append(URLEncoder.encode(params.get(key), "UTF-8"));
|
||||
}
|
||||
byte[] postDataBytes = postData.toString().getBytes("UTF-8");
|
||||
|
||||
connection.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length));
|
||||
connection.setDoOutput(true);
|
||||
connection.getOutputStream().write(postDataBytes);
|
||||
|
||||
return getResponse(connection);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
writeError("Error while handling POST request: " + e.toString());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the old refresh token to strava and retrieves a new one and an access token
|
||||
* @param refreshInfo Refresh data with a the cliend_id, b the client_secret and c the refresh_token
|
||||
* @return A tuple with a the access_token and b the new refresh_token or {@code null} if there was an error
|
||||
*/
|
||||
static Tuple getAccessToken(Triplet refreshInfo)
|
||||
{
|
||||
String json = makeOnePostRequest(refreshInfo);
|
||||
try
|
||||
{
|
||||
Object obj = parser.parse(json);
|
||||
JSONObject data = (JSONObject) obj;
|
||||
return new Tuple(data.get("access_token").toString(), data.get("refresh_token").toString());
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
writeError("Athlete " + athleteId + ": Error parsing general information.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
//TODO: tokens need to be added by Enduco
|
||||
List<String> tokens = new ArrayList<>();
|
||||
|
||||
//TODO: Should we expect problems with memory??
|
||||
Map<String,File> allFiles = new HashMap<>();
|
||||
for (String token : tokens)
|
||||
// TODO: tokens need to be added by Enduco
|
||||
|
||||
// Triplet a is client_id, b is client_secret, c is refresh_token
|
||||
List<Triplet> refreshTokens = new ArrayList<>();
|
||||
// don't know if you need it but here you will find the new refresh tokens after
|
||||
// the requests
|
||||
List<Triplet> newRefreshTokens = new ArrayList<>();
|
||||
|
||||
// TODO: Should we expect problems with memory??
|
||||
Map<String, File> allFiles = new HashMap<>();
|
||||
for (Triplet oneUser : refreshTokens)
|
||||
{
|
||||
File athlete = oneAthlete(token);
|
||||
if(athlete != null)
|
||||
// a is access_token and b is new refresh_token
|
||||
Tuple withAccessToken = getAccessToken(oneUser);
|
||||
if (withAccessToken == null)
|
||||
{
|
||||
allFiles.put("Athlete_"+athleteId+".json", athlete);
|
||||
writeError("Coulnd't get new access token for client "+athleteId);
|
||||
}
|
||||
newRefreshTokens.add(new Triplet(oneUser.getA(), oneUser.getB(), withAccessToken.getB()));
|
||||
|
||||
File athlete = oneAthlete(withAccessToken.getA());
|
||||
if (athlete != null)
|
||||
{
|
||||
allFiles.put("Athlete_" + athleteId + ".json", athlete);
|
||||
}
|
||||
athleteId++;
|
||||
}
|
||||
@@ -340,4 +453,42 @@ public class Main
|
||||
writeError("Files coulnd't be zipped");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Tuple
|
||||
{
|
||||
private String a;
|
||||
private String b;
|
||||
|
||||
public Tuple(String a, String b)
|
||||
{
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public String getA()
|
||||
{
|
||||
return a;
|
||||
}
|
||||
|
||||
public String getB()
|
||||
{
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
class Triplet extends Tuple
|
||||
{
|
||||
private String c;
|
||||
|
||||
public Triplet(String a, String b, String c)
|
||||
{
|
||||
super(a, b);
|
||||
this.c = c;
|
||||
}
|
||||
|
||||
public String getC()
|
||||
{
|
||||
return c;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user