Twidere-App-Android-Twitter.../twidere/src/main/java/org/mariotaku/twidere/util/MicroBlogAPIFactory.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);
}
}