Skip to content

Instantly share code, notes, and snippets.

@johan974
Created May 27, 2017 07:10
Show Gist options
  • Save johan974/88e09992b288560db519946ed8faf129 to your computer and use it in GitHub Desktop.
Save johan974/88e09992b288560db519946ed8faf129 to your computer and use it in GitHub Desktop.
New logging process
public class OkHttpGeocaching {
PersistentCookieStore cookieStore;
boolean forceOkHttpLogin = false;
String username;
String password;
boolean forceLogin = false;
String gcId;
Context context;
String status = "not started";
private OkHttpGeocaching() {}
public OkHttpGeocaching(Context theContext) {
context = theContext;
}
public String tryOkHttpOauth( String username, String password, boolean forceLogin, String gcId) {
this.username = username;
this.password = password;
this.forceLogin = forceLogin;
this.gcId = gcId;
try {
// **********************************************************************************
// STEP 1 - login with a persistent cookie
Request requestShowLoginPage = new Request.Builder().
url(GeocachingComAccess.login_url).
addHeader( "User-Agent", GeocachingComAccess.HTTP_USER_AGENT).
addHeader( "Pragma", "no-cache").
addHeader( "Accept-Language", "en").
addHeader("Content-Type", "application/x-www-form-urlencoded").
build();
status = "1 - starting";
cookieStore = new PersistentCookieStore( context);
CookieManager cookieManager = new CookieManager( cookieStore, CookiePolicy.ACCEPT_ALL);
// To be removed in real life. This forces a new login by removing the persistant cookies.
cookieStore.removeAll();
OkHttpClient okHttpClient = new OkHttpClient.Builder().
cookieJar(new JavaNetCookieJar(cookieManager)).
readTimeout( 20, TimeUnit.SECONDS).
connectTimeout( 15, TimeUnit.SECONDS).
build();
// Execute the call and receive a 200
Call call = okHttpClient.newCall( requestShowLoginPage);
Response response = call.execute();
if( response.code() != 200) {
Logger.v("Invalid login status: " + response.code());
status = "2 - no login page";
} else {
status = "2 - login page";
}
Logger.v( "OkHttp/01: ok");
printCookies( cookieManager);
// Check the login page is given with the request token
String text = response.body().string();
String requestVerificationToken = HtmlTagRetriever.getHtmlTagValue( text, "input", "name", "__RequestVerificationToken", "value");
if( requestVerificationToken == null || requestVerificationToken.isEmpty()) {
String errorMessage = "Could not find the new request verification token";
Logger.v("login: -> " + errorMessage);
return "No request verification token";
}
status = "2b - request verification token ok";
RequestBody formBody = new FormBody.Builder().
add( "__EVENTTARGET", "").
add( "__EVENTARGUMENT", "").
add( "Username", username).
add( "Password", password).
add( "__RequestVerificationToken", requestVerificationToken).
add( "Submit", "Log In").
add( "ctl00$ContentBody$cbRememberMe", "on").build();
Request requestLogin = new Request.Builder().
url(GeocachingComAccess.login_url).
addHeader( "User-Agent", GeocachingComAccess.HTTP_USER_AGENT).
addHeader( "Pragma", "no-cache").
addHeader( "Accept-Language", "en").
addHeader("Content-Type", "application/x-www-form-urlencoded").
post( formBody).
build();
call = okHttpClient.newCall( requestLogin);
response = call.execute();
if( response.code() != 200) {
Logger.v("Invalid login status: " + response.code());
status = "3 - Invalid login";
return status;
}
status = "3 - Valid login page";
Logger.v( "OkHttp/01: ok");
printCookies( cookieManager);
// 3 - check the login page is given with the request token
text = response.body().string();
if( text.contains( "isLoggedIn: true") == false) {
Logger.v( "Is not logged in");
status = "4 - Is not logged in";
} else {
status = "4 - Is logged in";
}
Logger.v( "OkHttp/02: content = " + text);
// **********************************************************************************
// STEP 2 - re-use the persistent cookie + get access to a geocache
Request getPageHtml = new Request.Builder().
url(GeocachingComAccess.lookup_url+ "/" + gcId).
addHeader( "User-Agent", GeocachingComAccess.HTTP_USER_AGENT).
addHeader( "Pragma", "no-cache").
addHeader( "Accept-Language", "en").
addHeader("Content-Type", "application/x-www-form-urlencoded").
build();
call = okHttpClient.newCall( getPageHtml);
response = call.execute();
if( response.code() != 200) {
status = "5 - Invalid geocache html access: " + response.code();
Logger.v( status);
return status;
}
status = "5 - Valid geocache page access";
Logger.v( status);
text = response.body().string();
String geocacheTitle;
Pattern pat = Pattern.compile(".*<meta .*?property=\"og:url\" content=\"[^&]+&amp;title=([^&^\"]+).*");
Matcher mat = pat.matcher( text);
if (mat.find()) {
geocacheTitle = mat.group(1);
status = "6 - Geocache html page found";
} else {
status = "6 - Geocache html page NOT found";
return status;
}
// https://www.geocaching.com/geocache/GC19A68_aardig?guid=0eae1de3-74c0-426b-a892-108b9630e2db"
// the guid is not needed
String urlGcIdAndTitle = GeocachingComAccess.gpx_url + "/" + gcId + "_" + geocacheTitle;
FormBody.Builder formBodyBuilder = new FormBody.Builder();
formBodyBuilder.add( "__EVENTTARGET", "").
add( "__EVENTARGUMENT", "").
add( "ctl00$ContentBody$btnGPXDL", "GPX file");
addViewstates( formBodyBuilder, text);
Request requestGpxFile = new Request.Builder().
url( urlGcIdAndTitle).
addHeader( "User-Agent", GeocachingComAccess.HTTP_USER_AGENT).
addHeader( "Pragma", "no-cache").
addHeader( "Accept-Language", "en").
addHeader("Content-Type", "application/x-www-form-urlencoded").
post( formBodyBuilder.build()).
build();
call = okHttpClient.newCall( requestGpxFile);
response = call.execute();
if( response.code() != 200) {
status = "7 - Cannot load GPX file" + response.code();
Logger.v( status );
return status;
}
status = "7 - Loading GPX file";
text = response.body().string();
if( text.contains( "<gpx xmlns:xsd")) {
status = "8 - GPX file is of GPX format";
} else {
status = "8 - GPX file is NOT of GPX format";
}
// **********************************************************************************
// STEP 3 - Log a geocache
// A - build the requeste and get the log page (as a start)
long old_gc_gid = gccodeToGCId( gcId); // this method is also used in/by c:geo
String logUrl = "https://www.geocaching.com/seek/log.aspx?ID=" + old_gc_gid + "&lcn=1";
// build the GET request
Request getLogHtml = new Request.Builder().
url( logUrl).
addHeader( "User-Agent", GeocachingComAccess.HTTP_USER_AGENT).
addHeader( "Pragma", "no-cache").
addHeader( "Accept-Language", "en").
addHeader( "Content-Type", "application/x-www-form-urlencoded").
build();
call = okHttpClient.newCall( getLogHtml);
response = call.execute();
if( response.code() != 200) {
status = "9 - Invalid access to log html page: " + response.code();
Logger.v( status);
return status;
}
status = "9 - Valid access to log html page";
Logger.v( status);
text = response.body().string();
if( text.contains( "Geocaching - New Log for")) {
status = "10 - Valid contents of the log html page";
Logger.v( status);
} else {
status = "10 - Invalid contents to log html page";
Logger.v( status);
return status;
}
// B - POST for an OATH token: https://www.geocaching.com/account/oauth/token
RequestBody oauthFormBody = new FormBody.Builder().build(); // empty
Request requestOauthToken = new Request.Builder().
url( "https://www.geocaching.com/account/oauth/token").
addHeader( "User-Agent", GeocachingComAccess.HTTP_USER_AGENT).
addHeader( "Pragma", "no-cache").
addHeader( "Accept-Language", "en").
addHeader( "Accept", "application/json, text/javascript, */*; q=0.01").
addHeader("Content-Type", "application/x-www-form-urlencoded").
post( oauthFormBody).
build();
call = okHttpClient.newCall( requestOauthToken);
response = call.execute();
if( response.code() != 200) {
status = "11 - Invalid post for oauth"+ response.code();
Logger.v( status);
return status;
}
status = "11 - Valid post for oauth";
Logger.v( status);
text = response.body().string();
JSONObject conversationObject = new JSONObject( text);
String accessToken = conversationObject.getString( "access_token");
String tokenType = conversationObject.getString( "token_type");
Logger.v( "Token type = " + tokenType);
Logger.v( "Accesws token = " + accessToken);
if( accessToken != null && accessToken.contains( "ey")) {
status = "12 - Valid access token";
Logger.v( status);
} else {
status = "12 - Invalid access token";
Logger.v( status);
return status;
}
// STEP 3 - POST voor de log https://www.geocaching.com/api/proxy/web/v1/Geocache/GC19A68/GeocacheLog
FormBody logFormBody = new FormBody.Builder().
add( "logTextMaxLength", "4000"). // may not be needed.
add( "maxImages","1"). // may not be needed.
add( "geocache[id]", "" + old_gc_gid).
add( "geocache[referenceCode]", gcId).
add( "geocache[postedCoordinates][latitude]", "52.23305"). // for testing just hard coded. Normally available in the geocache
add( "geocache[postedCoordinates][longitude]", "6.114983").
add( "geocache[callerSpecific][favorited]", "false").
add( "geocache[owner][id]", "" + 1138217). // owner of the geocache
// add( "geocache[owner][referenceCode]", "PR1N06K"). // via the base32/64 ... from id to reference code
add( "geocache[geocacheType][id]", "" + 8). // here for testing. Normally available in the geocache
add( "geocache[geocacheType][name]", "Unknown+Cache"). // here for testing. Normally available in the geocache
add( "geocache[isEvent]", "false"). // based on the actual geocache type
/* DEPENDING ... if you are the user, you will see different fields
add( "logTypes[0][value]", "46"). for the caches of yourself
add( "logTypes[0][name]", "Owner+maintenance").
add( "logTypes[0][selected]", "false").
add( "logTypes[1][value]", "4").
add( "logTypes[1][name]", "Write+note").
add( "logTypes[1][selected]", "true").
*/
add( "logTypes[0][value]", "2"). // for geocaches not owned by yourself
add( "logTypes[0][name]", "Found+It").
add( "logTypes[0][selected]", "true").
add( "logTypes[1][value]", "3").
add( "logTypes[1][name]", "Didn't+Find+It").
add( "logTypes[1][selected]", "false").
add( "logTypes[2][value]", "4").
add( "logTypes[2][name]", "Write+note").
add( "logTypes[2][selected]", "false").
add( "logType", "2"). // redundant, again the type 2 (as above) is chosen.
// add( "ownerIsViewing", "true"). // if you are the owner of the geocache. Available in the geocache info.
add( "ownerIsViewing", "false"). // @for not owner
add( "logDate", "2017-05-25"). // log date, watch for the format !
add( "logText", "Leuke+geocache+met+een+testbericht+versie2" /*+ new SimpleDateFormat( "yyyy-MM-dd_kk_mm_ss").format( new Date())*/).
add( "isWaiting", "true").build();
// TAKE CARE: no viewstates were used here. In some old parts they are still used.
Request requestLog = new Request.Builder().
url( "https://www.geocaching.com/api/proxy/web/v1/Geocache/" + gcId + "/GeocacheLog").
addHeader( "User-Agent", GeocachingComAccess.HTTP_USER_AGENT).
addHeader( "Pragma", "no-cache").
addHeader( "Accept-Language", "en").
addHeader( "Accept", "*/*").
addHeader( "Referer", "https://www.geocaching.com/play/geocache/gc19a68/log"). // normally put in by OkHttp, otherwise you can fit it in. Just test.
addHeader( "Authorization", "bearer " + accessToken). // A VERY IMPORTANT LINE !!!!!!
addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").
post( logFormBody).
build();
call = okHttpClient.newCall( requestLog);
response = call.execute();
if( response.code() != 200) {
status = "13 - Cannot log the request" + response.code();
Logger.v( status );
return status;
}
status = "13 - Log request honored!!";
return status;
} catch (Exception e) {
e.printStackTrace();
}
// Step 3 - log the geocache
return status;
}
void printCookies( CookieManager cookieManager) {
if( cookieManager == null) {
Logger.v("Cookies: null");
return ;
}
List<HttpCookie> cookies = cookieManager.getCookieStore().getCookies();
if( cookies == null) {
Logger.v("Cookies: none");
return ;
}
for( HttpCookie cookie : cookies) {
Logger.v(" > cookie: url " + cookie.getCommentURL() + ", name = " + cookie.getName());
}
}
/*
View states
*/
private static final Pattern patternViewstateFieldCount = Pattern.compile("id=\"__VIEWSTATEFIELDCOUNT\"[^(value)]+value=\"(\\d+)\"[^>]+>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
private static final Pattern patternViewstates = Pattern.compile("id=\"__VIEWSTATE(\\d*)\"[^(value)]+value=\"([^\"]+)\"[^>]+>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
private void addViewstates( FormBody.Builder formBodyBuilder, String webPage) {
String[] viewStates = getViewstates( webPage);
if( isEmpty( viewStates)) {
return; // no updates
}
formBodyBuilder.add( "__VIEWSTATE", viewStates[0]);
if (viewStates.length > 1) {
for (int i = 1; i < viewStates.length; i++) {
formBodyBuilder.add( "__VIEWSTATE" + i, viewStates[i]);
}
formBodyBuilder.add( "__VIEWSTATEFIELDCOUNT", viewStates.length + "");
}
}
private String[] getViewstates(String page) {
// Get the number of viewstates.
// If there is only one viewstate, __VIEWSTATEFIELDCOUNT is not present
int count = 1;
final Matcher matcherViewstateCount = patternViewstateFieldCount.matcher(page);
if (matcherViewstateCount.find()) {
count = Integer.parseInt(matcherViewstateCount.group(1));
}
String[] viewstates = new String[count];
int no;
final Matcher matcherViewstates = patternViewstates.matcher(page);
while (matcherViewstates.find()) {
String sno = matcherViewstates.group(1); // number of viewstate
if ("".equals(sno)) {
no = 0;
}
else {
no = Integer.parseInt(sno);
}
// System.out.println("1 v " + no + "=" + matcherViewstates.group(2));
viewstates[no] = matcherViewstates.group(2);
}
return viewstates;
}
private boolean isEmpty(String[] a) {
if( a == null) {
return true;
}
for( int i = 0; i < a.length; i++) {
if((a[i] == null) || (a[i].equals(""))) {
return true;
}
}
return false;
}
/*
OLD GCID
*/
private final static String SEQUENCE_GCID = "0123456789ABCDEFGHJKMNPQRTVWXYZ";
private final static long GC_BASE31 = 31;
private final static long GC_BASE16 = 16;
public long gccodeToGCId( final String gccode) {
long base = GC_BASE31;
final String geocodeWO = gccode.substring(2).toUpperCase(Locale.US);
if ((geocodeWO.length() < 4) || (geocodeWO.length() == 4 && SEQUENCE_GCID.indexOf(geocodeWO.charAt(0)) < 16)) {
base = GC_BASE16;
}
long gcid = 0;
for (int p = 0; p < geocodeWO.length(); p++) {
gcid = base * gcid + SEQUENCE_GCID.indexOf(geocodeWO.charAt(p));
}
if (base == GC_BASE31) {
gcid += Math.pow(16, 4) - 16 * Math.pow(31, 3);
}
return gcid;
}
}
This code demonstrates the new logging approach. This isolated code is based on OkHttp. It shows that logging is quite easy to do.
This code snippet is just for proofing that it works. The complete test (from sign-in, get geocache file, get GPX and logging) is added as a file.
// STEP 3 - Log a geocache
// A - build the requeste and get the log page (as a start)
long old_gc_gid = gccodeToGCId( gcId); // this method is also used in/by c:geo
String logUrl = "https://www.geocaching.com/seek/log.aspx?ID=" + old_gc_gid + "&lcn=1";
// build the GET request
Request getLogHtml = new Request.Builder().
url( logUrl).
addHeader( "User-Agent", GeocachingComAccess.HTTP_USER_AGENT).
addHeader( "Pragma", "no-cache").
addHeader( "Accept-Language", "en").
addHeader( "Content-Type", "application/x-www-form-urlencoded").
build();
call = okHttpClient.newCall( getLogHtml);
response = call.execute();
if( response.code() != 200) {
status = "9 - Invalid access to log html page: " + response.code();
Logger.v( status);
return status;
}
status = "9 - Valid access to log html page";
Logger.v( status);
text = response.body().string();
if( text.contains( "Geocaching - New Log for")) {
status = "10 - Valid contents of the log html page";
Logger.v( status);
} else {
status = "10 - Invalid contents to log html page";
Logger.v( status);
return status;
}
// B - POST for an OATH token: https://www.geocaching.com/account/oauth/token
RequestBody oauthFormBody = new FormBody.Builder().build(); // empty
Request requestOauthToken = new Request.Builder().
url( "https://www.geocaching.com/account/oauth/token").
addHeader( "User-Agent", GeocachingComAccess.HTTP_USER_AGENT).
addHeader( "Pragma", "no-cache").
addHeader( "Accept-Language", "en").
addHeader( "Accept", "application/json, text/javascript, */*; q=0.01").
addHeader("Content-Type", "application/x-www-form-urlencoded").
post( oauthFormBody).
build();
call = okHttpClient.newCall( requestOauthToken);
response = call.execute();
if( response.code() != 200) {
status = "11 - Invalid post for oauth"+ response.code();
Logger.v( status);
return status;
}
status = "11 - Valid post for oauth";
Logger.v( status);
text = response.body().string();
JSONObject conversationObject = new JSONObject( text);
String accessToken = conversationObject.getString( "access_token");
String tokenType = conversationObject.getString( "token_type");
Logger.v( "Token type = " + tokenType);
Logger.v( "Accesws token = " + accessToken);
if( accessToken != null && accessToken.contains( "ey")) {
status = "12 - Valid access token";
Logger.v( status);
} else {
status = "12 - Invalid access token";
Logger.v( status);
return status;
}
// STEP 3 - POST voor de log https://www.geocaching.com/api/proxy/web/v1/Geocache/GC19A68/GeocacheLog
FormBody logFormBody = new FormBody.Builder().
add( "logTextMaxLength", "4000"). // may not be needed.
add( "maxImages","1"). // may not be needed.
add( "geocache[id]", "" + old_gc_gid).
add( "geocache[referenceCode]", gcId).
add( "geocache[postedCoordinates][latitude]", "52.23305"). // for testing just hard coded. Normally available in the geocache
add( "geocache[postedCoordinates][longitude]", "6.114983").
add( "geocache[callerSpecific][favorited]", "false").
add( "geocache[owner][id]", "" + 1138217). // owner of the geocache
// add( "geocache[owner][referenceCode]", "PR1N06K"). // via the base32/64 ... from id to reference code
add( "geocache[geocacheType][id]", "" + 8). // here for testing. Normally available in the geocache
add( "geocache[geocacheType][name]", "Unknown+Cache"). // here for testing. Normally available in the geocache
add( "geocache[isEvent]", "false"). // based on the actual geocache type
/* DEPENDING ... if you are the user, you will see different fields
add( "logTypes[0][value]", "46"). for the caches of yourself
add( "logTypes[0][name]", "Owner+maintenance").
add( "logTypes[0][selected]", "false").
add( "logTypes[1][value]", "4").
add( "logTypes[1][name]", "Write+note").
add( "logTypes[1][selected]", "true").
*/
add( "logTypes[0][value]", "2"). // for geocaches not owned by yourself
add( "logTypes[0][name]", "Found+It").
add( "logTypes[0][selected]", "true").
add( "logTypes[1][value]", "3").
add( "logTypes[1][name]", "Didn't+Find+It").
add( "logTypes[1][selected]", "false").
add( "logTypes[2][value]", "4").
add( "logTypes[2][name]", "Write+note").
add( "logTypes[2][selected]", "false").
add( "logType", "2"). // redundant, again the type 2 (as above) is chosen.
// add( "ownerIsViewing", "true"). // if you are the owner of the geocache. Available in the geocache info.
add( "ownerIsViewing", "false"). // @for not owner
add( "logDate", "2017-05-25"). // log date, watch for the format !
add( "logText", "Leuke+geocache+met+een+testbericht+versie2" /*+ new SimpleDateFormat( "yyyy-MM-dd_kk_mm_ss").format( new Date())*/).
add( "isWaiting", "true").build();
// TAKE CARE: no viewstates were used here. In some old parts they are still used.
Request requestLog = new Request.Builder().
url( "https://www.geocaching.com/api/proxy/web/v1/Geocache/" + gcId + "/GeocacheLog").
addHeader( "User-Agent", GeocachingComAccess.HTTP_USER_AGENT).
addHeader( "Pragma", "no-cache").
addHeader( "Accept-Language", "en").
addHeader( "Accept", "*/*").
addHeader( "Referer", "https://www.geocaching.com/play/geocache/gc19a68/log"). // normally put in by OkHttp, otherwise you can fit it in. Just test.
addHeader( "Authorization", "bearer " + accessToken). // A VERY IMPORTANT LINE !!!!!!
addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").
post( logFormBody).
build();
call = okHttpClient.newCall( requestLog);
response = call.execute();
if( response.code() != 200) {
status = "13 - Cannot log the request" + response.code();
Logger.v( status );
return status;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment