mirror of
https://github.com/FliegendeWurst/googleplay-api.git
synced 2024-11-22 12:54:58 +00:00
Fix #18
As described in play-store-api code [1] recenlty google introduced a Time To Live to each auth token, which caused them to expire in a matter of seconds. Moreover, in order to retrieve a long-ttl token, a second auth request now is needed, sending the Master Token returned from the first request. This 'second round' request will return the actual authSubToken. Another addition is that the code now return response's text for some generic errors, in order to help debugging those problems.
This commit is contained in:
parent
25bfb4aaec
commit
df2fa823fd
@ -50,6 +50,8 @@ class GooglePlayAPI(object):
|
|||||||
self.gsfId = None
|
self.gsfId = None
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.deviceBuilder = config.DeviceBuilder(device_codename)
|
self.deviceBuilder = config.DeviceBuilder(device_codename)
|
||||||
|
# save last response text for error logging
|
||||||
|
self.lastResponseText = None
|
||||||
|
|
||||||
def encrypt_password(self, login, passwd):
|
def encrypt_password(self, login, passwd):
|
||||||
"""Encrypt the password using the google publickey, using
|
"""Encrypt the password using the google publickey, using
|
||||||
@ -152,12 +154,14 @@ class GooglePlayAPI(object):
|
|||||||
# AC2DM token
|
# AC2DM token
|
||||||
params = self.deviceBuilder.getLoginParams(email, encryptedPass)
|
params = self.deviceBuilder.getLoginParams(email, encryptedPass)
|
||||||
response = requests.post(self.AUTHURL, data=params, verify=ssl_verify)
|
response = requests.post(self.AUTHURL, data=params, verify=ssl_verify)
|
||||||
|
if self.debug:
|
||||||
|
self.lastResponseText = response.text
|
||||||
data = response.text.split()
|
data = response.text.split()
|
||||||
params = {}
|
params = {}
|
||||||
for d in data:
|
for d in data:
|
||||||
if "=" not in d:
|
if "=" not in d:
|
||||||
continue
|
continue
|
||||||
k, v = d.split("=")[0:2]
|
k, v = d.split("=", 1)
|
||||||
params[k.strip().lower()] = v.strip()
|
params[k.strip().lower()] = v.strip()
|
||||||
if "auth" in params:
|
if "auth" in params:
|
||||||
ac2dmToken = params["auth"]
|
ac2dmToken = params["auth"]
|
||||||
@ -168,6 +172,8 @@ class GooglePlayAPI(object):
|
|||||||
"to unlock, or setup an app-specific password")
|
"to unlock, or setup an app-specific password")
|
||||||
raise LoginError("server says: " + params["error"])
|
raise LoginError("server says: " + params["error"])
|
||||||
else:
|
else:
|
||||||
|
if self.debug:
|
||||||
|
print('Last response text: %s' % self.lastResponseText)
|
||||||
raise LoginError("Auth token not found.")
|
raise LoginError("Auth token not found.")
|
||||||
|
|
||||||
self.gsfId = self.checkin(email, ac2dmToken)
|
self.gsfId = self.checkin(email, ac2dmToken)
|
||||||
@ -187,31 +193,58 @@ class GooglePlayAPI(object):
|
|||||||
raise LoginError('Either (email,pass) or (gsfId, authSubToken) is needed')
|
raise LoginError('Either (email,pass) or (gsfId, authSubToken) is needed')
|
||||||
|
|
||||||
def getAuthSubToken(self, email, passwd):
|
def getAuthSubToken(self, email, passwd):
|
||||||
params = self.deviceBuilder.getAuthParams(email, passwd)
|
requestParams = self.deviceBuilder.getAuthParams(email, passwd)
|
||||||
response = requests.post(self.AUTHURL, data=params, verify=ssl_verify)
|
response = requests.post(self.AUTHURL, data=requestParams, verify=ssl_verify)
|
||||||
data = response.text.split()
|
data = response.text.split()
|
||||||
|
if self.debug:
|
||||||
|
self.lastResponseText = response.text
|
||||||
params = {}
|
params = {}
|
||||||
for d in data:
|
for d in data:
|
||||||
if "=" not in d:
|
if "=" not in d:
|
||||||
continue
|
continue
|
||||||
k, v = d.split("=")[0:2]
|
k, v = d.split("=", 1)
|
||||||
params[k.strip().lower()] = v.strip()
|
params[k.strip().lower()] = v.strip()
|
||||||
if "auth" in params:
|
if "token" in params:
|
||||||
self.setAuthSubToken(params["auth"])
|
firstToken = params["token"]
|
||||||
|
if self.debug:
|
||||||
|
print('Master token: %s' % firstToken)
|
||||||
|
secondToken = self.getSecondRoundToken(requestParams, firstToken)
|
||||||
|
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:
|
||||||
|
if self.debug:
|
||||||
|
print('Last response text: %s' % self.lastResponseText)
|
||||||
raise LoginError("Auth token not found.")
|
raise LoginError("Auth token not found.")
|
||||||
|
|
||||||
def _check_response_integrity(self, apps):
|
def getSecondRoundToken(self, previousParams, firstToken):
|
||||||
"""Like described in issue #18, after some time it seems
|
previousParams['Token'] = firstToken
|
||||||
that google invalidates the token. And the strange thing is that when
|
previousParams['service'] = 'androidmarket'
|
||||||
sending requests with an invalid token, it won't throw an error but
|
previousParams['check_email'] = '1'
|
||||||
it returns empty responses. This is a function used to check if the
|
previousParams['token_request_options'] = 'CAA4AQ=='
|
||||||
content returned is valid (usually a docId field is always present)"""
|
previousParams['system_partition'] = '1'
|
||||||
if any([a['docId'] == '' for a in apps]):
|
previousParams['_opt_is_called_from_account_manager'] = '1'
|
||||||
raise LoginError('Unexpected behaviour, probably expired '
|
previousParams['google_play_services_version'] = '11518448'
|
||||||
'token')
|
previousParams.pop('Email')
|
||||||
|
previousParams.pop('EncryptedPasswd')
|
||||||
|
response = requests.post(self.AUTHURL, data=previousParams, verify=ssl_verify)
|
||||||
|
data = response.text.split()
|
||||||
|
if self.debug:
|
||||||
|
self.lastResponseText = response.text
|
||||||
|
params = {}
|
||||||
|
for d in data:
|
||||||
|
if "=" not in d:
|
||||||
|
continue
|
||||||
|
k, v = d.split("=", 1)
|
||||||
|
params[k.strip().lower()] = v.strip()
|
||||||
|
if "auth" in params:
|
||||||
|
return params["auth"]
|
||||||
|
elif "error" in params:
|
||||||
|
raise LoginError("server says: " + params["error"])
|
||||||
|
else:
|
||||||
|
if self.debug:
|
||||||
|
print('Last response text: %s' % self.lastResponseText)
|
||||||
|
raise LoginError("Auth token not found.")
|
||||||
|
|
||||||
def executeRequestApi2(self, path, datapost=None,
|
def executeRequestApi2(self, path, datapost=None,
|
||||||
post_content_type="application/x-www-form-urlencoded; charset=UTF-8"):
|
post_content_type="application/x-www-form-urlencoded; charset=UTF-8"):
|
||||||
@ -232,8 +265,12 @@ class GooglePlayAPI(object):
|
|||||||
verify=ssl_verify,
|
verify=ssl_verify,
|
||||||
timeout=60)
|
timeout=60)
|
||||||
|
|
||||||
|
if self.debug:
|
||||||
|
self.lastResponseText = response.text
|
||||||
message = googleplay_pb2.ResponseWrapper.FromString(response.content)
|
message = googleplay_pb2.ResponseWrapper.FromString(response.content)
|
||||||
if message.commands.displayErrorMessage != "":
|
if message.commands.displayErrorMessage != "":
|
||||||
|
if self.debug:
|
||||||
|
print('Last response text: %s' % self.lastResponseText)
|
||||||
raise RequestError(message.commands.displayErrorMessage)
|
raise RequestError(message.commands.displayErrorMessage)
|
||||||
|
|
||||||
return message
|
return message
|
||||||
@ -270,6 +307,8 @@ class GooglePlayAPI(object):
|
|||||||
if len(response.payload.listResponse.cluster) == 0:
|
if len(response.payload.listResponse.cluster) == 0:
|
||||||
# strange behaviour, probably due to
|
# strange behaviour, probably due to
|
||||||
# expired token
|
# expired token
|
||||||
|
if self.debug:
|
||||||
|
print('Last response text: %s' % self.lastResponseText)
|
||||||
raise LoginError('Unexpected behaviour, probably expired '
|
raise LoginError('Unexpected behaviour, probably expired '
|
||||||
'token')
|
'token')
|
||||||
cluster = response.payload.listResponse.cluster[0]
|
cluster = response.payload.listResponse.cluster[0]
|
||||||
@ -293,7 +332,6 @@ class GooglePlayAPI(object):
|
|||||||
path = "details?doc=%s" % requests.utils.quote(packageName)
|
path = "details?doc=%s" % requests.utils.quote(packageName)
|
||||||
data = self.executeRequestApi2(path)
|
data = self.executeRequestApi2(path)
|
||||||
app = utils.fromDocToDictionary(data.payload.detailsResponse.docV2)
|
app = utils.fromDocToDictionary(data.payload.detailsResponse.docV2)
|
||||||
self._check_response_integrity([app])
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
def bulkDetails(self, packageNames):
|
def bulkDetails(self, packageNames):
|
||||||
@ -324,7 +362,6 @@ class GooglePlayAPI(object):
|
|||||||
result.append(None)
|
result.append(None)
|
||||||
else:
|
else:
|
||||||
appDetails = utils.fromDocToDictionary(entry.doc)
|
appDetails = utils.fromDocToDictionary(entry.doc)
|
||||||
self._check_response_integrity([appDetails])
|
|
||||||
result.append(appDetails)
|
result.append(appDetails)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@ -486,6 +523,8 @@ class GooglePlayAPI(object):
|
|||||||
timeout=60)
|
timeout=60)
|
||||||
resObj = googleplay_pb2.ResponseWrapper.FromString(response.content)
|
resObj = googleplay_pb2.ResponseWrapper.FromString(response.content)
|
||||||
if resObj.commands.displayErrorMessage != "":
|
if resObj.commands.displayErrorMessage != "":
|
||||||
|
if self.debug:
|
||||||
|
print('Last response text: %s' % self.lastResponseText)
|
||||||
raise RequestError(resObj.commands.displayErrorMessage)
|
raise RequestError(resObj.commands.displayErrorMessage)
|
||||||
elif resObj.payload.deliveryResponse.appDeliveryData.downloadUrl == "":
|
elif resObj.payload.deliveryResponse.appDeliveryData.downloadUrl == "":
|
||||||
raise RequestError('App not purchased')
|
raise RequestError('App not purchased')
|
||||||
@ -550,6 +589,8 @@ class GooglePlayAPI(object):
|
|||||||
|
|
||||||
resObj = googleplay_pb2.ResponseWrapper.FromString(response.content)
|
resObj = googleplay_pb2.ResponseWrapper.FromString(response.content)
|
||||||
if resObj.commands.displayErrorMessage != "":
|
if resObj.commands.displayErrorMessage != "":
|
||||||
|
if self.debug:
|
||||||
|
print('Last response text: %s' % self.lastResponseText)
|
||||||
raise RequestError(resObj.commands.displayErrorMessage)
|
raise RequestError(resObj.commands.displayErrorMessage)
|
||||||
else:
|
else:
|
||||||
dlToken = resObj.payload.buyResponse.downloadToken
|
dlToken = resObj.payload.buyResponse.downloadToken
|
||||||
|
Loading…
Reference in New Issue
Block a user