Improved file delivery + minor changes

* Updated user agent string with data taken from play-store-api
* *download* and *delivery* functions will now return a python generator
rather than raw bytes, to prevent app loading entire files in memory.
This generator can be iterated to get chunk of bytes to write, as
discussed in issue #35.
* as a consequence of the previous point, there is no more progress bar
feature for downloading. It should be implemented by developers using
the API.
This commit is contained in:
Domenico Iezzi 2018-01-26 18:24:19 +01:00
parent e1c73c8af0
commit 23937e6cb8
No known key found for this signature in database
GPG Key ID: 7AC94D5DDA2FB7EE
4 changed files with 27 additions and 37 deletions

View File

@ -77,12 +77,21 @@ class DeviceBuilder(object):
",device={device}" ",device={device}"
",hardware={hardware}" ",hardware={hardware}"
",product={product}" ",product={product}"
"").format(versionString=version_string, ",platformVersionRelease={platform_v}"
versionCode=self.device.get('vending.version'), ",model={model}"
sdk=self.device.get('build.version.sdk_int'), ",buildId={build_id}"
device=self.device.get('build.device'), ",isWideScreen=0"
hardware=self.device.get('build.hardware'), ",supportedAbis={supported_abis}"
product=self.device.get('build.product')) ")").format(versionString=version_string,
versionCode=self.device.get('vending.version'),
sdk=self.device.get('build.version.sdk_int'),
device=self.device.get('build.device'),
hardware=self.device.get('build.hardware'),
product=self.device.get('build.product'),
platform_v=self.device.get('build.version.release'),
model=self.device.get('build.model'),
build_id=self.device.get('build.id'),
supported_abis=self.device.get('platforms'))
def getAuthParams(self, email, passwd): def getAuthParams(self, email, passwd):
return {"Email": email, return {"Email": email,

View File

@ -5,7 +5,6 @@ from Crypto.Util import asn1
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from Crypto.Hash import SHA from Crypto.Hash import SHA
from Crypto.Cipher import PKCS1_OAEP from Crypto.Cipher import PKCS1_OAEP
from clint.textui import progress
import requests import requests
from base64 import b64decode, urlsafe_b64encode from base64 import b64decode, urlsafe_b64encode
@ -478,30 +477,16 @@ class GooglePlayAPI(object):
output.append(review) output.append(review)
return output return output
def _deliver_data(self, url, cookies, progress_bar): def _deliver_data(self, url, cookies):
headers = self.getDefaultHeaders() headers = self.getDefaultHeaders()
if not progress_bar:
return requests.get(url, headers=headers,
cookies=cookies, verify=ssl_verify,
stream=True,
timeout=60,
proxies=self.proxies_config).content
response_content = bytes()
response = requests.get(url, headers=headers, response = requests.get(url, headers=headers,
cookies=cookies, verify=ssl_verify, cookies=cookies, verify=ssl_verify,
stream=True, timeout=60, stream=True, timeout=60,
proxies=self.proxies_config) proxies=self.proxies_config)
total_length = int(response.headers.get('content-length')) return response.iter_content(chunk_size=(32 * 1 << 10))
chunk_size = 32 * (1 << 10) # 32 KB
bar = progress.Bar(expected_size=(total_length >> 10))
for index, chunk in enumerate(response.iter_content(chunk_size=chunk_size)):
response_content += chunk
bar.show(index * chunk_size >> 10)
bar.done()
return response_content
def delivery(self, packageName, versionCode=None, offerType=1, def delivery(self, packageName, versionCode=None, offerType=1,
downloadToken=None, progress_bar=False, expansion_files=False): downloadToken=None, expansion_files=False):
"""Download an already purchased app. """Download an already purchased app.
Args: Args:
@ -553,7 +538,7 @@ class GooglePlayAPI(object):
cookies = { cookies = {
str(cookie.name): str(cookie.value) str(cookie.name): str(cookie.value)
} }
result['data'] = self._deliver_data(downloadUrl, cookies, progress_bar) result['data'] = 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 resObj.payload.deliveryResponse.appDeliveryData.additionalFile:
@ -566,12 +551,11 @@ class GooglePlayAPI(object):
obbType = 'patch' obbType = 'patch'
a['type'] = obbType a['type'] = obbType
a['versionCode'] = obb.versionCode a['versionCode'] = obb.versionCode
a['data'] = self._deliver_data(obb.downloadUrl, None, progress_bar) a['data'] = self._deliver_data(obb.downloadUrl, None)
result['additionalData'].append(a) result['additionalData'].append(a)
return result return result
def download(self, packageName, versionCode=None, offerType=1, def download(self, packageName, versionCode=None, offerType=1, expansion_files=False):
progress_bar=False, expansion_files=False):
"""Download an app and return its raw data (APK file). Free apps need """Download an app and return its raw data (APK file). Free apps need
to be "purchased" first, in order to retrieve the download cookie. to be "purchased" first, in order to retrieve the download cookie.
If you want to download an already purchased app, use *delivery* method. If you want to download an already purchased app, use *delivery* method.
@ -612,7 +596,7 @@ class GooglePlayAPI(object):
else: else:
dlToken = resObj.payload.buyResponse.downloadToken dlToken = resObj.payload.buyResponse.downloadToken
return self.delivery(packageName, versionCode, offerType, dlToken, return self.delivery(packageName, versionCode, offerType, dlToken,
progress_bar=progress_bar, expansion_files=expansion_files) expansion_files=expansion_files)
@staticmethod @staticmethod
def getDevicesCodenames(): def getDevicesCodenames():

View File

@ -11,5 +11,4 @@ setup(name='gpapi',
package_data={'gpapi': ['device.properties']}, package_data={'gpapi': ['device.properties']},
install_requires=['pycryptodome', install_requires=['pycryptodome',
'protobuf', 'protobuf',
'clint',
'requests']) 'requests'])

12
test.py
View File

@ -37,9 +37,10 @@ for a in apps:
docid = apps[0]['docId'] docid = apps[0]['docId']
print('\nTelegram docid is: %s\n' % docid) print('\nTelegram docid is: %s\n' % docid)
print('\nAttempting to download %s\n' % docid) print('\nAttempting to download %s\n' % docid)
fl = server.download(docid, None, progress_bar=True) fl = server.delivery(docid, versionCode=None)
with open(docid + '.apk', 'wb') as f: with open(docid + '.apk', 'wb') as apk_file:
f.write(fl['data']) for chunk in fl.get('data'):
apk_file.write(chunk)
print('\nDownload successful\n') print('\nDownload successful\n')
# DOWNLOAD APP NOT PURCHASED # DOWNLOAD APP NOT PURCHASED
@ -52,10 +53,7 @@ try:
app = server.search('nova launcher prime', 3, None) app = server.search('nova launcher prime', 3, None)
app = filter(lambda x: x['docId'] == 'com.teslacoilsw.launcher.prime', app) app = filter(lambda x: x['docId'] == 'com.teslacoilsw.launcher.prime', app)
app = list(app)[0] app = list(app)[0]
fl = server.delivery(app['docId'], app['versionCode'], progress_bar=True) fl = server.delivery(app['docId'], app['versionCode'])
with open(docid + '.apk', 'wb') as f:
f.write(fl['data'])
print('\nDownload successful\n')
except RequestError as e: except RequestError as e:
errorThrown = True errorThrown = True
print(e) print(e)