259 lines
11 KiB
Java
259 lines
11 KiB
Java
package org.mariotaku.twidere.util;
|
|
|
|
import android.accounts.Account;
|
|
import android.accounts.AccountManager;
|
|
import android.content.Context;
|
|
import android.content.pm.PackageInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.os.Build;
|
|
import android.support.annotation.NonNull;
|
|
import android.support.annotation.Nullable;
|
|
import android.support.annotation.WorkerThread;
|
|
import android.text.TextUtils;
|
|
import android.webkit.URLUtil;
|
|
|
|
import org.mariotaku.microblog.library.MicroBlog;
|
|
import org.mariotaku.restfu.http.Endpoint;
|
|
import org.mariotaku.restfu.http.MultiValueMap;
|
|
import org.mariotaku.restfu.http.SimpleValueMap;
|
|
import org.mariotaku.restfu.oauth.OAuthEndpoint;
|
|
import org.mariotaku.restfu.oauth.OAuthToken;
|
|
import org.mariotaku.twidere.TwidereConstants;
|
|
import org.mariotaku.twidere.extension.model.AccountExtensionsKt;
|
|
import org.mariotaku.twidere.extension.model.CredentialsExtensionsKt;
|
|
import org.mariotaku.twidere.model.ConsumerKeyType;
|
|
import org.mariotaku.twidere.model.UserKey;
|
|
import org.mariotaku.twidere.model.account.cred.Credentials;
|
|
import org.mariotaku.twidere.model.util.AccountUtils;
|
|
import org.mariotaku.twidere.util.api.TwitterAndroidExtraHeaders;
|
|
import org.mariotaku.twidere.util.api.TwitterMacExtraHeaders;
|
|
import org.mariotaku.twidere.util.api.UserAgentExtraHeaders;
|
|
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
import kotlin.Pair;
|
|
import okhttp3.HttpUrl;
|
|
import okhttp3.internal.Version;
|
|
|
|
/**
|
|
* Created by mariotaku on 15/5/7.
|
|
*/
|
|
public class MicroBlogAPIFactory implements TwidereConstants {
|
|
|
|
public static final String CARDS_PLATFORM_ANDROID_12 = "Android-12";
|
|
|
|
|
|
public static final SimpleValueMap sTwitterConstantPool = new SimpleValueMap();
|
|
public static final SimpleValueMap sFanfouConstantPool = new SimpleValueMap();
|
|
|
|
static {
|
|
sTwitterConstantPool.put("include_cards", "true");
|
|
sTwitterConstantPool.put("cards_platform", CARDS_PLATFORM_ANDROID_12);
|
|
sTwitterConstantPool.put("include_my_retweet", "true");
|
|
sTwitterConstantPool.put("include_rts", "true");
|
|
sTwitterConstantPool.put("include_reply_count", "true");
|
|
sTwitterConstantPool.put("include_descendent_reply_count", "true");
|
|
sTwitterConstantPool.put("full_text", "true");
|
|
sTwitterConstantPool.put("model_version", "7");
|
|
sTwitterConstantPool.put("skip_aggregation", "false");
|
|
sTwitterConstantPool.put("include_ext_alt_text", "true");
|
|
sTwitterConstantPool.put("tweet_mode", "extended");
|
|
|
|
sFanfouConstantPool.put("format", "html");
|
|
}
|
|
|
|
private MicroBlogAPIFactory() {
|
|
}
|
|
|
|
@WorkerThread
|
|
public static MicroBlog getDefaultTwitterInstance(final Context context) {
|
|
if (context == null) return null;
|
|
final UserKey accountKey = Utils.INSTANCE.getDefaultAccountKey(context);
|
|
if (accountKey == null) return null;
|
|
return getInstance(context, accountKey);
|
|
}
|
|
|
|
@WorkerThread
|
|
public static MicroBlog getInstance(@NonNull final Context context, @NonNull final UserKey accountKey) {
|
|
final AccountManager am = AccountManager.get(context);
|
|
final Account account = AccountUtils.findByAccountKey(am, accountKey);
|
|
if (account == null) return null;
|
|
final Credentials credentials = AccountExtensionsKt.getCredentials(account, am);
|
|
final String accountType = AccountExtensionsKt.getAccountType(account, am);
|
|
return CredentialsExtensionsKt.newMicroBlogInstance(credentials, context, accountType,
|
|
MicroBlog.class);
|
|
}
|
|
|
|
public static boolean verifyApiFormat(@NonNull String format) {
|
|
final String url = getApiBaseUrl(format, "test");
|
|
return URLUtil.isHttpsUrl(url) || URLUtil.isHttpUrl(url);
|
|
}
|
|
|
|
@NonNull
|
|
public static String getApiBaseUrl(@NonNull String format, @Nullable final String domain) {
|
|
final Matcher matcher = Pattern.compile("\\[(\\.?)DOMAIN(\\.?)]", Pattern.CASE_INSENSITIVE).matcher(format);
|
|
final String baseUrl;
|
|
if (!matcher.find()) {
|
|
// For backward compatibility
|
|
format = substituteLegacyApiBaseUrl(format, domain);
|
|
if (!format.endsWith("/1.1") && !format.endsWith("/1.1/")) {
|
|
baseUrl = format;
|
|
} else {
|
|
final String versionSuffix = "/1.1";
|
|
final int suffixLength = versionSuffix.length();
|
|
final int lastIndex = format.lastIndexOf(versionSuffix);
|
|
baseUrl = format.substring(0, lastIndex) + format.substring(lastIndex + suffixLength);
|
|
}
|
|
} else if (TextUtils.isEmpty(domain)) {
|
|
baseUrl = matcher.replaceAll("");
|
|
} else {
|
|
baseUrl = matcher.replaceAll("$1" + domain + "$2");
|
|
}
|
|
// In case someone set invalid base url
|
|
if (HttpUrl.parse(baseUrl) == null) {
|
|
return getApiBaseUrl(DEFAULT_TWITTER_API_URL_FORMAT, domain);
|
|
}
|
|
return baseUrl;
|
|
}
|
|
|
|
@NonNull
|
|
private static String substituteLegacyApiBaseUrl(@NonNull String format, @Nullable String domain) {
|
|
final int idxOfSlash = format.indexOf("://");
|
|
// Not an url
|
|
if (idxOfSlash < 0) return format;
|
|
final int startOfHost = idxOfSlash + 3;
|
|
if (startOfHost < 0) return getApiBaseUrl("https://[DOMAIN.]twitter.com/", domain);
|
|
final int endOfHost = format.indexOf('/', startOfHost);
|
|
final String host = endOfHost != -1 ? format.substring(startOfHost, endOfHost) : format.substring(startOfHost);
|
|
final StringBuilder sb = new StringBuilder();
|
|
sb.append(format, 0, startOfHost);
|
|
if (host.equalsIgnoreCase("api.twitter.com")) {
|
|
if (domain != null) {
|
|
sb.append(domain);
|
|
sb.append(".twitter.com");
|
|
} else {
|
|
sb.append("twitter.com");
|
|
}
|
|
} else if (host.equalsIgnoreCase("api.fanfou.com")) {
|
|
if (domain != null) {
|
|
sb.append(domain);
|
|
sb.append(".fanfou.com");
|
|
} else {
|
|
sb.append("fanfou.com");
|
|
}
|
|
} else {
|
|
return format;
|
|
}
|
|
if (endOfHost != -1) {
|
|
sb.append(format.substring(endOfHost));
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
@NonNull
|
|
public static String getApiUrl(@NonNull final String pattern, @Nullable final String domain, String appendPath) {
|
|
String urlBase = getApiBaseUrl(pattern, domain);
|
|
if (urlBase.endsWith("/")) {
|
|
urlBase = urlBase.substring(0, urlBase.length() - 1);
|
|
}
|
|
if (appendPath == null) return urlBase + "/";
|
|
if (appendPath.startsWith("/")) {
|
|
appendPath = appendPath.substring(1);
|
|
}
|
|
return urlBase + "/" + appendPath;
|
|
}
|
|
|
|
@WorkerThread
|
|
@Nullable
|
|
public static ExtraHeaders getExtraHeaders(Context context, ConsumerKeyType type) {
|
|
switch (type) {
|
|
case TWITTER_FOR_ANDROID: {
|
|
return TwitterAndroidExtraHeaders.INSTANCE;
|
|
}
|
|
case TWITTER_FOR_IPHONE:
|
|
case TWITTER_FOR_IPAD: {
|
|
return new UserAgentExtraHeaders("Twitter/6.75.2 CFNetwork/811.4.18 Darwin/16.5.0");
|
|
}
|
|
case TWITTER_FOR_MAC: {
|
|
return TwitterMacExtraHeaders.INSTANCE;
|
|
}
|
|
case TWEETDECK: {
|
|
return new UserAgentExtraHeaders(UserAgentUtils.getDefaultUserAgentStringSafe(context));
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static String getTwidereUserAgent(final Context context) {
|
|
final PackageManager pm = context.getPackageManager();
|
|
try {
|
|
final PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);
|
|
return String.format(Locale.US, "%s/%s %s Android/%s", TWIDERE_APP_NAME,
|
|
pi.versionName, Version.userAgent(), Build.VERSION.RELEASE);
|
|
} catch (final PackageManager.NameNotFoundException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
public static Endpoint getOAuthRestEndpoint(@NonNull String apiUrlFormat, boolean sameOAuthSigningUrl, boolean noVersionSuffix) {
|
|
return getOAuthEndpoint(apiUrlFormat, "api", noVersionSuffix ? null : "1.1", sameOAuthSigningUrl);
|
|
}
|
|
|
|
public static Endpoint getOAuthSignInEndpoint(@NonNull String apiUrlFormat, boolean sameOAuthSigningUrl) {
|
|
return getOAuthEndpoint(apiUrlFormat, "api", null, sameOAuthSigningUrl, true);
|
|
}
|
|
|
|
public static Endpoint getOAuthEndpoint(String apiUrlFormat, @Nullable String domain,
|
|
@Nullable String versionSuffix,
|
|
boolean sameOAuthSigningUrl) {
|
|
return getOAuthEndpoint(apiUrlFormat, domain, versionSuffix, sameOAuthSigningUrl, false);
|
|
}
|
|
|
|
public static Endpoint getOAuthEndpoint(@NonNull String apiUrlFormat, @Nullable String domain,
|
|
@Nullable String versionSuffix,
|
|
boolean sameOAuthSigningUrl, boolean fixUrl) {
|
|
String endpointUrl, signEndpointUrl;
|
|
endpointUrl = getApiUrl(apiUrlFormat, domain, versionSuffix);
|
|
if (fixUrl) {
|
|
int[] authorityRange = UriUtils.getAuthorityRange(endpointUrl);
|
|
if (authorityRange != null && endpointUrl.regionMatches(authorityRange[0],
|
|
"api.fanfou.com", 0, authorityRange[1] - authorityRange[0])) {
|
|
endpointUrl = endpointUrl.substring(0, authorityRange[0]) + "fanfou.com" +
|
|
endpointUrl.substring(authorityRange[1]);
|
|
}
|
|
}
|
|
if (!sameOAuthSigningUrl) {
|
|
signEndpointUrl = getApiUrl(DEFAULT_TWITTER_API_URL_FORMAT, domain, versionSuffix);
|
|
} else {
|
|
signEndpointUrl = endpointUrl;
|
|
}
|
|
return new OAuthEndpoint(endpointUrl, signEndpointUrl);
|
|
}
|
|
|
|
public static OAuthToken getOAuthToken(String consumerKey, String consumerSecret) {
|
|
if (isValidConsumerKeySecret(consumerKey) && isValidConsumerKeySecret(consumerSecret))
|
|
return new OAuthToken(consumerKey, consumerSecret);
|
|
return new OAuthToken(TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET);
|
|
}
|
|
|
|
public static boolean isValidConsumerKeySecret(@NonNull CharSequence text) {
|
|
for (int i = 0, j = text.length(); i < j; i++) {
|
|
if (!isAsciiLetterOrDigit(text.charAt(i))) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static boolean isAsciiLetterOrDigit(int codePoint) {
|
|
return 'A' <= codePoint && codePoint <= 'Z' || 'a' <= codePoint && codePoint <= 'z'
|
|
|| '0' <= codePoint && codePoint <= '9';
|
|
}
|
|
|
|
public interface ExtraHeaders {
|
|
@NonNull
|
|
List<Pair<String, String>> get(MultiValueMap<String> headers);
|
|
}
|
|
}
|