mirror of
https://github.com/FliegendeWurst/googleplay-api.git
synced 2024-11-22 12:54:58 +00:00
Slighty more readable code + cleanup
This commit is contained in:
parent
9a9ba1434a
commit
525c24fae0
@ -8,7 +8,6 @@ from Crypto.Cipher import PKCS1_OAEP
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
from base64 import b64decode, urlsafe_b64encode
|
from base64 import b64decode, urlsafe_b64encode
|
||||||
from itertools import chain
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from . import googleplay_pb2, config, utils
|
from . import googleplay_pb2, config, utils
|
||||||
@ -17,12 +16,24 @@ ssl_verify = True
|
|||||||
|
|
||||||
BASE = "https://android.clients.google.com/"
|
BASE = "https://android.clients.google.com/"
|
||||||
FDFE = BASE + "fdfe/"
|
FDFE = BASE + "fdfe/"
|
||||||
UPLOADURL = FDFE + "uploadDeviceConfig"
|
CHECKIN_URL = BASE + "checkin"
|
||||||
SEARCHURL = FDFE + "search"
|
AUTH_URL = BASE + "auth"
|
||||||
CHECKINURL = BASE + "checkin"
|
|
||||||
AUTHURL = BASE + "auth"
|
UPLOAD_URL = FDFE + "uploadDeviceConfig"
|
||||||
LOGURL = FDFE + "log"
|
SEARCH_URL = FDFE + "search"
|
||||||
TOCURL = FDFE + "toc"
|
DETAILS_URL = FDFE + "details"
|
||||||
|
HOME_URL = FDFE + "homeV2"
|
||||||
|
BROWSE_URL = FDFE + "browse"
|
||||||
|
DELIVERY_URL = FDFE + "delivery"
|
||||||
|
PURCHASE_URL = FDFE + "purchase"
|
||||||
|
SEARCH_SUGGEST_URL = FDFE + "searchSuggest"
|
||||||
|
BULK_URL = FDFE + "bulkDetails"
|
||||||
|
LOG_URL = FDFE + "log"
|
||||||
|
TOC_URL = FDFE + "toc"
|
||||||
|
LIST_URL = FDFE + "list"
|
||||||
|
REVIEWS_URL = FDFE + "rev"
|
||||||
|
|
||||||
|
CONTENT_TYPE_URLENC = "application/x-www-form-urlencoded; charset=UTF-8"
|
||||||
|
|
||||||
|
|
||||||
class LoginError(Exception):
|
class LoginError(Exception):
|
||||||
@ -70,8 +81,8 @@ class GooglePlayAPI(object):
|
|||||||
binaryKey = b64decode(config.GOOGLE_PUBKEY)
|
binaryKey = b64decode(config.GOOGLE_PUBKEY)
|
||||||
i = utils.readInt(binaryKey, 0)
|
i = utils.readInt(binaryKey, 0)
|
||||||
modulus = utils.toBigInt(binaryKey[4:][0:i])
|
modulus = utils.toBigInt(binaryKey[4:][0:i])
|
||||||
j = utils.readInt(binaryKey, i+4)
|
j = utils.readInt(binaryKey, i + 4)
|
||||||
exponent = utils.toBigInt(binaryKey[i+8:][0:j])
|
exponent = utils.toBigInt(binaryKey[i + 8:][0:j])
|
||||||
|
|
||||||
seq = asn1.DerSequence()
|
seq = asn1.DerSequence()
|
||||||
seq.append(modulus)
|
seq.append(modulus)
|
||||||
@ -114,7 +125,7 @@ class GooglePlayAPI(object):
|
|||||||
request = self.deviceBuilder.getAndroidCheckinRequest()
|
request = self.deviceBuilder.getAndroidCheckinRequest()
|
||||||
|
|
||||||
stringRequest = request.SerializeToString()
|
stringRequest = request.SerializeToString()
|
||||||
res = requests.post(CHECKINURL, data=stringRequest,
|
res = requests.post(CHECKIN_URL, data=stringRequest,
|
||||||
headers=headers, verify=ssl_verify,
|
headers=headers, verify=ssl_verify,
|
||||||
proxies=self.proxies_config)
|
proxies=self.proxies_config)
|
||||||
response = googleplay_pb2.AndroidCheckinResponse()
|
response = googleplay_pb2.AndroidCheckinResponse()
|
||||||
@ -128,7 +139,7 @@ class GooglePlayAPI(object):
|
|||||||
request2.accountCookie.append("[" + email + "]")
|
request2.accountCookie.append("[" + email + "]")
|
||||||
request2.accountCookie.append(ac2dmToken)
|
request2.accountCookie.append(ac2dmToken)
|
||||||
stringRequest = request2.SerializeToString()
|
stringRequest = request2.SerializeToString()
|
||||||
requests.post(CHECKINURL, data=stringRequest,
|
requests.post(CHECKIN_URL, data=stringRequest,
|
||||||
headers=headers, verify=ssl_verify,
|
headers=headers, verify=ssl_verify,
|
||||||
proxies=self.proxies_config)
|
proxies=self.proxies_config)
|
||||||
|
|
||||||
@ -146,7 +157,7 @@ class GooglePlayAPI(object):
|
|||||||
headers["X-DFE-SmallestScreenWidthDp"] = "320"
|
headers["X-DFE-SmallestScreenWidthDp"] = "320"
|
||||||
headers["X-DFE-Filter-Level"] = "3"
|
headers["X-DFE-Filter-Level"] = "3"
|
||||||
stringRequest = upload.SerializeToString()
|
stringRequest = upload.SerializeToString()
|
||||||
response = requests.post(UPLOADURL, data=stringRequest,
|
response = requests.post(UPLOAD_URL, data=stringRequest,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
verify=ssl_verify,
|
verify=ssl_verify,
|
||||||
timeout=60,
|
timeout=60,
|
||||||
@ -179,7 +190,7 @@ class GooglePlayAPI(object):
|
|||||||
params['callerPkg'] = 'com.google.android.gms'
|
params['callerPkg'] = 'com.google.android.gms'
|
||||||
headers = self.deviceBuilder.getAuthHeaders(self.gsfId)
|
headers = self.deviceBuilder.getAuthHeaders(self.gsfId)
|
||||||
headers['app'] = 'com.google.android.gsm'
|
headers['app'] = 'com.google.android.gsm'
|
||||||
response = requests.post(AUTHURL, data=params, verify=ssl_verify,
|
response = requests.post(AUTH_URL, data=params, verify=ssl_verify,
|
||||||
proxies=self.proxies_config)
|
proxies=self.proxies_config)
|
||||||
data = response.text.split()
|
data = response.text.split()
|
||||||
params = {}
|
params = {}
|
||||||
@ -217,7 +228,7 @@ class GooglePlayAPI(object):
|
|||||||
requestParams['app'] = 'com.android.vending'
|
requestParams['app'] = 'com.android.vending'
|
||||||
headers = self.deviceBuilder.getAuthHeaders(self.gsfId)
|
headers = self.deviceBuilder.getAuthHeaders(self.gsfId)
|
||||||
headers['app'] = 'com.android.vending'
|
headers['app'] = 'com.android.vending'
|
||||||
response = requests.post(AUTHURL,
|
response = requests.post(AUTH_URL,
|
||||||
data=requestParams,
|
data=requestParams,
|
||||||
verify=ssl_verify,
|
verify=ssl_verify,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
@ -250,7 +261,7 @@ class GooglePlayAPI(object):
|
|||||||
params.pop('EncryptedPasswd')
|
params.pop('EncryptedPasswd')
|
||||||
headers = self.deviceBuilder.getAuthHeaders(self.gsfId)
|
headers = self.deviceBuilder.getAuthHeaders(self.gsfId)
|
||||||
headers['app'] = 'com.android.vending'
|
headers['app'] = 'com.android.vending'
|
||||||
response = requests.post(AUTHURL,
|
response = requests.post(AUTH_URL,
|
||||||
data=params,
|
data=params,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
verify=ssl_verify,
|
verify=ssl_verify,
|
||||||
@ -269,17 +280,14 @@ class GooglePlayAPI(object):
|
|||||||
else:
|
else:
|
||||||
raise LoginError("Auth token not found.")
|
raise LoginError("Auth token not found.")
|
||||||
|
|
||||||
def executeRequestApi2(self, path, post_data=None, content_type=None, params=None):
|
def executeRequestApi2(self, path, post_data=None, content_type=CONTENT_TYPE_URLENC, params=None):
|
||||||
if self.authSubToken is None:
|
if self.authSubToken is None:
|
||||||
raise Exception("You need to login before executing any request")
|
raise Exception("You need to login before executing any request")
|
||||||
headers = self.getDefaultHeaders()
|
headers = self.getDefaultHeaders()
|
||||||
|
|
||||||
if content_type is None:
|
|
||||||
content_type = "application/x-www-form-urlencoded; charset=UTF-8"
|
|
||||||
headers["Content-Type"] = content_type
|
headers["Content-Type"] = content_type
|
||||||
url = FDFE + path
|
|
||||||
if post_data is not None:
|
if post_data is not None:
|
||||||
response = requests.post(url,
|
response = requests.post(path,
|
||||||
data=str(post_data),
|
data=str(post_data),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
params=params,
|
params=params,
|
||||||
@ -287,7 +295,7 @@ class GooglePlayAPI(object):
|
|||||||
timeout=60,
|
timeout=60,
|
||||||
proxies=self.proxies_config)
|
proxies=self.proxies_config)
|
||||||
else:
|
else:
|
||||||
response = requests.get(url,
|
response = requests.get(path,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
params=params,
|
params=params,
|
||||||
verify=ssl_verify,
|
verify=ssl_verify,
|
||||||
@ -305,7 +313,7 @@ class GooglePlayAPI(object):
|
|||||||
"q": requests.utils.quote(query),
|
"q": requests.utils.quote(query),
|
||||||
"ssis": "120",
|
"ssis": "120",
|
||||||
"sst": "2"}
|
"sst": "2"}
|
||||||
data = self.executeRequestApi2("searchSuggest", params=params)
|
data = self.executeRequestApi2(SEARCH_SUGGEST_URL, params=params)
|
||||||
response = data.payload.searchSuggestResponse
|
response = data.payload.searchSuggestResponse
|
||||||
return [{"type": e.type,
|
return [{"type": e.type,
|
||||||
"suggestedQuery": e.suggestedQuery,
|
"suggestedQuery": e.suggestedQuery,
|
||||||
@ -324,9 +332,9 @@ class GooglePlayAPI(object):
|
|||||||
remaining = nb_result
|
remaining = nb_result
|
||||||
output = []
|
output = []
|
||||||
|
|
||||||
nextPath = "search?c=3&q=%s" % requests.utils.quote(query)
|
nextPath = SEARCH_URL + "?c=3&q={}".format(requests.utils.quote(query))
|
||||||
if (offset is not None):
|
if (offset is not None):
|
||||||
nextPath += "&o=%d" % int(offset)
|
nextPath += "&o={}".format(offset)
|
||||||
while remaining > 0 and nextPath is not None:
|
while remaining > 0 and nextPath is not None:
|
||||||
currentPath = nextPath
|
currentPath = nextPath
|
||||||
data = self.executeRequestApi2(currentPath)
|
data = self.executeRequestApi2(currentPath)
|
||||||
@ -337,12 +345,12 @@ class GooglePlayAPI(object):
|
|||||||
if utils.hasSearchResponse(response.payload):
|
if utils.hasSearchResponse(response.payload):
|
||||||
# we still need to fetch the first page, so go to
|
# we still need to fetch the first page, so go to
|
||||||
# next loop iteration without decrementing counter
|
# next loop iteration without decrementing counter
|
||||||
nextPath = response.payload.searchResponse.nextPageUrl
|
nextPath = FDFE + response.payload.searchResponse.nextPageUrl
|
||||||
continue
|
continue
|
||||||
if utils.hasListResponse(response.payload):
|
if utils.hasListResponse(response.payload):
|
||||||
cluster = response.payload.listResponse.cluster
|
cluster = response.payload.listResponse.cluster
|
||||||
if len(cluster) == 0:
|
if len(cluster) == 0:
|
||||||
# strange behaviour, probably due to expired token
|
# unexpected behaviour, probably due to expired token
|
||||||
raise LoginError('Unexpected behaviour, probably expired '
|
raise LoginError('Unexpected behaviour, probably expired '
|
||||||
'token')
|
'token')
|
||||||
cluster = cluster[0]
|
cluster = cluster[0]
|
||||||
@ -352,7 +360,9 @@ class GooglePlayAPI(object):
|
|||||||
nextPath = cluster.doc[0].containerMetadata.nextPageUrl
|
nextPath = cluster.doc[0].containerMetadata.nextPageUrl
|
||||||
else:
|
else:
|
||||||
nextPath = None
|
nextPath = None
|
||||||
apps = list(chain.from_iterable([doc.child for doc in cluster.doc]))
|
apps = []
|
||||||
|
for doc in cluster.doc:
|
||||||
|
apps.extend(doc.child)
|
||||||
output += list(map(utils.fromDocToDictionary, apps))
|
output += list(map(utils.fromDocToDictionary, apps))
|
||||||
remaining -= len(apps)
|
remaining -= len(apps)
|
||||||
|
|
||||||
@ -365,7 +375,7 @@ class GooglePlayAPI(object):
|
|||||||
"""Get app details from a package name.
|
"""Get app details from a package name.
|
||||||
|
|
||||||
packageName is the app unique ID (usually starting with 'com.')."""
|
packageName is the app unique ID (usually starting with 'com.')."""
|
||||||
path = "details?doc=%s" % requests.utils.quote(packageName)
|
path = DETAILS_URL + "?doc={}".format(requests.utils.quote(packageName))
|
||||||
data = self.executeRequestApi2(path)
|
data = self.executeRequestApi2(path)
|
||||||
return utils.fromDocToDictionary(data.payload.detailsResponse.docV2)
|
return utils.fromDocToDictionary(data.payload.detailsResponse.docV2)
|
||||||
|
|
||||||
@ -383,12 +393,11 @@ class GooglePlayAPI(object):
|
|||||||
a list of dictionaries containing docv2 data, or None
|
a list of dictionaries containing docv2 data, or None
|
||||||
if the app doesn't exist"""
|
if the app doesn't exist"""
|
||||||
|
|
||||||
path = "bulkDetails"
|
|
||||||
params = {'au': '1'}
|
params = {'au': '1'}
|
||||||
req = googleplay_pb2.BulkDetailsRequest()
|
req = googleplay_pb2.BulkDetailsRequest()
|
||||||
req.docid.extend(packageNames)
|
req.docid.extend(packageNames)
|
||||||
data = req.SerializeToString()
|
data = req.SerializeToString()
|
||||||
message = self.executeRequestApi2(path,
|
message = self.executeRequestApi2(BULK_URL,
|
||||||
post_data=data.decode("utf-8"),
|
post_data=data.decode("utf-8"),
|
||||||
content_type="application/x-protobuf",
|
content_type="application/x-protobuf",
|
||||||
params=params)
|
params=params)
|
||||||
@ -398,7 +407,7 @@ class GooglePlayAPI(object):
|
|||||||
for entry in response.entry]
|
for entry in response.entry]
|
||||||
|
|
||||||
def getHomeApps(self):
|
def getHomeApps(self):
|
||||||
path = "homeV2?c=3&nocache_isui=true"
|
path = HOME_URL + "?c=3&nocache_isui=true"
|
||||||
data = self.executeRequestApi2(path)
|
data = self.executeRequestApi2(path)
|
||||||
output = []
|
output = []
|
||||||
cluster = data.preFetch[0].response.payload.listResponse.cluster[0]
|
cluster = data.preFetch[0].response.payload.listResponse.cluster[0]
|
||||||
@ -412,11 +421,11 @@ class GooglePlayAPI(object):
|
|||||||
"""Browse categories. If neither cat nor subcat are specified,
|
"""Browse categories. If neither cat nor subcat are specified,
|
||||||
return a list of categories, otherwise it return a list of apps
|
return a list of categories, otherwise it return a list of apps
|
||||||
using cat (category ID) and subCat (subcategory ID) as filters."""
|
using cat (category ID) and subCat (subcategory ID) as filters."""
|
||||||
path = "browse?c=3"
|
path = BROWSE_URL + "?c=3"
|
||||||
if cat is not None:
|
if cat is not None:
|
||||||
path += "&cat=%s" % requests.utils.quote(cat)
|
path += "&cat={}".format(requests.utils.quote(cat))
|
||||||
if subCat is not None:
|
if subCat is not None:
|
||||||
path += "&ctr=%s" % requests.utils.quote(subCat)
|
path += "&ctr={}".format(requests.utils.quote(subCat))
|
||||||
data = self.executeRequestApi2(path)
|
data = self.executeRequestApi2(path)
|
||||||
|
|
||||||
if cat is None and subCat is None:
|
if cat is None and subCat is None:
|
||||||
@ -430,8 +439,8 @@ class GooglePlayAPI(object):
|
|||||||
clusters = []
|
clusters = []
|
||||||
|
|
||||||
if utils.hasPrefetch(data):
|
if utils.hasPrefetch(data):
|
||||||
clusters = chain.from_iterable([pf.response.payload.listResponse.cluster
|
for pf in data.preFetch:
|
||||||
for pf in data.preFetch])
|
clusters.extend(pf.response.payload.listResponse.cluster)
|
||||||
|
|
||||||
# result contains apps of a specific category
|
# result contains apps of a specific category
|
||||||
# organized by sections
|
# organized by sections
|
||||||
@ -452,25 +461,30 @@ class GooglePlayAPI(object):
|
|||||||
If ctr (subcategory ID) is None, returns a list of valid subcategories.
|
If ctr (subcategory ID) is None, returns a list of valid subcategories.
|
||||||
|
|
||||||
If ctr is provided, list apps within this subcategory."""
|
If ctr is provided, list apps within this subcategory."""
|
||||||
path = "list?c=3&cat=%s" % requests.utils.quote(cat)
|
path = LIST_URL + "?c=3&cat={}".format(requests.utils.quote(cat))
|
||||||
if ctr is not None:
|
if ctr is not None:
|
||||||
path += "&ctr=%s" % requests.utils.quote(ctr)
|
path += "&ctr={}".format(requests.utils.quote(ctr))
|
||||||
if nb_results is not None:
|
if nb_results is not None:
|
||||||
path += "&n=%s" % requests.utils.quote(nb_results)
|
path += "&n={}".format(requests.utils.quote(nb_results))
|
||||||
if offset is not None:
|
if offset is not None:
|
||||||
path += "&o=%s" % requests.utils.quote(offset)
|
path += "&o={}".format(requests.utils.quote(offset))
|
||||||
data = self.executeRequestApi2(path)
|
data = self.executeRequestApi2(path)
|
||||||
|
clusters = []
|
||||||
|
docs = []
|
||||||
if ctr is None:
|
if ctr is None:
|
||||||
# list subcategories
|
# list subcategories
|
||||||
clusters = chain.from_iterable([pf.response.payload.listResponse.cluster
|
for pf in data.preFetch:
|
||||||
for pf in data.preFetch])
|
clusters.extend(pf.response.payload.listResponse.cluster)
|
||||||
docs = chain.from_iterable([c.doc for c in clusters])
|
for c in clusters:
|
||||||
|
docs.extend(c.doc)
|
||||||
return [d.docid for d in docs]
|
return [d.docid for d in docs]
|
||||||
else:
|
else:
|
||||||
# list apps for specific subcat
|
childs = []
|
||||||
docs = chain.from_iterable([c.doc for c in
|
clusters.extend(data.payload.listResponse.cluster)
|
||||||
data.payload.listResponse.cluster])
|
for c in clusters:
|
||||||
childs = chain.from_iterable([d.child for d in docs])
|
docs.extend(c.doc)
|
||||||
|
for d in docs:
|
||||||
|
childs.extend(d.child)
|
||||||
return [utils.fromDocToDictionary(c)
|
return [utils.fromDocToDictionary(c)
|
||||||
for c in childs]
|
for c in childs]
|
||||||
|
|
||||||
@ -489,12 +503,12 @@ class GooglePlayAPI(object):
|
|||||||
dict object containing all the protobuf data returned from
|
dict object containing all the protobuf data returned from
|
||||||
the api
|
the api
|
||||||
"""
|
"""
|
||||||
path = "rev?doc=%s&sort=%d" % (requests.utils.quote(packageName), sort)
|
path = REVIEWS_URL + "?doc={}&sort={}".format(requests.utils.quote(packageName), sort)
|
||||||
if (nb_results is not None):
|
if nb_results is not None:
|
||||||
path += "&n=%d" % int(nb_results)
|
path += "&n={}".format(nb_results)
|
||||||
if (offset is not None):
|
if offset is not None:
|
||||||
path += "&o=%d" % int(offset)
|
path += "&o={}".format(offset)
|
||||||
if(filterByDevice):
|
if filterByDevice:
|
||||||
path += "&dfil=1"
|
path += "&dfil=1"
|
||||||
data = self.executeRequestApi2(path)
|
data = self.executeRequestApi2(path)
|
||||||
output = []
|
output = []
|
||||||
@ -552,15 +566,13 @@ class GooglePlayAPI(object):
|
|||||||
# pick up latest version
|
# pick up latest version
|
||||||
versionCode = self.details(packageName).get('versionCode')
|
versionCode = self.details(packageName).get('versionCode')
|
||||||
|
|
||||||
path = "delivery"
|
|
||||||
params = {'ot': str(offerType),
|
params = {'ot': str(offerType),
|
||||||
'doc': packageName,
|
'doc': packageName,
|
||||||
'vc': str(versionCode)}
|
'vc': str(versionCode)}
|
||||||
headers = self.getDefaultHeaders()
|
headers = self.getDefaultHeaders()
|
||||||
if downloadToken is not None:
|
if downloadToken is not None:
|
||||||
params['dtok'] = downloadToken
|
params['dtok'] = downloadToken
|
||||||
url = "https://android.clients.google.com/fdfe/%s" % path
|
response = requests.get(DELIVERY_URL, headers=headers,
|
||||||
response = requests.get(url, headers=headers,
|
|
||||||
params=params, verify=ssl_verify,
|
params=params, verify=ssl_verify,
|
||||||
timeout=60,
|
timeout=60,
|
||||||
proxies=self.proxies_config)
|
proxies=self.proxies_config)
|
||||||
@ -619,14 +631,12 @@ class GooglePlayAPI(object):
|
|||||||
# pick up latest version
|
# pick up latest version
|
||||||
versionCode = self.details(packageName).get('versionCode')
|
versionCode = self.details(packageName).get('versionCode')
|
||||||
|
|
||||||
path = "purchase"
|
|
||||||
headers = self.getDefaultHeaders()
|
headers = self.getDefaultHeaders()
|
||||||
params = {'ot': str(offerType),
|
params = {'ot': str(offerType),
|
||||||
'doc': packageName,
|
'doc': packageName,
|
||||||
'vc': str(versionCode)}
|
'vc': str(versionCode)}
|
||||||
url = FDFE + path
|
|
||||||
self.log(packageName)
|
self.log(packageName)
|
||||||
response = requests.post(url, headers=headers,
|
response = requests.post(PURCHASE_URL, headers=headers,
|
||||||
params=params, verify=ssl_verify,
|
params=params, verify=ssl_verify,
|
||||||
timeout=60,
|
timeout=60,
|
||||||
proxies=self.proxies_config)
|
proxies=self.proxies_config)
|
||||||
@ -646,7 +656,7 @@ class GooglePlayAPI(object):
|
|||||||
log_request.timestamp = timestamp
|
log_request.timestamp = timestamp
|
||||||
|
|
||||||
string_request = log_request.SerializeToString()
|
string_request = log_request.SerializeToString()
|
||||||
response = requests.post(LOGURL,
|
response = requests.post(LOG_URL,
|
||||||
data=string_request,
|
data=string_request,
|
||||||
headers=self.getDefaultHeaders(),
|
headers=self.getDefaultHeaders(),
|
||||||
verify=ssl_verify,
|
verify=ssl_verify,
|
||||||
|
Loading…
Reference in New Issue
Block a user