bulkDetails return dict + created utils.py

Signed-off-by: Domenico Iezzi <domenico.iezzi.201@gmail.com>
This commit is contained in:
Domenico Iezzi 2017-09-18 16:33:11 +02:00
parent 1cd3b7598b
commit 83207aa799
3 changed files with 76 additions and 102 deletions

View File

@ -20,8 +20,6 @@ device = {}
for (key, value) in config.items('angler'): for (key, value) in config.items('angler'):
device[key] = value device[key] = value
def getDeviceConfig(): def getDeviceConfig():
libList = device['sharedlibraries'].split(",") libList = device['sharedlibraries'].split(",")
featureList = device['features'].split(",") featureList = device['features'].split(",")

View File

@ -1,22 +1,24 @@
#!/usr/bin/python #!/usr/bin/python
import requests
from google.protobuf import descriptor from google.protobuf import descriptor
from google.protobuf.internal.containers import RepeatedCompositeFieldContainer from google.protobuf.internal.containers import RepeatedCompositeFieldContainer
from google.protobuf import text_format from google.protobuf import text_format
from google.protobuf.message import Message, DecodeError from google.protobuf.message import Message
from Crypto.Util import asn1 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
import googleplay_pb2 import requests
import config import config
import base64 import base64
import struct import struct
import itertools import itertools
import googleplay_pb2
import utils
ssl_verify = True ssl_verify = True
@ -64,26 +66,11 @@ class GooglePlayAPI(object):
"""Encrypt the password using the google publickey, using """Encrypt the password using the google publickey, using
the RSA encryption algorithm""" the RSA encryption algorithm"""
def readInt(byteArray, start):
"""Read the byte array, starting from *start* position,
as an 32-bit unsigned integer"""
return struct.unpack("!L", byteArray[start:][0:4])[0]
def toBigInt(byteArray):
"""Convert the byte array to a BigInteger"""
array = byteArray[::-1] # reverse array
out = 0
for key, value in enumerate(array):
decoded = struct.unpack("B", bytes([value]))[0]
out = out | decoded << key*8
return out
binaryKey = base64.b64decode(config.GOOGLE_PUBKEY) binaryKey = base64.b64decode(config.GOOGLE_PUBKEY)
i = readInt(binaryKey, 0) i = utils.readInt(binaryKey, 0)
modulus = toBigInt(binaryKey[4:][0:i]) modulus = utils.toBigInt(binaryKey[4:][0:i])
j = readInt(binaryKey, i+4) j = utils.readInt(binaryKey, i+4)
exponent = 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)
@ -96,39 +83,6 @@ class GooglePlayAPI(object):
h = b'\x00' + SHA.new(binaryKey).digest()[0:4] h = b'\x00' + SHA.new(binaryKey).digest()[0:4]
return base64.urlsafe_b64encode(h + encrypted) return base64.urlsafe_b64encode(h + encrypted)
def toDict(self, protoObj):
"""Converts the (protobuf) result from an API call into a dict, for
easier introspection."""
iterable = False
if isinstance(protoObj, RepeatedCompositeFieldContainer):
iterable = True
else:
protoObj = [protoObj]
retlist = []
for po in protoObj:
msg = dict()
for fielddesc, value in po.ListFields():
# print value, type(value), getattr(value, "__iter__", False)
if fielddesc.type == descriptor.FieldDescriptor.TYPE_GROUP or \
isinstance(value, RepeatedCompositeFieldContainer) or \
isinstance(value, Message):
msg[fielddesc.name] = self.toDict(value)
else:
msg[fielddesc.name] = value
retlist.append(msg)
if not iterable:
if len(retlist) > 0:
return retlist[0]
else:
return None
return retlist
def toStr(self, protoObj):
"""Used for pretty printing a result from the API."""
return text_format.MessageToString(protoObj)
def _try_register_preFetch(self, protoObj): def _try_register_preFetch(self, protoObj):
fields = [i.name for (i, _) in protoObj.ListFields()] fields = [i.name for (i, _) in protoObj.ListFields()]
if ("preFetch" in fields): if ("preFetch" in fields):
@ -323,7 +277,7 @@ class GooglePlayAPI(object):
message = googleplay_pb2.ResponseWrapper.FromString(data) message = googleplay_pb2.ResponseWrapper.FromString(data)
if message.commands.displayErrorMessage != "": if message.commands.displayErrorMessage != "":
raise DecodeError(message.commands.displayErrorMessage) raise RequestError(message.commands.displayErrorMessage)
self._try_register_preFetch(message) self._try_register_preFetch(message)
return message return message
@ -343,48 +297,7 @@ class GooglePlayAPI(object):
# childs representing the applications. So we chain together every child # childs representing the applications. So we chain together every child
# of every doc # of every doc
apps = itertools.chain.from_iterable([doc.child for doc in cluster.doc]) apps = itertools.chain.from_iterable([doc.child for doc in cluster.doc])
output = [] output = list(map(utils.fromDocToDictionary, apps))
for a in apps:
elem = {
"docId": a.docid,
"title": a.title,
"author": a.creator,
"offer": [{
"micros": o.micros,
"currencyCode": o.currencyCode,
"formattedAmount": o.formattedAmount,
"checkoutFlowRequired": o.checkoutFlowRequired,
"offerType": o.offerType
} for o in a.offer],
"images": [{
"imageType": img.imageType,
"width": img.Dimension.width if hasattr(img.Dimension, "width") else 0,
"height": img.Dimension.height if hasattr(img.Dimension, "height") else 0,
"url": img.imageUrl,
"supportsFifeUrlOptions": img.supportsFifeUrlOptions
} for img in a.image],
"versionCode": a.details.appDetails.versionCode,
"installationSize": a.details.appDetails.installationSize,
"numDownloads": a.details.appDetails.numDownloads,
"uploadDate": a.details.appDetails.uploadDate,
"files": [{
"fileType": f.fileType,
"version": f.versionCode,
"size": f.size
} for f in a.details.appDetails.file],
"unstable": a.details.appDetails.unstable,
"containsAds": a.details.appDetails.containsAds,
"dependencies": [{
"packageName": d.packageName,
"version": d.version
} for d in a.details.appDetails.dependencies.dependency],
"category": {
"appType": a.relatedLinks.categoryInfo.appType,
"appCategory": a.relatedLinks.categoryInfo.appCategory
},
"detailsUrl": a.detailsUrl
}
output.append(elem)
return output return output
def details(self, packageName): def details(self, packageName):
@ -401,6 +314,7 @@ class GooglePlayAPI(object):
requires only one request. requires only one request.
packageNames is a list of app ID (usually starting with 'com.').""" packageNames is a list of app ID (usually starting with 'com.')."""
path = "bulkDetails" path = "bulkDetails"
req = googleplay_pb2.BulkDetailsRequest() req = googleplay_pb2.BulkDetailsRequest()
req.docid.extend(packageNames) req.docid.extend(packageNames)
@ -408,7 +322,10 @@ class GooglePlayAPI(object):
message = self.executeRequestApi2(path, message = self.executeRequestApi2(path,
data.decode("utf-8"), data.decode("utf-8"),
"application/x-protobuf") "application/x-protobuf")
return message.payload.bulkDetailsResponse response = message.payload.bulkDetailsResponse
detailsList = [entry.doc for entry in response.entry]
result = list(map(utils.fromDocToDictionary, detailsList))
return result
def browse(self, cat=None, ctr=None): def browse(self, cat=None, ctr=None):
"""Browse categories. """Browse categories.
@ -477,7 +394,7 @@ 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 != "":
raise DecodeError(resObj.commands.displayErrorMessage) raise RequestError(resObj.commands.displayErrorMessage)
else: else:
dlToken = resObj.payload.buyResponse.downloadToken dlToken = resObj.payload.buyResponse.downloadToken
path = "delivery" path = "delivery"

59
utils.py Normal file
View File

@ -0,0 +1,59 @@
import struct
def fromDocToDictionary(app):
return {
"docId": app.docid,
"title": app.title,
"author": app.creator,
"offer": [{
"micros": o.micros,
"currencyCode": o.currencyCode,
"formattedAmount": o.formattedAmount,
"checkoutFlowRequired": o.checkoutFlowRequired,
"offerType": o.offerType
} for o in app.offer],
"images": [{
"imageType": img.imageType,
"width": img.Dimension.width if hasattr(img.Dimension, "width")
else 0,
"height": img.Dimension.height if hasattr(img.Dimension, "height")
else 0,
"url": img.imageUrl,
"supportsFifeUrlOptions": img.supportsFifeUrlOptions
} for img in app.image],
"versionCode": app.details.appDetails.versionCode,
"installationSize": app.details.appDetails.installationSize,
"numDownloads": app.details.appDetails.numDownloads,
"uploadDate": app.details.appDetails.uploadDate,
"files": [{
"fileType": f.fileType,
"version": f.versionCode,
"size": f.size
} for f in app.details.appDetails.file],
"unstable": app.details.appDetails.unstable,
"containsAds": app.details.appDetails.containsAds,
"dependencies": [{
"packageName": d.packageName,
"version": d.version
} for d in app.details.appDetails.dependencies.dependency],
"category": {
"appType": app.relatedLinks.categoryInfo.appType,
"appCategory": app.relatedLinks.categoryInfo.appCategory
},
"detailsUrl": app.detailsUrl
}
def readInt(byteArray, start):
"""Read the byte array, starting from *start* position,
as an 32-bit unsigned integer"""
return struct.unpack("!L", byteArray[start:][0:4])[0]
def toBigInt(byteArray):
"""Convert the byte array to a BigInteger"""
array = byteArray[::-1] # reverse array
out = 0
for key, value in enumerate(array):
decoded = struct.unpack("B", bytes([value]))[0]
out = out | decoded << key*8
return out