mirror of
https://github.com/FliegendeWurst/googleplay-api.git
synced 2024-11-25 14:24:56 +00:00
bulkDetails return dict + created utils.py
Signed-off-by: Domenico Iezzi <domenico.iezzi.201@gmail.com>
This commit is contained in:
parent
1cd3b7598b
commit
83207aa799
@ -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(",")
|
||||||
|
117
googleplay.py
117
googleplay.py
@ -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
59
utils.py
Normal 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
|
Loading…
Reference in New Issue
Block a user