mirror of
https://github.com/FliegendeWurst/googleplay-api.git
synced 2024-11-22 12:54:58 +00:00
Some improvements to code
- Remove unstable userProfile API - Changed names for some variables - Disabled print calls. Loggin is now entirely up to the user. - Implemented device config token, retrieved after the `uploadDevice` procedure - Updated headers from play-store-api project - Fixed initialization for obb_download_test.py
This commit is contained in:
parent
1185a13a40
commit
6f5565bdef
@ -15,6 +15,15 @@ from . import googleplay_pb2, config, utils
|
|||||||
|
|
||||||
ssl_verify = True
|
ssl_verify = True
|
||||||
|
|
||||||
|
BASE = "https://android.clients.google.com/"
|
||||||
|
FDFE = BASE + "fdfe/"
|
||||||
|
UPLOADURL = FDFE + "uploadDeviceConfig"
|
||||||
|
SEARCHURL = FDFE + "search"
|
||||||
|
CHECKINURL = BASE + "checkin"
|
||||||
|
AUTHURL = BASE + "auth"
|
||||||
|
LOGURL = FDFE + "log"
|
||||||
|
TOCURL = FDFE + "toc"
|
||||||
|
|
||||||
|
|
||||||
class LoginError(Exception):
|
class LoginError(Exception):
|
||||||
def __init__(self, value):
|
def __init__(self, value):
|
||||||
@ -38,21 +47,11 @@ class GooglePlayAPI(object):
|
|||||||
Usual APIs methods are login(), search(), details(), bulkDetails(),
|
Usual APIs methods are login(), search(), details(), bulkDetails(),
|
||||||
download(), browse(), reviews() and list()."""
|
download(), browse(), reviews() and list()."""
|
||||||
|
|
||||||
BASE = "https://android.clients.google.com/"
|
def __init__(self, locale, timezone, device_codename='bacon',
|
||||||
FDFE = BASE + "fdfe/"
|
|
||||||
UPLOADURL = FDFE + "uploadDeviceConfig"
|
|
||||||
SEARCHURL = FDFE + "search"
|
|
||||||
CHECKINURL = BASE + "checkin"
|
|
||||||
AUTHURL = BASE + "auth"
|
|
||||||
LOGURL = FDFE + "log"
|
|
||||||
|
|
||||||
proxies_config = None
|
|
||||||
|
|
||||||
def __init__(self, locale, timezone, debug=False, device_codename='bacon',
|
|
||||||
proxies_config=None):
|
proxies_config=None):
|
||||||
self.authSubToken = None
|
self.authSubToken = None
|
||||||
self.gsfId = None
|
self.gsfId = None
|
||||||
self.debug = debug
|
self.device_config_token = None
|
||||||
self.proxies_config = proxies_config
|
self.proxies_config = proxies_config
|
||||||
self.deviceBuilder = config.DeviceBuilder(device_codename)
|
self.deviceBuilder = config.DeviceBuilder(device_codename)
|
||||||
self.set_locale(locale)
|
self.set_locale(locale)
|
||||||
@ -88,21 +87,23 @@ class GooglePlayAPI(object):
|
|||||||
def setAuthSubToken(self, authSubToken):
|
def setAuthSubToken(self, authSubToken):
|
||||||
self.authSubToken = authSubToken
|
self.authSubToken = authSubToken
|
||||||
|
|
||||||
# put your auth token in config.py to avoid multiple login requests
|
|
||||||
if self.debug:
|
|
||||||
print("authSubToken: " + authSubToken)
|
|
||||||
|
|
||||||
def getDefaultHeaders(self):
|
def getDefaultHeaders(self):
|
||||||
"""Return the default set of request headers, which
|
"""Return the default set of request headers, which
|
||||||
can later be expanded, based on the request type"""
|
can later be expanded, based on the request type"""
|
||||||
|
|
||||||
headers = {"Accept-Language": self.deviceBuilder.locale.replace('_', '-'),
|
headers = {"Accept-Language": self.deviceBuilder.locale.replace('_', '-'),
|
||||||
"X-DFE-Encoded-Targets": config.DFE_TARGETS,
|
"X-DFE-Encoded-Targets": config.DFE_TARGETS,
|
||||||
"User-Agent": self.deviceBuilder.getUserAgent()}
|
"User-Agent": self.deviceBuilder.getUserAgent(),
|
||||||
|
"X-DFE-Client-Id": "am-android-google",
|
||||||
|
"X-DFE-Network-Type": "4",
|
||||||
|
"X-DFE-Content-Filters": "",
|
||||||
|
"X-DFE-Request-Params": "timeoutMs=4000"}
|
||||||
if self.gsfId is not None:
|
if self.gsfId is not None:
|
||||||
headers["X-DFE-Device-Id"] = "{0:x}".format(self.gsfId)
|
headers["X-DFE-Device-Id"] = "{0:x}".format(self.gsfId)
|
||||||
if self.authSubToken is not None:
|
if self.authSubToken is not None:
|
||||||
headers["Authorization"] = "GoogleLogin auth=%s" % self.authSubToken
|
headers["Authorization"] = "GoogleLogin auth=%s" % self.authSubToken
|
||||||
|
if self.device_config_token is not None:
|
||||||
|
headers["X-DFE-Device-Config-Token"] = self.device_config_token
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
def checkin(self, email, ac2dmToken):
|
def checkin(self, email, ac2dmToken):
|
||||||
@ -112,7 +113,7 @@ class GooglePlayAPI(object):
|
|||||||
request = self.deviceBuilder.getAndroidCheckinRequest()
|
request = self.deviceBuilder.getAndroidCheckinRequest()
|
||||||
|
|
||||||
stringRequest = request.SerializeToString()
|
stringRequest = request.SerializeToString()
|
||||||
res = requests.post(self.CHECKINURL, data=stringRequest,
|
res = requests.post(CHECKINURL, 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()
|
||||||
@ -126,7 +127,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(self.CHECKINURL, data=stringRequest,
|
requests.post(CHECKINURL, data=stringRequest,
|
||||||
headers=headers, verify=ssl_verify,
|
headers=headers, verify=ssl_verify,
|
||||||
proxies=self.proxies_config)
|
proxies=self.proxies_config)
|
||||||
|
|
||||||
@ -141,14 +142,21 @@ class GooglePlayAPI(object):
|
|||||||
headers = self.getDefaultHeaders()
|
headers = self.getDefaultHeaders()
|
||||||
headers["X-DFE-Enabled-Experiments"] = "cl:billing.select_add_instrument_by_default"
|
headers["X-DFE-Enabled-Experiments"] = "cl:billing.select_add_instrument_by_default"
|
||||||
headers["X-DFE-Unsupported-Experiments"] = "nocache:billing.use_charging_poller,market_emails,buyer_currency,prod_baseline,checkin.set_asset_paid_app_field,shekel_test,content_ratings,buyer_currency_in_app,nocache:encrypted_apk,recent_changes"
|
headers["X-DFE-Unsupported-Experiments"] = "nocache:billing.use_charging_poller,market_emails,buyer_currency,prod_baseline,checkin.set_asset_paid_app_field,shekel_test,content_ratings,buyer_currency_in_app,nocache:encrypted_apk,recent_changes"
|
||||||
headers["X-DFE-Client-Id"] = "am-android-google"
|
|
||||||
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()
|
||||||
res = requests.post(self.UPLOADURL, data=stringRequest,
|
response = requests.post(UPLOADURL, data=stringRequest,
|
||||||
headers=headers, verify=ssl_verify,
|
headers=headers,
|
||||||
|
verify=ssl_verify,
|
||||||
|
timeout=60,
|
||||||
proxies=self.proxies_config)
|
proxies=self.proxies_config)
|
||||||
googleplay_pb2.ResponseWrapper.FromString(res.content)
|
response = googleplay_pb2.ResponseWrapper.FromString(response.content)
|
||||||
|
try:
|
||||||
|
if response.payload.HasField('uploadDeviceConfigResponse'):
|
||||||
|
self.device_config_token = response.payload.uploadDeviceConfigResponse
|
||||||
|
self.device_config_token = self.device_config_token.uploadDeviceConfigToken
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
def login(self, email=None, password=None, gsfId=None, authSubToken=None):
|
def login(self, email=None, password=None, gsfId=None, authSubToken=None):
|
||||||
"""Login to your Google Account.
|
"""Login to your Google Account.
|
||||||
@ -170,7 +178,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(self.AUTHURL, data=params, verify=ssl_verify,
|
response = requests.post(AUTHURL, data=params, verify=ssl_verify,
|
||||||
proxies=self.proxies_config)
|
proxies=self.proxies_config)
|
||||||
data = response.text.split()
|
data = response.text.split()
|
||||||
params = {}
|
params = {}
|
||||||
@ -191,11 +199,7 @@ class GooglePlayAPI(object):
|
|||||||
raise LoginError("Auth token not found.")
|
raise LoginError("Auth token not found.")
|
||||||
|
|
||||||
self.gsfId = self.checkin(email, ac2dmToken)
|
self.gsfId = self.checkin(email, ac2dmToken)
|
||||||
if self.debug:
|
|
||||||
print("Google Services Framework Id: %s" % "{0:x}".format(self.gsfId))
|
|
||||||
self.getAuthSubToken(email, encryptedPass)
|
self.getAuthSubToken(email, encryptedPass)
|
||||||
if self.debug:
|
|
||||||
print("Uploading device configuration")
|
|
||||||
self.uploadDeviceConfig()
|
self.uploadDeviceConfig()
|
||||||
elif gsfId is not None and authSubToken is not None:
|
elif gsfId is not None and authSubToken is not None:
|
||||||
# no need to initialize API
|
# no need to initialize API
|
||||||
@ -212,7 +216,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(self.AUTHURL,
|
response = requests.post(AUTHURL,
|
||||||
data=requestParams,
|
data=requestParams,
|
||||||
verify=ssl_verify,
|
verify=ssl_verify,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
@ -225,11 +229,9 @@ class GooglePlayAPI(object):
|
|||||||
k, v = d.split("=", 1)
|
k, v = d.split("=", 1)
|
||||||
params[k.strip().lower()] = v.strip()
|
params[k.strip().lower()] = v.strip()
|
||||||
if "token" in params:
|
if "token" in params:
|
||||||
firstToken = params["token"]
|
master_token = params["token"]
|
||||||
if self.debug:
|
second_round_token = self.getSecondRoundToken(master_token, requestParams)
|
||||||
print('Master token: %s' % firstToken)
|
self.setAuthSubToken(second_round_token)
|
||||||
secondToken = self.getSecondRoundToken(firstToken, requestParams)
|
|
||||||
self.setAuthSubToken(secondToken)
|
|
||||||
elif "error" in params:
|
elif "error" in params:
|
||||||
raise LoginError("server says: " + params["error"])
|
raise LoginError("server says: " + params["error"])
|
||||||
else:
|
else:
|
||||||
@ -247,7 +249,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(self.AUTHURL,
|
response = requests.post(AUTHURL,
|
||||||
data=params,
|
data=params,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
verify=ssl_verify,
|
verify=ssl_verify,
|
||||||
@ -266,19 +268,18 @@ class GooglePlayAPI(object):
|
|||||||
else:
|
else:
|
||||||
raise LoginError("Auth token not found.")
|
raise LoginError("Auth token not found.")
|
||||||
|
|
||||||
def executeRequestApi2(self, path, datapost=None,
|
def executeRequestApi2(self, path, post_data=None, content_type=None):
|
||||||
post_content_type="application/x-www-form-urlencoded; charset=UTF-8"):
|
|
||||||
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 datapost is not None:
|
if content_type is None:
|
||||||
headers["Content-Type"] = post_content_type
|
content_type = "application/x-www-form-urlencoded; charset=UTF-8"
|
||||||
|
headers["Content-Type"] = content_type
|
||||||
url = self.FDFE + path
|
url = FDFE + path
|
||||||
if datapost is not None:
|
if post_data is not None:
|
||||||
response = requests.post(url,
|
response = requests.post(url,
|
||||||
data=str(datapost),
|
data=str(post_data),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
verify=ssl_verify,
|
verify=ssl_verify,
|
||||||
timeout=60,
|
timeout=60,
|
||||||
@ -346,24 +347,6 @@ class GooglePlayAPI(object):
|
|||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def userProfile(self):
|
|
||||||
path = 'api/userProfile'
|
|
||||||
headers = self.getDefaultHeaders()
|
|
||||||
|
|
||||||
url = self.FDFE + path
|
|
||||||
response = requests.get(url,
|
|
||||||
headers=headers,
|
|
||||||
verify=ssl_verify,
|
|
||||||
timeout=60,
|
|
||||||
proxies=self.proxies_config)
|
|
||||||
|
|
||||||
message = googleplay_pb2.UserProfileResponseWrapper.FromString(response.content)
|
|
||||||
if message.commands.displayErrorMessage != "":
|
|
||||||
raise RequestError(message.commands.displayErrorMessage)
|
|
||||||
|
|
||||||
result = utils.fromDocToDictionary(message.payload.response.doc[0])
|
|
||||||
return result
|
|
||||||
|
|
||||||
def details(self, packageName):
|
def details(self, packageName):
|
||||||
"""Get app details from a package name.
|
"""Get app details from a package name.
|
||||||
|
|
||||||
@ -391,8 +374,8 @@ class GooglePlayAPI(object):
|
|||||||
req.docid.extend(packageNames)
|
req.docid.extend(packageNames)
|
||||||
data = req.SerializeToString()
|
data = req.SerializeToString()
|
||||||
message = self.executeRequestApi2(path,
|
message = self.executeRequestApi2(path,
|
||||||
data.decode("utf-8"),
|
post_data=data.decode("utf-8"),
|
||||||
"application/x-protobuf")
|
content_type="application/x-protobuf")
|
||||||
response = message.payload.bulkDetailsResponse
|
response = message.payload.bulkDetailsResponse
|
||||||
return [None if not utils.hasDoc(entry) else
|
return [None if not utils.hasDoc(entry) else
|
||||||
utils.fromDocToDictionary(entry.doc)
|
utils.fromDocToDictionary(entry.doc)
|
||||||
@ -554,24 +537,24 @@ class GooglePlayAPI(object):
|
|||||||
params=params, verify=ssl_verify,
|
params=params, verify=ssl_verify,
|
||||||
timeout=60,
|
timeout=60,
|
||||||
proxies=self.proxies_config)
|
proxies=self.proxies_config)
|
||||||
resObj = googleplay_pb2.ResponseWrapper.FromString(response.content)
|
response = googleplay_pb2.ResponseWrapper.FromString(response.content)
|
||||||
if resObj.commands.displayErrorMessage != "":
|
if response.commands.displayErrorMessage != "":
|
||||||
raise RequestError(resObj.commands.displayErrorMessage)
|
raise RequestError(response.commands.displayErrorMessage)
|
||||||
elif resObj.payload.deliveryResponse.appDeliveryData.downloadUrl == "":
|
elif response.payload.deliveryResponse.appDeliveryData.downloadUrl == "":
|
||||||
raise RequestError('App not purchased')
|
raise RequestError('App not purchased')
|
||||||
else:
|
else:
|
||||||
result = {}
|
result = {}
|
||||||
result['docId'] = packageName
|
result['docId'] = packageName
|
||||||
result['additionalData'] = []
|
result['additionalData'] = []
|
||||||
downloadUrl = resObj.payload.deliveryResponse.appDeliveryData.downloadUrl
|
downloadUrl = response.payload.deliveryResponse.appDeliveryData.downloadUrl
|
||||||
cookie = resObj.payload.deliveryResponse.appDeliveryData.downloadAuthCookie[0]
|
cookie = response.payload.deliveryResponse.appDeliveryData.downloadAuthCookie[0]
|
||||||
cookies = {
|
cookies = {
|
||||||
str(cookie.name): str(cookie.value)
|
str(cookie.name): str(cookie.value)
|
||||||
}
|
}
|
||||||
result['file'] = self._deliver_data(downloadUrl, cookies)
|
result['file'] = self._deliver_data(downloadUrl, cookies)
|
||||||
if not expansion_files:
|
if not expansion_files:
|
||||||
return result
|
return result
|
||||||
for obb in resObj.payload.deliveryResponse.appDeliveryData.additionalFile:
|
for obb in response.payload.deliveryResponse.appDeliveryData.additionalFile:
|
||||||
a = {}
|
a = {}
|
||||||
# fileType == 0 -> main
|
# fileType == 0 -> main
|
||||||
# fileType == 1 -> patch
|
# fileType == 1 -> patch
|
||||||
@ -614,18 +597,18 @@ class GooglePlayAPI(object):
|
|||||||
params = {'ot': str(offerType),
|
params = {'ot': str(offerType),
|
||||||
'doc': packageName,
|
'doc': packageName,
|
||||||
'vc': str(versionCode)}
|
'vc': str(versionCode)}
|
||||||
url = self.FDFE + path
|
url = FDFE + path
|
||||||
self.log(packageName)
|
self.log(packageName)
|
||||||
response = requests.post(url, headers=headers,
|
response = requests.post(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)
|
||||||
|
|
||||||
resObj = googleplay_pb2.ResponseWrapper.FromString(response.content)
|
response = googleplay_pb2.ResponseWrapper.FromString(response.content)
|
||||||
if resObj.commands.displayErrorMessage != "":
|
if response.commands.displayErrorMessage != "":
|
||||||
raise RequestError(resObj.commands.displayErrorMessage)
|
raise RequestError(response.commands.displayErrorMessage)
|
||||||
else:
|
else:
|
||||||
dlToken = resObj.payload.buyResponse.downloadToken
|
dlToken = response.payload.buyResponse.downloadToken
|
||||||
return self.delivery(packageName, versionCode, offerType, dlToken,
|
return self.delivery(packageName, versionCode, offerType, dlToken,
|
||||||
expansion_files=expansion_files)
|
expansion_files=expansion_files)
|
||||||
|
|
||||||
@ -636,7 +619,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(self.LOGURL,
|
response = requests.post(LOGURL,
|
||||||
data=string_request,
|
data=string_request,
|
||||||
headers=self.getDefaultHeaders(),
|
headers=self.getDefaultHeaders(),
|
||||||
verify=ssl_verify,
|
verify=ssl_verify,
|
||||||
@ -644,8 +627,7 @@ class GooglePlayAPI(object):
|
|||||||
proxies=self.proxies_config)
|
proxies=self.proxies_config)
|
||||||
response = googleplay_pb2.ResponseWrapper.FromString(response.content)
|
response = googleplay_pb2.ResponseWrapper.FromString(response.content)
|
||||||
if response.commands.displayErrorMessage != "":
|
if response.commands.displayErrorMessage != "":
|
||||||
raise RequestError(resObj.commands.displayErrorMessage)
|
raise RequestError(response.commands.displayErrorMessage)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getDevicesCodenames():
|
def getDevicesCodenames():
|
||||||
|
@ -8,7 +8,7 @@ ap.add_argument('-p', '--password', dest='password', help='google password')
|
|||||||
|
|
||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
|
||||||
server = GooglePlayAPI(debug=True, locale='it_IT', timezone='Europe/Rome')
|
server = GooglePlayAPI('it_IT', 'Europe/Rome')
|
||||||
|
|
||||||
# LOGIN
|
# LOGIN
|
||||||
|
|
||||||
|
5
test.py
5
test.py
@ -9,7 +9,7 @@ ap.add_argument('-p', '--password', dest='password', help='google password')
|
|||||||
|
|
||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
|
||||||
server = GooglePlayAPI('it_IT', 'Europe/Rome', debug=True)
|
server = GooglePlayAPI('it_IT', 'Europe/Rome')
|
||||||
|
|
||||||
# LOGIN
|
# LOGIN
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ gsfId = server.gsfId
|
|||||||
authSubToken = server.authSubToken
|
authSubToken = server.authSubToken
|
||||||
|
|
||||||
print('\nNow trying secondary login with ac2dm token and gsfId saved\n')
|
print('\nNow trying secondary login with ac2dm token and gsfId saved\n')
|
||||||
server = GooglePlayAPI('it_IT', 'Europe/Rome', debug=True)
|
server = GooglePlayAPI('it_IT', 'Europe/Rome')
|
||||||
server.login(None, None, gsfId, authSubToken)
|
server.login(None, None, gsfId, authSubToken)
|
||||||
|
|
||||||
# SEARCH
|
# SEARCH
|
||||||
@ -64,7 +64,6 @@ if not errorThrown:
|
|||||||
|
|
||||||
|
|
||||||
# BULK DETAILS
|
# BULK DETAILS
|
||||||
|
|
||||||
testApps = ['org.mozilla.focus', 'com.non.existing.app']
|
testApps = ['org.mozilla.focus', 'com.non.existing.app']
|
||||||
bulk = server.bulkDetails(testApps)
|
bulk = server.bulkDetails(testApps)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user