mirror of
https://github.com/FliegendeWurst/googleplay-api.git
synced 2024-11-22 21:04:59 +00:00
Implemented ac2dm login
Signed-off-by: Domenico Iezzi <domenico.iezzi.201@gmail.com>
This commit is contained in:
parent
7cb82a47bd
commit
31d96764b6
@ -10,5 +10,5 @@ This project is released under the BSD license.
|
|||||||
|
|
||||||
# API reversing
|
# API reversing
|
||||||
|
|
||||||
Currently I'm trying to reverse a more recent version of the GooglePlay API on LineageOS 14.1 (Android 7.1) using [mitmproxy](https://mitmproxy.org/).
|
Since I started playing with a more recent version of the GooglePlay API on LineageOS 14.1 (Android 7.1) using [mitmproxy](https://mitmproxy.org/), I gathered some information about new APIs.
|
||||||
Checkout the Documentation folder for more details on single API endpoints.
|
Checkout the Documentation folder for more details on single API endpoints.
|
||||||
|
79
config.py
79
config.py
@ -1,3 +1,6 @@
|
|||||||
|
import googleplay_pb2
|
||||||
|
import time
|
||||||
|
|
||||||
# separator used by search.py, categories.py, ...
|
# separator used by search.py, categories.py, ...
|
||||||
SEPARATOR = ";"
|
SEPARATOR = ";"
|
||||||
|
|
||||||
@ -12,3 +15,79 @@ AUTH_TOKEN = ""
|
|||||||
if any([each == None for each in [ANDROID_ID, GOOGLE_LOGIN, GOOGLE_PASSWORD]]):
|
if any([each == None for each in [ANDROID_ID, GOOGLE_LOGIN, GOOGLE_PASSWORD]]):
|
||||||
raise Exception("config.py not updated")
|
raise Exception("config.py not updated")
|
||||||
|
|
||||||
|
# All the following data is taken from play-store-api
|
||||||
|
# https://github.com/yeriomin/play-store-api/blob/master/src/main/resources/device-bacon.properties
|
||||||
|
# The device used as a reference is a OnePlus One (bacon)
|
||||||
|
|
||||||
|
LIB_LIST = "com.qualcomm.qcnvitems,com.android.location.provider,com.android.future.usb.accessory,android.ext.shared,javax.obex,android.ext.services,com.dsi.ant.antradio_library,com.qualcomm.qcrilhook,android.test.runner,org.apache.http.legacy,com.android.nfc_extras,com.android.media.remotedisplay,com.android.mediadrm.signer"
|
||||||
|
FEATURE_LIST = "android.hardware.sensor.proximity,android.hardware.sensor.accelerometer,android.hardware.faketouch,org.cyanogenmod.appsuggest,android.hardware.usb.accessory,android.hardware.telephony.cdma,android.software.backup,android.hardware.touchscreen,android.hardware.touchscreen.multitouch,android.software.print,android.hardware.consumerir,org.cyanogenmod.partner,org.cyanogenmod.telephony,android.software.voice_recognizers,android.hardware.fingerprint,android.hardware.sensor.gyroscope"
|
||||||
|
",android.hardware.audio.low_latency,android.hardware.opengles.aep,android.hardware.bluetooth,android.hardware.camera.autofocus,com.google.android.feature.GOOGLE_BUILD,android.hardware.telephony.gsm,android.software.sip.voip,org.cyanogenmod.profiles,android.hardware.usb.host,com.cyanogenmod.android,android.hardware.audio.output,android.hardware.camera.flash,android.hardware.camera.front,android.hardware.sensor.hifi_sensors,android.hardware.screen.portrait,android.hardware.nfc"
|
||||||
|
",com.nxp.mifare,android.hardware.sensor.stepdetector,org.cyanogenmod.audio,org.cyanogenmod.theme,android.software.home_screen,android.hardware.microphone,org.cyanogenmod.statusbar,android.hardware.bluetooth_le,android.hardware.sensor.compass,android.hardware.touchscreen.multitouch.jazzhand,android.hardware.sensor.barometer,android.software.app_widgets,android.software.input_methods,android.hardware.sensor.light,android.software.device_admin,android.hardware.camera"
|
||||||
|
",org.cyanogenmod.hardware,android.hardware.screen.landscape,org.cyanogenmod.weather,org.cyanogenmod.performance,android.software.managed_users,android.software.webview,android.hardware.sensor.stepcounter,android.hardware.camera.capability.manual_post_processing,org.cyanogenmod.livedisplay,android.hardware.camera.any,android.hardware.camera.capability.raw,android.software.connectionservice,android.hardware.touchscreen.multitouch.distinct,android.hardware.location.network"
|
||||||
|
",android.software.sip,android.hardware.camera.capability.manual_sensor,android.hardware.camera.level.full,android.hardware.wifi.direct,android.software.live_wallpaper,com.google.android.feature.GOOGLE_EXPERIENCE,com.google.android.feature.EXCHANGE_6_2,org.cyanogenmod.theme.v1,org.cyanogenmod.livelockscreen,android.hardware.location.gps,android.software.midi,android.hardware.nfc.hce,android.hardware.wifi,android.hardware.location,android.hardware.telephony"
|
||||||
|
LOCALE_LIST = "hi,so_ET,ro_MD,in,sn_ZW,sw_UG,es_BO,dyo,ru_KZ,en_JE,zu,en_JM,pt_BR,en_MS,ar_SD,en_ZM,es_PA,en_GG,es_SV,en_SE,es,rof,fr_SC,fr_GA,en_CM,ta,en_SX,fr_MC,fy,to,fr_RW,en_SD,qu,en_KE,rw_RW,gv_IM,sv_FI,cgg,pt_GW,fr_CF,sv_SE,dje,en_SS,ar_DZ,si,es_UY,ar_SA,tr_TR,dua,fr_BL,nb_SJ,fr_CA,ff,es_PE,om,en_FK,cs_CZ,zu_ZA,sl_SI,es_NI,en_GY,fr_ML,fr_MF,nmg,fo_DK,en_LR,el_CY,nus,mt,en_NU,en_UG,ta_MY,pt_ST,ha_NE,ca_FR,ru,es_IC,ar_KW,it_IT,en_GI,ji,hr,ka_GE,pt_PT,nl,en_TV,ru_RU,pa,mgh,es_ES,km,ee_TG"
|
||||||
|
",ca_AD,twq,ar_YE,eo,ne,as_IN,es_GT,vi_VN,de_CH,ig_NG,or_IN,mua,pl_PL,lv,fr_DZ,lb,hr_HR,haw,sw_KE,shi,mn,om_ET,fr_LU,es_PR,lo_LA,es_HN,kl_GL,bo_IN,et_EE,en_ZA,fr_TG,br_FR,yo_NG,tr_CY,sr,bem,fr_PF,ti_ET,hu,mk,de_LI,so_SO,nb_NO,luo,en_ZW,sk_SK,ksh,sk,nyn,fa,zgh,fr_HT,en_CY,uz,rm,en_MH,sn,to_TO,te,sq_MK,ha_GH,ta_IN,en_MW,da,en_BS,ms_SG,ps_AF,lt_LT,br,it_CH,fr_NE,en_LC,bm_ML,kk_KZ,qu_BO,tr,nl_SR,ln,sw,luy,en,fo,en_GD,asa,lag,fr_GQ,fr,fr_GN,dz,ar_SO,dz_BT,ca,es_CL,rn_BI,sq_XK,en_CC,en_SI"
|
||||||
|
",el_GR,yo_BJ,vi,my,de_LU,mk_MK,ak_GH,fr_GF,en_PK,my_MM,fr_CG,cy,es_PH,en_IN,ksf,en_LS,fy_NL,ce,ff_MR,af_ZA,fa_IR,bn_BD,vun,ks,bg,sq_AL,fr_BF,rw,af_NA,dsb,qu_PE,en_DM,ar_TN,nd,en_UM,en_FM,en_NR,ro,uk,se_SE,ln_CF,pt_MZ,am_ET,kl,pt,ta_SG,th,se_NO,ff_GN,ky,en_NG,ur_PK,af,en_DE,so,sah,fr_SN,ar_EH,vai,gu_IN,en_WS,es_EA,ms,fr_MG,th_TH,fr_RE,ru_BY,nl_SX,lv_LV,ki_KE,fr_CI,en_BB,ja,kde,am,nl_BQ,bo_CN,ga_IE,sl,bn_IN,mer,en_SZ,fr_CM,dav,ti_ER,da_GL,kw_GB,ga,mfe,it,it_SM,fo_FO,en_BW,en_SG,en_KN"
|
||||||
|
",cs,chr,km_KH,en_SC,mr_IN,el,en_PN,mg_MG,ru_KG,en_PW,en_SB,fur,en_BZ,ka,bm,de_DE,te_IN,ml_IN,hy,sw_TZ,kw,kn,ru_UA,ln_CD,et,fr_CH,en_DG,bn,ps,qu_EC,lt,ii_CN,en_FJ,eu,en_TC,ksb,pt_CV,gl_ES,en_VU,en_MP,ee,ar_PS,wae,nl_BE,xog,is,fr_PM,saq,iw_IL,om_KE,en_FI,nn_NO,pt_MO,mgo,en_US,fr_BE,ar,gd,kok,de,kln,kam,mt_MT,be,ce_RU,en_BE,fr_SY,es_MX,sv_AX,agq,sq,hr_BA,tzm,de_AT,os_RU,es_DO,en_BI,mg,ar_SY,yav,ks_IN,ro_RO,lu_CD,en_PG,jgo,is_IS,es_CU,ff_CM,en_VG,az,en_GU,fr_MR,ug_CN,in_ID,en_AU,nl_CW"
|
||||||
|
",ru_MD,naq,gd_GB,en_CK,ml,ja_JP,sw_CD,uk_UA,ta_LK,pl,es_VE,da_DK,be_BY,fa_AF,pt_AO,fr_MQ,bs,mas,ar_QA,en_IO,en_SH,en_NL,es_GQ,lg,hu_HU,fr_BJ,en_MO,brx,fr_WF,ar_OM,ca_ES,en_GB,ug,ha,en_NA,en_NF,sv,as,ig,en_KI,en_CX,en_TO,sbp,bo,ne_NP,bg_BG,jmc,en_GM,ar_JO,en_HK,ar_IQ,fr_DJ,fr_GP,lkt,kn_IN,ha_NG,en_IL,en_KY,en_TT,fil,fr_BI,sg,hsb,ca_IT,teo,fr_TN,en_AS,kk,guz,fr_VU,mr,es_EC,en_TZ,ko_KR,ar_MA,ar_LB,fr_CD,en_DK,es_CO,ur_IN,rwk,es_PY,ms_MY,cy_GB,en_PH,seh,ar_BH,en_TK,en_RW,eu_ES,ki,fr_TD"
|
||||||
|
",smn,ses,so_KE,es_CR,en_MY,en_AI,lo,en_MG,en_PR,gsw,en_VI,en_BM,se,en_IE,en_SL,khq,en_CH,ee_GH,ko,lb_LU,en_AT,nn,ar_ER,lrc,ar_TD,ar_MR,fr_YT,en_GH,en_MU,si_LK,gv,ky_KG,nl_NL,rm_CH,ar_IL,ti,iw,hy_AM,se_FI,pt_TL,en_AG,or,bez,ff_SN,en_IM,fr_MA,en_MT,nd_ZW,fi_FI,en_NZ,de_BE,fr_KM,bas,ak,nl_AW,ar_AE,kab,ar_EG,ur,es_AR,ar_DJ,ar_KM,kkj,fi,lu,fr_FR,ebu,os,ne_IN,ln_AO,gu,zh,os_GE,sg_CF,mn_MN,gl,lg_UG,ko_KP,rn,mzn,es_US,hi_IN,ar_LY,ms_BN,fr_NC,so_DJ,ii,en_ER,ar_SS,kea,ln_CG,fr_MU"
|
||||||
|
",nb,yo,nnh,en_VC,ewo,en_CA"
|
||||||
|
GL_EXTENSIONS = "GL_AMD_compressed_ATC_texture,GL_AMD_performance_monitor,GL_ANDROID_extension_pack_es31a,GL_APPLE_texture_2D_limited_npot,GL_ARB_vertex_buffer_object,GL_ARM_shader_framebuffer_fetch_depth_stencil,GL_EXT_YUV_target,GL_EXT_blit_framebuffer_params,GL_EXT_buffer_storage,GL_EXT_color_buffer_float,GL_EXT_color_buffer_half_float,GL_EXT_copy_image,GL_EXT_debug_label,GL_EXT_debug_marker,GL_EXT_discard_framebuffer,GL_EXT_disjoint_timer_query,GL_EXT_draw_buffers_indexed,GL_EXT_geometry_shader"
|
||||||
|
",GL_EXT_gpu_shader5,GL_EXT_multisampled_render_to_texture,GL_EXT_primitive_bounding_box,GL_EXT_robustness,GL_EXT_sRGB,GL_EXT_sRGB_write_control,GL_EXT_shader_framebuffer_fetch,GL_EXT_shader_io_blocks,GL_EXT_tessellation_shader,GL_EXT_texture_border_clamp,GL_EXT_texture_buffer,GL_EXT_texture_cube_map_array,GL_EXT_texture_filter_anisotropic,GL_EXT_texture_format_BGRA8888,GL_EXT_texture_norm16,GL_EXT_texture_sRGB_R8,GL_EXT_texture_sRGB_decode,GL_EXT_texture_type_2_10_10_10_REV"
|
||||||
|
",GL_KHR_blend_equation_advanced,GL_KHR_blend_equation_advanced_coherent,GL_KHR_debug,GL_KHR_no_error,GL_KHR_texture_compression_astc_hdr,GL_KHR_texture_compression_astc_ldr,GL_OES_EGL_image,GL_OES_EGL_image_external,GL_OES_EGL_sync,GL_OES_blend_equation_separate,GL_OES_blend_func_separate,GL_OES_blend_subtract,GL_OES_compressed_ETC1_RGB8_texture,GL_OES_compressed_paletted_texture,GL_OES_depth24,GL_OES_depth_texture,GL_OES_depth_texture_cube_map,GL_OES_draw_texture"
|
||||||
|
",GL_OES_element_index_uint,GL_OES_framebuffer_object,GL_OES_get_program_binary,GL_OES_matrix_palette,GL_OES_packed_depth_stencil,GL_OES_point_size_array,GL_OES_point_sprite,GL_OES_read_format,GL_OES_rgb8_rgba8,GL_OES_sample_shading,GL_OES_sample_variables,GL_OES_shader_image_atomic,GL_OES_shader_multisample_interpolation,GL_OES_standard_derivatives,GL_OES_stencil_wrap,GL_OES_surfaceless_context,GL_OES_texture_3D,GL_OES_texture_compression_astc,GL_OES_texture_cube_map"
|
||||||
|
",GL_OES_texture_env_crossbar,GL_OES_texture_float,GL_OES_texture_float_linear,GL_OES_texture_half_float,GL_OES_texture_half_float_linear,GL_OES_texture_mirrored_repeat,GL_OES_texture_npot,GL_OES_texture_stencil8,GL_OES_texture_storage_multisample_2d_array,GL_OES_vertex_array_object,GL_OES_vertex_half_float,GL_OVR_multiview,GL_OVR_multiview2,GL_OVR_multiview_multisampled_render_to_texture,GL_QCOM_alpha_test,GL_QCOM_extended_get,GL_QCOM_tiled_rendering,GL_EXT_multi_draw_arrays"
|
||||||
|
",GL_EXT_shader_texture_lod,GL_IMG_multisampled_render_to_texture,GL_IMG_program_binary,GL_IMG_read_format,GL_IMG_shader_binary,GL_IMG_texture_compression_pvrtc,GL_IMG_texture_format_BGRA8888,GL_IMG_texture_npot,GL_IMG_vertex_array_object,GL_OES_byte_coordinates,GL_OES_extended_matrix_palette,GL_OES_fixed_point,GL_OES_fragment_precision_high,GL_OES_mapbuffer,GL_OES_matrix_get,GL_OES_query_matrix,GL_OES_required_internalformat,GL_OES_single_precision,GL_OES_stencil8"
|
||||||
|
|
||||||
|
|
||||||
|
libList = LIB_LIST.split(",")
|
||||||
|
featureList = FEATURE_LIST.split(",")
|
||||||
|
localeList = LOCALE_LIST.split(",")
|
||||||
|
glList = GL_EXTENSIONS.split(",")
|
||||||
|
currentTime = int(time.time())
|
||||||
|
|
||||||
|
deviceConfig = googleplay_pb2.DeviceConfigurationProto()
|
||||||
|
deviceConfig.touchScreen = 3
|
||||||
|
deviceConfig.keyboard = 1
|
||||||
|
deviceConfig.navigation = 1
|
||||||
|
deviceConfig.screenLayout = 2
|
||||||
|
deviceConfig.hasHardKeyboard = False
|
||||||
|
deviceConfig.hasFiveWayNavigation = False
|
||||||
|
deviceConfig.screenDensity = 420
|
||||||
|
deviceConfig.screenWidth = 1080
|
||||||
|
deviceConfig.screenHeight = 1920
|
||||||
|
deviceConfig.nativePlatform.append("armeabi-v7a")
|
||||||
|
deviceConfig.nativePlatform.append("armeabi")
|
||||||
|
for x in libList:
|
||||||
|
deviceConfig.systemSharedLibrary.append(x)
|
||||||
|
for x in featureList:
|
||||||
|
deviceConfig.systemAvailableFeature.append(x)
|
||||||
|
for x in localeList:
|
||||||
|
deviceConfig.systemSupportedLocale.append(x)
|
||||||
|
deviceConfig.glEsVersion = 131072
|
||||||
|
for x in glList:
|
||||||
|
deviceConfig.glExtension.append(x)
|
||||||
|
|
||||||
|
androidBuild = googleplay_pb2.AndroidBuildProto()
|
||||||
|
androidBuild.id = 'oneplus/bacon/A0001:6.0.1/MHC19Q/ZNH2KAS1KN:user/release-keys'
|
||||||
|
androidBuild.product = 'bacon'
|
||||||
|
androidBuild.carrier = 'oneplus'
|
||||||
|
androidBuild.radio = '.4.0.1.c7-00013-M8974AAAAANAZM-1'
|
||||||
|
androidBuild.bootloader = 'unknown'
|
||||||
|
androidBuild.device = 'A0001'
|
||||||
|
androidBuild.sdkVersion = 25
|
||||||
|
androidBuild.model = 'A0001'
|
||||||
|
androidBuild.manufacturer = 'OnePlus'
|
||||||
|
androidBuild.buildProduct = 'bacon'
|
||||||
|
androidBuild.client = 'android-google'
|
||||||
|
androidBuild.otaInstalled = False
|
||||||
|
androidBuild.timestamp = currentTime
|
||||||
|
androidBuild.googleServices = 10548448
|
||||||
|
|
||||||
|
androidCheckin = googleplay_pb2.AndroidCheckinProto()
|
||||||
|
androidCheckin.build.CopyFrom(androidBuild)
|
||||||
|
androidCheckin.lastCheckinMsec = 0
|
||||||
|
androidCheckin.cellOperator = '310260'
|
||||||
|
androidCheckin.simOperator = '310260'
|
||||||
|
androidCheckin.roaming = 'mobile-notroaming'
|
||||||
|
androidCheckin.userNumber = 0
|
||||||
|
1336
googleplay.proto
1336
googleplay.proto
File diff suppressed because it is too large
Load Diff
192
googleplay.py
192
googleplay.py
@ -6,9 +6,15 @@ 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, DecodeError
|
||||||
|
from Crypto.Util import asn1
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Crypto.Hash import SHA
|
||||||
|
from Crypto.Cipher import PKCS1_OAEP
|
||||||
|
|
||||||
import googleplay_pb2
|
import googleplay_pb2
|
||||||
import config
|
import config
|
||||||
|
import base64
|
||||||
|
import struct
|
||||||
|
|
||||||
ssl_verify = True
|
ssl_verify = True
|
||||||
|
|
||||||
@ -42,10 +48,7 @@ class GooglePlayAPI(object):
|
|||||||
|
|
||||||
SERVICE = "androidmarket"
|
SERVICE = "androidmarket"
|
||||||
|
|
||||||
# https://developers.google.com/identity/protocols/AuthForInstalledApps
|
|
||||||
URL_LOGIN = "https://android.clients.google.com/auth"
|
URL_LOGIN = "https://android.clients.google.com/auth"
|
||||||
ACCOUNT_TYPE_GOOGLE = "GOOGLE"
|
|
||||||
ACCOUNT_TYPE_HOSTED = "HOSTED"
|
|
||||||
ACCOUNT_TYPE_HOSTED_OR_GOOGLE = "HOSTED_OR_GOOGLE"
|
ACCOUNT_TYPE_HOSTED_OR_GOOGLE = "HOSTED_OR_GOOGLE"
|
||||||
authSubToken = None
|
authSubToken = None
|
||||||
|
|
||||||
@ -59,14 +62,42 @@ class GooglePlayAPI(object):
|
|||||||
self.androidId = androidId
|
self.androidId = androidId
|
||||||
self.lang = lang
|
self.lang = lang
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
|
self.gsfId = None
|
||||||
|
self.ac2dmToken = None
|
||||||
|
self.authSubToken = None
|
||||||
|
|
||||||
def encrypt_password(self, login, passwd):
|
def encrypt_password(self, login, passwd):
|
||||||
|
|
||||||
def readInt(byteArray, start):
|
def readInt(byteArray, start):
|
||||||
return struct.unpack(">I", byteArray[0:4])[0]
|
# [start:] remove elements before start
|
||||||
|
# [0:4] select the first four elements from start
|
||||||
|
return struct.unpack("!L", byteArray[start:][0:4])[0]
|
||||||
|
|
||||||
binaryKey = base64.b64encode(bytes(config.GOOGLE_PUBKEY, 'utf-8'))
|
|
||||||
|
def toBigInt(byteArray):
|
||||||
|
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)
|
||||||
i = readInt(binaryKey, 0)
|
i = readInt(binaryKey, 0)
|
||||||
|
modulus = toBigInt(binaryKey[4:][0:i])
|
||||||
|
j = readInt(binaryKey, i+4)
|
||||||
|
exponent = toBigInt(binaryKey[i+8:][0:j])
|
||||||
|
|
||||||
|
seq = asn1.DerSequence()
|
||||||
|
seq.append(modulus)
|
||||||
|
seq.append(exponent)
|
||||||
|
|
||||||
|
publicKey = RSA.importKey(seq.encode())
|
||||||
|
cipher = PKCS1_OAEP.new(publicKey)
|
||||||
|
combined = login.encode() + b'\x00' + passwd.encode()
|
||||||
|
encrypted = cipher.encrypt(combined)
|
||||||
|
h = b'\x00' + SHA.new(binaryKey).digest()[0:4]
|
||||||
|
return base64.urlsafe_b64encode(h + encrypted)
|
||||||
|
|
||||||
|
|
||||||
def toDict(self, protoObj):
|
def toDict(self, protoObj):
|
||||||
@ -114,6 +145,84 @@ class GooglePlayAPI(object):
|
|||||||
if self.debug:
|
if self.debug:
|
||||||
print("authSubToken: " + authSubToken)
|
print("authSubToken: " + authSubToken)
|
||||||
|
|
||||||
|
def setAc2dmToken(self, ac2dmToken):
|
||||||
|
self.ac2dmToken = ac2dmToken
|
||||||
|
|
||||||
|
# put your auth token in config.py to avoid multiple login requests
|
||||||
|
if self.debug:
|
||||||
|
print("ac2dmToken: " + ac2dmToken)
|
||||||
|
|
||||||
|
def getDefaultHeaders(self):
|
||||||
|
"""Return the default set of request parameters, which
|
||||||
|
can later be updated, based on the request type"""
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Accept-Language": "en-US",
|
||||||
|
"X-DFE-Encoded-Targets":
|
||||||
|
"CAEScFfqlIEG6gUYogFWrAISK1WDAg+hAZoCDgIU1gYEOIACFkLMAeQBnASLATlASUuyAyqCAjY5igOMBQzfA/IClwFbApUC4ANbtgKVAS7OAX8YswHFBhgDwAOPAmGEBt4OfKkB5weSB5AFASkiN68akgMaxAMSAQEBA9kBO7UBFE1KVwIDBGs3go6BBgEBAgMECQgJAQIEAQMEAQMBBQEBBAUEFQYCBgUEAwMBDwIBAgOrARwBEwMEAg0mrwESfTEcAQEKG4EBMxghChMBDwYGASI3hAEODEwXCVh/EREZA4sBYwEdFAgIIwkQcGQRDzQ2fTC2AjfVAQIBAYoBGRg2FhYFBwEqNzACJShzFFblAo0CFxpFNBzaAd0DHjIRI4sBJZcBPdwBCQGhAUd2A7kBLBVPngEECHl0UEUMtQETigHMAgUFCc0BBUUlTywdHDgBiAJ+vgKhAU0uAcYCAWQ/"
|
||||||
|
"5ALUAw1UwQHUBpIBCdQDhgL4AY4CBQICjARbGFBGWzA1CAEMOQH+BRAOCAZywAIDyQZ2MgM3BxsoAgUEBwcHFia3AgcGTBwHBYwBAlcBggFxSGgIrAEEBw4QEqUCASsWadsHCgUCBQMD7QICA3tXCUw7ugJZAwGyAUwpIwM5AwkDBQMJA5sBCw8BNxBVVBwVKhebARkBAwsQEAgEAhESAgQJEBCZATMdzgEBBwG8AQQYKSMUkAEDAwY/CTs4/wEaAUt1AwEDAQUBAgIEAwYEDx1dB2wGeBFgTQ",
|
||||||
|
"User-Agent": "Android-Finsky/7.1.15 (api=3,versionCode=80798000,sdk=25,device=A0001,hardware=bacon,product=bacon)",
|
||||||
|
}
|
||||||
|
if self.gsfId is not None:
|
||||||
|
headers["X-DFE-Device-Id"] = "{0:x}".format(self.gsfId)
|
||||||
|
if self.authSubToken is not None:
|
||||||
|
headers["Authorization"] = "GoogleLogin auth=%s" % self.authSubToken
|
||||||
|
return headers
|
||||||
|
|
||||||
|
def checkin(self, email):
|
||||||
|
headers = self.getDefaultHeaders()
|
||||||
|
headers["Content-Type"] = "application/x-protobuffer"
|
||||||
|
url = "https://android.clients.google.com/checkin"
|
||||||
|
|
||||||
|
request = googleplay_pb2.AndroidCheckinRequest()
|
||||||
|
request.id = 0
|
||||||
|
request.checkin.CopyFrom(config.androidCheckin)
|
||||||
|
request.locale = self.lang
|
||||||
|
request.timeZone = 'America/New_York'
|
||||||
|
request.version = 3
|
||||||
|
request.deviceConfiguration.CopyFrom(config.deviceConfig)
|
||||||
|
request.fragment = 0
|
||||||
|
|
||||||
|
stringRequest = request.SerializeToString()
|
||||||
|
res = requests.post(url, data=stringRequest,
|
||||||
|
headers=headers, verify=ssl_verify)
|
||||||
|
response = googleplay_pb2.AndroidCheckinResponse()
|
||||||
|
response.ParseFromString(res.content)
|
||||||
|
|
||||||
|
securityToken = "{0:x}".format(response.securityToken)
|
||||||
|
gsfId = "{0:x}".format(response.androidId)
|
||||||
|
print("String representation of androidId: %s" % gsfId)
|
||||||
|
|
||||||
|
# checkin again to upload gfsid
|
||||||
|
request2 = googleplay_pb2.AndroidCheckinRequest()
|
||||||
|
request2.CopyFrom(request)
|
||||||
|
request2.id = response.androidId
|
||||||
|
request2.securityToken = response.securityToken
|
||||||
|
request2.accountCookie.append("[" + email + "]")
|
||||||
|
request2.accountCookie.append(self.ac2dmToken)
|
||||||
|
stringRequest = request2.SerializeToString()
|
||||||
|
res2 = requests.post(url, data=stringRequest,
|
||||||
|
headers=headers, verify=ssl_verify)
|
||||||
|
|
||||||
|
return response.androidId
|
||||||
|
|
||||||
|
def uploadDeviceConfig(self):
|
||||||
|
upload = googleplay_pb2.UploadDeviceConfigRequest()
|
||||||
|
upload.deviceConfiguration.CopyFrom(config.deviceConfig)
|
||||||
|
headers = self.getDefaultHeaders()
|
||||||
|
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-Client-Id"] = "am-android-google"
|
||||||
|
headers["X-DFE-SmallestScreenWidthDp"] = "320"
|
||||||
|
headers["X-DFE-Filter-Level"] = "3"
|
||||||
|
url = "https://android.clients.google.com/fdfe/uploadDeviceConfig"
|
||||||
|
stringRequest = upload.SerializeToString()
|
||||||
|
res = requests.post(url, data=stringRequest,
|
||||||
|
headers=headers, verify=ssl_verify)
|
||||||
|
response = googleplay_pb2.ResponseWrapper.FromString(res.content)
|
||||||
|
print(res.text)
|
||||||
|
|
||||||
|
|
||||||
def login(self, email=None, password=None, authSubToken=None):
|
def login(self, email=None, password=None, authSubToken=None):
|
||||||
"""Login to your Google Account. You must provide either:
|
"""Login to your Google Account. You must provide either:
|
||||||
- an email and password
|
- an email and password
|
||||||
@ -126,26 +235,26 @@ class GooglePlayAPI(object):
|
|||||||
if (email is None or password is None):
|
if (email is None or password is None):
|
||||||
raise Exception("You should provide at least " +
|
raise Exception("You should provide at least " +
|
||||||
"authSubToken or (email and password)")
|
"authSubToken or (email and password)")
|
||||||
|
|
||||||
|
encryptedPass = self.encrypt_password(email, password).decode('utf-8')
|
||||||
|
# AC2DM token
|
||||||
params = {
|
params = {
|
||||||
"Email": email,
|
"Email": email,
|
||||||
"Passwd": password,
|
"EncryptedPasswd": encryptedPass,
|
||||||
"service": self.SERVICE,
|
"service": "ac2dm",
|
||||||
|
"add_account": "1",
|
||||||
"accountType": self.ACCOUNT_TYPE_HOSTED_OR_GOOGLE,
|
"accountType": self.ACCOUNT_TYPE_HOSTED_OR_GOOGLE,
|
||||||
"has_permission": "1",
|
"has_permission": "1",
|
||||||
|
"app": "com.google.android.gsf",
|
||||||
"source": "android",
|
"source": "android",
|
||||||
"androidId": self.androidId,
|
|
||||||
"app": "com.android.vending",
|
|
||||||
"device_country": "en",
|
"device_country": "en",
|
||||||
"operatorCountry": "en",
|
|
||||||
"lang": self.lang,
|
"lang": self.lang,
|
||||||
"sdk_version": "24"
|
"sdk_version": "25",
|
||||||
|
"client_sig": "38918a453d07199354f8b19af05ec6562ced5788"
|
||||||
}
|
}
|
||||||
headers = {
|
response = requests.post(self.URL_LOGIN, data=params, verify=ssl_verify)
|
||||||
"Accept-Encoding": "",
|
|
||||||
}
|
|
||||||
response = requests.post(self.URL_LOGIN, data=params,
|
|
||||||
headers=headers, verify=ssl_verify)
|
|
||||||
data = response.text.split()
|
data = response.text.split()
|
||||||
|
print(response.text)
|
||||||
params = {}
|
params = {}
|
||||||
for d in data:
|
for d in data:
|
||||||
if "=" not in d:
|
if "=" not in d:
|
||||||
@ -153,30 +262,52 @@ class GooglePlayAPI(object):
|
|||||||
k, v = d.split("=")[0:2]
|
k, v = d.split("=")[0:2]
|
||||||
params[k.strip().lower()] = v.strip()
|
params[k.strip().lower()] = v.strip()
|
||||||
if "auth" in params:
|
if "auth" in params:
|
||||||
self.setAuthSubToken(params["auth"])
|
self.setAc2dmToken(params["auth"])
|
||||||
elif "error" in params:
|
elif "error" in params:
|
||||||
raise LoginError("server says: " + params["error"])
|
raise LoginError("server says: " + params["error"])
|
||||||
else:
|
else:
|
||||||
raise LoginError("Auth token not found.")
|
raise LoginError("Auth token not found.")
|
||||||
|
|
||||||
|
self.gsfId = self.checkin(email)
|
||||||
|
self.getAuthSubToken(email, encryptedPass)
|
||||||
|
self.uploadDeviceConfig()
|
||||||
|
|
||||||
|
def getAuthSubToken(self, email, passwd):
|
||||||
|
params = {
|
||||||
|
"Email": email,
|
||||||
|
"EncryptedPasswd": passwd,
|
||||||
|
"accountType": self.ACCOUNT_TYPE_HOSTED_OR_GOOGLE,
|
||||||
|
"has_permission": "1",
|
||||||
|
"source": "android",
|
||||||
|
"device_country": "en",
|
||||||
|
"service": "androidmarket",
|
||||||
|
"app": "com.android.vending",
|
||||||
|
"lang": self.lang,
|
||||||
|
"sdk_version": "25",
|
||||||
|
"client_sig": "38918a453d07199354f8b19af05ec6562ced5788"
|
||||||
|
}
|
||||||
|
response = requests.post(self.URL_LOGIN, data=params, verify=ssl_verify)
|
||||||
|
data = response.text.split()
|
||||||
|
print(response.text)
|
||||||
|
params = {}
|
||||||
|
for d in data:
|
||||||
|
if "=" not in d:
|
||||||
|
continue
|
||||||
|
k, v = d.split("=")[0:2]
|
||||||
|
params[k.strip().lower()] = v.strip()
|
||||||
|
if "auth" in params:
|
||||||
|
self.setAuthSubToken(params["auth"])
|
||||||
|
elif "error" in params:
|
||||||
|
raise LoginError("server says: " + params["error"])
|
||||||
|
else:
|
||||||
|
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"):
|
||||||
if (datapost is None and path in self.preFetch):
|
if (datapost is None and path in self.preFetch):
|
||||||
data = self.preFetch[path]
|
data = self.preFetch[path]
|
||||||
else:
|
else:
|
||||||
headers = {
|
headers = self.getDefaultHeaders()
|
||||||
"Accept-Language": self.lang,
|
|
||||||
"Authorization": "GoogleLogin auth=%s" % self.authSubToken,
|
|
||||||
"X-DFE-Enabled-Experiments": "cl:billing.select_add_instrument_by_default",
|
|
||||||
"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",
|
|
||||||
"X-DFE-Device-Id": self.androidId,
|
|
||||||
"X-DFE-Client-Id": "am-android-google",
|
|
||||||
"User-Agent": "Android-Finsky/4.4.3 (api=3,versionCode=8013013,sdk=24,device=angler,hardware=angler,product=angler)",
|
|
||||||
"X-DFE-SmallestScreenWidthDp": "335",
|
|
||||||
"X-DFE-Filter-Level": "3",
|
|
||||||
"Accept-Encoding": "",
|
|
||||||
"Host": "android.clients.google.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
if datapost is not None:
|
if datapost is not None:
|
||||||
headers["Content-Type"] = post_content_type
|
headers["Content-Type"] = post_content_type
|
||||||
@ -189,6 +320,7 @@ class GooglePlayAPI(object):
|
|||||||
response = requests.get(url, headers=headers,
|
response = requests.get(url, headers=headers,
|
||||||
verify=ssl_verify)
|
verify=ssl_verify)
|
||||||
data = response.content
|
data = response.content
|
||||||
|
print(data)
|
||||||
|
|
||||||
message = googleplay_pb2.ResponseWrapper.FromString(data)
|
message = googleplay_pb2.ResponseWrapper.FromString(data)
|
||||||
self._try_register_preFetch(message)
|
self._try_register_preFetch(message)
|
||||||
|
12803
googleplay_pb2.py
12803
googleplay_pb2.py
File diff suppressed because one or more lines are too long
3
test.py
3
test.py
@ -1,4 +1,5 @@
|
|||||||
from googleplay import GooglePlayAPI
|
from googleplay import GooglePlayAPI
|
||||||
|
|
||||||
server = GooglePlayAPI(None, None, True)
|
server = GooglePlayAPI(None, None, True)
|
||||||
server.login(None, None, 'IQWsuxlELYuZ4baSwXjLlylVtUlSewJguIR0jfdrFAhqQHfK87hAj5jDB9yUzrxuk1mvUw.')
|
server.login("maracaiboez@gmail.com", "mannaggiasanta", None)
|
||||||
|
server.search("firefox", 1, None)
|
||||||
|
Loading…
Reference in New Issue
Block a user