org.andstatus.app.net.social.ConnectionTwitter.java Source code

Java tutorial

Introduction

Here is the source code for org.andstatus.app.net.social.ConnectionTwitter.java

Source

/*
 * Copyright (C) 2013 yvolk (Yuri Volkov), http://yurivolkov.com
 *
 * 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.andstatus.app.net.social;

import android.net.Uri;
import android.text.TextUtils;

import org.andstatus.app.net.http.ConnectionException;
import org.andstatus.app.util.MyLog;
import org.andstatus.app.util.SharedPreferencesUtil;
import org.andstatus.app.util.TriState;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;

/**
 * Twitter API implementations
 * @author yvolk@yurivolkov.com
 */
public abstract class ConnectionTwitter extends Connection {
    private static final String TAG = ConnectionTwitter.class.getSimpleName();

    /**
     * URL of the API. Not logged
     * @param routine
     * @return URL or an empty string in a case the API routine is not supported
     */
    @Override
    protected String getApiPath1(ApiRoutineEnum routine) {
        String url;
        switch (routine) {
        case ACCOUNT_RATE_LIMIT_STATUS:
            url = "account/rate_limit_status" + EXTENSION;
            break;
        case ACCOUNT_VERIFY_CREDENTIALS:
            url = "account/verify_credentials" + EXTENSION;
            break;
        case DIRECT_MESSAGES:
            url = "direct_messages" + EXTENSION;
            break;
        case CREATE_FAVORITE:
            url = "favorites/create/";
            break;
        case DESTROY_FAVORITE:
            url = "favorites/destroy/";
            break;
        case FOLLOW_USER:
            url = "friendships/create" + EXTENSION;
            break;
        case GET_FRIENDS_IDS:
            url = "friends/ids" + EXTENSION;
            break;
        case GET_USER:
            url = "users/show" + EXTENSION;
            break;
        case POST_DIRECT_MESSAGE:
            url = "direct_messages/new" + EXTENSION;
            break;
        case POST_REBLOG:
            url = "statuses/retweet/";
            break;
        case DESTROY_MESSAGE:
            url = "statuses/destroy/";
            break;
        case STATUSES_HOME_TIMELINE:
            url = "statuses/home_timeline" + EXTENSION;
            break;
        case STATUSES_MENTIONS_TIMELINE:
            url = "statuses/mentions" + EXTENSION;
            break;
        case STATUSES_USER_TIMELINE:
            url = "statuses/user_timeline" + EXTENSION;
            break;
        case GET_MESSAGE:
            url = "statuses/show" + EXTENSION;
            break;
        case POST_MESSAGE:
            url = "statuses/update" + EXTENSION;
            break;
        case STOP_FOLLOWING_USER:
            url = "friendships/destroy" + EXTENSION;
            break;
        default:
            url = "";
            break;
        }
        return prependWithBasicPath(url);
    }

    @Override
    public boolean destroyStatus(String statusId) throws ConnectionException {
        JSONObject jso = http.postRequest(getApiPath(ApiRoutineEnum.DESTROY_MESSAGE) + statusId + EXTENSION);
        if (jso != null && MyLog.isLoggable(null, MyLog.VERBOSE)) {
            try {
                MyLog.v(TAG, "destroyStatus response: " + jso.toString(2));
            } catch (JSONException e) {
                MyLog.e(this, e);
                jso = null;
            }
        }
        return jso != null;
    }

    /**
     * @see <a
     *      href="https://dev.twitter.com/docs/api/1.1/post/friendships/create">POST friendships/create</a>
     * @see <a
     *      href="https://dev.twitter.com/docs/api/1.1/post/friendships/destroy">POST friendships/destroy</a>
     */
    @Override
    public MbUser followUser(String userId, Boolean follow) throws ConnectionException {
        JSONObject out = new JSONObject();
        try {
            out.put("user_id", userId);
        } catch (JSONException e) {
            MyLog.e(this, e);
        }
        JSONObject user = postRequest(follow ? ApiRoutineEnum.FOLLOW_USER : ApiRoutineEnum.STOP_FOLLOWING_USER,
                out);
        return userFromJson(user);
    }

    /**
     * Returns an array of numeric IDs for every user the specified user is following.
     * Current implementation is restricted to 5000 IDs (no paged cursors are used...)
     * @see <a
     *      href="https://dev.twitter.com/docs/api/1.1/get/friends/ids">GET friends/ids</a>
     * @throws ConnectionException
     */
    @Override
    public List<String> getIdsOfUsersFollowedBy(String userId) throws ConnectionException {
        String method = "getIdsOfUsersFollowedBy";
        Uri sUri = Uri.parse(getApiPath(ApiRoutineEnum.GET_FRIENDS_IDS));
        Uri.Builder builder = sUri.buildUpon();
        builder.appendQueryParameter("user_id", userId);
        List<String> list = new ArrayList<String>();
        JSONArray jArr = getRequestArrayInObject(builder.build().toString(), "ids");
        try {
            for (int index = 0; jArr != null && index < jArr.length(); index++) {
                list.add(jArr.getString(index));
            }
        } catch (JSONException e) {
            throw ConnectionException.loggedJsonException(this, method, e, jArr);
        }
        return list;
    }

    /**
     * Returns a single status, specified by the id parameter below.
     * The status's author will be returned inline.
     * @see <a
     *      href="https://dev.twitter.com/docs/api/1/get/statuses/show/%3Aid">Twitter
     *      REST API Method: statuses/destroy</a>
     * 
     * @throws ConnectionException
     */
    @Override
    public MbMessage getMessage1(String messageId) throws ConnectionException {
        Uri sUri = Uri.parse(getApiPath(ApiRoutineEnum.GET_MESSAGE));
        Uri.Builder builder = sUri.buildUpon();
        builder.appendQueryParameter("id", messageId);
        JSONObject message = http.getRequest(builder.build().toString());
        return messageFromJson(message);
    }

    @Override
    public List<MbTimelineItem> getTimeline(ApiRoutineEnum apiRoutine, TimelinePosition sinceId, int limit,
            String userId) throws ConnectionException {
        String url = this.getApiPath(apiRoutine);
        Uri sUri = Uri.parse(url);
        Uri.Builder builder = sUri.buildUpon();
        if (!sinceId.isEmpty()) {
            builder.appendQueryParameter("since_id", sinceId.getPosition());
        }
        if (fixedDownloadLimitForApiRoutine(limit, apiRoutine) > 0) {
            builder.appendQueryParameter("count",
                    String.valueOf(fixedDownloadLimitForApiRoutine(limit, apiRoutine)));
        }
        if (!TextUtils.isEmpty(userId)) {
            builder.appendQueryParameter("user_id", userId);
        }
        JSONArray jArr = http.getRequestAsArray(builder.build().toString());
        return jArrToTimeline(jArr, apiRoutine, url);
    }

    private MbTimelineItem timelineItemFromJson(JSONObject jso) throws ConnectionException {
        MbTimelineItem item = new MbTimelineItem();
        item.mbMessage = messageFromJson(jso);
        item.timelineItemDate = item.mbMessage.sentDate;
        item.timelineItemPosition = new TimelinePosition(item.mbMessage.oid);
        return item;
    }

    protected MbMessage messageFromJson(JSONObject jso) throws ConnectionException {
        if (jso == null) {
            return MbMessage.getEmpty();
        }
        String oid = jso.optString("id_str");
        if (TextUtils.isEmpty(oid)) {
            // This is for the Status.net
            oid = jso.optString("id");
        }
        MbMessage message = MbMessage.fromOriginAndOid(data.getOriginId(), oid);
        message.actor = MbUser.fromOriginAndUserOid(data.getOriginId(), data.getAccountUserOid());
        try {
            message.sentDate = dateFromJson(jso, "created_at");

            JSONObject sender;
            if (jso.has("sender")) {
                sender = jso.getJSONObject("sender");
                message.sender = userFromJson(sender);
            } else if (jso.has("user")) {
                sender = jso.getJSONObject("user");
                message.sender = userFromJson(sender);
            } else if (jso.has("from_user")) {
                // This is in the search results, 
                // see https://dev.twitter.com/docs/api/1/get/search
                String senderName = jso.getString("from_user");
                String senderOid = jso.optString("from_user_id_str");
                if (SharedPreferencesUtil.isEmpty(senderOid)) {
                    senderOid = jso.optString("from_user_id");
                }
                if (!SharedPreferencesUtil.isEmpty(senderOid)) {
                    message.sender = MbUser.fromOriginAndUserOid(data.getOriginId(), senderOid);
                    message.sender.setUserName(senderName);
                }
            }

            // Is this a reblog?
            if (jso.has("retweeted_status")) {
                JSONObject rebloggedMessage = jso.getJSONObject("retweeted_status");
                message.rebloggedMessage = messageFromJson(rebloggedMessage);
            }
            setMessageBodyFromJson(message, jso);
            if (jso.has("recipient")) {
                JSONObject recipient = jso.getJSONObject("recipient");
                message.recipient = userFromJson(recipient);
            }
            if (jso.has("source")) {
                message.via = jso.getString("source");
            }
            if (jso.has("favorited")) {
                message.favoritedByActor = TriState
                        .fromBoolean(SharedPreferencesUtil.isTrue(jso.getString("favorited")));
            }

            // If the Msg is a Reply to other message
            String inReplyToUserOid = "";
            String inReplyToUserName = "";
            String inReplyToMessageOid = "";
            if (jso.has("in_reply_to_user_id_str")) {
                inReplyToUserOid = jso.getString("in_reply_to_user_id_str");
            } else if (jso.has("in_reply_to_user_id")) {
                // This is for Status.net
                inReplyToUserOid = jso.getString("in_reply_to_user_id");
            }
            if (SharedPreferencesUtil.isEmpty(inReplyToUserOid)) {
                inReplyToUserOid = "";
            }
            if (!SharedPreferencesUtil.isEmpty(inReplyToUserOid)) {
                if (jso.has("in_reply_to_screen_name")) {
                    inReplyToUserName = jso.getString("in_reply_to_screen_name");
                }
                // Construct "User" from available info
                JSONObject inReplyToUser = new JSONObject();
                inReplyToUser.put("id_str", inReplyToUserOid);
                inReplyToUser.put("screen_name", inReplyToUserName);
                if (jso.has("in_reply_to_status_id_str")) {
                    inReplyToMessageOid = jso.getString("in_reply_to_status_id_str");
                } else if (jso.has("in_reply_to_status_id")) {
                    // This is for identi.ca
                    inReplyToMessageOid = jso.getString("in_reply_to_status_id");
                }
                if (SharedPreferencesUtil.isEmpty(inReplyToMessageOid)) {
                    inReplyToUserOid = "";
                }
                if (!SharedPreferencesUtil.isEmpty(inReplyToMessageOid)) {
                    // Construct Related "Msg" from available info
                    // and add it recursively
                    JSONObject inReplyToMessage = new JSONObject();
                    inReplyToMessage.put("id_str", inReplyToMessageOid);
                    inReplyToMessage.put("user", inReplyToUser);
                    message.inReplyToMessage = messageFromJson(inReplyToMessage);
                }
            }
        } catch (JSONException e) {
            throw ConnectionException.loggedJsonException(this, "Parsing message", e, jso);
        } catch (Exception e) {
            MyLog.e(this, "messageFromJson", e);
            return MbMessage.getEmpty();
        }
        return message;
    }

    protected void setMessageBodyFromJson(MbMessage message, JSONObject jso) throws JSONException {
        if (jso.has("text")) {
            message.setBody(jso.getString("text"));
        }
    }

    protected MbUser userFromJson(JSONObject jso) throws ConnectionException {
        if (jso == null) {
            return MbUser.getEmpty();
        }
        String oid = "";
        if (jso.has("id_str")) {
            oid = jso.optString("id_str");
        } else if (jso.has("id")) {
            oid = jso.optString("id");
        }
        if (SharedPreferencesUtil.isEmpty(oid)) {
            oid = "";
        }
        String userName = "";
        if (jso.has("screen_name")) {
            userName = jso.optString("screen_name");
            if (SharedPreferencesUtil.isEmpty(userName)) {
                userName = "";
            }
        }
        MbUser user = MbUser.fromOriginAndUserOid(data.getOriginId(), oid);
        user.actor = MbUser.fromOriginAndUserOid(data.getOriginId(), data.getAccountUserOid());
        user.setUserName(userName);
        user.realName = jso.optString("name");
        if (!SharedPreferencesUtil.isEmpty(user.realName)) {
            user.setUrl(data.getOriginUrl());
        }
        user.avatarUrl = jso.optString("profile_image_url");
        user.description = jso.optString("description");
        user.homepage = jso.optString("url");
        user.createdDate = dateFromJson(jso, "created_at");
        if (!jso.isNull("following")) {
            user.followedByActor = TriState.fromBoolean(jso.optBoolean("following"));
        }
        if (jso.has("status")) {
            JSONObject latestMessage;
            try {
                latestMessage = jso.getJSONObject("status");
                // This message doesn't have a sender!
                user.latestMessage = messageFromJson(latestMessage);
            } catch (JSONException e) {
                throw ConnectionException.loggedJsonException(this, "getting status from user", e, jso);
            }
        }
        return user;
    }

    @Override
    public List<MbTimelineItem> search(String searchQuery, int limit) throws ConnectionException {
        ApiRoutineEnum apiRoutine = ApiRoutineEnum.SEARCH_MESSAGES;
        String url = this.getApiPath(apiRoutine);
        Uri sUri = Uri.parse(url);
        Uri.Builder builder = sUri.buildUpon();
        if (fixedDownloadLimitForApiRoutine(limit, apiRoutine) > 0) {
            builder.appendQueryParameter("count",
                    String.valueOf(fixedDownloadLimitForApiRoutine(limit, apiRoutine)));
        }
        if (!TextUtils.isEmpty(searchQuery)) {
            builder.appendQueryParameter("q", searchQuery);
        }
        JSONArray jArr = http.getRequestAsArray(builder.build().toString());
        return jArrToTimeline(jArr, apiRoutine, url);
    }

    List<MbTimelineItem> jArrToTimeline(JSONArray jArr, ApiRoutineEnum apiRoutine, String url)
            throws ConnectionException {
        List<MbTimelineItem> timeline = new ArrayList<MbTimelineItem>();
        if (jArr != null) {
            // Read the activities in chronological order
            for (int index = jArr.length() - 1; index >= 0; index--) {
                try {
                    JSONObject jso = jArr.getJSONObject(index);
                    MbTimelineItem item = timelineItemFromJson(jso);
                    timeline.add(item);
                } catch (JSONException e) {
                    throw ConnectionException.loggedJsonException(this, "Parsing " + apiRoutine, e, null);
                }
            }
        }
        if (apiRoutine.isMsgPublic()) {
            setMessagesPublic(timeline);
        }
        MyLog.d(this, apiRoutine + " '" + url + "' " + timeline.size() + " items");
        return timeline;
    }

    /**
     * @see <a
     *      href="https://dev.twitter.com/docs/api/1.1/get/users/show">GET users/show</a>
     */
    @Override
    public MbUser getUser(String userId) throws ConnectionException {
        Uri sUri = Uri.parse(getApiPath(ApiRoutineEnum.GET_USER));
        Uri.Builder builder = sUri.buildUpon();
        builder.appendQueryParameter("user_id", userId);
        JSONObject jso = http.getRequest(builder.build().toString());
        return userFromJson(jso);
    }

    @Override
    public MbMessage postDirectMessage(String message, String userId, Uri mediaUri) throws ConnectionException {
        JSONObject formParams = new JSONObject();
        try {
            formParams.put("text", message);
            if (!TextUtils.isEmpty(userId)) {
                formParams.put("user_id", userId);
            }
        } catch (JSONException e) {
            MyLog.e(this, e);
        }
        JSONObject jso = postRequest(ApiRoutineEnum.POST_DIRECT_MESSAGE, formParams);
        return messageFromJson(jso);
    }

    @Override
    public MbMessage postReblog(String rebloggedId) throws ConnectionException {
        JSONObject jso = http.postRequest(getApiPath(ApiRoutineEnum.POST_REBLOG) + rebloggedId + EXTENSION);
        return messageFromJson(jso);
    }

    /**
     * Check API requests status.
     * 
     * Returns the remaining number of API requests available to the requesting 
     * user before the API limit is reached for the current hour. Calls to 
     * rate_limit_status do not count against the rate limit.  If authentication 
     * credentials are provided, the rate limit status for the authenticating 
     * user is returned.  Otherwise, the rate limit status for the requester's 
     * IP address is returned.
     * @see <a
       href="https://dev.twitter.com/docs/api/1/get/account/rate_limit_status">GET 
       account/rate_limit_status</a>
     * 
     * @throws ConnectionException
     */
    @Override
    public MbRateLimitStatus rateLimitStatus() throws ConnectionException {
        JSONObject result = http.getRequest(getApiPath(ApiRoutineEnum.ACCOUNT_RATE_LIMIT_STATUS));
        MbRateLimitStatus status = new MbRateLimitStatus();
        if (result != null) {
            switch (data.getOriginType().getApi()) {
            case TWITTER1P0:
            case GNUSOCIAL_TWITTER:
                status.remaining = result.optInt("remaining_hits");
                status.limit = result.optInt("hourly_limit");
                break;
            default:
                JSONObject resources = null;
                try {
                    resources = result.getJSONObject("resources");
                    JSONObject limitObject = resources.getJSONObject("statuses")
                            .getJSONObject("/statuses/home_timeline");
                    status.remaining = limitObject.optInt("remaining");
                    status.limit = limitObject.optInt("limit");
                } catch (JSONException e) {
                    throw ConnectionException.loggedJsonException(this, "getting rate limits", e, resources);
                }
                break;
            }
        }
        return status;
    }

    @Override
    public MbMessage updateStatus(String message, String inReplyToId, Uri mediaUri) throws ConnectionException {
        JSONObject formParams = new JSONObject();
        try {
            formParams.put("status", message);
            if (!TextUtils.isEmpty(inReplyToId)) {
                formParams.put("in_reply_to_status_id", inReplyToId);
            }
        } catch (JSONException e) {
            MyLog.e(this, e);
        }
        JSONObject jso = postRequest(ApiRoutineEnum.POST_MESSAGE, formParams);
        return messageFromJson(jso);
    }

    /**
     * @see <a
     *      href="http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-account%C2%A0verify_credentials">Twitter
     *      REST API Method: account verify_credentials</a>
     */
    @Override
    public MbUser verifyCredentials() throws ConnectionException {
        JSONObject user = http.getRequest(getApiPath(ApiRoutineEnum.ACCOUNT_VERIFY_CREDENTIALS));
        return userFromJson(user);
    }

    protected final JSONObject postRequest(ApiRoutineEnum apiRoutine, JSONObject formParams)
            throws ConnectionException {
        return http.postRequest(getApiPath(apiRoutine), formParams);
    }

    @Override
    public boolean userObjectHasMessage() {
        return true;
    }
}