From 969a12dc380b9cc8cfaad3b79eba4e2f3a6c3c26 Mon Sep 17 00:00:00 2001 From: nuclearfog Date: Sat, 8 Jan 2022 15:00:20 +0100 Subject: [PATCH] replaced user endpoints, added TwitterException interface --- .../twidda/activities/LoginActivity.java | 4 +- .../nuclearfog/twidda/backend/LinkLoader.java | 12 +- .../twidda/backend/Registration.java | 18 +- .../nuclearfog/twidda/backend/UserAction.java | 17 +- .../nuclearfog/twidda/backend/UserLoader.java | 20 +- .../twidda/backend/api/Twitter.java | 300 +++++++++++++++--- .../twidda/backend/api/TwitterException.java | 118 ++++++- .../nuclearfog/twidda/backend/api/UserV1.java | 6 + .../backend/apiold/EngineException.java | 100 ++---- .../twidda/backend/apiold/TwitterEngine.java | 201 +----------- .../twidda/backend/utils/ErrorHandler.java | 83 +++-- .../twidda/fragments/UserFragment.java | 7 +- 12 files changed, 507 insertions(+), 379 deletions(-) diff --git a/app/src/main/java/org/nuclearfog/twidda/activities/LoginActivity.java b/app/src/main/java/org/nuclearfog/twidda/activities/LoginActivity.java index 5c691ad3..80552ab3 100644 --- a/app/src/main/java/org/nuclearfog/twidda/activities/LoginActivity.java +++ b/app/src/main/java/org/nuclearfog/twidda/activities/LoginActivity.java @@ -23,9 +23,9 @@ import androidx.appcompat.widget.Toolbar; import org.nuclearfog.twidda.R; import org.nuclearfog.twidda.backend.Registration; import org.nuclearfog.twidda.backend.api.Twitter; -import org.nuclearfog.twidda.backend.apiold.EngineException; import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.ErrorHandler; +import org.nuclearfog.twidda.backend.utils.ErrorHandler.TwitterError; import org.nuclearfog.twidda.database.GlobalSettings; import static android.content.Intent.ACTION_VIEW; @@ -195,7 +195,7 @@ public class LoginActivity extends AppCompatActivity implements OnClickListener * * @param error Twitter exception */ - public void onError(EngineException error) { + public void onError(TwitterError error) { ErrorHandler.handleFailure(this, error); } } \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/LinkLoader.java b/app/src/main/java/org/nuclearfog/twidda/backend/LinkLoader.java index 2f36f1e3..0949b04f 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/LinkLoader.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/LinkLoader.java @@ -15,7 +15,7 @@ import org.nuclearfog.twidda.activities.TweetActivity; import org.nuclearfog.twidda.activities.TweetEditor; import org.nuclearfog.twidda.activities.UserLists; import org.nuclearfog.twidda.activities.UserProfile; -import org.nuclearfog.twidda.backend.apiold.TwitterEngine; +import org.nuclearfog.twidda.backend.api.Twitter; import org.nuclearfog.twidda.model.User; import java.lang.ref.WeakReference; @@ -42,12 +42,12 @@ public class LinkLoader extends AsyncTask { private static final Pattern LIST_PATH = Pattern.compile("[\\w]+/lists"); private WeakReference callback; - private TwitterEngine mTwitter; + private Twitter mTwitter; - public LinkLoader(MainActivity callback) { + public LinkLoader(MainActivity activity) { super(); - this.callback = new WeakReference<>(callback); - mTwitter = TwitterEngine.getInstance(callback); + this.callback = new WeakReference<>(activity); + mTwitter = Twitter.get(activity); } @Override @@ -105,7 +105,7 @@ public class LinkLoader extends AsyncTask { int end = path.indexOf('/'); if (end > 0) path = path.substring(0, end); - User user = mTwitter.getUser(path); + User user = mTwitter.showUser(path); data.putSerializable(KEY_PROFILE_DATA, user); data.putBoolean(KEY_PROFILE_DISABLE_RELOAD, true); dataHolder = new DataHolder(data, UserProfile.class); diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/Registration.java b/app/src/main/java/org/nuclearfog/twidda/backend/Registration.java index 45500a38..4894704d 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/Registration.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/Registration.java @@ -2,8 +2,11 @@ package org.nuclearfog.twidda.backend; import android.os.AsyncTask; +import androidx.annotation.Nullable; + import org.nuclearfog.twidda.activities.LoginActivity; import org.nuclearfog.twidda.backend.api.Twitter; +import org.nuclearfog.twidda.backend.api.TwitterException; import org.nuclearfog.twidda.database.AccountDatabase; import org.nuclearfog.twidda.database.AppDatabase; import org.nuclearfog.twidda.database.GlobalSettings; @@ -19,6 +22,8 @@ import java.lang.ref.WeakReference; */ public class Registration extends AsyncTask { + @Nullable + private TwitterException exception; private WeakReference callback; private AccountDatabase accountDB; private AppDatabase database; @@ -56,8 +61,8 @@ public class Registration extends AsyncTask { User user = twitter.login(param[0], param[1]); database.storeUser(user); return ""; - } catch (Exception err) { - err.printStackTrace(); + } catch (TwitterException exception) { + this.exception = exception; } return null; } @@ -65,12 +70,15 @@ public class Registration extends AsyncTask { @Override protected void onPostExecute(String redirectionURL) { - if (callback.get() != null) { + LoginActivity activity = callback.get(); + if (activity != null) { if (redirectionURL != null) { if (!redirectionURL.isEmpty()) { - callback.get().connect(redirectionURL); + activity.connect(redirectionURL); + } else if (exception != null) { + activity.onError(exception); } else { - callback.get().onSuccess(); + activity.onSuccess(); } } } diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/UserAction.java b/app/src/main/java/org/nuclearfog/twidda/backend/UserAction.java index 722dd27e..13470eaf 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/UserAction.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/UserAction.java @@ -3,6 +3,7 @@ package org.nuclearfog.twidda.backend; import android.os.AsyncTask; import org.nuclearfog.twidda.activities.UserProfile; +import org.nuclearfog.twidda.backend.api.Twitter; import org.nuclearfog.twidda.backend.apiold.EngineException; import org.nuclearfog.twidda.backend.apiold.TwitterEngine; import org.nuclearfog.twidda.model.Relation; @@ -61,20 +62,22 @@ public class UserAction extends AsyncTask { private EngineException twException; private WeakReference callback; private TwitterEngine mTwitter; + private Twitter twitter2; private ExcludeDatabase exclDB; private AppDatabase appDB; private long userId; /** - * @param callback Callback to return the result + * @param activity Callback to return the result * @param userId ID of the twitter user */ - public UserAction(UserProfile callback, long userId) { + public UserAction(UserProfile activity, long userId) { super(); - this.callback = new WeakReference<>(callback); - mTwitter = TwitterEngine.getInstance(callback); - exclDB = new ExcludeDatabase(callback); - appDB = new AppDatabase(callback); + this.callback = new WeakReference<>(activity); + mTwitter = TwitterEngine.getInstance(activity); + twitter2 = Twitter.get(activity); + exclDB = new ExcludeDatabase(activity); + appDB = new AppDatabase(activity); this.userId = userId; } @@ -95,7 +98,7 @@ public class UserAction extends AsyncTask { case PROFILE_lOAD: // load user information from twitter - user = mTwitter.getUser(userId); + user = twitter2.showUser(userId); publishProgress(user); appDB.storeUser(user); // load user relations from twitter diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/UserLoader.java b/app/src/main/java/org/nuclearfog/twidda/backend/UserLoader.java index 00380848..a535f1c6 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/UserLoader.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/UserLoader.java @@ -6,8 +6,6 @@ import androidx.annotation.Nullable; import org.nuclearfog.twidda.backend.api.Twitter; import org.nuclearfog.twidda.backend.api.TwitterException; -import org.nuclearfog.twidda.backend.apiold.EngineException; -import org.nuclearfog.twidda.backend.apiold.TwitterEngine; import org.nuclearfog.twidda.backend.lists.Users; import org.nuclearfog.twidda.fragments.UserFragment; @@ -67,10 +65,9 @@ public class UserLoader extends AsyncTask { } @Nullable - private EngineException twException; + private TwitterException twException; private final WeakReference callback; - private final TwitterEngine mTwitter; - private Twitter mTwitter2; + private Twitter mTwitter; private final Type type; private final String search; @@ -80,8 +77,7 @@ public class UserLoader extends AsyncTask { public UserLoader(UserFragment callback, Type type, long id, String search) { super(); this.callback = new WeakReference<>(callback); - mTwitter = TwitterEngine.getInstance(callback.getContext()); - mTwitter2 = Twitter.get(callback.getContext()); + mTwitter = Twitter.get(callback.getContext()); this.type = type; this.search = search; this.id = id; @@ -100,16 +96,16 @@ public class UserLoader extends AsyncTask { return mTwitter.getFollowing(id, cursor); case RETWEET: - return mTwitter2.getRetweetingUsers(id); + return mTwitter.getRetweetingUsers(id); case FAVORIT: - return mTwitter2.getLikingUsers(id); + return mTwitter.getLikingUsers(id); case SEARCH: return mTwitter.searchUsers(search, cursor); case SUBSCRIBER: - return mTwitter.getListFollower(id, cursor); + return mTwitter.getListSubscriber(id, cursor); case LISTMEMBER: return mTwitter.getListMember(id, cursor); @@ -121,10 +117,8 @@ public class UserLoader extends AsyncTask { return mTwitter.getMutedUsers(cursor); } - } catch (EngineException twException) { + } catch (TwitterException twException) { this.twException = twException; - } catch (TwitterException err) { - } return null; } diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/Twitter.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/Twitter.java index a5b49c48..a842dff3 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/api/Twitter.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/Twitter.java @@ -11,12 +11,14 @@ import org.nuclearfog.twidda.backend.lists.Users; import org.nuclearfog.twidda.backend.utils.StringTools; import org.nuclearfog.twidda.backend.utils.TLSSocketFactory; import org.nuclearfog.twidda.backend.utils.Tokens; +import org.nuclearfog.twidda.database.ExcludeDatabase; import org.nuclearfog.twidda.model.User; import org.nuclearfog.twidda.database.GlobalSettings; import java.io.IOException; import java.security.KeyStore; import java.util.Arrays; +import java.util.Set; import java.util.TreeSet; import javax.net.ssl.TrustManagerFactory; @@ -36,19 +38,31 @@ import okhttp3.Response; public class Twitter { private static final String OAUTH = "1.0"; + public static final String SIGNATURE_ALG = "HMAC-SHA256"; + private static final String API = "https://api.twitter.com/"; private static final String AUTHENTICATE = API + "oauth/authenticate"; private static final String REQUEST_TOKEN = API + "oauth/request_token"; private static final String OAUTH_VERIFIER = API + "oauth/access_token"; private static final String CREDENTIALS = API + "1.1/account/verify_credentials.json"; private static final String USER_LOOKUP = API + "1.1/users/show.json"; + private static final String USER_FOLLOWING = API + "1.1/friends/list.json"; + private static final String USER_FOLLOWER = API + "1.1/followers/list.json"; + private static final String USER_SEARCH = API + "1.1/users/search.json"; + private static final String USER_LIST_MEMBER = API + "1.1/lists/members.json"; + private static final String USER_LIST_SUBSCRIBER = API + "1.1/lists/subscribers.json"; + private static final String BLOCK_LIST = API + "1.1/blocks/list.json"; + private static final String MUTES_LIST = API + "1.1/mutes/users/list.json"; public static final String REQUEST_URL = AUTHENTICATE + "?oauth_token="; - public static final String SIGNATURE_ALG = "HMAC-SHA256"; + + + private static final String SKIP_STAT = "skip_status=true"; private static Twitter instance; private OkHttpClient client; private GlobalSettings settings; + private ExcludeDatabase filterList; private Tokens tokens; @@ -65,8 +79,9 @@ public class Twitter { } else { client = new OkHttpClient().newBuilder().build(); } - settings = GlobalSettings.getInstance(context); tokens = Tokens.getInstance(context); + settings = GlobalSettings.getInstance(context); + filterList = new ExcludeDatabase(context); } /** @@ -94,7 +109,7 @@ public class Twitter { Uri uri = Uri.parse(AUTHENTICATE + "?" + res); return uri.getQueryParameter("oauth_token"); } else { - throw new TwitterException(response.code()); + throw new TwitterException(response); } } catch (IOException e) { throw new TwitterException(e); @@ -108,8 +123,6 @@ public class Twitter { */ public User login(String oauth_token, String pin) throws TwitterException { try { - if (oauth_token == null) - throw new TwitterException(TwitterException.TOKEN_NOT_SET); String paramPin = "oauth_verifier=" + pin; String paramToken = "oauth_token=" + oauth_token; Response response = post(OAUTH_VERIFIER, paramPin, paramToken); @@ -123,37 +136,13 @@ public class Twitter { settings.setogin(true); return getCredentials(); } else { - throw new TwitterException(response.code()); + throw new TwitterException(response); } } catch (IOException e) { throw new TwitterException(e); } } - /** - * lookup single user - * - * @param id ID of the user - * @return user information - */ - public User showUser(long id) throws TwitterException { - try { - String param = "user_id=" + id; - String extra = "include_entities=true"; - Response response = get(USER_LOOKUP, param, extra); - if (response.code() == 200 && response.body() != null) { - JSONObject json = new JSONObject(response.body().string()); - return new UserV1(json, settings.getCurrentUserId()); - } else { - throw new TwitterException(response.code()); - } - } catch (IOException err) { - throw new TwitterException(err); - } catch (JSONException err) { - throw new TwitterException(err); - } - } - /** * get credentials of the current user * @@ -162,11 +151,15 @@ public class Twitter { public User getCredentials() throws TwitterException { try { Response response = get(CREDENTIALS); - if (response.code() == 200 && response.body() != null) { + if (response.body() != null) { JSONObject json = new JSONObject(response.body().string()); - return new UserV1(json); + if (response.code() == 200) { + return new UserV1(json); + } else { + throw new TwitterException(json); + } } else { - throw new TwitterException(response.code()); + throw new TwitterException(response); } } catch (IOException err) { throw new TwitterException(err); @@ -175,6 +168,108 @@ public class Twitter { } } + /** + * lookup user and return user information + * + * @param id ID of the user + * @return user information + */ + public User showUser(long id) throws TwitterException { + String param = "user_id=" + id; + String extra = "include_entities=true"; + return showUser(param, extra); + } + + /** + * lookup user and return user information + * + * @param name screen name of the user + * @return user information + */ + public User showUser(String name) throws TwitterException { + String param = "screen_name=" + name; + String extra = "include_entities=true"; + return showUser(param, extra); + } + + /** + * create a list of users a specified user is following + * + * @param userId ID of the user + * @param cursor cursor value used to parse the list + * @return list of users + */ + public Users getFollowing(long userId, long cursor) throws TwitterException { + String paramId = "user_id=" + userId; + String paramCsr = "cursor=" + cursor; + String paramCnt = "count=" + settings.getListSize(); + return getUsers1(USER_FOLLOWING, paramId, paramCsr, paramCnt, SKIP_STAT); + } + + /** + * create a list of users following a specified user + * + * @param userId ID of the user + * @param cursor cursor value used to parse the list + * @return list of users + */ + public Users getFollower(long userId, long cursor) throws TwitterException { + String paramId = "user_id=" + userId; + String paramCsr = "cursor=" + cursor; + String paramCnt = "count=" + settings.getListSize(); + return getUsers1(USER_FOLLOWER, paramId, paramCsr, paramCnt, SKIP_STAT); + } + + /** + * create a list of user list members + * + * @param listId ID of the list + * @param cursor cursor value used to parse the list + * @return list of users + */ + public Users getListMember(long listId, long cursor) throws TwitterException { + String paramId = "list_id=" + listId; + String paramCsr = "cursor=" + cursor; + String paramCnt = "count=" + settings.getListSize(); + return getUsers1(USER_LIST_MEMBER, paramId, paramCsr, paramCnt, SKIP_STAT); + } + + /** + * create a list of user list subscriber + * + * @param listId ID of the list + * @param cursor cursor value used to parse the list + * @return list of users + */ + public Users getListSubscriber(long listId, long cursor) throws TwitterException { + String paramId = "list_id=" + listId; + String paramCsr = "cursor=" + cursor; + String paramCnt = "count=" + settings.getListSize(); + return getUsers1(USER_LIST_SUBSCRIBER, paramId, paramCsr, paramCnt, SKIP_STAT); + } + + /** + * get block list of the current user + * + * @param cursor cursor value used to parse the list + * @return list of users + */ + public Users getBlockedUsers(long cursor) throws TwitterException { + String paramCsr = "cursor=" + cursor; + return getUsers1(BLOCK_LIST, paramCsr, SKIP_STAT); + } + + /** + * get mute list of the current user + * + * @param cursor cursor value used to parse the list + * @return list of users + */ + public Users getMutedUsers(long cursor) throws TwitterException { + String paramCsr = "cursor=" + cursor; + return getUsers1(MUTES_LIST, paramCsr, SKIP_STAT); + } + /** * get users retweeting a tweet * @@ -183,7 +278,7 @@ public class Twitter { */ public Users getRetweetingUsers(long tweetId) throws TwitterException { String endpoint = API + "2/tweets/" + tweetId + "/retweeted_by"; - return getUsers(endpoint); + return getUsers2(endpoint); } /** @@ -194,29 +289,144 @@ public class Twitter { */ public Users getLikingUsers(long tweetId) throws TwitterException { String endpoint = API + "2/tweets/" + tweetId + "/liking_users"; - return getUsers(endpoint); + return getUsers2(endpoint); } /** - * get a list of twitter users + * search for users matching a search string + * + * @param search search string + * @param page page of the search results + * @return list of users + */ + public Users searchUsers(String search, long page) throws TwitterException { + // search endpoint only supports pages parameter + long currentPage = page > 0 ? page : 1; + long nextPage = currentPage + 1; + String paramQuery = "q=" + search; + String paramPage = "page=" + currentPage; + String paramCnt = "count=" + settings.getListSize(); + + try { + Response response = get(USER_SEARCH, paramQuery, paramPage, paramCnt); + if (response.body() != null) { + JSONArray array = new JSONArray(response.body().string()); + if (response.code() == 200) { + if (array.length() < 20) + nextPage = 0; + Users users = new Users(currentPage - 1, nextPage); + long homeId = settings.getCurrentUserId(); + // filter results if enabled + if (settings.filterResults()) { + Set exclude = filterList.getExcludeSet(); + for (int i = 0; i < array.length(); i++) { + User user = new UserV1(array.getJSONObject(i), homeId); + if (!exclude.contains(user.getId())) { + users.add(user); + } + } + } else { + for (int i = 0; i < array.length(); i++) { + User user = new UserV1(array.getJSONObject(i), homeId); + users.add(user); + } + } + return users; + } + } + throw new TwitterException(response); + } catch (IOException err) { + throw new TwitterException(err); + } catch (JSONException err) { + throw new TwitterException(err); + } + } + + /** + * lookup single user + * + * @param params additional parameter added to request + * @return user information + */ + public User showUser(String... params) throws TwitterException { + try { + Response response = get(USER_LOOKUP, params); + if (response.body() != null) { + JSONObject json = new JSONObject(response.body().string()); + if (response.code() == 200) { + return new UserV1(json, settings.getCurrentUserId()); + } else { + throw new TwitterException(json); + } + } else { + throw new TwitterException(response); + } + } catch (IOException err) { + throw new TwitterException(err); + } catch (JSONException err) { + throw new TwitterException(err); + } + } + + /** + * create a list of users using API v 1.1 + * + * @param endpoint endpoint url to get the user data from + * @param params additional parameters + * @return user list + */ + private Users getUsers1(String endpoint, String... params) throws TwitterException { + try { + Response response = get(endpoint, params); + if (response.body() != null) { + JSONObject json = new JSONObject(response.body().string()); + if (response.code() == 200) { + JSONArray array = json.getJSONArray("users"); + long prevCursor = json.getLong("previous_cursor"); + long nextCursor = json.getLong("next_cursor"); + Users users = new Users(prevCursor, nextCursor); + long homeId = settings.getCurrentUserId(); + for (int i = 0; i < array.length(); i++) { + users.add(new UserV1(array.getJSONObject(i), homeId)); + } + return users; + } else { + throw new TwitterException(json); + } + } else { + throw new TwitterException(response); + } + } catch (IOException err) { + throw new TwitterException(err); + } catch (JSONException err) { + throw new TwitterException(err); + } + } + + /** + * create a list of users using API v 2 * * @param endpoint endpoint url to get the user data from * @return user list */ - private Users getUsers(String endpoint) throws TwitterException { + private Users getUsers2(String endpoint) throws TwitterException { try { Response response = get(endpoint, UserV2.PARAMS); - if (response.code() == 200 && response.body() != null) { + if (response.body() != null) { JSONObject json = new JSONObject(response.body().string()); - JSONArray array = json.getJSONArray("data"); - Users users = new Users(); - long homeId = settings.getCurrentUserId(); - for (int i = 0 ; i < array.length() ; i++) { - users.add(new UserV2(array.getJSONObject(i), homeId)); + if (response.code() == 200) { + JSONArray array = json.getJSONArray("data"); + Users users = new Users(); + long homeId = settings.getCurrentUserId(); + for (int i = 0; i < array.length(); i++) { + users.add(new UserV2(array.getJSONObject(i), homeId)); + } + return users; + } else { + throw new TwitterException(json); } - return users; } else { - throw new TwitterException(response.code()); + throw new TwitterException(response); } } catch (IOException err) { throw new TwitterException(err); diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/TwitterException.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/TwitterException.java index 2f27f407..57476caf 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/api/TwitterException.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/TwitterException.java @@ -1,24 +1,128 @@ package org.nuclearfog.twidda.backend.api; +import org.json.JSONArray; +import org.json.JSONObject; +import org.nuclearfog.twidda.backend.utils.ErrorHandler.TwitterError; + import java.io.IOException; +import okhttp3.Response; -public class TwitterException extends Exception { +/** + * + * @author nuclearfog + */ +public class TwitterException extends Exception implements TwitterError { - public static final int TOKEN_NOT_SET = 600; + private int httpCode = -1; + private int errorCode = -1; + private int retryAfter = -1; + private String message = ""; - private int httpCode; TwitterException(Exception e) { super(e); httpCode = -1; } - TwitterException(int httpCode) { - this.httpCode = httpCode; + + TwitterException(Response response) { + this.httpCode = response.code(); + this.message = response.message(); } - public int getCode() { - return httpCode; + + TwitterException(JSONObject json) { + JSONArray errors = json.optJSONArray("errors"); + if (errors != null) { + JSONObject error = errors.optJSONObject(0); + if (error != null) { + message = error.optString("message"); + errorCode = error.optInt("code"); + retryAfter = error.optInt("x-rate-limit-remaining", -1); + } + } + } + + @Override + public int getErrorType() { + switch (errorCode) { + case 88: + case 420: // + case 429: // Rate limit exceeded! + return RATE_LIMIT_EX; + + + case 17: + case 50: // USER not found + case 63: // USER suspended + case 108: + return USER_NOT_FOUND; + + case 32: + return ACCESS_TOKEN_DEAD; + + case 416: + return APP_SUSPENDED; + + case 34: // + case 144: // TWEET not found + return RESOURCE_NOT_FOUND; + + case 150: + return CANT_SEND_DM; + + case 120: + return ACCOUNT_UPDATE_FAILED; + + case 136: + case 179: + return NOT_AUTHORIZED; + + case 186: + return TWEET_TOO_LONG; + + case 187: + return DUPLICATE_TWEET; + + case 349: + return NO_DM_TO_USER; + + case 215: // Invalid API keys + case 261: + return ERROR_API_ACCESS_DENIED; + + case 354: + return DM_TOO_LONG; + + case 89: + return TOKEN_EXPIRED; + + case 385: // replying tweet that is not visible or deleted + return TWEET_CANT_REPLY; + + default: + if (httpCode == 401) { + return NOT_AUTHORIZED; + } else if (httpCode == 403) { + return REQUEST_FORBIDDEN; + } else if (httpCode == 408) { + return REQUEST_CANCELLED; + } else if (this.getCause() instanceof IOException) { + return NO_CONNECTION; + } else { + return ERROR_NOT_DEFINED; + } + } + } + + @Override + public int getTimeToWait() { + return retryAfter; + } + + @Override + public String getMessage() { + return message; } } \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/UserV1.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/UserV1.java index 84692cb9..baf9ef5f 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/api/UserV1.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/UserV1.java @@ -181,6 +181,12 @@ class UserV1 implements User { return isCurrentUser; } + @NonNull + @Override + public String toString() { + return username + " " + screenName; + } + /** * set time of account creation * diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/apiold/EngineException.java b/app/src/main/java/org/nuclearfog/twidda/backend/apiold/EngineException.java index 79e24deb..a5eabfd9 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/apiold/EngineException.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/apiold/EngineException.java @@ -1,7 +1,6 @@ package org.nuclearfog.twidda.backend.apiold; -import static org.nuclearfog.twidda.backend.apiold.EngineException.ErrorType.RESOURCE_NOT_FOUND; -import static org.nuclearfog.twidda.backend.apiold.EngineException.ErrorType.USER_NOT_FOUND; +import org.nuclearfog.twidda.backend.utils.ErrorHandler.TwitterError; import twitter4j.TwitterException; @@ -10,9 +9,9 @@ import twitter4j.TwitterException; * * @author nuclearfog */ -public class EngineException extends Exception { +public class EngineException extends Exception implements TwitterError { - private final ErrorType errorType; + private int errorType; private String msg; private int retryAfter; @@ -30,7 +29,7 @@ public class EngineException extends Exception { case 88: case 420: // case 429: // Rate limit exceeded! - errorType = ErrorType.RATE_LIMIT_EX; + errorType = RATE_LIMIT_EX; retryAfter = error.getRetryAfter(); break; @@ -38,81 +37,81 @@ public class EngineException extends Exception { case 50: // USER not found case 63: // USER suspended case 108: - errorType = ErrorType.USER_NOT_FOUND; + errorType = USER_NOT_FOUND; break; case 32: - errorType = ErrorType.ACCESS_TOKEN_DEAD; + errorType = ACCESS_TOKEN_DEAD; break; case 416: - errorType = ErrorType.APP_SUSPENDED; + errorType = APP_SUSPENDED; break; case 34: // case 144: // TWEET not found - errorType = ErrorType.RESOURCE_NOT_FOUND; + errorType = RESOURCE_NOT_FOUND; break; case 150: - errorType = ErrorType.CANT_SEND_DM; + errorType = CANT_SEND_DM; break; case 120: - errorType = ErrorType.ACCOUNT_UPDATE_FAILED; + errorType = ACCOUNT_UPDATE_FAILED; break; case 136: case 179: - errorType = ErrorType.NOT_AUTHORIZED; + errorType = NOT_AUTHORIZED; break; case 186: - errorType = ErrorType.TWEET_TOO_LONG; + errorType = TWEET_TOO_LONG; break; case 187: - errorType = ErrorType.DUPLICATE_TWEET; + errorType = DUPLICATE_TWEET; break; case 349: - errorType = ErrorType.NO_DM_TO_USER; + errorType = NO_DM_TO_USER; break; case 215: // Invalid API keys case 261: - errorType = ErrorType.ERROR_API_ACCESS_DENIED; + errorType = ERROR_API_ACCESS_DENIED; break; case 354: - errorType = ErrorType.DM_TOO_LONG; + errorType = DM_TOO_LONG; break; case 89: - errorType = ErrorType.TOKEN_EXPIRED; + errorType = TOKEN_EXPIRED; break; case 385: // replying tweet that is not visible or deleted - errorType = ErrorType.TWEET_CANT_REPLY; + errorType = TWEET_CANT_REPLY; break; default: if (error.getStatusCode() == 401) { - errorType = ErrorType.NOT_AUTHORIZED; + errorType = NOT_AUTHORIZED; } else if (error.getStatusCode() == 403) { - errorType = ErrorType.REQUEST_FORBIDDEN; + errorType = REQUEST_FORBIDDEN; } else if (error.getStatusCode() == 408) { - errorType = ErrorType.REQUEST_CANCELLED; + errorType = REQUEST_CANCELLED; } else if (error.isCausedByNetworkIssue()) { - errorType = ErrorType.NO_CONNECTION; + errorType = NO_CONNECTION; } else { - errorType = ErrorType.ERROR_NOT_DEFINED; + errorType = ERROR_NOT_DEFINED; msg = error.getErrorMessage(); } break; } } else { - errorType = ErrorType.ERROR_NOT_DEFINED; + errorType = ERROR_NOT_DEFINED; msg = exception.getMessage(); } } @@ -122,22 +121,22 @@ public class EngineException extends Exception { * * @param errorCode custom error code */ - EngineException(InternalErrorType errorCode) { + EngineException(int errorCode) { switch (errorCode) { case FILENOTFOUND: - errorType = ErrorType.NO_MEDIA_FOUND; + errorType = NO_MEDIA_FOUND; break; case TOKENNOTSET: - errorType = ErrorType.NO_LINK_DEFINED; + errorType = NO_LINK_DEFINED; break; case BITMAP_FAILURE: - errorType = ErrorType.IMAGE_NOT_LOADED; + errorType = IMAGE_NOT_LOADED; break; default: - errorType = ErrorType.ERROR_NOT_DEFINED; + errorType = ERROR_NOT_DEFINED; break; } } @@ -153,9 +152,9 @@ public class EngineException extends Exception { /** * get type of error defined by twitter API * - * @return type of error {@link ErrorType} + * @return error code */ - public ErrorType getErrorType() { + public int getErrorType() { return errorType; } @@ -176,41 +175,4 @@ public class EngineException extends Exception { public int getTimeToWait() { return retryAfter; } - - /** - * enum of error types used by this class - */ - public enum ErrorType { - RATE_LIMIT_EX, - USER_NOT_FOUND, - APP_SUSPENDED, - ACCESS_TOKEN_DEAD, - TWEET_CANT_REPLY, - RESOURCE_NOT_FOUND, - CANT_SEND_DM, - NOT_AUTHORIZED, - TWEET_TOO_LONG, - DUPLICATE_TWEET, - NO_DM_TO_USER, - DM_TOO_LONG, - TOKEN_EXPIRED, - NO_MEDIA_FOUND, - NO_LINK_DEFINED, - NO_CONNECTION, - IMAGE_NOT_LOADED, - REQUEST_CANCELLED, - REQUEST_FORBIDDEN, - ACCOUNT_UPDATE_FAILED, - ERROR_API_ACCESS_DENIED, - ERROR_NOT_DEFINED - } - - /** - * error types only accessible by {@link TwitterEngine} - */ - enum InternalErrorType { - FILENOTFOUND, - TOKENNOTSET, - BITMAP_FAILURE - } } \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/apiold/TwitterEngine.java b/app/src/main/java/org/nuclearfog/twidda/backend/apiold/TwitterEngine.java index b0baebe4..a6902e70 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/apiold/TwitterEngine.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/apiold/TwitterEngine.java @@ -10,7 +10,6 @@ import androidx.annotation.Nullable; import org.nuclearfog.twidda.backend.holder.ListHolder; import org.nuclearfog.twidda.backend.holder.TweetHolder; import org.nuclearfog.twidda.backend.lists.Directmessages; -import org.nuclearfog.twidda.backend.lists.Users; import org.nuclearfog.twidda.backend.lists.UserLists; import org.nuclearfog.twidda.backend.utils.ProxySetup; import org.nuclearfog.twidda.backend.utils.TLSSocketFactory; @@ -239,37 +238,6 @@ public class TwitterEngine { } } - /** - * Get User search result - * - * @param search Search String - * @return List of Users - * @throws EngineException if access is unavailable - */ - public Users searchUsers(String search, long cursor) throws EngineException { - try { - List users; - int currentPage = 1; - if (cursor > 0) - currentPage = (int) cursor; - long prevPage = currentPage - 1; - long nextPage = currentPage + 1; - if (settings.filterResults()) { - users = convertUserListFiltered(twitter.searchUsers(search, currentPage)); - } else { - users = convertUserList(twitter.searchUsers(search, currentPage)); - } - if (users.size() < 20) { - nextPage = 0; - } - Users result = new Users(prevPage, nextPage); - result.addAll(users); - return result; - } catch (Exception err) { - throw new EngineException(err); - } - } - /** * Get User Tweets * @@ -357,21 +325,6 @@ public class TwitterEngine { } } - /** - * Get User - * - * @param username screen name of the user - * @return User Object - * @throws EngineException if Access is unavailable - */ - public User getUser(String username) throws EngineException { - try { - return new UserV1(twitter.showUser(username), twitter.getId()); - } catch (Exception err) { - throw new EngineException(err); - } - } - /** * Efficient Access of Connection Information * @@ -517,70 +470,6 @@ public class TwitterEngine { } } - /** - * get Following User List - * - * @param userId User ID - * @return List of Following User with cursors - * @throws EngineException if Access is unavailable - */ - public Users getFollowing(long userId, long cursor) throws EngineException { - try { - PagableResponseList list = twitter.getFriendsList(userId, cursor); - return createUserList(list, cursor); - } catch (Exception err) { - throw new EngineException(err); - } - } - - /** - * get Follower - * - * @param userId User ID - * @return List of Follower with cursors attached - * @throws EngineException if Access is unavailable - */ - public Users getFollower(long userId, long cursor) throws EngineException { - try { - PagableResponseList list = twitter.getFollowersList(userId, cursor); - return createUserList(list, cursor); - } catch (Exception err) { - throw new EngineException(err); - } - } - - /** - * get a list of blocked users - * - * @param cursor list cursor - * @return user list - * @throws EngineException if twitter service is unavailable - */ - public Users getBlockedUsers(long cursor) throws EngineException { - try { - PagableResponseList list = twitter.getBlocksList(cursor); - return createUserList(list, cursor); - } catch (Exception err) { - throw new EngineException(err); - } - } - - /** - * get a list of muted users - * - * @param cursor list cursor - * @return user list - * @throws EngineException if twitter service is unavailable - */ - public Users getMutedUsers(long cursor) throws EngineException { - try { - PagableResponseList list = twitter.getMutesList(cursor); - return createUserList(list, cursor); - } catch (Exception err) { - throw new EngineException(err); - } - } - /** * get a list of blocked/muted user IDs * @@ -991,38 +880,6 @@ public class TwitterEngine { } } - /** - * Get subscriber of a user list - * - * @param listId ID of the list - * @return list of users following the list - * @throws EngineException if access is unavailable - */ - public Users getListFollower(long listId, long cursor) throws EngineException { - try { - PagableResponseList users = twitter.getUserListSubscribers(listId, cursor); - return createUserList(users, cursor); - } catch (Exception err) { - throw new EngineException(err); - } - } - - /** - * Get member of a list - * - * @param listId ID of the list - * @return list of users - * @throws EngineException if access is unavailable - */ - public Users getListMember(long listId, long cursor) throws EngineException { - try { - PagableResponseList users = twitter.getUserListMembers(listId, cursor); - return createUserList(users, cursor); - } catch (Exception err) { - throw new EngineException(err); - } - } - /** * get tweets of a lists * @@ -1055,7 +912,7 @@ public class TwitterEngine { InputStream stream = url.openConnection().getInputStream(); return BitmapFactory.decodeStream(stream); } catch (IOException err) { - throw new EngineException(EngineException.InternalErrorType.BITMAP_FAILURE); + throw new EngineException(EngineException.BITMAP_FAILURE); } } @@ -1071,7 +928,7 @@ public class TwitterEngine { UploadedMedia media = twitter.uploadMedia("", new FileInputStream(path)); return media.getMediaId(); } catch (FileNotFoundException err) { - throw new EngineException(EngineException.InternalErrorType.FILENOTFOUND); + throw new EngineException(EngineException.FILENOTFOUND); } catch (Exception err) { throw new EngineException(err); } @@ -1089,7 +946,7 @@ public class TwitterEngine { UploadedMedia media = twitter.uploadMediaChunked("", new FileInputStream(path)); return media.getMediaId(); } catch (FileNotFoundException err) { - throw new EngineException(EngineException.InternalErrorType.FILENOTFOUND); + throw new EngineException(EngineException.FILENOTFOUND); } catch (Exception err) { throw new EngineException(err); } @@ -1145,58 +1002,6 @@ public class TwitterEngine { } } - /** - * create user list from {@link PagableResponseList} - * - * @param list user list - * @param cursor prev cursor of the list - * @return user list - * @throws TwitterException if access is unavailable - */ - private Users createUserList(PagableResponseList list, long cursor) throws TwitterException { - long prevCursor = cursor > 0 ? cursor : 0; - long nextCursor = list.getNextCursor(); - Users result = new Users(prevCursor, nextCursor); - if (!list.isEmpty()) { - result.addAll(convertUserList(list)); - } - return result; - } - - /** - * convert {@link twitter4j.User} to User List - * - * @param users Twitter4J user List - * @return User - */ - private List convertUserList(List users) throws TwitterException { - long id = twitter.getId(); - ArrayList result = new ArrayList<>(); - result.ensureCapacity(users.size()); - for (twitter4j.User user : users) { - result.add(new UserV1(user, id)); - } - return result; - } - - /** - * convert {@link twitter4j.User} list to User List and filter excluded users - * - * @param users Twitter4J user List - * @return User - */ - private List convertUserListFiltered(List users) throws TwitterException { - long id = twitter.getId(); - Set exclude = excludeDB.getExcludeSet(); - List result = new LinkedList<>(); - for (twitter4j.User user : users) { - if (!exclude.contains(user.getId())) { - result.add(new UserV1(user, id)); - } - } - return result; - } - /** * create paging for tweets * diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/utils/ErrorHandler.java b/app/src/main/java/org/nuclearfog/twidda/backend/utils/ErrorHandler.java index 120d09e2..7505b50e 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/utils/ErrorHandler.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/utils/ErrorHandler.java @@ -27,7 +27,7 @@ public final class ErrorHandler { * @param context current activity context * @param error Error exception thrown by TwitterEngine */ - public static void handleFailure(@NonNull Context context, @Nullable EngineException error) { + public static void handleFailure(@NonNull Context context, @Nullable TwitterError error) { String message = getErrorMessage(context, error); Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); } @@ -39,10 +39,10 @@ public final class ErrorHandler { * @param error Twitter error * @return message string */ - public static String getErrorMessage(Context context, @Nullable EngineException error) { + public static String getErrorMessage(Context context, @Nullable TwitterError error) { if (error != null) { switch (error.getErrorType()) { - case RATE_LIMIT_EX: + case TwitterError.RATE_LIMIT_EX: if (error.getTimeToWait() > 0) { String errMsg = context.getString(R.string.error_limit_exceeded); if (error.getTimeToWait() >= 60) @@ -52,68 +52,68 @@ public final class ErrorHandler { } return context.getString(R.string.error_rate_limit); - case USER_NOT_FOUND: + case TwitterError.USER_NOT_FOUND: return context.getString(R.string.error_user_not_found); - case RESOURCE_NOT_FOUND: + case TwitterError.RESOURCE_NOT_FOUND: return context.getString(R.string.error_not_found); - case CANT_SEND_DM: + case TwitterError.CANT_SEND_DM: return context.getString(R.string.error_send_dm_to_user); - case NOT_AUTHORIZED: + case TwitterError.NOT_AUTHORIZED: return context.getString(R.string.error_not_authorized); - case TWEET_TOO_LONG: + case TwitterError.TWEET_TOO_LONG: return context.getString(R.string.error_status_length); - case DUPLICATE_TWEET: + case TwitterError.DUPLICATE_TWEET: return context.getString(R.string.error_duplicate_status); - case NO_DM_TO_USER: + case TwitterError.NO_DM_TO_USER: return context.getString(R.string.error_dm_send); - case DM_TOO_LONG: + case TwitterError.DM_TOO_LONG: return context.getString(R.string.error_dm_length); - case TOKEN_EXPIRED: + case TwitterError.TOKEN_EXPIRED: return context.getString(R.string.error_accesstoken); - case NO_MEDIA_FOUND: + case TwitterError.NO_MEDIA_FOUND: return context.getString(R.string.error_file_not_found); - case NO_LINK_DEFINED: + case TwitterError.NO_LINK_DEFINED: return context.getString(R.string.error_token_not_set); - case NO_CONNECTION: + case TwitterError.NO_CONNECTION: return context.getString(R.string.error_connection_failed); - case IMAGE_NOT_LOADED: + case TwitterError.IMAGE_NOT_LOADED: return context.getString(R.string.error_image_loading); - case ACCESS_TOKEN_DEAD: + case TwitterError.ACCESS_TOKEN_DEAD: return context.getString(R.string.error_corrupt_api_key); - case TWEET_CANT_REPLY: + case TwitterError.TWEET_CANT_REPLY: return context.getString(R.string.error_cant_reply_to_tweet); - case ACCOUNT_UPDATE_FAILED: + case TwitterError.ACCOUNT_UPDATE_FAILED: return context.getString(R.string.error_acc_update); - case REQUEST_CANCELLED: + case TwitterError.REQUEST_CANCELLED: return context.getString(R.string.error_result_cancelled); - case REQUEST_FORBIDDEN: + case TwitterError.REQUEST_FORBIDDEN: return context.getString(R.string.error_forbidden_api_access); - case APP_SUSPENDED: - case ERROR_API_ACCESS_DENIED: + case TwitterError.APP_SUSPENDED: + case TwitterError.ERROR_API_ACCESS_DENIED: GlobalSettings settings = GlobalSettings.getInstance(context); if (settings.isCustomApiSet()) return context.getString(R.string.error_api_access_denied); return context.getString(R.string.error_api_key_expired); - case ERROR_NOT_DEFINED: + case TwitterError.ERROR_NOT_DEFINED: return error.getMessage(); default: @@ -123,4 +123,39 @@ public final class ErrorHandler { return context.getString(R.string.error_not_defined); } } + + public interface TwitterError { + + int ERROR_NOT_DEFINED = -1; + int RATE_LIMIT_EX = 0; + int USER_NOT_FOUND = 1; + int RESOURCE_NOT_FOUND = 2; + int CANT_SEND_DM = 3; + int NOT_AUTHORIZED = 4; + int TWEET_TOO_LONG = 5; + int DUPLICATE_TWEET = 6; + int NO_DM_TO_USER = 7; + int DM_TOO_LONG = 8; + int TOKEN_EXPIRED = 9; + int NO_MEDIA_FOUND = 10; + int NO_LINK_DEFINED = 11; + int NO_CONNECTION = 12; + int IMAGE_NOT_LOADED = 13; + int ACCESS_TOKEN_DEAD = 14; + int TWEET_CANT_REPLY = 15; + int ACCOUNT_UPDATE_FAILED = 16; + int REQUEST_CANCELLED = 17; + int REQUEST_FORBIDDEN = 18; + int APP_SUSPENDED = 19; + int ERROR_API_ACCESS_DENIED = 20; + int FILENOTFOUND = 23; + int TOKENNOTSET = 22; + int BITMAP_FAILURE = 21; + + int getErrorType(); + + int getTimeToWait(); + + String getMessage(); + } } \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/fragments/UserFragment.java b/app/src/main/java/org/nuclearfog/twidda/fragments/UserFragment.java index fe49c543..7acbee2e 100644 --- a/app/src/main/java/org/nuclearfog/twidda/fragments/UserFragment.java +++ b/app/src/main/java/org/nuclearfog/twidda/fragments/UserFragment.java @@ -22,6 +22,7 @@ import org.nuclearfog.twidda.backend.ListManager; import org.nuclearfog.twidda.backend.ListManager.ListManagerCallback; import org.nuclearfog.twidda.backend.UserLoader; import org.nuclearfog.twidda.backend.UserLoader.Type; +import org.nuclearfog.twidda.backend.api.TwitterException; import org.nuclearfog.twidda.backend.apiold.EngineException; import org.nuclearfog.twidda.backend.lists.Users; import org.nuclearfog.twidda.model.User; @@ -258,10 +259,10 @@ public class UserFragment extends ListFragment implements UserClickListener, /** * called when an error occurs * - * @param error Engine exception + * @param exception Twitter exception */ - public void onError(EngineException error) { - ErrorHandler.handleFailure(requireContext(), error); + public void onError(TwitterException exception) { + ErrorHandler.handleFailure(requireContext(), exception); adapter.disableLoading(); setRefresh(false); }