org.quantumbadger.redreader.reddit.RedditAPI.java Source code

Java tutorial

Introduction

Here is the source code for org.quantumbadger.redreader.reddit.RedditAPI.java

Source

/*******************************************************************************
 * This file is part of RedReader.
 *
 * RedReader is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * RedReader is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with RedReader.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/

package org.quantumbadger.redreader.reddit;

import android.content.Context;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.message.BasicNameValuePair;
import org.quantumbadger.redreader.account.RedditAccount;
import org.quantumbadger.redreader.activities.BugReportActivity;
import org.quantumbadger.redreader.cache.CacheManager;
import org.quantumbadger.redreader.cache.CacheRequest;
import org.quantumbadger.redreader.cache.RequestFailureType;
import org.quantumbadger.redreader.common.Constants;
import org.quantumbadger.redreader.jsonwrap.JsonBufferedArray;
import org.quantumbadger.redreader.jsonwrap.JsonValue;
import org.quantumbadger.redreader.reddit.things.RedditSubreddit;
import org.quantumbadger.redreader.reddit.things.RedditThing;
import org.quantumbadger.redreader.reddit.things.RedditUser;

import java.net.URI;
import java.util.*;

public final class RedditAPI {

    public static void submit(final CacheManager cm, final APIResponseHandler.ActionResponseHandler responseHandler,
            final RedditAccount user, final boolean is_self, final String subreddit, final String title,
            final String body, final String captchaId, final String captchaText, final Context context) {

        final LinkedList<NameValuePair> postFields = new LinkedList<NameValuePair>();
        postFields.add(new BasicNameValuePair("kind", is_self ? "self" : "link"));
        postFields.add(new BasicNameValuePair("sendreplies", "true"));
        postFields.add(new BasicNameValuePair("uh", user.modhash));
        postFields.add(new BasicNameValuePair("sr", subreddit));
        postFields.add(new BasicNameValuePair("title", title));
        postFields.add(new BasicNameValuePair("captcha", captchaText));
        postFields.add(new BasicNameValuePair("iden", captchaId));

        if (is_self)
            postFields.add(new BasicNameValuePair("text", body));
        else
            postFields.add(new BasicNameValuePair("url", body));

        cm.makeRequest(new APIPostRequest(Constants.Reddit.getUri("/api/submit"), user, postFields, context) {

            @Override
            public void onJsonParseStarted(JsonValue result, long timestamp, UUID session, boolean fromCache) {

                System.out.println(result.toString());

                try {
                    final APIResponseHandler.APIFailureType failureType = findFailureType(result);

                    if (failureType != null) {
                        responseHandler.notifyFailure(failureType);
                        return;
                    }

                } catch (Throwable t) {
                    notifyFailure(RequestFailureType.PARSE, t, null, "JSON failed to parse");
                }

                responseHandler.notifySuccess();
            }

            @Override
            protected void onCallbackException(Throwable t) {
                BugReportActivity.handleGlobalError(context, t);
            }

            @Override
            protected void onFailure(RequestFailureType type, Throwable t, StatusLine status,
                    String readableMessage) {
                responseHandler.notifyFailure(type, t, status, readableMessage);
            }
        });
    }

    public static void comment(final CacheManager cm,
            final APIResponseHandler.ActionResponseHandler responseHandler, final RedditAccount user,
            final String parentIdAndType, final String markdown, final Context context) {

        final LinkedList<NameValuePair> postFields = new LinkedList<NameValuePair>();
        postFields.add(new BasicNameValuePair("thing_id", parentIdAndType));
        postFields.add(new BasicNameValuePair("uh", user.modhash));
        postFields.add(new BasicNameValuePair("text", markdown));

        cm.makeRequest(new APIPostRequest(Constants.Reddit.getUri("/api/comment"), user, postFields, context) {

            @Override
            public void onJsonParseStarted(JsonValue result, long timestamp, UUID session, boolean fromCache) {

                try {
                    final APIResponseHandler.APIFailureType failureType = findFailureType(result);

                    if (failureType != null) {
                        responseHandler.notifyFailure(failureType);
                        return;
                    }

                } catch (Throwable t) {
                    notifyFailure(RequestFailureType.PARSE, t, null, "JSON failed to parse");
                }

                responseHandler.notifySuccess();
            }

            @Override
            protected void onCallbackException(Throwable t) {
                BugReportActivity.handleGlobalError(context, t);
            }

            @Override
            protected void onFailure(RequestFailureType type, Throwable t, StatusLine status,
                    String readableMessage) {
                responseHandler.notifyFailure(type, t, status, readableMessage);
            }
        });
    }

    public static void editComment(final CacheManager cm,
            final APIResponseHandler.ActionResponseHandler responseHandler, final RedditAccount user,
            final String commentIdAndType, final String markdown, final Context context) {

        final LinkedList<NameValuePair> postFields = new LinkedList<NameValuePair>();
        postFields.add(new BasicNameValuePair("thing_id", commentIdAndType));
        postFields.add(new BasicNameValuePair("uh", user.modhash));
        postFields.add(new BasicNameValuePair("text", markdown));

        cm.makeRequest(new APIPostRequest(Constants.Reddit.getUri("/api/editusertext"), user, postFields, context) {

            @Override
            public void onJsonParseStarted(JsonValue result, long timestamp, UUID session, boolean fromCache) {

                try {
                    final APIResponseHandler.APIFailureType failureType = findFailureType(result);

                    if (failureType != null) {
                        responseHandler.notifyFailure(failureType);
                        return;
                    }

                } catch (Throwable t) {
                    notifyFailure(RequestFailureType.PARSE, t, null, "JSON failed to parse");
                }

                responseHandler.notifySuccess();
            }

            @Override
            protected void onCallbackException(Throwable t) {
                BugReportActivity.handleGlobalError(context, t);
            }

            @Override
            protected void onFailure(RequestFailureType type, Throwable t, StatusLine status,
                    String readableMessage) {
                responseHandler.notifyFailure(type, t, status, readableMessage);
            }
        });
    }

    public static void newCaptcha(final CacheManager cm,
            final APIResponseHandler.NewCaptchaResponseHandler responseHandler, final RedditAccount user,
            final Context context) {

        final LinkedList<NameValuePair> postFields = new LinkedList<NameValuePair>();
        postFields.add(new BasicNameValuePair("uh", user.modhash));

        cm.makeRequest(new APIPostRequest(Constants.Reddit.getUri("/api/new_captcha"), user, postFields, context) {

            @Override
            public void onJsonParseStarted(JsonValue result, long timestamp, UUID session, boolean fromCache) {

                try {
                    final APIResponseHandler.APIFailureType failureType = findFailureType(result);

                    if (failureType != null) {
                        responseHandler.notifyFailure(failureType);
                        return;
                    }

                } catch (Throwable t) {
                    notifyFailure(RequestFailureType.PARSE, t, null, "JSON failed to parse");
                }

                responseHandler.notifySuccess(findCaptchaId(result));
            }

            @Override
            protected void onCallbackException(Throwable t) {
                BugReportActivity.handleGlobalError(context, t);
            }

            @Override
            protected void onFailure(RequestFailureType type, Throwable t, StatusLine status,
                    String readableMessage) {
                responseHandler.notifyFailure(type, t, status, readableMessage);
            }
        });
    }

    public static enum RedditAction {
        UPVOTE, UNVOTE, DOWNVOTE, SAVE, HIDE, UNSAVE, UNHIDE, REPORT
    }

    public static void action(final CacheManager cm, final APIResponseHandler.ActionResponseHandler responseHandler,
            final RedditAccount user, final String idAndType, final RedditAction action, final Context context) {

        final LinkedList<NameValuePair> postFields = new LinkedList<NameValuePair>();
        postFields.add(new BasicNameValuePair("id", idAndType));
        postFields.add(new BasicNameValuePair("uh", user.modhash));

        final URI url = prepareActionUri(action, postFields);

        cm.makeRequest(new APIPostRequest(url, user, postFields, context) {
            @Override
            protected void onCallbackException(final Throwable t) {
                BugReportActivity.handleGlobalError(context, t);
            }

            @Override
            protected void onFailure(final RequestFailureType type, final Throwable t, final StatusLine status,
                    final String readableMessage) {
                responseHandler.notifyFailure(type, t, status, readableMessage);
            }

            @Override
            public void onJsonParseStarted(final JsonValue result, final long timestamp, final UUID session,
                    final boolean fromCache) {

                try {

                    final APIResponseHandler.APIFailureType failureType = findFailureType(result);

                    if (failureType != null) {
                        responseHandler.notifyFailure(failureType);
                        return;
                    }

                } catch (Throwable t) {
                    notifyFailure(RequestFailureType.PARSE, t, null, "JSON failed to parse");
                }

                responseHandler.notifySuccess();
            }
        });
    }

    private static URI prepareActionUri(final RedditAction action, final LinkedList<NameValuePair> postFields) {
        switch (action) {
        case DOWNVOTE:
            postFields.add(new BasicNameValuePair("dir", "-1"));
            return Constants.Reddit.getUri(Constants.Reddit.PATH_VOTE);

        case UNVOTE:
            postFields.add(new BasicNameValuePair("dir", "0"));
            return Constants.Reddit.getUri(Constants.Reddit.PATH_VOTE);

        case UPVOTE:
            postFields.add(new BasicNameValuePair("dir", "1"));
            return Constants.Reddit.getUri(Constants.Reddit.PATH_VOTE);

        case SAVE:
            return Constants.Reddit.getUri(Constants.Reddit.PATH_SAVE);
        case HIDE:
            return Constants.Reddit.getUri(Constants.Reddit.PATH_HIDE);
        case UNSAVE:
            return Constants.Reddit.getUri(Constants.Reddit.PATH_UNSAVE);
        case UNHIDE:
            return Constants.Reddit.getUri(Constants.Reddit.PATH_UNHIDE);
        case REPORT:
            return Constants.Reddit.getUri(Constants.Reddit.PATH_REPORT);

        default:
            throw new RuntimeException("Unknown post action");
        }
    }

    // TODO just put this in the main menu fragment
    public static void getUserSubreddits(final CacheManager cm,
            final APIResponseHandler.SubredditResponseHandler responseHandler, final RedditAccount user,
            final CacheRequest.DownloadType downloadType, final boolean cancelExisting, final Context context) {
        final URI uri;
        if (user.isAnonymous()) {
            uri = Constants.Reddit.getUri(Constants.Reddit.PATH_SUBREDDITS_POPULAR);
        } else {
            uri = Constants.Reddit.getUri(Constants.Reddit.PATH_SUBREDDITS_MINE_SUBSCRIBER);
        }

        cm.makeRequest(new APIGetRequest(uri, user, Constants.Priority.API_SUBREDDIT_LIST,
                Constants.FileType.SUBREDDIT_LIST, downloadType, true, cancelExisting, context) {

            @Override
            protected void onDownloadNecessary() {
                responseHandler.notifyDownloadNecessary();
            }

            @Override
            protected void onDownloadStarted() {
                responseHandler.notifyDownloadStarted();
            }

            @Override
            protected void onCallbackException(final Throwable t) {
                BugReportActivity.handleGlobalError(context, t);
            }

            @Override
            protected void onFailure(final RequestFailureType type, final Throwable t, final StatusLine status,
                    final String readableMessage) {
                responseHandler.notifyFailure(type, t, status, readableMessage);
            }

            @Override
            public void onJsonParseStarted(final JsonValue result, final long timestamp, final UUID session,
                    final boolean fromCache) {

                final ArrayList<RedditSubreddit> output = new ArrayList<RedditSubreddit>();

                try {

                    final JsonBufferedArray subreddits = result.asObject().getObject("data").getArray("children");

                    for (final JsonValue v : subreddits) {
                        final RedditThing thing = v.asObject(RedditThing.class);
                        final RedditSubreddit subreddit = thing.asSubreddit();

                        output.add(subreddit);
                    }

                    Collections.sort(output);

                    responseHandler.notifySuccess(output, timestamp);

                } catch (Throwable t) {
                    // TODO look for error
                    notifyFailure(RequestFailureType.PARSE, t, null, "JSON parse failed for unknown reason");
                }
            }
        });
    }

    public static void getUser(final CacheManager cm, final String usernameToGet,
            final APIResponseHandler.UserResponseHandler responseHandler, final RedditAccount user,
            final CacheRequest.DownloadType downloadType, final boolean cancelExisting, final Context context) {

        final URI uri = Constants.Reddit.getUri("/user/" + usernameToGet + "/about.json");

        cm.makeRequest(new APIGetRequest(uri, user, Constants.Priority.API_USER_ABOUT,
                Constants.FileType.USER_ABOUT, downloadType, true, cancelExisting, context) {

            @Override
            protected void onDownloadNecessary() {
            }

            @Override
            protected void onDownloadStarted() {
                responseHandler.notifyDownloadStarted();
            }

            @Override
            protected void onCallbackException(final Throwable t) {
                BugReportActivity.handleGlobalError(context, t);
            }

            @Override
            protected void onFailure(final RequestFailureType type, final Throwable t, final StatusLine status,
                    final String readableMessage) {
                responseHandler.notifyFailure(type, t, status, readableMessage);
            }

            @Override
            public void onJsonParseStarted(final JsonValue result, final long timestamp, final UUID session,
                    final boolean fromCache) {

                try {

                    final RedditThing userThing = result.asObject(RedditThing.class);
                    final RedditUser userResult = userThing.asUser();
                    responseHandler.notifySuccess(userResult, timestamp);

                } catch (Throwable t) {
                    // TODO look for error
                    notifyFailure(RequestFailureType.PARSE, t, null, "JSON parse failed for unknown reason");
                }
            }
        });
    }

    // lol, reddit api
    private static APIResponseHandler.APIFailureType findFailureType(final JsonValue response) {

        // TODO doesn't handle unknown failures

        // TODO handle 403 forbidden

        switch (response.getType()) {

        case OBJECT:

            for (final Map.Entry<String, JsonValue> v : response.asObject()) {
                final APIResponseHandler.APIFailureType failureType = findFailureType(v.getValue());
                if (failureType != null)
                    return failureType;
            }

            break;

        case ARRAY:

            for (final JsonValue v : response.asArray()) {
                final APIResponseHandler.APIFailureType failureType = findFailureType(v);
                if (failureType != null)
                    return failureType;
            }

            break;

        case STRING:

            if (Constants.Reddit.isApiErrorUser(response.asString()))
                return APIResponseHandler.APIFailureType.INVALID_USER;

            if (Constants.Reddit.isApiErrorCaptcha(response.asString()))
                return APIResponseHandler.APIFailureType.BAD_CAPTCHA;

            if (Constants.Reddit.isApiErrorNotAllowed(response.asString()))
                return APIResponseHandler.APIFailureType.NOTALLOWED;

            if (Constants.Reddit.isApiErrorSubredditRequired(response.asString()))
                return APIResponseHandler.APIFailureType.SUBREDDIT_REQUIRED;

            break;

        default:
            // Ignore
        }

        return null;
    }

    // lol, reddit api
    private static String findCaptchaId(final JsonValue response) {

        switch (response.getType()) {

        case OBJECT:

            for (final Map.Entry<String, JsonValue> v : response.asObject()) {
                final String captchaId = findCaptchaId(v.getValue());
                if (captchaId != null)
                    return captchaId;
            }

            break;

        case ARRAY:

            for (final JsonValue v : response.asArray()) {
                final String captchaId = findCaptchaId(v);
                if (captchaId != null)
                    return captchaId;
            }

            break;

        case STRING:

            if (response.asString().length() > 20) { // This is probably it :S
                return response.asString();
            }

            break;

        default:
            // Ignore
        }

        return null;
    }

    private static abstract class APIPostRequest extends CacheRequest {

        @Override
        protected void onDownloadNecessary() {
        }

        @Override
        protected void onDownloadStarted() {
        }

        public APIPostRequest(final URI url, final RedditAccount user, final List<NameValuePair> postFields,
                final Context context) {
            super(url, user, null, Constants.Priority.API_ACTION, 0, DownloadType.FORCE, Constants.FileType.NOCACHE,
                    true, true, postFields, false, false, context);
        }

        @Override
        protected final void onSuccess(final CacheManager.ReadableCacheFile cacheFile, final long timestamp,
                final UUID session, final boolean fromCache, final String mimetype) {
            throw new RuntimeException("onSuccess called for uncached request");
        }

        @Override
        protected final void onProgress(final long bytesRead, final long totalBytes) {
            throw new RuntimeException("onProgress called for uncached request");
        }

        @Override
        public abstract void onJsonParseStarted(JsonValue result, long timestamp, UUID session, boolean fromCache);
    }

    // TODO merge get and post into one?
    private static abstract class APIGetRequest extends CacheRequest {

        public APIGetRequest(final URI url, final RedditAccount user, final int priority, final int fileType,
                final DownloadType downloadType, final boolean cache, final boolean cancelExisting,
                final Context context) {
            super(url, user, null, priority, 0, downloadType, fileType, true, true, null, cache, cancelExisting,
                    context);
        }

        @Override
        protected final void onSuccess(final CacheManager.ReadableCacheFile cacheFile, final long timestamp,
                final UUID session, final boolean fromCache, final String mimetype) {
            if (!cache)
                throw new RuntimeException("onSuccess called for uncached request");
        }

        @Override
        protected final void onProgress(final long bytesRead, final long totalBytes) {
            if (!cache)
                throw new RuntimeException("onProgress called for uncached request");
        }

        @Override
        public abstract void onJsonParseStarted(JsonValue result, long timestamp, UUID session, boolean fromCache);
    }
}