Moved former google-play-crawler to a separate library

This commit is contained in:
Sergey Eremin
2016-12-18 05:32:30 +03:00
parent a5c3488be1
commit 81fd44ef80
8 changed files with 2 additions and 1017 deletions

View File

@@ -1,5 +1,4 @@
apply plugin: 'com.android.application'
apply plugin: 'com.google.protobuf'
android {
compileSdkVersion 25
@@ -21,50 +20,8 @@ android {
lintOptions {
disable 'GoogleAppIndexingWarning','GoogleAppIndexingApiWarning'
}
sourceSets {
debug {
java {
srcDirs 'src/main/java', "${buildDir}/generated/source/proto/debug/javalite"
}
}
release {
java {
srcDirs 'src/main/java', "${buildDir}/generated/source/proto/release/javalite"
}
}
main {
proto {
srcDir 'src/main/proto'
}
}
}
}
dependencies {
compile group: 'com.google.protobuf', name: 'protobuf-lite', version: '3.0.0'
compile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.5.0'
compile 'com.github.yeriomin:play-store-api:2eee3e034b'
}
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.0.0'
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.0.1'
}
javalite {
artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
}
}
generateProtoTasks {
all()*.plugins {
javalite { }
}
ofNonTest()*.plugins {
grpc {
option 'lite'
}
}
}
}

View File

@@ -1,9 +0,0 @@
package com.github.yeriomin.playstoreapi;
public interface DeviceInfoProvider {
AndroidCheckinRequest generateAndroidCheckinRequest();
DeviceConfigurationProto getDeviceConfigurationProto();
String getUserAgentString();
int getSdkVersion();
}

View File

@@ -1,436 +0,0 @@
package com.github.yeriomin.playstoreapi;
import java.io.IOException;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
/**
* This class provides
* <code>checkin, search, details, bulkDetails, browse, list and download</code>
* capabilities. It uses <code>Apache Commons HttpClient</code> for POST and GET
* requests.
* <p>
* <p>
* <b>XXX : DO NOT call checkin, login and download consecutively. To allow
* server to catch up, sleep for a while before download! (5 sec will do!) Also
* it is recommended to call checkin once and use generated android-id for
* further operations.</b>
* </p>
*
* @author akdeniz
*/
public class GooglePlayAPI {
private static final String SCHEME = "https://";
private static final String HOST = "android.clients.google.com";
private static final String CHECKIN_URL = SCHEME + HOST + "/checkin";
private static final String URL_LOGIN = SCHEME + HOST + "/auth";
private static final String C2DM_REGISTER_URL = SCHEME + HOST + "/c2dm/register2";
private static final String FDFE_URL = SCHEME + HOST + "/fdfe/";
private static final String LIST_URL = FDFE_URL + "list";
private static final String BROWSE_URL = FDFE_URL + "browse";
private static final String DETAILS_URL = FDFE_URL + "details";
private static final String SEARCH_URL = FDFE_URL + "search";
private static final String BULKDETAILS_URL = FDFE_URL + "bulkDetails";
private static final String PURCHASE_URL = FDFE_URL + "purchase";
private static final String REVIEWS_URL = FDFE_URL + "rev";
private static final String UPLOADDEVICECONFIG_URL = FDFE_URL + "uploadDeviceConfig";
private static final String RECOMMENDATIONS_URL = FDFE_URL + "rec";
private static final String ACCOUNT_TYPE_HOSTED_OR_GOOGLE = "HOSTED_OR_GOOGLE";
public enum REVIEW_SORT {
NEWEST(0), HIGHRATING(1), HELPFUL(2);
public int value;
REVIEW_SORT(int value) {
this.value = value;
}
}
public enum RECOMMENDATION_TYPE {
ALSO_VIEWED(1), ALSO_INSTALLED(2);
public int value;
RECOMMENDATION_TYPE(int value) {
this.value = value;
}
}
private String token;
private String gsfId;
private String email;
private String password;
private ThrottledOkHttpClient client;
private Locale locale;
private DeviceInfoProvider deviceInfoProvider;
private Map<String, String> searchNextPage = new HashMap<>();
public void setToken(String token) {
this.token = token;
}
public void setGsfId(String gsfId) {
this.gsfId = gsfId;
}
public void setDeviceInfoProvider(DeviceInfoProvider deviceInfoProvider) {
this.deviceInfoProvider = deviceInfoProvider;
}
private ThrottledOkHttpClient getClient() {
if (this.client == null) {
this.client = new ThrottledOkHttpClient();
}
return this.client;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
/**
* If this constructor is used, Android ID must be generated by calling
* <code>checkin()</code> or set by using <code>setGsfId</code> before
* using other abilities.
*/
public GooglePlayAPI(String email, String password) {
this.email = email;
this.password = password;
}
/**
* Performs authentication on "ac2dm" service and match up android id,
* security token and email by checking them in on this server.
* <p>
* This function sets check-inded android ID and that can be taken either by
* using <code>getToken()</code> or from returned
* {@link AndroidCheckinResponse} instance.
*/
public String getGsfId() throws IOException {
AndroidCheckinRequest request = this.deviceInfoProvider.generateAndroidCheckinRequest();
// this first checkin is for generating android-id
AndroidCheckinResponse checkinResponse = checkin(request.toByteArray());
this.gsfId = BigInteger.valueOf(checkinResponse.getAndroidId()).toString(16);
String securityToken = BigInteger.valueOf(checkinResponse.getSecurityToken()).toString(16);
AndroidCheckinRequest.Builder checkInbuilder = AndroidCheckinRequest.newBuilder(request);
String AC2DMToken = getAC2DMToken();
AndroidCheckinRequest build = checkInbuilder
.setId(new BigInteger(this.gsfId, 16).longValue())
.setSecurityToken(new BigInteger(securityToken, 16).longValue())
.addAccountCookie("[" + this.email + "]")
.addAccountCookie(AC2DMToken)
.build();
// this is the second checkin to match credentials with android-id
checkin(build.toByteArray());
return this.gsfId;
}
/**
* Posts given check-in request content and returns
* {@link AndroidCheckinResponse}.
*/
private AndroidCheckinResponse checkin(byte[] request) throws IOException {
Map<String, String> headers = getDefaultHeaders();
headers.put("Content-Type", "application/x-protobuffer");
byte[] content = getClient().post(CHECKIN_URL, request, headers);
return AndroidCheckinResponse.parseFrom(content);
}
/**
* Authenticates on server with given email and password and sets
* authentication token. This token can be used to login instead of using
* email and password every time.
*/
public String getToken() throws IOException {
Map<String, String> params = getDefaultLoginParams();
params.put("service", "androidmarket");
params.put("app", "com.android.vending");
params.put("androidId", this.getGsfId());
byte[] responseBytes = getClient().post(URL_LOGIN, params, getDefaultHeaders());
Map<String, String> response = parseResponse(new String(responseBytes));
if (response.containsKey("Auth")) {
return response.get("Auth");
} else {
throw new GooglePlayException("Authentication failed! (login)");
}
}
/**
* Logins AC2DM server and returns authentication string.
* <p>
* <p>
* client_sig is SHA1 digest of encoded certificate on
* <i>GoogleLoginService(package name : com.google.android.gsf)</i> system APK.
* But google doesn't seem to care of value of this parameter.
*/
public String getAC2DMToken() throws IOException {
Map<String, String> params = getDefaultLoginParams();
params.put("service", "ac2dm");
params.put("add_account", "1");
params.put("app", "com.google.android.gsf");
byte[] responseBytes = getClient().post(URL_LOGIN, params, getDefaultHeaders());
Map<String, String> response = parseResponse(new String(responseBytes));
if (response.containsKey("Auth")) {
return response.get("Auth");
} else {
throw new GooglePlayException("Authentication failed! (loginAC2DM)");
}
}
public Map<String, String> c2dmRegister(String application, String sender) throws IOException {
Map<String, String> params = new HashMap<>();
params.put("app", application);
params.put("sender", sender);
params.put("device", new BigInteger(this.getGsfId(), 16).toString());
Map<String, String> headers = getDefaultHeaders();
headers.put("Authorization", "GoogleLogin auth=" + getAC2DMToken());
byte[] responseBytes = getClient().post(C2DM_REGISTER_URL, params, headers);
return parseResponse(new String(responseBytes));
}
/**
* Equivalent of <code>search(query, null, null)</code>
*/
public SearchResponse search(String query) throws IOException {
return search(query, null, null);
}
/**
* Fetches a search results for given query. Offset and numberOfResults
* parameters are optional and <code>null</code> can be passed!
*
* Warning! offset and numberOfResults do not seem to work anymore.
* The api always returns first 30 results. Fetching further results is done through
* nextPageUrl returned with the search result.
*/
public SearchResponse search(String query, Integer offset, Integer numberOfResults) throws IOException {
String url = SEARCH_URL;
Map<String, String> params = new HashMap<>();
if (this.searchNextPage.containsKey(query)) {
url = this.searchNextPage.get(query);
if (null == url) {
throw new GooglePlayException("No more results for query " + query);
}
} else {
params = getDefaultGetParams(offset, numberOfResults);
params.put("q", query);
}
byte[] content = getClient().get(url, params, getDefaultHeaders());
ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content);
SearchResponse response = responseWrapper.getPayload().getSearchResponse();
if (response.getDocCount() > 0
&& response.getDocList().get(0).hasContainerMetadata()
&& response.getDocList().get(0).getContainerMetadata().hasNextPageUrl()
) {
this.searchNextPage.put(query, FDFE_URL + response.getDocList().get(0).getContainerMetadata().getNextPageUrl());
} else {
this.searchNextPage.put(query, null);
}
return responseWrapper.getPayload().getSearchResponse();
}
public boolean hasNextSearchPage(String query) {
return !this.searchNextPage.containsKey(query) || this.searchNextPage.get(query) != null;
}
/**
* Fetches detailed information about passed package name. If it is needed
* to fetch information about more than one application, consider to use
* <code>bulkDetails</code>.
*/
public DetailsResponse details(String packageName) throws IOException {
Map<String, String> params = new HashMap<>();
params.put("doc", packageName);
byte[] content = getClient().get(DETAILS_URL, params, getDefaultHeaders());
ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content);
return responseWrapper.getPayload().getDetailsResponse();
}
/**
* Equivalent of details but bulky one!
*/
public BulkDetailsResponse bulkDetails(List<String> packageNames) throws IOException {
BulkDetailsRequest.Builder bulkDetailsRequestBuilder = BulkDetailsRequest.newBuilder();
bulkDetailsRequestBuilder.addAllDocid(packageNames);
byte[] request = bulkDetailsRequestBuilder.build().toByteArray();
byte[] content = getClient().post(BULKDETAILS_URL, request, getDefaultHeaders());
ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content);
return responseWrapper.getPayload().getBulkDetailsResponse();
}
/**
* Fetches available categories
*/
public BrowseResponse browse() throws IOException {
return browse(null, null);
}
public BrowseResponse browse(String categoryId, String subCategoryId) throws IOException {
Map<String, String> params = getDefaultGetParams(null, null);
params.put("cat", categoryId);
params.put("ctr", subCategoryId);
byte[] content = getClient().get(BROWSE_URL, params, getDefaultHeaders());
ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content);
return responseWrapper.getPayload().getBrowseResponse();
}
/**
* Equivalent of <code>list(categoryId, null, null, null)</code>. It fetches
* sub-categories of given category!
*/
public ListResponse list(String categoryId) throws IOException {
return list(categoryId, null, null, null);
}
/**
* Fetches applications within supplied category and sub-category. If
* <code>null</code> is given for sub-category, it fetches sub-categories of
* passed category.
* <p>
* Default values for offset and numberOfResult are "0" and "20"
* respectively. These values are determined by Google Play Store.
*/
public ListResponse list(String categoryId, String subCategoryId, Integer offset, Integer numberOfResults) throws IOException {
Map<String, String> params = getDefaultGetParams(offset, numberOfResults);
params.put("cat", categoryId);
params.put("ctr", subCategoryId);
byte[] content = getClient().get(LIST_URL, params, getDefaultHeaders());
ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content);
return responseWrapper.getPayload().getListResponse();
}
/**
* This function is used for fetching download url and download cookie,
* rather than actual purchasing.
*/
public BuyResponse purchase(String packageName, int versionCode, int offerType) throws IOException {
Map<String, String> params = new HashMap<>();
params.put("ot", String.valueOf(offerType));
params.put("doc", packageName);
params.put("vc", String.valueOf(versionCode));
byte[] content = getClient().post(PURCHASE_URL, params, getDefaultHeaders());
ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content);
return responseWrapper.getPayload().getBuyResponse();
}
/**
* Fetches the reviews of given package name by sorting passed choice.
* <p>
* Default values for offset and numberOfResult are "0" and "20"
* respectively. These values are determined by Google Play Store.
*/
public ReviewResponse reviews(String packageName, REVIEW_SORT sort, Integer offset, Integer numberOfResults) throws IOException {
Map<String, String> params = getDefaultGetParams(offset, numberOfResults);
params.put("doc", packageName);
params.put("sort", (sort == null) ? null : String.valueOf(sort.value));
byte[] content = getClient().get(REVIEWS_URL, params, getDefaultHeaders());
ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content);
return responseWrapper.getPayload().getReviewResponse();
}
/**
* Uploads device configuration to google server so that can be seen from
* web as a registered device!!
*/
public UploadDeviceConfigResponse uploadDeviceConfig() throws IOException {
UploadDeviceConfigRequest request = UploadDeviceConfigRequest.newBuilder()
.setDeviceConfiguration(this.deviceInfoProvider.getDeviceConfigurationProto())
.build();
byte[] content = getClient().post(UPLOADDEVICECONFIG_URL, request.toByteArray(), getDefaultHeaders());
ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content);
return responseWrapper.getPayload().getUploadDeviceConfigResponse();
}
/**
* Fetches the recommendations of given package name.
* <p>
* Default values for offset and numberOfResult are "0" and "20"
* respectively. These values are determined by Google Play Store.
*/
public ListResponse recommendations(String packageName, RECOMMENDATION_TYPE type, Integer offset, Integer numberOfResults) throws IOException {
Map<String, String> params = getDefaultGetParams(offset, numberOfResults);
params.put("doc", packageName);
params.put("rt", (type == null) ? null : String.valueOf(type.value));
byte[] content = getClient().get(RECOMMENDATIONS_URL, params, getDefaultHeaders());
ResponseWrapper responseWrapper = ResponseWrapper.parseFrom(content);
return responseWrapper.getPayload().getListResponse();
}
/**
* login methods use this
* Most likely not all of these are required, but the Market app sends them, so we will too
*
*/
private Map<String, String> getDefaultLoginParams() {
Map<String, String> params = new HashMap<>();
params.put("Email", this.email);
params.put("Passwd", this.password);
params.put("accountType", ACCOUNT_TYPE_HOSTED_OR_GOOGLE);
params.put("has_permission", "1");
params.put("source", "android");
params.put("device_country", this.locale.getCountry().toLowerCase());
params.put("lang", this.locale.getLanguage().toLowerCase());
params.put("sdk_version", String.valueOf(this.deviceInfoProvider.getSdkVersion()));
params.put("client_sig", "38918a453d07199354f8b19af05ec6562ced5788");
return params;
}
/**
* Using Accept-Language you can fetch localized informations such as reviews and descriptions.
* Note that changing this value has no affect on localized application list that
* server provides. It depends on only your IP location.
*
*/
private Map<String, String> getDefaultHeaders() {
Map<String, String> headers = new HashMap<>();
if (this.token != null && !this.token.isEmpty()) {
headers.put("Authorization", "GoogleLogin auth=" + this.token);
}
headers.put("User-Agent", this.deviceInfoProvider.getUserAgentString());
if (this.gsfId != null && !this.gsfId.isEmpty()) {
headers.put("X-DFE-Device-Id", this.gsfId);
}
headers.put("Accept-Language", this.locale.toString().replace("_", "-"));
return headers;
}
/**
* Most list requests (apps, categories,..) take these params
*
* @param offset
* @param numberOfResults
*/
private Map<String, String> getDefaultGetParams(Integer offset, Integer numberOfResults) {
Map<String, String> params = new HashMap<>();
params.put("c", "3");
if (offset != null) {
params.put("o", String.valueOf(offset));
}
if (numberOfResults != null) {
params.put("n", String.valueOf(numberOfResults));
}
return params;
}
private static Map<String, String> parseResponse(String response) {
Map<String, String> keyValueMap = new HashMap<>();
StringTokenizer st = new StringTokenizer(response, "\n\r");
while (st.hasMoreTokens()) {
String[] keyValue = st.nextToken().split("=");
keyValueMap.put(keyValue[0], keyValue[1]);
}
return keyValueMap;
}
}

View File

@@ -1,24 +0,0 @@
package com.github.yeriomin.playstoreapi;
import java.io.IOException;
public class GooglePlayException extends IOException {
private int code;
public GooglePlayException(String message) {
super(message);
}
public GooglePlayException(String message, int code) {
super(message);
this.code = code;
}
public GooglePlayException(String message, Throwable cause) {
super(message, cause);
}
public int getCode() {
return this.code;
}
}

View File

@@ -1,89 +0,0 @@
package com.github.yeriomin.playstoreapi;
import java.util.Arrays;
import java.util.Properties;
public class PropertiesDeviceInfoProvider implements DeviceInfoProvider {
private Properties properties;
private String localeString;
public void setProperties(Properties properties) {
this.properties = properties;
}
public void setLocaleString(String localeString) {
this.localeString = localeString;
}
public int getSdkVersion() {
return Integer.parseInt(this.properties.getProperty("Build.VERSION.SDK_INT"));
}
public String getUserAgentString() {
return "Android-Finsky/7.1.15 ("
+ "api=3"
+ ",versionCode=80711500"
+ ",sdk=" + this.properties.getProperty("Build.VERSION.SDK_INT")
+ ",device=" + this.properties.getProperty("Build.DEVICE")
+ ",hardware=" + this.properties.getProperty("Build.HARDWARE")
+ ",product=" + this.properties.getProperty("Build.PRODUCT")
+ ")";
}
public AndroidCheckinRequest generateAndroidCheckinRequest() {
return AndroidCheckinRequest.newBuilder()
.setId(0)
.setCheckin(
AndroidCheckinProto.newBuilder()
.setBuild(
AndroidBuildProto.newBuilder()
.setId(this.properties.getProperty("Build.FINGERPRINT"))
.setProduct(this.properties.getProperty("Build.HARDWARE"))
.setCarrier(this.properties.getProperty("Build.BRAND"))
.setRadio(this.properties.getProperty("Build.RADIO"))
.setBootloader(this.properties.getProperty("Build.BOOTLOADER"))
.setDevice(this.properties.getProperty("Build.DEVICE"))
.setSdkVersion(Integer.getInteger(this.properties.getProperty("Build.VERSION.SDK_INT")))
.setModel(this.properties.getProperty("Build.MODEL"))
.setManufacturer(this.properties.getProperty("Build.MANUFACTURER"))
.setBuildProduct(this.properties.getProperty("Build.PRODUCT"))
.setClient(this.properties.getProperty("Client"))
.setOtaInstalled(Boolean.getBoolean(this.properties.getProperty("OtaInstalled")))
.setTimestamp(System.currentTimeMillis() / 1000)
.setGoogleServices(Integer.getInteger(this.properties.getProperty("GSF.version")))
)
.setLastCheckinMsec(0)
.setCellOperator(this.properties.getProperty("CellOperator"))
.setSimOperator(this.properties.getProperty("SimOperator"))
.setRoaming(this.properties.getProperty("Roaming"))
.setUserNumber(0)
)
.setLocale(this.localeString)
.setTimeZone(this.properties.getProperty("TimeZone"))
.setVersion(3)
.setDeviceConfiguration(getDeviceConfigurationProto())
.setFragment(0)
.build();
}
public DeviceConfigurationProto getDeviceConfigurationProto() {
return DeviceConfigurationProto.newBuilder()
.setTouchScreen(Integer.getInteger(this.properties.getProperty("TouchScreen")))
.setKeyboard(Integer.getInteger(this.properties.getProperty("Keyboard")))
.setNavigation(Integer.getInteger(this.properties.getProperty("Navigation")))
.setScreenLayout(Integer.getInteger(this.properties.getProperty("ScreenLayout")))
.setHasHardKeyboard(Boolean.getBoolean(this.properties.getProperty("HasHardKeyboard")))
.setHasFiveWayNavigation(Boolean.getBoolean(this.properties.getProperty("HasFiveWayNavigation")))
.setScreenDensity(Integer.getInteger(this.properties.getProperty("Screen.Density")))
.setScreenWidth(Integer.getInteger(this.properties.getProperty("Screen.Width")))
.setScreenHeight(Integer.getInteger(this.properties.getProperty("Screen.Height")))
.addAllNativePlatform(Arrays.asList(this.properties.getProperty("Platforms").split(",")))
.addAllSystemSharedLibrary(Arrays.asList(this.properties.getProperty("SharedLibraries").split(",")))
.addAllSystemAvailableFeature(Arrays.asList(this.properties.getProperty("Features").split(",")))
.addAllSystemSupportedLocale(Arrays.asList(this.properties.getProperty("Locales").split(",")))
.setGlEsVersion(Integer.getInteger(this.properties.getProperty("GL.Version")))
.addAllGlExtension(Arrays.asList(this.properties.getProperty("GL.Extensions").split(",")))
.build();
}
}

View File

@@ -1,134 +0,0 @@
package com.github.yeriomin.playstoreapi;
import android.os.SystemClock;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.FormBody;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
class ThrottledOkHttpClient {
private static final long DEFAULT_REQUEST_INTERVAL = 2000;
private long lastRequestTime;
private long requestInterval = DEFAULT_REQUEST_INTERVAL;
private OkHttpClient client;
public void setRequestInterval(long requestInterval) {
this.requestInterval = requestInterval;
}
public ThrottledOkHttpClient() {
this.client = new OkHttpClient.Builder()
.connectTimeout(3, TimeUnit.SECONDS)
.readTimeout(3, TimeUnit.SECONDS)
.cookieJar(new CookieJar() {
private final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>();
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
cookieStore.put(url, cookies);
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
List<Cookie> cookies = cookieStore.get(url);
return cookies != null ? cookies : new ArrayList<Cookie>();
}
})
.build();
}
public byte[] get(String url, Map<String, String> params) throws IOException {
return get(url, params, null);
}
public byte[] get(String url, Map<String, String> params, Map<String, String> headers) throws IOException {
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
if (null != params && !params.isEmpty()) {
for (String name: params.keySet()) {
urlBuilder.addQueryParameter(name, params.get(name));
}
}
Request.Builder requestBuilder = new Request.Builder()
.url(urlBuilder.build())
.get();
return request(requestBuilder, headers);
}
public byte[] post(String url, Map<String, String> params, Map<String, String> headers) throws IOException {
headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
FormBody.Builder bodyBuilder = new FormBody.Builder();
if (null != params && !params.isEmpty()) {
for (String name: params.keySet()) {
bodyBuilder.add(name, params.get(name));
}
}
Request.Builder requestBuilder = new Request.Builder()
.url(url)
.post(bodyBuilder.build());
return post(url, requestBuilder, headers);
}
public byte[] post(String url, byte[] body, Map<String, String> headers) throws IOException {
if (!headers.containsKey("Content-Type")) {
headers.put("Content-Type", "application/x-protobuf");
}
Request.Builder requestBuilder = new Request.Builder()
.url(url)
.post(RequestBody.create(MediaType.parse("application/x-protobuf"), body));
return post(url, requestBuilder, headers);
}
private byte[] post(String url, Request.Builder requestBuilder, Map<String, String> headers) throws IOException {
requestBuilder.url(url);
return request(requestBuilder, headers);
}
private byte[] request(Request.Builder requestBuilder, Map<String, String> headers) throws IOException {
if (this.lastRequestTime > 0) {
long msecRemaining = this.requestInterval - System.currentTimeMillis() + this.lastRequestTime;
if (msecRemaining > 0) {
SystemClock.sleep(msecRemaining);
}
}
Request request = requestBuilder
.headers(Headers.of(headers))
.build();
System.out.println("Requesting: " + request.url().toString());
Response response = client.newCall(request).execute();
int code = response.code();
byte[] content = response.body().bytes();
if (code >= 400) {
throw new GooglePlayException(String.valueOf(code) + " Probably an auth error: " + new String(content), code);
}
return content;
}
}

View File

@@ -1,281 +0,0 @@
package com.github.yeriomin.playstoreapi;
/**
* @author akdeniz
*/
public class Utils {
/*
*
id: "Xiaomi/gemini/gemini:6.0/MRA58K/V7.2.8.0.MAACNDB:user/release-keys"
product: "qcom"
carrier: "Xiaomi"
radio: "TH20.c1.3-0321_1155_57c5007"
bootloader: "unknown"
client: "unknown"
timestamp: 1458643019
googleServices: 10084448
device: "gemini"
sdkVersion: 23
model: "MI 5"
manufacturer: "Xiaomi"
buildProduct: "gemini"
otaInstalled: 0
Build.BOARD msm8996
Build.BOOTLOADER unknown
Build.BRAND Xiaomi
Build.DEVICE gemini
Build.DISPLAY MRA58K
Build.FINGERPRINT Xiaomi/gemini/gemini:6.0/MRA58K/V7.2.8.0.MAACNDB:user/release-keys
Build.HARDWARE qcom
Build.HOST qh-miui-ota-bd72.bj
Build.ID MRA58K
Build.MANUFACTURER Xiaomi
Build.MODEL MI 5
Build.PRODUCT gemini
Build.SERIAL d0189fa4
Build.TAGS release-keys
Build.TYPE user
Build.UNKNOWN unknown
Build.USER builder
Build.CPU_ABI arm64-v8a
Build.CPU_ABI2
Build.TIME 1458643019000
Build.VERSION.CODENAME REL
Build.VERSION.INCREMENTAL V7.2.8.0.0.MAACNDB
Build.VERSION.RELEASE 6.0
Build.VERSION.SDK_INT 23
*/
// /**
// * Generates android checkin request with properties of "Galaxy S3".
// * <p>
// * <a href=
// * "http://www.glbenchmark.com/phonedetails.jsp?benchmark=glpro25&D=Samsung+GT-I9300+Galaxy+S+III&testgroup=system"
// * > http://www.glbenchmark.com/phonedetails.jsp?benchmark=glpro25&D=Samsung
// * +GT-I9300+Galaxy+S+III&testgroup=system </a>
// */
// public static AndroidCheckinRequest generateAndroidCheckinRequest() {
//
// return AndroidCheckinRequest
// .newBuilder()
// .setId(0)
// .setCheckin(
// AndroidCheckinProto.newBuilder()
// .setBuild(
// AndroidBuildProto.newBuilder()
// .setId(Build.FINGERPRINT)
// .setProduct(Build.HARDWARE)
// .setCarrier(Build.BRAND)
// .setRadio(Build.RADIO)
// .setBootloader(Build.BOOTLOADER)
// .setDevice(Build.DEVICE)
// .setSdkVersion(Build.VERSION.SDK_INT)
// .setModel(Build.MODEL)
// .setManufacturer(Build.MANUFACTURER)
// .setBuildProduct(Build.PRODUCT)
// .setClient("android-google")
// .setOtaInstalled(false)
// .setTimestamp(new Date().getTime() / 1000)
// .setGoogleServices(16)
// )
// .setLastCheckinMsec(0)
// .setCellOperator("310260")
// .setSimOperator("310260")
// .setRoaming("mobile-notroaming")
// .setUserNumber(0)
// )
// .setLocale(Locale.getDefault().toString())
// .setTimeZone(TimeZone.getDefault().getID())
// .setVersion(3)
// .setDeviceConfiguration(getDeviceConfigurationProto())
// .setFragment(0)
// .build();
// }
//
// public static AndroidCheckinRequest generateAndroidCheckinRequestOriginal() {
//
// return AndroidCheckinRequest
// .newBuilder()
// .setId(0)
// .setCheckin(
// AndroidCheckinProto.newBuilder()
// .setBuild(
// AndroidBuildProto.newBuilder()
// .setId("samsung/m0xx/m0:4.0.4/IMM76D/I9300XXALF2:user/release-keys")
// .setProduct("smdk4x12")
// .setCarrier("Google")
// .setRadio("I9300XXALF2")
// .setBootloader("PRIMELA03")
// .setClient("android-google")
// .setTimestamp(new Date().getTime() / 1000)
// .setGoogleServices(16)
// .setDevice("m0")
// .setSdkVersion(16)
// .setModel("GT-I9300")
// .setManufacturer("Samsung")
// .setBuildProduct("m0xx")
// .setOtaInstalled(false)
// )
// .setLastCheckinMsec(0)
// .setCellOperator("310260")
// .setSimOperator("310260")
// .setRoaming("mobile-notroaming")
// .setUserNumber(0)
// )
// .setLocale("en_US")
// .setTimeZone("Europe/Istanbul")
// .setVersion(3)
// .setDeviceConfiguration(getDeviceConfigurationProto())
// .setFragment(0)
// .build();
// }
//
// public static AndroidCheckinRequest generateAndroidCheckinRequestNviennot() {
//
// return AndroidCheckinRequest
// .newBuilder()
// .setId(0)
// .setCheckin(
// AndroidCheckinProto.newBuilder()
// .setBuild(
// AndroidBuildProto.newBuilder()
// .setId("google/yakju/maguro:4.1.1/JRO03C/398337:user/release-keys")
// .setProduct("tuna")
// .setCarrier("Google")
// .setRadio("I9250XXLA2")
// .setBootloader("PRIMELA03")
// .setClient("android-google")
// .setTimestamp(new Date().getTime()/1000)
// .setGoogleServices(16)
// .setDevice("maguro")
// .setSdkVersion(16)
// .setModel("Galaxy Nexus")
// .setManufacturer("Samsung")
// .setBuildProduct("yakju")
// .setOtaInstalled(false)
// )
// .setLastCheckinMsec(0)
// .setCellOperator("310260")
// .setSimOperator("310260")
// .setRoaming("mobile-notroaming")
// .setUserNumber(0)
// )
// .setLocale("en_US")
// .setTimeZone("Europe/Istanbul")
// .setVersion(3)
// .setDeviceConfiguration(getDeviceConfigurationProto())
// .setFragment(0)
// .build();
// }
//
// public static AndroidCheckinRequest generateAndroidCheckinRequestRacoon() {
//
// return AndroidCheckinRequest
// .newBuilder()
// .setId(0)
// .setCheckin(
// AndroidCheckinProto.newBuilder()
// .setBuild(
// AndroidBuildProto.newBuilder()
// .setId("samsung/nobleltejv/noblelte:6.0.1/MMB29K/N920CXXU2BPD6:user/release-keys")
// .setProduct("noblelte")
// .setCarrier("Google")
// .setRadio("I9300XXALF2")
// .setBootloader("PRIMELA03")
// .setClient("android-google")
// .setTimestamp(new Date().getTime() / 1000)
// .setGoogleServices(16)
// .setDevice("noblelte")
// .setSdkVersion(23)
// .setModel("SM-N920C")
// .setManufacturer("Samsung")
// .setBuildProduct("noblelte")
// .setOtaInstalled(false)
// )
// .setLastCheckinMsec(0)
// .setCellOperator("310260")
// .setSimOperator("310260")
// .setRoaming("mobile-notroaming")
// .setUserNumber(0)
// )
// .setLocale("en_US")
// .setTimeZone("Europe/Berlin")
// .setVersion(3)
// .setDeviceConfiguration(getDeviceConfigurationProto())
// .setFragment(0)
// .build();
// }
//
// public static DeviceConfigurationProto getDeviceConfigurationProtoAAAAA() {
//// DisplayMetrics metrics = new DisplayMetrics();
//// WindowManager wm = (WindowManager) this.context.getSystemService(Context.WINDOW_SERVICE);
//// wm.getDefaultDisplay().getMetrics(metrics);
// return DeviceConfigurationProto.newBuilder()
// .setTouchScreen(3)
// .setKeyboard(1)
// .setNavigation(1)
// .setScreenLayout(2)
// .setHasHardKeyboard(false)
// .setHasFiveWayNavigation(false)
// .setScreenDensity(320)
// .setScreenWidth(720)
// .setScreenHeight(1184)
// .setGlEsVersion(131072)
// .addAllNativePlatform(Arrays.asList(Build.CPU_ABI, Build.CPU_ABI2))
//// .addAllNativePlatform(Arrays.asList("armeabi-v7a", "armeabi"))
// .addAllSystemSharedLibrary(
// Arrays.asList("android.test.runner", "com.android.future.usb.accessory", "com.android.location.provider",
// "com.android.nfc_extras", "com.google.android.maps", "com.google.android.media.effects",
// "com.google.widevine.software.drm", "javax.obex"))
// .addAllSystemAvailableFeature(
// Arrays.asList("android.hardware.bluetooth", "android.hardware.camera",
// "android.hardware.camera.autofocus", "android.hardware.camera.flash",
// "android.hardware.camera.front", "android.hardware.faketouch", "android.hardware.location",
// "android.hardware.location.gps", "android.hardware.location.network",
// "android.hardware.microphone", "android.hardware.nfc", "android.hardware.screen.landscape",
// "android.hardware.screen.portrait", "android.hardware.sensor.accelerometer",
// "android.hardware.sensor.barometer", "android.hardware.sensor.compass",
// "android.hardware.sensor.gyroscope", "android.hardware.sensor.light",
// "android.hardware.sensor.proximity", "android.hardware.telephony",
// "android.hardware.telephony.gsm", "android.hardware.touchscreen",
// "android.hardware.touchscreen.multitouch", "android.hardware.touchscreen.multitouch.distinct",
// "android.hardware.touchscreen.multitouch.jazzhand", "android.hardware.usb.accessory",
// "android.hardware.usb.host", "android.hardware.wifi", "android.hardware.wifi.direct",
// "android.software.live_wallpaper", "android.software.sip", "android.software.sip.voip",
// "com.cyanogenmod.android", "com.cyanogenmod.nfc.enhanced",
// "com.google.android.feature.GOOGLE_BUILD", "com.nxp.mifare", "com.tmobile.software.themes"))
// .addAllSystemSupportedLocale(
// Arrays.asList("af", "af_ZA", "am", "am_ET", "ar", "ar_EG", "bg", "bg_BG", "ca", "ca_ES", "cs", "cs_CZ",
// "da", "da_DK", "de", "de_AT", "de_CH", "de_DE", "de_LI", "el", "el_GR", "en", "en_AU", "en_CA",
// "en_GB", "en_NZ", "en_SG", "en_US", "es", "es_ES", "es_US", "fa", "fa_IR", "fi", "fi_FI", "fr",
// "fr_BE", "fr_CA", "fr_CH", "fr_FR", "hi", "hi_IN", "hr", "hr_HR", "hu", "hu_HU", "in", "in_ID",
// "it", "it_CH", "it_IT", "iw", "iw_IL", "ja", "ja_JP", "ko", "ko_KR", "lt", "lt_LT", "lv",
// "lv_LV", "ms", "ms_MY", "nb", "nb_NO", "nl", "nl_BE", "nl_NL", "pl", "pl_PL", "pt", "pt_BR",
// "pt_PT", "rm", "rm_CH", "ro", "ro_RO", "ru", "ru_RU", "sk", "sk_SK", "sl", "sl_SI", "sr",
// "sr_RS", "sv", "sv_SE", "sw", "sw_TZ", "th", "th_TH", "tl", "tl_PH", "tr", "tr_TR", "ug",
// "ug_CN", "uk", "uk_UA", "vi", "vi_VN", "zh_CN", "zh_TW", "zu", "zu_ZA"))
// .addAllGlExtension(
// Arrays.asList("GL_EXT_debug_marker", "GL_EXT_discard_framebuffer", "GL_EXT_multi_draw_arrays",
// "GL_EXT_shader_texture_lod", "GL_EXT_texture_format_BGRA8888",
// "GL_IMG_multisampled_render_to_texture", "GL_IMG_program_binary", "GL_IMG_read_format",
// "GL_IMG_shader_binary", "GL_IMG_texture_compression_pvrtc", "GL_IMG_texture_format_BGRA8888",
// "GL_IMG_texture_npot", "GL_IMG_vertex_array_object", "GL_OES_EGL_image",
// "GL_OES_EGL_image_external", "GL_OES_blend_equation_separate", "GL_OES_blend_func_separate",
// "GL_OES_blend_subtract", "GL_OES_byte_coordinates", "GL_OES_compressed_ETC1_RGB8_texture",
// "GL_OES_compressed_paletted_texture", "GL_OES_depth24", "GL_OES_depth_texture",
// "GL_OES_draw_texture", "GL_OES_egl_sync", "GL_OES_element_index_uint",
// "GL_OES_extended_matrix_palette", "GL_OES_fixed_point", "GL_OES_fragment_precision_high",
// "GL_OES_framebuffer_object", "GL_OES_get_program_binary", "GL_OES_mapbuffer",
// "GL_OES_matrix_get", "GL_OES_matrix_palette", "GL_OES_packed_depth_stencil",
// "GL_OES_point_size_array", "GL_OES_point_sprite", "GL_OES_query_matrix", "GL_OES_read_format",
// "GL_OES_required_internalformat", "GL_OES_rgb8_rgba8", "GL_OES_single_precision",
// "GL_OES_standard_derivatives", "GL_OES_stencil8", "GL_OES_stencil_wrap",
// "GL_OES_texture_cube_map", "GL_OES_texture_env_crossbar", "GL_OES_texture_float",
// "GL_OES_texture_half_float", "GL_OES_texture_mirrored_repeat", "GL_OES_vertex_array_object",
// "GL_OES_vertex_half_float")).build();
// }
}

View File

@@ -12,6 +12,7 @@ buildscript {
allprojects {
repositories {
maven { url 'https://jitpack.io' }
jcenter()
}
}