added option to use Twitter API v1.1 only

This commit is contained in:
nuclearfog 2023-01-20 20:47:09 +01:00
parent 4d4201a0fb
commit c127e8ec15
No known key found for this signature in database
GPG Key ID: 03488A185C476379
8 changed files with 94 additions and 53 deletions

View File

@ -70,7 +70,7 @@ public interface Connection {
* @param id ID of the status
* @return user list
*/
Users getRepostingUsers(long id) throws ConnectionException;
Users getRepostingUsers(long id, long cursor) throws ConnectionException;
/**
* get users liking a status
@ -78,7 +78,7 @@ public interface Connection {
* @param id ID of the status
* @return user list
*/
Users getFavoritingUsers(long id) throws ConnectionException;
Users getFavoritingUsers(long id, long cursor) throws ConnectionException;
/**
* create a list of users a specified user is following
@ -318,9 +318,10 @@ public interface Connection {
* @param id Id of the status
* @param minId get statuses with ID above the min ID
* @param maxId get statuses with ID under the max ID
* @param extras additional information like screen name of the status author
* @return list of statuses
*/
List<Status> getStatusReplies(long id, long minId, long maxId) throws ConnectionException;
List<Status> getStatusReplies(long id, long minId, long maxId, String... extras) throws ConnectionException;
/**
* lookup status by ID

View File

@ -207,13 +207,13 @@ public class Mastodon implements Connection {
@Override
public Users getRepostingUsers(long id) throws MastodonException {
public Users getRepostingUsers(long id, long cursor) throws MastodonException {
return getUsers(ENDPOINT_STATUS + id + "/reblogged_by", new ArrayList<>());
}
@Override
public Users getFavoritingUsers(long id) throws MastodonException {
public Users getFavoritingUsers(long id, long cursor) throws MastodonException {
return getUsers(ENDPOINT_STATUS + id + "/favourited_by", new ArrayList<>());
}
@ -459,7 +459,7 @@ public class Mastodon implements Connection {
@Override
public List<Status> getStatusReplies(long id, long minId, long maxId) throws MastodonException {
public List<Status> getStatusReplies(long id, long minId, long maxId, String... extras) throws MastodonException {
List<Status> statusThreads = getStatuses(ENDPOINT_STATUS + id + "/context", new ArrayList<>(0), minId, maxId);
List<Status> result = new LinkedList<>();
for (Status status : statusThreads) {

View File

@ -19,17 +19,22 @@ public class Tokens {
* false means there are no API keys available
* set to true when {@link #CONSUMER_TOKEN} and {@link #TOKEN_SECRET} are set
*/
public static final boolean USE_DEFAULT_KEYS = false;
public static final boolean USE_DEFAULT_KEYS = true;
/**
* for compability mode disable Twitter API version 2.0 (use only version 1.1) if the API access don't support this.
*/
public static final boolean DISABLE_API_V2 = true;
/**
* add here your consumer token
*/
private static final String CONSUMER_TOKEN = "xxx";
private static final String CONSUMER_TOKEN = "";
/**
* add here your token secret
*/
private static final String TOKEN_SECRET = "xxx";
private static final String TOKEN_SECRET = "";
private static Tokens instance;

View File

@ -53,7 +53,6 @@ import org.nuclearfog.twidda.model.UserList;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@ -130,6 +129,7 @@ public class Twitter implements Connection {
private static final String TWEET_UPLOAD = API + "/1.1/statuses/update.json";
private static final String TWEET_DELETE = API + "/1.1/statuses/destroy/";
private static final String TWEET_SEARCH_2 = API + "/2/tweets/search/recent";
private static final String TWEET_GET_RETWEETERS = API + "/1.1/statuses/retweeters/ids.json";
private static final String TWEET_UNI = API + "/2/tweets/";
// userlist endpoints
@ -308,16 +308,32 @@ public class Twitter implements Connection {
@Override
public Users getRepostingUsers(long tweetId) throws TwitterException {
public Users getRepostingUsers(long tweetId, long cursor) throws TwitterException {
List<String> params = new ArrayList<>();
if (!Tokens.DISABLE_API_V2) {
String endpoint = TWEET_UNI + tweetId + "/retweeted_by";
return getUsers2(endpoint, new ArrayList<>());
return getUsers2(endpoint, params);
} else {
params.add("id=" + tweetId);
params.add("count=" + settings.getListSize());
long[] ids = getUserIDs(TWEET_GET_RETWEETERS, params, cursor);
Users result = getUsers1(ids);
result.setPrevCursor(cursor);
result.setNextCursor(ids[ids.length - 1]); // Twitter bug: next cursor is always zero!
return result;
}
}
@Override
public Users getFavoritingUsers(long tweetId) throws TwitterException {
public Users getFavoritingUsers(long tweetId, long cursor) throws TwitterException {
if (!Tokens.DISABLE_API_V2) {
String endpoint = TWEET_UNI + tweetId + "/liking_users";
return getUsers2(endpoint, new ArrayList<>());
} else {
// API v1.1 doesn't support this!
return new Users(0L, 0L);
}
}
@ -389,17 +405,15 @@ public class Twitter implements Connection {
@Override
public Users getIncomingFollowRequests(long cursor) throws TwitterException {
long[] ids = getUserIDs(USERS_FOLLOW_INCOMING, cursor);
// remove last array entry (cursor) from ID list
return getUsers1(Arrays.copyOf(ids, ids.length - 1));
long[] ids = getUserIDs(USERS_FOLLOW_INCOMING, new ArrayList<>(), cursor);
return getUsers1(ids);
}
@Override
public Users getOutgoingFollowRequests(long cursor) throws TwitterException {
long[] ids = getUserIDs(USERS_FOLLOW_OUTGOING, cursor);
// remove last array entry (cursor) from ID list
return getUsers1(Arrays.copyOf(ids, ids.length - 1));
long[] ids = getUserIDs(USERS_FOLLOW_OUTGOING, new ArrayList<>(), cursor);
return getUsers1(ids);
}
@ -608,20 +622,35 @@ public class Twitter implements Connection {
@Override
public List<Status> getStatusReplies(long id, long minId, long maxId) throws TwitterException {
public List<Status> getStatusReplies(long id, long minId, long maxId, String... extras) throws TwitterException {
List<String> params = new ArrayList<>();
List<Status> replies = new LinkedList<>();
if (!Tokens.DISABLE_API_V2) {
params.add("query=" + StringTools.encode("conversation_id:" + id));
// Note: minId disabled! Twitter refuses API request containing minId of a tweet older than one week
List<Status> result = getTweets2(TWEET_SEARCH_2, params, 0, maxId);
List<Status> replies = new LinkedList<>();
// chose only the first tweet of a conversation
for (Status reply : result) {
if (reply.getRepliedStatusId() == id && reply.getId() > minId) {
replies.add(reply);
}
}
if (settings.filterResults() && !replies.isEmpty())
} else {
String replyUsername = extras[0];
if (replyUsername.startsWith("@")) {
replyUsername = replyUsername.substring(1);
}
params.add("q=" + StringTools.encode("to:" + replyUsername + " -filter:retweets"));
List<Status> result = getTweets1(TWEET_SEARCH, params, Math.max(id, minId), maxId);
for (Status reply : result) {
if (reply.getRepliedStatusId() == id) {
replies.add(reply);
}
}
}
if (settings.filterResults() && !replies.isEmpty()) {
filterTweets(replies);
}
return replies;
}
@ -631,12 +660,14 @@ public class Twitter implements Connection {
List<String> params = new ArrayList<>();
params.add("id=" + id);
Status status = getTweet1(TWEET_LOOKUP, params);
if (!Tokens.DISABLE_API_V2) {
try {
params.clear();
return getTweet2(TWEET2_LOOKUP + id, params, status);
} catch (Exception e) {
e.printStackTrace();
}
}
return status;
}
@ -1070,7 +1101,7 @@ public class Twitter implements Connection {
// add blocked user IDs
long cursor = -1;
for (int i = 0; i < 10 && cursor != 0; i++) {
long[] ids = getUserIDs(IDS_BLOCKED_USERS, cursor);
long[] ids = getUserIDs(IDS_BLOCKED_USERS, new ArrayList<>(), cursor);
for (int pos = 0; pos < ids.length - 2; pos++) {
result.add(ids[pos]);
}
@ -1079,7 +1110,7 @@ public class Twitter implements Connection {
// add muted user IDs
cursor = -1;
for (int i = 0; i < 10 && cursor != 0; i++) {
long[] ids = getUserIDs(IDS_MUTED_USERS, cursor);
long[] ids = getUserIDs(IDS_MUTED_USERS, new ArrayList<>(), cursor);
for (int pos = 0; pos < ids.length - 2; pos++) {
result.add(ids[pos]);
}
@ -1295,8 +1326,7 @@ public class Twitter implements Connection {
* @param cursor cursor value to parse the ID pages
* @return an array of user IDs + the list cursor on the last array index
*/
private long[] getUserIDs(String endpoint, long cursor) throws TwitterException {
List<String> params = new ArrayList<>();
private long[] getUserIDs(String endpoint, List<String> params, long cursor) throws TwitterException {
params.add("cursor=" + cursor);
try {
Response response = get(endpoint, params);
@ -1322,15 +1352,15 @@ public class Twitter implements Connection {
/**
* lookup a list of user IDs
*
* @param ids User IDs
* @param ids User IDs (last entry is ignored)
* @return a list of users
*/
private Users getUsers1(long[] ids) throws TwitterException {
List<String> params = new ArrayList<>();
if (ids.length > 0) {
StringBuilder idBuf = new StringBuilder("user_id=");
for (long id : ids) {
idBuf.append(id).append("%2C");
for (int i = 0 ; i < ids.length - 1 ; i++) {
idBuf.append(ids[i]).append("%2C");
}
params.add(idBuf.substring(0, idBuf.length() - 3));
return getUsers1(USERS_LOOKUP, params);

View File

@ -196,8 +196,8 @@ public class TweetV1 implements Status {
@Override
public long getConversationId() {
// not implemented in API V1.1
return 0;
// note: does not show the root tweet of the conversation
return replyTweetId;
}

View File

@ -165,18 +165,18 @@ public class StatusLoader extends AsyncTask<Long, Void, List<Status>> {
if (sinceId == 0 && maxId == 0) {
statuses = db.getReplies(id);
if (statuses.isEmpty()) {
statuses = connection.getStatusReplies(id, sinceId, maxId);
statuses = connection.getStatusReplies(id, sinceId, maxId, search);
if (!statuses.isEmpty() && db.containsStatus(id)) {
db.saveReplyTimeline(statuses);
}
}
} else if (sinceId > 0) {
statuses = connection.getStatusReplies(id, sinceId, maxId);
statuses = connection.getStatusReplies(id, sinceId, maxId, search);
if (!statuses.isEmpty() && db.containsStatus(id)) {
db.saveReplyTimeline(statuses);
}
} else if (maxId > 1) {
statuses = connection.getStatusReplies(id, sinceId, maxId);
statuses = connection.getStatusReplies(id, sinceId, maxId, search);
}
break;

View File

@ -115,10 +115,10 @@ public class UserLoader extends AsyncTask<Long, Void, Users> {
return connection.getFollowing(id, cursor);
case REPOST:
return connection.getRepostingUsers(id);
return connection.getRepostingUsers(id, cursor);
case FAVORIT:
return connection.getFavoritingUsers(id);
return connection.getFavoritingUsers(id, cursor);
case SEARCH:
return connection.searchUsers(search, cursor);

View File

@ -15,6 +15,7 @@ import static org.nuclearfog.twidda.ui.activities.UsersActivity.USERS_FAVORIT;
import static org.nuclearfog.twidda.ui.activities.UsersActivity.USERS_REPOST;
import static org.nuclearfog.twidda.ui.fragments.StatusFragment.KEY_STATUS_FRAGMENT_ID;
import static org.nuclearfog.twidda.ui.fragments.StatusFragment.KEY_STATUS_FRAGMENT_MODE;
import static org.nuclearfog.twidda.ui.fragments.StatusFragment.KEY_STATUS_FRAGMENT_SEARCH;
import static org.nuclearfog.twidda.ui.fragments.StatusFragment.STATUS_FRAGMENT_REPLY;
import android.content.ActivityNotFoundException;
@ -187,15 +188,18 @@ public class StatusActivity extends AppCompatActivity implements OnClickListener
clip = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
// get parameter
String replyUsername = "";
Object data = getIntent().getSerializableExtra(KEY_STATUS_DATA);
if (data instanceof Status) {
status = (Status) data;
Status embedded = status.getEmbeddedStatus();
if (embedded != null) {
id = embedded.getId();
replyUsername = embedded.getAuthor().getScreenname();
} else {
id = status.getId();
hidden = status.isHidden();
replyUsername = status.getAuthor().getScreenname();
}
} else {
id = getIntent().getLongExtra(KEY_STATUS_ID, -1);
@ -204,6 +208,7 @@ public class StatusActivity extends AppCompatActivity implements OnClickListener
// create list fragment for status replies
Bundle param = new Bundle();
param.putInt(KEY_STATUS_FRAGMENT_MODE, STATUS_FRAGMENT_REPLY);
param.putString(KEY_STATUS_FRAGMENT_SEARCH, replyUsername);
param.putLong(KEY_STATUS_FRAGMENT_ID, id);
// insert fragment into view