fixed oauth login, moved to header auth, added user v2 implementation
This commit is contained in:
parent
b30838ebd1
commit
16ee4bf4c3
|
@ -67,7 +67,6 @@ dependencies {
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
|
|
||||||
implementation 'org.twitter4j:twitter4j-core:4.0.7'
|
implementation 'org.twitter4j:twitter4j-core:4.0.7'
|
||||||
implementation 'com.github.takke:twitter4j-v2:1.0.3'
|
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
implementation 'com.squareup.picasso:picasso:2.8'
|
implementation 'com.squareup.picasso:picasso:2.8'
|
||||||
implementation 'com.larswerkman:LicenseView:1.1'
|
implementation 'com.larswerkman:LicenseView:1.1'
|
||||||
|
|
|
@ -22,6 +22,7 @@ import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
import org.nuclearfog.twidda.R;
|
import org.nuclearfog.twidda.R;
|
||||||
import org.nuclearfog.twidda.backend.Registration;
|
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.apiold.EngineException;
|
||||||
import org.nuclearfog.twidda.backend.utils.AppStyles;
|
import org.nuclearfog.twidda.backend.utils.AppStyles;
|
||||||
import org.nuclearfog.twidda.backend.utils.ErrorHandler;
|
import org.nuclearfog.twidda.backend.utils.ErrorHandler;
|
||||||
|
@ -54,6 +55,8 @@ public class LoginActivity extends AppCompatActivity implements OnClickListener
|
||||||
private EditText pinInput;
|
private EditText pinInput;
|
||||||
private ViewGroup root;
|
private ViewGroup root;
|
||||||
|
|
||||||
|
private String requestToken;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void attachBaseContext(Context newBase) {
|
protected void attachBaseContext(Context newBase) {
|
||||||
super.attachBaseContext(AppStyles.setFontScale(newBase));
|
super.attachBaseContext(AppStyles.setFontScale(newBase));
|
||||||
|
@ -156,7 +159,7 @@ public class LoginActivity extends AppCompatActivity implements OnClickListener
|
||||||
Toast.makeText(this, R.string.info_login_to_twitter, LENGTH_LONG).show();
|
Toast.makeText(this, R.string.info_login_to_twitter, LENGTH_LONG).show();
|
||||||
String twitterPin = pinInput.getText().toString();
|
String twitterPin = pinInput.getText().toString();
|
||||||
registerAsync = new Registration(this);
|
registerAsync = new Registration(this);
|
||||||
registerAsync.execute(twitterPin);
|
registerAsync.execute(requestToken, twitterPin);
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, R.string.error_enter_pin, LENGTH_LONG).show();
|
Toast.makeText(this, R.string.error_enter_pin, LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
@ -166,9 +169,11 @@ public class LoginActivity extends AppCompatActivity implements OnClickListener
|
||||||
/**
|
/**
|
||||||
* Called when a twitter login link was created
|
* Called when a twitter login link was created
|
||||||
*
|
*
|
||||||
* @param link Link to twitter login page
|
* @param requestToken temporary request token
|
||||||
*/
|
*/
|
||||||
public void connect(String link) {
|
public void connect(String requestToken) {
|
||||||
|
this.requestToken = requestToken;
|
||||||
|
String link = Twitter.REQUEST_URL + requestToken;
|
||||||
Intent loginIntent = new Intent(ACTION_VIEW, Uri.parse(link));
|
Intent loginIntent = new Intent(ACTION_VIEW, Uri.parse(link));
|
||||||
try {
|
try {
|
||||||
startActivity(loginIntent);
|
startActivity(loginIntent);
|
||||||
|
|
|
@ -3,9 +3,11 @@ package org.nuclearfog.twidda.backend;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
import org.nuclearfog.twidda.activities.LoginActivity;
|
import org.nuclearfog.twidda.activities.LoginActivity;
|
||||||
import org.nuclearfog.twidda.backend.api.TwitterImpl;
|
import org.nuclearfog.twidda.backend.api.Twitter;
|
||||||
import org.nuclearfog.twidda.database.AccountDatabase;
|
import org.nuclearfog.twidda.database.AccountDatabase;
|
||||||
|
import org.nuclearfog.twidda.database.AppDatabase;
|
||||||
import org.nuclearfog.twidda.database.GlobalSettings;
|
import org.nuclearfog.twidda.database.GlobalSettings;
|
||||||
|
import org.nuclearfog.twidda.model.User;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
@ -19,7 +21,8 @@ public class Registration extends AsyncTask<String, Void, String> {
|
||||||
|
|
||||||
private WeakReference<LoginActivity> callback;
|
private WeakReference<LoginActivity> callback;
|
||||||
private AccountDatabase accountDB;
|
private AccountDatabase accountDB;
|
||||||
private TwitterImpl twitter;
|
private AppDatabase database;
|
||||||
|
private Twitter twitter;
|
||||||
private GlobalSettings settings;
|
private GlobalSettings settings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,8 +35,9 @@ public class Registration extends AsyncTask<String, Void, String> {
|
||||||
this.callback = new WeakReference<>(activity);
|
this.callback = new WeakReference<>(activity);
|
||||||
// init database and storage
|
// init database and storage
|
||||||
accountDB = new AccountDatabase(activity);
|
accountDB = new AccountDatabase(activity);
|
||||||
|
database = new AppDatabase(activity);
|
||||||
settings = GlobalSettings.getInstance(activity);
|
settings = GlobalSettings.getInstance(activity);
|
||||||
twitter = TwitterImpl.get(activity);
|
twitter = Twitter.get(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,15 +46,15 @@ public class Registration extends AsyncTask<String, Void, String> {
|
||||||
try {
|
try {
|
||||||
// check if we need to backup current session
|
// check if we need to backup current session
|
||||||
if (settings.isLoggedIn() && !accountDB.exists(settings.getCurrentUserId())) {
|
if (settings.isLoggedIn() && !accountDB.exists(settings.getCurrentUserId())) {
|
||||||
String[] tokens = settings.getCurrentUserAccessToken();
|
accountDB.setLogin(settings.getCurrentUserId(), settings.getAccessToken(), settings.getTokenSecret());
|
||||||
accountDB.setLogin(settings.getCurrentUserId(), tokens[0], tokens[1]);
|
|
||||||
}
|
}
|
||||||
// no PIN means we need to request a token to login
|
// no PIN means we need to request a token to login
|
||||||
if (param.length == 0) {
|
if (param.length == 0) {
|
||||||
return twitter.getRequestURL();
|
return twitter.getRequestToken();
|
||||||
}
|
}
|
||||||
// login with pin
|
// login with pin
|
||||||
twitter.login(param[0]);
|
User user = twitter.login(param[0], param[1]);
|
||||||
|
database.storeUser(user);
|
||||||
return "";
|
return "";
|
||||||
} catch (Exception err) {
|
} catch (Exception err) {
|
||||||
err.printStackTrace();
|
err.printStackTrace();
|
||||||
|
|
|
@ -4,6 +4,8 @@ import android.os.AsyncTask;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
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.EngineException;
|
||||||
import org.nuclearfog.twidda.backend.apiold.TwitterEngine;
|
import org.nuclearfog.twidda.backend.apiold.TwitterEngine;
|
||||||
import org.nuclearfog.twidda.backend.lists.Users;
|
import org.nuclearfog.twidda.backend.lists.Users;
|
||||||
|
@ -68,6 +70,7 @@ public class UserLoader extends AsyncTask<Long, Void, Users> {
|
||||||
private EngineException twException;
|
private EngineException twException;
|
||||||
private final WeakReference<UserFragment> callback;
|
private final WeakReference<UserFragment> callback;
|
||||||
private final TwitterEngine mTwitter;
|
private final TwitterEngine mTwitter;
|
||||||
|
private Twitter mTwitter2;
|
||||||
|
|
||||||
private final Type type;
|
private final Type type;
|
||||||
private final String search;
|
private final String search;
|
||||||
|
@ -78,6 +81,7 @@ public class UserLoader extends AsyncTask<Long, Void, Users> {
|
||||||
super();
|
super();
|
||||||
this.callback = new WeakReference<>(callback);
|
this.callback = new WeakReference<>(callback);
|
||||||
mTwitter = TwitterEngine.getInstance(callback.getContext());
|
mTwitter = TwitterEngine.getInstance(callback.getContext());
|
||||||
|
mTwitter2 = Twitter.get(callback.getContext());
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.search = search;
|
this.search = search;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
@ -96,10 +100,10 @@ public class UserLoader extends AsyncTask<Long, Void, Users> {
|
||||||
return mTwitter.getFollowing(id, cursor);
|
return mTwitter.getFollowing(id, cursor);
|
||||||
|
|
||||||
case RETWEET:
|
case RETWEET:
|
||||||
return mTwitter.getRetweeter(id, cursor);
|
return mTwitter2.getRetweetingUsers(id);
|
||||||
|
|
||||||
case FAVORIT:
|
case FAVORIT:
|
||||||
return mTwitter.getFavoriter(id, cursor);
|
return mTwitter2.getLikingUsers(id);
|
||||||
|
|
||||||
case SEARCH:
|
case SEARCH:
|
||||||
return mTwitter.searchUsers(search, cursor);
|
return mTwitter.searchUsers(search, cursor);
|
||||||
|
@ -119,6 +123,8 @@ public class UserLoader extends AsyncTask<Long, Void, Users> {
|
||||||
}
|
}
|
||||||
} catch (EngineException twException) {
|
} catch (EngineException twException) {
|
||||||
this.twException = twException;
|
this.twException = twException;
|
||||||
|
} catch (TwitterException err) {
|
||||||
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,300 @@
|
||||||
|
package org.nuclearfog.twidda.backend.api;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
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.model.User;
|
||||||
|
import org.nuclearfog.twidda.database.GlobalSettings;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* new API implementation to replace twitter4j and add version 2.0 support
|
||||||
|
*
|
||||||
|
* @author nuclearfog
|
||||||
|
*/
|
||||||
|
public class Twitter {
|
||||||
|
|
||||||
|
private static final String OAUTH = "1.0";
|
||||||
|
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";
|
||||||
|
public static final String REQUEST_URL = AUTHENTICATE + "?oauth_token=";
|
||||||
|
public static final String SIGNATURE_ALG = "HMAC-SHA256";
|
||||||
|
|
||||||
|
private static Twitter instance;
|
||||||
|
|
||||||
|
private OkHttpClient client;
|
||||||
|
private GlobalSettings settings;
|
||||||
|
private Tokens tokens;
|
||||||
|
|
||||||
|
|
||||||
|
private Twitter(Context context) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
try {
|
||||||
|
TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||||
|
factory.init((KeyStore) null);
|
||||||
|
X509TrustManager manager = (X509TrustManager) factory.getTrustManagers()[0];
|
||||||
|
client = new OkHttpClient().newBuilder().sslSocketFactory(new TLSSocketFactory(), manager).build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
client = new OkHttpClient().newBuilder().build();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
client = new OkHttpClient().newBuilder().build();
|
||||||
|
}
|
||||||
|
settings = GlobalSettings.getInstance(context);
|
||||||
|
tokens = Tokens.getInstance(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get singleton instance
|
||||||
|
*
|
||||||
|
* @return instance of this class
|
||||||
|
*/
|
||||||
|
public static Twitter get(Context context) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new Twitter(context);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* request temporary access token to pass it to the Twitter login page
|
||||||
|
*
|
||||||
|
* @return a temporary access token created by Twitter
|
||||||
|
*/
|
||||||
|
public String getRequestToken() throws TwitterException {
|
||||||
|
try {
|
||||||
|
Response response = post(REQUEST_TOKEN);
|
||||||
|
if (response.code() == 200 && response.body() != null) {
|
||||||
|
String res = response.body().string();
|
||||||
|
Uri uri = Uri.parse(AUTHENTICATE + "?" + res);
|
||||||
|
return uri.getQueryParameter("oauth_token");
|
||||||
|
} else {
|
||||||
|
throw new TwitterException(response.code());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new TwitterException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* login to twitter using pin and add store access tokens
|
||||||
|
*
|
||||||
|
* @param pin pin from the login website
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
if (response.code() == 200 && response.body() != null) {
|
||||||
|
String res = response.body().string();
|
||||||
|
// extrect tokens from link
|
||||||
|
Uri uri = Uri.parse(OAUTH_VERIFIER + "?" + res);
|
||||||
|
settings.setAccessToken(uri.getQueryParameter("oauth_token"));
|
||||||
|
settings.setTokenSecret(uri.getQueryParameter("oauth_token_secret"));
|
||||||
|
settings.setUserId(Long.parseLong(uri.getQueryParameter("user_id")));
|
||||||
|
settings.setogin(true);
|
||||||
|
return getCredentials();
|
||||||
|
} else {
|
||||||
|
throw new TwitterException(response.code());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new TwitterException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get credentials of the current user
|
||||||
|
*
|
||||||
|
* @return current user
|
||||||
|
*/
|
||||||
|
public User getCredentials() throws TwitterException {
|
||||||
|
try {
|
||||||
|
Response response = get(CREDENTIALS);
|
||||||
|
if (response.code() == 200 && response.body() != null) {
|
||||||
|
JSONObject json = new JSONObject(response.body().string());
|
||||||
|
return new UserV1(json);
|
||||||
|
} else {
|
||||||
|
throw new TwitterException(response.code());
|
||||||
|
}
|
||||||
|
} catch (IOException err) {
|
||||||
|
throw new TwitterException(err);
|
||||||
|
} catch (JSONException err) {
|
||||||
|
throw new TwitterException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get users retweeting a tweet
|
||||||
|
*
|
||||||
|
* @param tweetId ID of the tweet
|
||||||
|
* @return user list
|
||||||
|
*/
|
||||||
|
public Users getRetweetingUsers(long tweetId) throws TwitterException {
|
||||||
|
String endpoint = API + "2/tweets/" + tweetId + "/retweeted_by";
|
||||||
|
return getUsers(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get users liking a tweet
|
||||||
|
*
|
||||||
|
* @param tweetId ID of the tweet
|
||||||
|
* @return user list
|
||||||
|
*/
|
||||||
|
public Users getLikingUsers(long tweetId) throws TwitterException {
|
||||||
|
String endpoint = API + "2/tweets/" + tweetId + "/liking_users";
|
||||||
|
return getUsers(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get a list of twitter users
|
||||||
|
*
|
||||||
|
* @param endpoint endpoint url to get the user data from
|
||||||
|
* @return user list
|
||||||
|
*/
|
||||||
|
private Users getUsers(String endpoint) throws TwitterException {
|
||||||
|
try {
|
||||||
|
Response response = get(endpoint, UserV2.PARAMS);
|
||||||
|
if (response.code() == 200 && 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));
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
} else {
|
||||||
|
throw new TwitterException(response.code());
|
||||||
|
}
|
||||||
|
} catch (IOException err) {
|
||||||
|
throw new TwitterException(err);
|
||||||
|
} catch (JSONException err) {
|
||||||
|
throw new TwitterException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create and call POST endpoint
|
||||||
|
*
|
||||||
|
* @param endpoint endpoint url
|
||||||
|
* @return http resonse
|
||||||
|
*/
|
||||||
|
private Response post(String endpoint, String... params) throws IOException {
|
||||||
|
String authHeader = buildHeader("POST", endpoint, params);
|
||||||
|
String url = appendParams(endpoint, params);
|
||||||
|
RequestBody body = RequestBody.create(MediaType.parse("text/plain"), "");
|
||||||
|
Request request = new Request.Builder().url(url).addHeader("Authorization", authHeader).post(body).build();
|
||||||
|
return client.newCall(request).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create and call GET endpoint
|
||||||
|
*
|
||||||
|
* @param endpoint endpoint url
|
||||||
|
* @return http response
|
||||||
|
*/
|
||||||
|
private Response get(String endpoint, String... params) throws IOException {
|
||||||
|
String authHeader = buildHeader("GET", endpoint, params);
|
||||||
|
String url = appendParams(endpoint, params);
|
||||||
|
Request request = new Request.Builder().url(url).addHeader("Authorization", authHeader).get().build();
|
||||||
|
return client.newCall(request).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create http header with credentials and signature
|
||||||
|
*
|
||||||
|
* @param method endpoint method to call
|
||||||
|
* @param endpoint endpoint url
|
||||||
|
* @param params parameter to add to signature
|
||||||
|
* @return header string
|
||||||
|
*/
|
||||||
|
private String buildHeader(String method, String endpoint, String... params) {
|
||||||
|
String timeStamp = StringTools.getTimestamp();
|
||||||
|
String random = StringTools.getRandomString();
|
||||||
|
String signkey = tokens.getConsumerSec() + "&";
|
||||||
|
String oauth_token_param = "";
|
||||||
|
|
||||||
|
// init default parameters
|
||||||
|
TreeSet<String> sortedParams = new TreeSet<>();
|
||||||
|
sortedParams.add("oauth_callback=oob");
|
||||||
|
sortedParams.add("oauth_consumer_key=" + tokens.getConsumerKey());
|
||||||
|
sortedParams.add("oauth_nonce=" + random);
|
||||||
|
sortedParams.add("oauth_signature_method=" + SIGNATURE_ALG);
|
||||||
|
sortedParams.add("oauth_timestamp=" + timeStamp);
|
||||||
|
sortedParams.add("oauth_version=" + OAUTH);
|
||||||
|
// add custom parameters
|
||||||
|
sortedParams.addAll(Arrays.asList(params));
|
||||||
|
|
||||||
|
// only add tokens if there is no login process
|
||||||
|
if (!REQUEST_TOKEN.equals(endpoint) && !OAUTH_VERIFIER.equals(endpoint)) {
|
||||||
|
sortedParams.add("oauth_token=" + settings.getAccessToken());
|
||||||
|
oauth_token_param = ", oauth_token=\"" + settings.getAccessToken() + "\"";
|
||||||
|
signkey += settings.getTokenSecret();
|
||||||
|
}
|
||||||
|
|
||||||
|
// build string with sorted parameters
|
||||||
|
StringBuilder paramStr = new StringBuilder();
|
||||||
|
for (String param : sortedParams)
|
||||||
|
paramStr.append(param).append('&');
|
||||||
|
paramStr.deleteCharAt(paramStr.length() - 1);
|
||||||
|
|
||||||
|
// calculate oauth signature
|
||||||
|
String signature = StringTools.sign(method, endpoint, paramStr.toString(), signkey);
|
||||||
|
|
||||||
|
// create header string
|
||||||
|
return "OAuth oauth_callback=\"oob\"" +
|
||||||
|
", oauth_consumer_key=\"" + tokens.getConsumerKey() + "\"" +
|
||||||
|
", oauth_nonce=\""+ random + "\"" +
|
||||||
|
", oauth_signature=\"" + signature + "\"" +
|
||||||
|
", oauth_signature_method=\""+ SIGNATURE_ALG + "\"" +
|
||||||
|
", oauth_timestamp=\"" + timeStamp + "\""
|
||||||
|
+ oauth_token_param +
|
||||||
|
", oauth_version=\"" + OAUTH + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* build url with param
|
||||||
|
*
|
||||||
|
* @param url url without parameters
|
||||||
|
* @param params parameters
|
||||||
|
* @return url with parameters
|
||||||
|
*/
|
||||||
|
private String appendParams(String url, String[] params) {
|
||||||
|
if (params.length > 0) {
|
||||||
|
StringBuilder result = new StringBuilder(url);
|
||||||
|
result.append('?');
|
||||||
|
for (String param : params)
|
||||||
|
result.append(param).append('&');
|
||||||
|
result.deleteCharAt(result.length() - 1);
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.nuclearfog.twidda.backend.api;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
|
||||||
|
public class TwitterException extends Exception {
|
||||||
|
|
||||||
|
public static final int TOKEN_NOT_SET = 600;
|
||||||
|
|
||||||
|
private int httpCode;
|
||||||
|
|
||||||
|
TwitterException(Exception e) {
|
||||||
|
super(e);
|
||||||
|
httpCode = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
TwitterException(int httpCode) {
|
||||||
|
this.httpCode = httpCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return httpCode;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,196 +0,0 @@
|
||||||
package org.nuclearfog.twidda.backend.api;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
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.model.User;
|
|
||||||
import org.nuclearfog.twidda.database.GlobalSettings;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
|
||||||
import javax.net.ssl.X509TrustManager;
|
|
||||||
|
|
||||||
import okhttp3.MediaType;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.RequestBody;
|
|
||||||
import okhttp3.Response;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* new API implementation to replace twitter4j and add version 2.0 support
|
|
||||||
*
|
|
||||||
* @author nuclearfog
|
|
||||||
*/
|
|
||||||
public class TwitterImpl {
|
|
||||||
|
|
||||||
public static final String HASH = "HMAC-SHA256";
|
|
||||||
private static final String OAUTH = "1.0";
|
|
||||||
|
|
||||||
private static final String API = "https://api.twitter.com/";
|
|
||||||
private static final String REQUEST_TOKEN = API + "oauth/request_token";
|
|
||||||
private static final String AUTHENTICATE = API + "oauth/authenticate";
|
|
||||||
private static final String OAUTH_VERIFIER = API + "oauth/access_token";
|
|
||||||
private static final String CREDENTIALS = API + "1.1/account/verify_credentials.json";
|
|
||||||
|
|
||||||
private static TwitterImpl instance;
|
|
||||||
|
|
||||||
private GlobalSettings settings;
|
|
||||||
private Tokens tokens;
|
|
||||||
|
|
||||||
private OkHttpClient client;
|
|
||||||
|
|
||||||
|
|
||||||
private TwitterImpl(Context context) {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
try {
|
|
||||||
TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
|
||||||
factory.init((KeyStore) null);
|
|
||||||
X509TrustManager manager = (X509TrustManager) factory.getTrustManagers()[0];
|
|
||||||
client = new OkHttpClient().newBuilder().sslSocketFactory(new TLSSocketFactory(), manager).build();
|
|
||||||
} catch (Exception e) {
|
|
||||||
client = new OkHttpClient().newBuilder().build();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
client = new OkHttpClient().newBuilder().build();
|
|
||||||
}
|
|
||||||
settings = GlobalSettings.getInstance(context);
|
|
||||||
tokens = Tokens.getInstance(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get singleton instance
|
|
||||||
*
|
|
||||||
* @return instance of this class
|
|
||||||
*/
|
|
||||||
public static TwitterImpl get(Context context) {
|
|
||||||
if (instance == null) {
|
|
||||||
instance = new TwitterImpl(context);
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* request login page url and generate forst token
|
|
||||||
*
|
|
||||||
* @return url to the login page
|
|
||||||
*/
|
|
||||||
public String getRequestURL() throws IOException {
|
|
||||||
Response response = post(REQUEST_TOKEN, "oauth_callback=oob");
|
|
||||||
if (response.code() == 200 && response.body() != null) {
|
|
||||||
String res = response.body().string();
|
|
||||||
Uri uri = Uri.parse(AUTHENTICATE + "?" + res);
|
|
||||||
String token = uri.getQueryParameter("oauth_token");
|
|
||||||
tokens.setTokens(token);
|
|
||||||
return AUTHENTICATE + "?oauth_token=" + token;
|
|
||||||
} else {
|
|
||||||
// todo add exception
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* login to twitter using pin and add store access tokens
|
|
||||||
*
|
|
||||||
* @param pin pin from the login website
|
|
||||||
*/
|
|
||||||
public void login(String pin) throws IOException, JSONException {
|
|
||||||
Response response = post(OAUTH_VERIFIER, "oauth_verifier=" + pin, "oauth_token=" + tokens.getToken());
|
|
||||||
if (response.code() == 200 && response.body() != null) {
|
|
||||||
String res = response.body().string();
|
|
||||||
Uri uri = Uri.parse(OAUTH_VERIFIER + "?" + res);
|
|
||||||
String token = uri.getQueryParameter("oauth_token");
|
|
||||||
String tokenSec = uri.getQueryParameter("oauth_token_secret");
|
|
||||||
tokens.setTokens(token, tokenSec);
|
|
||||||
settings.setUserId(getCredentials().getId());
|
|
||||||
} else {
|
|
||||||
// todo add exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get credentials of the current user
|
|
||||||
*
|
|
||||||
* @return current user
|
|
||||||
*/
|
|
||||||
public User getCredentials() throws IOException, JSONException {
|
|
||||||
Response response = get(CREDENTIALS);
|
|
||||||
if (response.code() == 200 && response.body() != null) {
|
|
||||||
JSONObject json = new JSONObject(response.body().string());
|
|
||||||
return new UserV1(json, -1);
|
|
||||||
} else {
|
|
||||||
throw new IOException("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create and call POST endpoint
|
|
||||||
*
|
|
||||||
* @param endpoint endpoint url
|
|
||||||
* @param add additional parameters
|
|
||||||
* @return http resonse
|
|
||||||
*/
|
|
||||||
private Response post(String endpoint, String... add) throws IOException {
|
|
||||||
String oauth_sec = "";
|
|
||||||
if (settings.isLoggedIn()) {
|
|
||||||
oauth_sec = tokens.getTokenSec();
|
|
||||||
}
|
|
||||||
String param = buildParamString(add);
|
|
||||||
param += "&oauth_signature=" + StringTools.signPost(endpoint, param, tokens.getConsumerSec() + "&" +oauth_sec);
|
|
||||||
RequestBody body = RequestBody.create(MediaType.parse("text/plain"), "");
|
|
||||||
Request request = new Request.Builder().url(endpoint + "?" + param).post(body).build();
|
|
||||||
return client.newCall(request).execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create and call GET endpoint
|
|
||||||
*
|
|
||||||
* @param endpoint endpoint url
|
|
||||||
* @param add additional parameters
|
|
||||||
* @return http response
|
|
||||||
*/
|
|
||||||
private Response get(String endpoint, String... add) throws IOException {
|
|
||||||
String oauth_sec = "";
|
|
||||||
if (settings.isLoggedIn()) {
|
|
||||||
oauth_sec = tokens.getTokenSec();
|
|
||||||
}
|
|
||||||
String param = buildParamString(add);
|
|
||||||
param += "&oauth_signature=" + StringTools.signGet(endpoint, param, tokens.getConsumerSec() + "&" + oauth_sec);
|
|
||||||
Request request = new Request.Builder().url(endpoint + "?" + param).get().build();
|
|
||||||
return client.newCall(request).execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* build twitter API parameters
|
|
||||||
*
|
|
||||||
* @param add additional parameters
|
|
||||||
* @return parameter string
|
|
||||||
*/
|
|
||||||
private String buildParamString(String... add) {
|
|
||||||
// sort parameters
|
|
||||||
TreeSet<String> params = new TreeSet<>();
|
|
||||||
params.add("oauth_consumer_key=" + tokens.getConsumerKey());
|
|
||||||
params.add("oauth_nonce=" + StringTools.getRandomString());
|
|
||||||
params.add("oauth_signature_method=" + HASH);
|
|
||||||
params.add("oauth_timestamp=" + StringTools.getTimestamp());
|
|
||||||
params.add("oauth_version=" + OAUTH);
|
|
||||||
params.addAll(Arrays.asList(add));
|
|
||||||
if (settings.isLoggedIn()) {
|
|
||||||
params.add("oauth_token=" + tokens.getToken());
|
|
||||||
}
|
|
||||||
// append sorted parameters to string
|
|
||||||
StringBuilder param = new StringBuilder();
|
|
||||||
for (String e : params)
|
|
||||||
param.append(e).append('&');
|
|
||||||
return param.deleteCharAt(param.length() - 1).toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,6 +6,11 @@ import org.nuclearfog.twidda.model.User;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 1.1 implementation of User
|
||||||
|
*
|
||||||
|
* @author nuclearfog
|
||||||
|
*/
|
||||||
class UserV1 implements User {
|
class UserV1 implements User {
|
||||||
|
|
||||||
private static final SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.US);
|
private static final SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.US);
|
||||||
|
@ -31,6 +36,12 @@ class UserV1 implements User {
|
||||||
|
|
||||||
|
|
||||||
UserV1(JSONObject json, long twitterId) {
|
UserV1(JSONObject json, long twitterId) {
|
||||||
|
this(json);
|
||||||
|
isCurrentUser = twitterId == userID;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
UserV1(JSONObject json) {
|
||||||
String bannerLink = json.optString("profile_banner_url");
|
String bannerLink = json.optString("profile_banner_url");
|
||||||
description = json.optString("description");
|
description = json.optString("description");
|
||||||
username = json.optString("name");
|
username = json.optString("name");
|
||||||
|
@ -46,12 +57,12 @@ class UserV1 implements User {
|
||||||
favorCount = json.optInt("favourites_count");
|
favorCount = json.optInt("favourites_count");
|
||||||
followReqSent = json.optBoolean("follow_request_sent");
|
followReqSent = json.optBoolean("follow_request_sent");
|
||||||
defaultImage = json.optBoolean("default_profile_image");
|
defaultImage = json.optBoolean("default_profile_image");
|
||||||
isCurrentUser = twitterId == userID;
|
profileUrl = json.optString("profile_image_url_https");
|
||||||
|
|
||||||
if (bannerLink.length() > 4)
|
|
||||||
profileBannerUrl = bannerLink.substring(0, bannerLink.length() - 4);
|
|
||||||
|
|
||||||
setDate(json.optString("created_at"));
|
setDate(json.optString("created_at"));
|
||||||
|
isCurrentUser = true;
|
||||||
|
if (bannerLink.length() > 4) {
|
||||||
|
profileBannerUrl = bannerLink.substring(0, bannerLink.length() - 4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
package org.nuclearfog.twidda.backend.api;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.nuclearfog.twidda.model.User;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* implementation of User accessed by API 2.0
|
||||||
|
*
|
||||||
|
* @author nuclearfog
|
||||||
|
*/
|
||||||
|
class UserV2 implements User {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* extra parameters required to fetch additional data
|
||||||
|
*/
|
||||||
|
public static final String PARAMS = "user.fields=profile_image_url";
|
||||||
|
// ISO8601 time format used by twitter
|
||||||
|
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
|
||||||
|
|
||||||
|
private long userID;
|
||||||
|
private long created;
|
||||||
|
private String username;
|
||||||
|
private String screenName;
|
||||||
|
private String description;
|
||||||
|
private String location;
|
||||||
|
private String profileUrl;
|
||||||
|
private String profileImageUrl;
|
||||||
|
private String profileBannerUrl;
|
||||||
|
private int following;
|
||||||
|
private int follower;
|
||||||
|
private int tweetCount;
|
||||||
|
private int favorCount;
|
||||||
|
private boolean isCurrentUser;
|
||||||
|
private boolean isVerified;
|
||||||
|
private boolean isProtected;
|
||||||
|
private boolean followReqSent;
|
||||||
|
private boolean defaultImage;
|
||||||
|
|
||||||
|
|
||||||
|
UserV2(JSONObject json, long twitterId) {
|
||||||
|
userID = json.optLong("id");
|
||||||
|
username = json.optString("name");
|
||||||
|
screenName = json.optString("username"); // username -> screenname
|
||||||
|
isProtected = json.optBoolean("protected");
|
||||||
|
location = json.optString("location");
|
||||||
|
profileUrl = json.optString("profile_image_url");
|
||||||
|
description = json.optString("description");
|
||||||
|
isVerified = json.optBoolean("verified");
|
||||||
|
profileImageUrl = json.optString("profile_image_url");
|
||||||
|
following = json.optInt("public_metrics.following_count");
|
||||||
|
follower = json.optInt("public_metrics.followers_count");
|
||||||
|
tweetCount = json.optInt("public_metrics.tweet_count");
|
||||||
|
isCurrentUser = userID == twitterId;
|
||||||
|
setDate(json.optString("created_at"));
|
||||||
|
// not defined jet in V2
|
||||||
|
profileBannerUrl = "";
|
||||||
|
favorCount = 0;
|
||||||
|
followReqSent = false;
|
||||||
|
defaultImage = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getId() {
|
||||||
|
return userID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getScreenname() {
|
||||||
|
return screenName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCreatedAt() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getImageUrl() {
|
||||||
|
return profileImageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBannerUrl() {
|
||||||
|
return profileBannerUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProfileUrl() {
|
||||||
|
return profileUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isVerified() {
|
||||||
|
return isVerified;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isProtected() {
|
||||||
|
return isProtected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean followRequested() {
|
||||||
|
return followReqSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFollowing() {
|
||||||
|
return following;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFollower() {
|
||||||
|
return follower;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTweetCount() {
|
||||||
|
return tweetCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFavoriteCount() {
|
||||||
|
return favorCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasDefaultProfileImage() {
|
||||||
|
return defaultImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCurrentUser() {
|
||||||
|
return isCurrentUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set time of account creation
|
||||||
|
*
|
||||||
|
* @param dateStr date string from twitter
|
||||||
|
*/
|
||||||
|
private void setDate(String dateStr) {
|
||||||
|
try {
|
||||||
|
created = sdf.parse(dateStr).getTime();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// make date invalid so it will be not shown
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,6 @@ import org.nuclearfog.twidda.backend.lists.UserLists;
|
||||||
import org.nuclearfog.twidda.backend.utils.ProxySetup;
|
import org.nuclearfog.twidda.backend.utils.ProxySetup;
|
||||||
import org.nuclearfog.twidda.backend.utils.TLSSocketFactory;
|
import org.nuclearfog.twidda.backend.utils.TLSSocketFactory;
|
||||||
import org.nuclearfog.twidda.backend.utils.Tokens;
|
import org.nuclearfog.twidda.backend.utils.Tokens;
|
||||||
import org.nuclearfog.twidda.database.AccountDatabase;
|
|
||||||
import org.nuclearfog.twidda.database.ExcludeDatabase;
|
import org.nuclearfog.twidda.database.ExcludeDatabase;
|
||||||
import org.nuclearfog.twidda.database.GlobalSettings;
|
import org.nuclearfog.twidda.database.GlobalSettings;
|
||||||
import org.nuclearfog.twidda.model.Location;
|
import org.nuclearfog.twidda.model.Location;
|
||||||
|
@ -42,7 +41,6 @@ import twitter4j.DirectMessage;
|
||||||
import twitter4j.DirectMessageList;
|
import twitter4j.DirectMessageList;
|
||||||
import twitter4j.GeoLocation;
|
import twitter4j.GeoLocation;
|
||||||
import twitter4j.IDs;
|
import twitter4j.IDs;
|
||||||
import twitter4j.LikesExKt;
|
|
||||||
import twitter4j.PagableResponseList;
|
import twitter4j.PagableResponseList;
|
||||||
import twitter4j.Paging;
|
import twitter4j.Paging;
|
||||||
import twitter4j.Query;
|
import twitter4j.Query;
|
||||||
|
@ -53,10 +51,7 @@ import twitter4j.Twitter;
|
||||||
import twitter4j.TwitterException;
|
import twitter4j.TwitterException;
|
||||||
import twitter4j.TwitterFactory;
|
import twitter4j.TwitterFactory;
|
||||||
import twitter4j.UploadedMedia;
|
import twitter4j.UploadedMedia;
|
||||||
import twitter4j.User2;
|
|
||||||
import twitter4j.UsersResponse;
|
|
||||||
import twitter4j.auth.AccessToken;
|
import twitter4j.auth.AccessToken;
|
||||||
import twitter4j.auth.RequestToken;
|
|
||||||
import twitter4j.conf.ConfigurationBuilder;
|
import twitter4j.conf.ConfigurationBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,10 +63,7 @@ public class TwitterEngine {
|
||||||
|
|
||||||
private static final TwitterEngine mTwitter = new TwitterEngine();
|
private static final TwitterEngine mTwitter = new TwitterEngine();
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private RequestToken reqToken;
|
|
||||||
private GlobalSettings settings;
|
private GlobalSettings settings;
|
||||||
private AccountDatabase accountDB;
|
|
||||||
private ExcludeDatabase excludeDB;
|
private ExcludeDatabase excludeDB;
|
||||||
private Tokens tokens;
|
private Tokens tokens;
|
||||||
private Twitter twitter;
|
private Twitter twitter;
|
||||||
|
@ -123,13 +115,13 @@ public class TwitterEngine {
|
||||||
// initialize database and settings
|
// initialize database and settings
|
||||||
mTwitter.settings = GlobalSettings.getInstance(context);
|
mTwitter.settings = GlobalSettings.getInstance(context);
|
||||||
mTwitter.tokens = Tokens.getInstance(context);
|
mTwitter.tokens = Tokens.getInstance(context);
|
||||||
mTwitter.accountDB = new AccountDatabase(context);
|
|
||||||
mTwitter.excludeDB = new ExcludeDatabase(context);
|
mTwitter.excludeDB = new ExcludeDatabase(context);
|
||||||
// check if already logged in
|
// check if already logged in
|
||||||
if (mTwitter.settings.isLoggedIn()) {
|
if (mTwitter.settings.isLoggedIn()) {
|
||||||
// init login access
|
// init login access
|
||||||
String[] keys = mTwitter.settings.getCurrentUserAccessToken();
|
String accessToken = mTwitter.settings.getAccessToken();
|
||||||
AccessToken token = new AccessToken(keys[0], keys[1]);
|
String tokenSecret = mTwitter.settings.getTokenSecret();
|
||||||
|
AccessToken token = new AccessToken(accessToken, tokenSecret);
|
||||||
mTwitter.initTwitter(token);
|
mTwitter.initTwitter(token);
|
||||||
} else {
|
} else {
|
||||||
// init empty session
|
// init empty session
|
||||||
|
@ -139,21 +131,6 @@ public class TwitterEngine {
|
||||||
return mTwitter;
|
return mTwitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* get singleton instance with empty session
|
|
||||||
*
|
|
||||||
* @return TwitterEngine Instance
|
|
||||||
*/
|
|
||||||
public static TwitterEngine getEmptyInstance(Context context) {
|
|
||||||
// initialize storage
|
|
||||||
mTwitter.settings = GlobalSettings.getInstance(context);
|
|
||||||
mTwitter.accountDB = new AccountDatabase(context);
|
|
||||||
// init empty session
|
|
||||||
mTwitter.isInitialized = false;
|
|
||||||
mTwitter.initTwitter(null);
|
|
||||||
return mTwitter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* reset Twitter state
|
* reset Twitter state
|
||||||
*/
|
*/
|
||||||
|
@ -161,54 +138,6 @@ public class TwitterEngine {
|
||||||
mTwitter.isInitialized = false;
|
mTwitter.isInitialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Request Registration Website
|
|
||||||
*
|
|
||||||
* @return Link to App Registration
|
|
||||||
* @throws EngineException if internet connection is unavailable
|
|
||||||
*/
|
|
||||||
public String request() throws EngineException {
|
|
||||||
try {
|
|
||||||
if (reqToken == null) {
|
|
||||||
// request token without redirecting to the callback url
|
|
||||||
reqToken = twitter.getOAuthRequestToken("oob");
|
|
||||||
}
|
|
||||||
} catch (Exception err) {
|
|
||||||
throw new EngineException(err);
|
|
||||||
}
|
|
||||||
return reqToken.getAuthenticationURL();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get account access keys, store them and initialize Twitter login
|
|
||||||
*
|
|
||||||
* @param twitterPin PIN from the twitter login page, after successful login
|
|
||||||
* @throws EngineException if pin is false or request token is null
|
|
||||||
*/
|
|
||||||
public void initialize(String twitterPin) throws EngineException {
|
|
||||||
try {
|
|
||||||
// check if corresponding request key is valid
|
|
||||||
if (reqToken != null) {
|
|
||||||
// get login keys
|
|
||||||
AccessToken accessToken = twitter.getOAuthAccessToken(reqToken, twitterPin);
|
|
||||||
String key1 = accessToken.getToken();
|
|
||||||
String key2 = accessToken.getTokenSecret();
|
|
||||||
// init twitter login
|
|
||||||
initTwitter(new AccessToken(key1, key2));
|
|
||||||
// save login to storage and database
|
|
||||||
settings.setConnection(key1, key2, twitter.getId());
|
|
||||||
accountDB.setLogin(twitter.getId(), key1, key2);
|
|
||||||
// request token is not needed anymore
|
|
||||||
reqToken = null;
|
|
||||||
} else {
|
|
||||||
// request token does not exist, open login page first
|
|
||||||
throw new EngineException(EngineException.InternalErrorType.TOKENNOTSET);
|
|
||||||
}
|
|
||||||
} catch (Exception err) {
|
|
||||||
throw new EngineException(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Home Timeline
|
* Get Home Timeline
|
||||||
*
|
*
|
||||||
|
@ -841,54 +770,6 @@ public class TwitterEngine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get User who retweeted a Tweet
|
|
||||||
*
|
|
||||||
* @param tweetID Tweet ID
|
|
||||||
* @return List of users
|
|
||||||
* @throws EngineException if Access is unavailable
|
|
||||||
*/
|
|
||||||
public Users getRetweeter(long tweetID, long cursor) throws EngineException {
|
|
||||||
try {
|
|
||||||
int load = settings.getListSize();
|
|
||||||
IDs userIDs = twitter.getRetweeterIds(tweetID, load, cursor);
|
|
||||||
long[] ids = userIDs.getIDs();
|
|
||||||
long prevCursor = cursor > 0 ? cursor : 0;
|
|
||||||
long nextCursor = userIDs.getNextCursor(); // fixme next cursor always zero
|
|
||||||
Users result = new Users(prevCursor, nextCursor);
|
|
||||||
if (ids.length > 0) {
|
|
||||||
result.addAll(convertUserList(twitter.lookupUsers(ids)));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (Exception err) {
|
|
||||||
throw new EngineException(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get user who liked a tweet
|
|
||||||
*
|
|
||||||
* @param tweetId Tweet ID
|
|
||||||
* @return list of users liking a tweet
|
|
||||||
* @throws EngineException if Access is unavailable
|
|
||||||
*/
|
|
||||||
public Users getFavoriter(long tweetId, long cursor) throws EngineException {
|
|
||||||
try {
|
|
||||||
UsersResponse response = LikesExKt.getLikingUsers(twitter, tweetId, null, null, null);
|
|
||||||
List<User2> users = response.getUsers();
|
|
||||||
long[] ids = new long[users.size()];
|
|
||||||
for (int i = 0 ; i < ids.length ; i++)
|
|
||||||
ids[i] = users.get(i).getId();
|
|
||||||
// lookup users with Twitter4J for maximum compability
|
|
||||||
Users result = new Users(cursor, 0);
|
|
||||||
result.addAll(convertUserList(twitter.lookupUsers(ids)));
|
|
||||||
return result;
|
|
||||||
} catch (TwitterException err) {
|
|
||||||
throw new EngineException(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get list of Direct Messages
|
* get list of Direct Messages
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package org.nuclearfog.twidda.backend.utils;
|
package org.nuclearfog.twidda.backend.utils;
|
||||||
|
|
||||||
import static org.nuclearfog.twidda.backend.api.TwitterImpl.HASH;
|
import static org.nuclearfog.twidda.backend.api.Twitter.SIGNATURE_ALG;
|
||||||
|
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
|
|
||||||
|
@ -161,28 +161,16 @@ public final class StringTools {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sign GET API request
|
* generate signature for oauth
|
||||||
*
|
*
|
||||||
* @param endpoint API endpoint
|
* @param method method e.g. POST,GET or PUT
|
||||||
* @param param API parameter to sign
|
* @param endpoint endpoint URL
|
||||||
|
* @param param parameter
|
||||||
* @param keyString key used to sign
|
* @param keyString key used to sign
|
||||||
* @return sign string
|
* @return key signature
|
||||||
*/
|
*/
|
||||||
public static String signGet(String endpoint, String param, String keyString) {
|
public static String sign(String method, String endpoint, String param, String keyString) {
|
||||||
String input = "GET&" + encode(endpoint) + "&" + encode(param);
|
String input = method + "&" + encode(endpoint) + "&" + encode(param);
|
||||||
return encode(computeSignature(input, keyString));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* sign POST API request
|
|
||||||
*
|
|
||||||
* @param endpoint API endpoint
|
|
||||||
* @param param API parameter to sign
|
|
||||||
* @param keyString key used to sign
|
|
||||||
* @return sign string
|
|
||||||
*/
|
|
||||||
public static String signPost(String endpoint, String param, String keyString) {
|
|
||||||
String input = "POST&" + encode(endpoint) + "&" + encode(param);
|
|
||||||
return encode(computeSignature(input, keyString));
|
return encode(computeSignature(input, keyString));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,8 +183,8 @@ public final class StringTools {
|
||||||
*/
|
*/
|
||||||
private static String computeSignature(String baseString, String keyString) {
|
private static String computeSignature(String baseString, String keyString) {
|
||||||
try {
|
try {
|
||||||
SecretKey secretKey = new SecretKeySpec(keyString.getBytes(), HASH);
|
SecretKey secretKey = new SecretKeySpec(keyString.getBytes(), SIGNATURE_ALG);
|
||||||
Mac mac = Mac.getInstance(HASH);
|
Mac mac = Mac.getInstance(SIGNATURE_ALG);
|
||||||
mac.init(secretKey);
|
mac.init(secretKey);
|
||||||
return new String(Base64.encode(mac.doFinal(baseString.getBytes()), Base64.DEFAULT)).trim();
|
return new String(Base64.encode(mac.doFinal(baseString.getBytes()), Base64.DEFAULT)).trim();
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||||
|
|
|
@ -26,7 +26,6 @@ public class Tokens {
|
||||||
|
|
||||||
private static Tokens instance;
|
private static Tokens instance;
|
||||||
private GlobalSettings settings;
|
private GlobalSettings settings;
|
||||||
private String token, tokenSec;
|
|
||||||
|
|
||||||
|
|
||||||
private Tokens(Context context) {
|
private Tokens(Context context) {
|
||||||
|
@ -66,33 +65,4 @@ public class Tokens {
|
||||||
return settings.getConsumerSecret();
|
return settings.getConsumerSecret();
|
||||||
return API_SECRET;
|
return API_SECRET;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* set oauth tokens
|
|
||||||
*
|
|
||||||
* @param tokens oauth tokens (single or pair)
|
|
||||||
*/
|
|
||||||
public void setTokens(String... tokens) {
|
|
||||||
if (tokens.length == 2) {
|
|
||||||
token = tokens[0];
|
|
||||||
tokenSec = tokens[1];
|
|
||||||
settings.setConnection(token, tokenSec);
|
|
||||||
} else if (tokens.length == 1) {
|
|
||||||
token = tokens[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return first oauth token
|
|
||||||
*/
|
|
||||||
public String getToken() {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return second secret oauth token
|
|
||||||
*/
|
|
||||||
public String getTokenSec() {
|
|
||||||
return tokenSec;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -55,10 +55,16 @@ class AccountDB implements Account {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getKeys() {
|
public String getAccessToken() {
|
||||||
return new String[]{key1, key2};
|
return key1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTokenSecret() {
|
||||||
|
return key2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
|
|
@ -847,16 +847,58 @@ public class GlobalSettings {
|
||||||
return loggedIn;
|
return loggedIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get Access tokens
|
* set app login status
|
||||||
*
|
*
|
||||||
* @return access tokens
|
* @param login true if current user is logged in successfully
|
||||||
*/
|
*/
|
||||||
public String[] getCurrentUserAccessToken() {
|
public void setogin(boolean login) {
|
||||||
String[] out = new String[2];
|
loggedIn = login;
|
||||||
out[0] = auth_key1;
|
Editor e = settings.edit();
|
||||||
out[1] = auth_key2;
|
e.putBoolean(LOGGED_IN, login);
|
||||||
return out;
|
e.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return access token of the current user
|
||||||
|
*
|
||||||
|
* @return first access token
|
||||||
|
*/
|
||||||
|
public String getAccessToken() {
|
||||||
|
return auth_key1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set access token of the current user
|
||||||
|
*
|
||||||
|
* @param token first access token
|
||||||
|
*/
|
||||||
|
public void setAccessToken(String token) {
|
||||||
|
this.auth_key1 = token;
|
||||||
|
Editor e = settings.edit();
|
||||||
|
e.putString(CURRENT_AUTH_KEY1, token);
|
||||||
|
e.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return second access token of the current user
|
||||||
|
* @return first access token
|
||||||
|
*/
|
||||||
|
public String getTokenSecret() {
|
||||||
|
return auth_key2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set second access token of the current user
|
||||||
|
*
|
||||||
|
* @param token first access token
|
||||||
|
*/
|
||||||
|
public void setTokenSecret(String token) {
|
||||||
|
this.auth_key2 = token;
|
||||||
|
Editor e = settings.edit();
|
||||||
|
e.putString(CURRENT_AUTH_KEY2, token);
|
||||||
|
e.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -887,28 +929,10 @@ public class GlobalSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set Access tokens and user ID
|
* set current user ID
|
||||||
*
|
*
|
||||||
* @param key1 1st access token
|
* @param userId current user ID
|
||||||
* @param key2 2nd access token
|
|
||||||
* @param userId User ID
|
|
||||||
*/
|
*/
|
||||||
public void setConnection(String key1, String key2, long userId) {
|
|
||||||
setConnection(key1, key2);
|
|
||||||
setUserId(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConnection(String key1, String key2) {
|
|
||||||
this.auth_key1 = key1;
|
|
||||||
this.auth_key2 = key2;
|
|
||||||
loggedIn = true;
|
|
||||||
Editor e = settings.edit();
|
|
||||||
e.putString(CURRENT_AUTH_KEY1, key1);
|
|
||||||
e.putString(CURRENT_AUTH_KEY2, key2);
|
|
||||||
e.putBoolean(LOGGED_IN, true);
|
|
||||||
e.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUserId(long userId) {
|
public void setUserId(long userId) {
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
Editor e = settings.edit();
|
Editor e = settings.edit();
|
||||||
|
|
|
@ -85,8 +85,9 @@ public class AccountFragment extends ListFragment implements OnAccountClickListe
|
||||||
@Override
|
@Override
|
||||||
public void onAccountClick(Account account) {
|
public void onAccountClick(Account account) {
|
||||||
// set new account
|
// set new account
|
||||||
String[] token = account.getKeys();
|
settings.setAccessToken(account.getAccessToken());
|
||||||
settings.setConnection(token[0], token[1], account.getId());
|
settings.setTokenSecret(account.getTokenSecret());
|
||||||
|
settings.setUserId(account.getId());
|
||||||
// finish activity and return to parent activity
|
// finish activity and return to parent activity
|
||||||
requireActivity().setResult(RET_ACCOUNT_CHANGE);
|
requireActivity().setResult(RET_ACCOUNT_CHANGE);
|
||||||
requireActivity().finish();
|
requireActivity().finish();
|
||||||
|
|
|
@ -26,7 +26,12 @@ public interface Account {
|
||||||
User getUser();
|
User getUser();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return oauth keys
|
* @return first access token of the user
|
||||||
*/
|
*/
|
||||||
String[] getKeys();
|
String getAccessToken();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return second access token of the user
|
||||||
|
*/
|
||||||
|
String getTokenSecret();
|
||||||
}
|
}
|
Loading…
Reference in New Issue