mirror of
https://github.com/FliegendeWurst/googleplay-api.git
synced 2024-11-25 14:24:56 +00:00
New search implementation (protbuf + code)
Signed-off-by: Domenico Iezzi <domenico.iezzi.201@gmail.com>
This commit is contained in:
parent
b2e71522ae
commit
2f68991ad1
@ -5,15 +5,7 @@ import time
|
|||||||
SEPARATOR = ";"
|
SEPARATOR = ";"
|
||||||
|
|
||||||
LANG = "en_US"
|
LANG = "en_US"
|
||||||
ANDROID_ID = "320d104c4dc6eaa4"
|
|
||||||
GOOGLE_PUBKEY = "AAAAgMom/1a/v0lblO2Ubrt60J2gcuXSljGFQXgcyZWveWLEwo6prwgi3iJIZdodyhKZQrNWp5nKJ3srRXcUW+F1BD3baEVGcmEgqaLZUNBjm057pKRI16kB0YppeGx5qIQ5QjKzsR8ETQbKLNWgRY0QRNVz34kMJR3P/LgHax/6rmf5AAAAAwEAAQ=="
|
GOOGLE_PUBKEY = "AAAAgMom/1a/v0lblO2Ubrt60J2gcuXSljGFQXgcyZWveWLEwo6prwgi3iJIZdodyhKZQrNWp5nKJ3srRXcUW+F1BD3baEVGcmEgqaLZUNBjm057pKRI16kB0YppeGx5qIQ5QjKzsR8ETQbKLNWgRY0QRNVz34kMJR3P/LgHax/6rmf5AAAAAwEAAQ=="
|
||||||
GOOGLE_LOGIN = ""
|
|
||||||
GOOGLE_PASSWORD = ""
|
|
||||||
AUTH_TOKEN = ""
|
|
||||||
|
|
||||||
# force the user to edit this file
|
|
||||||
if any([each == None for each in [ANDROID_ID, GOOGLE_LOGIN, GOOGLE_PASSWORD]]):
|
|
||||||
raise Exception("config.py not updated")
|
|
||||||
|
|
||||||
# All the following data is taken from play-store-api
|
# All the following data is taken from play-store-api
|
||||||
# https://github.com/yeriomin/play-store-api/blob/master/src/main/resources/device-bacon.properties
|
# https://github.com/yeriomin/play-store-api/blob/master/src/main/resources/device-bacon.properties
|
||||||
|
@ -1126,3 +1126,31 @@ message StatCounters {
|
|||||||
message UsageStatsExtensionProto {
|
message UsageStatsExtensionProto {
|
||||||
optional AndroidDataUsageProto dataUsage = 1;
|
optional AndroidDataUsageProto dataUsage = 1;
|
||||||
}
|
}
|
||||||
|
message SearchClusterResponse {
|
||||||
|
optional bytes info = 1;
|
||||||
|
repeated SearchPreFetch preFetch = 3;
|
||||||
|
}
|
||||||
|
message SearchPreFetch {
|
||||||
|
optional string url = 1;
|
||||||
|
optional SearchResponseWrapper response = 2;
|
||||||
|
optional string etag = 3;
|
||||||
|
optional int64 ttl = 4;
|
||||||
|
optional int64 softTtl = 5;
|
||||||
|
}
|
||||||
|
message SearchResponseWrapper {
|
||||||
|
optional SearchClusterWrapper wrapper = 1;
|
||||||
|
}
|
||||||
|
message SearchClusterWrapper {
|
||||||
|
optional SearchClusterWrapper2 wrapper = 1;
|
||||||
|
}
|
||||||
|
message SearchClusterWrapper2 {
|
||||||
|
repeated SearchCluster cluster = 2;
|
||||||
|
}
|
||||||
|
message SearchCluster {
|
||||||
|
optional string id = 1;
|
||||||
|
optional string type = 2;
|
||||||
|
optional int64 int1 = 3;
|
||||||
|
optional int64 int2 = 4;
|
||||||
|
optional string category = 5;
|
||||||
|
repeated DocV2 doc = 11;
|
||||||
|
}
|
||||||
|
@ -52,15 +52,10 @@ class GooglePlayAPI(object):
|
|||||||
ACCOUNT_TYPE_HOSTED_OR_GOOGLE = "HOSTED_OR_GOOGLE"
|
ACCOUNT_TYPE_HOSTED_OR_GOOGLE = "HOSTED_OR_GOOGLE"
|
||||||
authSubToken = None
|
authSubToken = None
|
||||||
|
|
||||||
def __init__(self, androidId=None, lang=None, debug=False):
|
def __init__(self, debug=False):
|
||||||
# you must use a device-associated androidId value
|
# you must use a device-associated androidId value
|
||||||
self.preFetch = {}
|
self.preFetch = {}
|
||||||
if androidId is None:
|
self.lang = config.LANG
|
||||||
androidId = config.ANDROID_ID
|
|
||||||
if lang is None:
|
|
||||||
lang = config.LANG
|
|
||||||
self.androidId = androidId
|
|
||||||
self.lang = lang
|
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.gsfId = None
|
self.gsfId = None
|
||||||
self.ac2dmToken = None
|
self.ac2dmToken = None
|
||||||
@ -191,7 +186,8 @@ class GooglePlayAPI(object):
|
|||||||
|
|
||||||
securityToken = "{0:x}".format(response.securityToken)
|
securityToken = "{0:x}".format(response.securityToken)
|
||||||
gsfId = "{0:x}".format(response.androidId)
|
gsfId = "{0:x}".format(response.androidId)
|
||||||
print("String representation of androidId: %s" % gsfId)
|
print("GsfId value: %d" % response.androidId)
|
||||||
|
print("Hex string representation of gsfId: %s" % gsfId)
|
||||||
|
|
||||||
# checkin again to upload gfsid
|
# checkin again to upload gfsid
|
||||||
request2 = googleplay_pb2.AndroidCheckinRequest()
|
request2 = googleplay_pb2.AndroidCheckinRequest()
|
||||||
@ -223,20 +219,28 @@ class GooglePlayAPI(object):
|
|||||||
print(res.text)
|
print(res.text)
|
||||||
|
|
||||||
|
|
||||||
def login(self, email=None, password=None, authSubToken=None):
|
def login(self, email=None, password=None, ac2dmToken=None, gsfId=None):
|
||||||
"""Login to your Google Account. You must provide either:
|
"""Login to your Google Account.
|
||||||
- an email and password
|
For first time login you should provide:
|
||||||
- a valid Google authSubToken"""
|
* email
|
||||||
if (authSubToken is not None):
|
* password
|
||||||
self.setAuthSubToken(authSubToken)
|
For the following logins you need to provide all parameters (you
|
||||||
|
should save gsfId and ac2dmToken somewhere"""
|
||||||
|
encryptedPass = self.encrypt_password(email, password).decode('utf-8')
|
||||||
|
if (all( [each != None for each in [email, password, ac2dmToken, gsfId]] )):
|
||||||
|
# this means that we already setup our account, we just need to get
|
||||||
|
# a token
|
||||||
|
self.setAc2dmToken(ac2dmToken)
|
||||||
|
self.gsfId = gsfId
|
||||||
|
self.getAuthSubToken(email, encryptedPass)
|
||||||
# check if token is valid with a simple search
|
# check if token is valid with a simple search
|
||||||
self.search('firefox', 1, None)
|
self.search('firefox', 1, None)
|
||||||
else:
|
else:
|
||||||
|
# First time setup, where we obtain an ac2dm token and
|
||||||
|
# upload device information
|
||||||
if (email is None or password is None):
|
if (email is None or password is None):
|
||||||
raise Exception("You should provide at least " +
|
raise Exception("You should provide both email and pass")
|
||||||
"authSubToken or (email and password)")
|
|
||||||
|
|
||||||
encryptedPass = self.encrypt_password(email, password).decode('utf-8')
|
|
||||||
# AC2DM token
|
# AC2DM token
|
||||||
params = {
|
params = {
|
||||||
"Email": email,
|
"Email": email,
|
||||||
@ -254,7 +258,6 @@ class GooglePlayAPI(object):
|
|||||||
}
|
}
|
||||||
response = requests.post(self.URL_LOGIN, data=params, verify=ssl_verify)
|
response = requests.post(self.URL_LOGIN, data=params, verify=ssl_verify)
|
||||||
data = response.text.split()
|
data = response.text.split()
|
||||||
print(response.text)
|
|
||||||
params = {}
|
params = {}
|
||||||
for d in data:
|
for d in data:
|
||||||
if "=" not in d:
|
if "=" not in d:
|
||||||
@ -288,7 +291,6 @@ class GooglePlayAPI(object):
|
|||||||
}
|
}
|
||||||
response = requests.post(self.URL_LOGIN, data=params, verify=ssl_verify)
|
response = requests.post(self.URL_LOGIN, data=params, verify=ssl_verify)
|
||||||
data = response.text.split()
|
data = response.text.split()
|
||||||
print(response.text)
|
|
||||||
params = {}
|
params = {}
|
||||||
for d in data:
|
for d in data:
|
||||||
if "=" not in d:
|
if "=" not in d:
|
||||||
@ -320,33 +322,26 @@ class GooglePlayAPI(object):
|
|||||||
response = requests.get(url, headers=headers,
|
response = requests.get(url, headers=headers,
|
||||||
verify=ssl_verify)
|
verify=ssl_verify)
|
||||||
data = response.content
|
data = response.content
|
||||||
print(data)
|
|
||||||
|
|
||||||
message = googleplay_pb2.ResponseWrapper.FromString(data)
|
message = googleplay_pb2.ResponseWrapper.FromString(data)
|
||||||
self._try_register_preFetch(message)
|
self._try_register_preFetch(message)
|
||||||
|
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def search(self, query, nb_results, offset=None):
|
def search(self, query, nb_result, offset=None):
|
||||||
"""Search for apps."""
|
|
||||||
path = "search?c=3&q=%s" % requests.utils.quote(query)
|
path = "search?c=3&q=%s" % requests.utils.quote(query)
|
||||||
|
|
||||||
if (offset is not None):
|
if (offset is not None):
|
||||||
path += "&o=%d" % int(offset)
|
path += "&o=%d" % int(offset)
|
||||||
|
|
||||||
message = self.executeRequestApi2(path)
|
headers = self.getDefaultHeaders()
|
||||||
response = message.payload.searchResponse
|
|
||||||
if len(response.doc) == 0:
|
url = "https://android.clients.google.com/fdfe/%s" % path
|
||||||
raise DecodeError
|
response = requests.get(url, headers=headers,
|
||||||
remaining = int(nb_results) - len(response.doc[0].child)
|
verify=ssl_verify)
|
||||||
messagenext = message
|
data = response.content
|
||||||
allmessages = message
|
cluster = googleplay_pb2.SearchClusterResponse.FromString(data)
|
||||||
while remaining > 0:
|
return cluster.preFetch[0].response.wrapper.wrapper.cluster[0]
|
||||||
pathnext = response.doc[0].containerMetadata.nextPageUrl
|
|
||||||
messagenext = self.executeRequestApi2(pathnext)
|
|
||||||
remaining -= len(response.doc[0].child)
|
|
||||||
allmessages.MergeFrom(messagenext)
|
|
||||||
return allmessages.payload.searchResponse
|
|
||||||
|
|
||||||
def details(self, packageName):
|
def details(self, packageName):
|
||||||
"""Get app details from a package name.
|
"""Get app details from a package name.
|
||||||
|
File diff suppressed because one or more lines are too long
17
test.py
17
test.py
@ -1,5 +1,16 @@
|
|||||||
from googleplay import GooglePlayAPI
|
from googleplay import GooglePlayAPI
|
||||||
|
|
||||||
server = GooglePlayAPI(None, None, True)
|
EMAIL = ""
|
||||||
server.login("", "", None)
|
PASSWD = ""
|
||||||
server.search("firefox", 1, None)
|
AC2DM = ""
|
||||||
|
GSFID = 0
|
||||||
|
|
||||||
|
server = GooglePlayAPI(True)
|
||||||
|
if AC2DM == "":
|
||||||
|
server.login(EMAIL, PASSWD, None, None)
|
||||||
|
print(server.search('firefox', 1, None))
|
||||||
|
else:
|
||||||
|
server.gsfId = GSFID
|
||||||
|
server.login(EMAIL, PASSWD, AC2DM, GSFID)
|
||||||
|
print(server.search('firefox', 1, None).doc[0].child[0])
|
||||||
|
print(server.download('org.telegram.messenger', 10853))
|
||||||
|
Loading…
Reference in New Issue
Block a user