def main(): # get args sys.argv.pop(0) searchQuery = ' '.join(sys.argv) # read token cache tokenCache = configparser.ConfigParser() tokenCache.read('.cache.ini') try: gsfId = int(tokenCache['DEFAULT'] ['gsfId']) # does not get saved as int, but as str! authSubToken = tokenCache['DEFAULT']['authSubToken'] timezone = tokenCache['DEFAULT']['timezone'] locale = tokenCache['DEFAULT']['locale'] except KeyError: print( "Missing login data. Please, check your cache file, or run login.py" ) sys.exit(1) except configparser.ParsingError: print( "The cache file could not be read correctly. Please, check it, or run login.py" ) sys.exit(1) except configparser.Error as e: print("Error ", e, " while reading cache file") sys.exit(1) server = GooglePlayAPI(locale, timezone) # log in with saved credentials try: server.login(None, None, gsfId, authSubToken) except: print("Error while trying to login to GP servers") sys.exit(2) resultRaw = server.search(searchQuery, 20) for app in resultRaw: docid = app.get('docId') details = server.details(docid) filename = app.get('filename') if filename is None: filename = details.get('docId') + '.apk' if details is None: print('Package ', docid, ' does not exist') continue print(docid, ' , ', filename, details['versionCode'])
def GET_APP(gsfId, authSubToken, package): server = GooglePlayAPI("it_IT", "Europe/Rome") # LOGIN print("\nLogin with ac2dm token and gsfId saved\n") server.login(None, None, gsfId, authSubToken) # DOWNLOAD docid = package print("\nAttempting to download {}\n".format(docid)) fl = server.download(docid) with open(docid + ".apk", "wb") as apk_file: for chunk in fl.get("file").get("data"): apk_file.write(chunk) print("\nDownload successful\n")
def loginToAccount(self, maxAttempt=4): connected = 0 attempt = 0 while True: user = self.requestRessource() if not user[EMAIL] == None: break time.sleep(2) # Connection to a google account while connected == 0: attempt += 1 if attempt > maxAttempt: raise ConnectionImpossible() # create api connection api = GooglePlayAPI(proxies_config=user[PROXY]) print(user[EMAIL]) # connect it try: if user[AUTH_TOKEN]: print(user[AUTH_TOKEN]) api.login(None, None, user[ANDROID_ID], user[AUTH_TOKEN]) else: api.login(user[EMAIL], user[PASSWORD], None, None) user[ANDROID_ID] = api.gsfId connected = 1 except: print("Connection failed, switching account") user[AUTH_TOKEN] = None self.releaseRessource(user) while True: user = self.requestRessource() if not user[EMAIL] == None: break time.sleep(2) #save auth token user[AUTH_TOKEN] = api.authSubToken return (api, user)
def connect_to_googleplay_api(self): api = GooglePlayAPI() error = None try: if self.token is False: logging(self, "Using credentials to connect to API") username = self.config["gmail_address"] passwd = None if self.config["gmail_password"]: logging(self, "Using plaintext password") passwd = self.config["gmail_password"] elif self.config["keyring_service"] and HAVE_KEYRING == True: passwd = keyring.get_password(self.config["keyring_service"], username) elif self.config["keyring_service"] and HAVE_KEYRING == False: print("You asked for keyring service but keyring package is not installed") sys.exit(ERRORS.KEYRING_NOT_INSTALLED) api.login(email=username, password=passwd) else: logging(self, "Using token to connect to API") api.login(authSubToken=self.token, gsfId=int(self.gsfid,16)) except LoginError as exc: error = exc.value success = False else: self.playstore_api = api try: self.raw_search(list(), 'firefox', 1) except (ValueError, IndexError) as ve: # invalid token or expired logging(self, "Token has expired or is invalid. Retrieving a new one...") self.retrieve_token(self.token_url, force_new=True) api.login(authSubToken=self.token, gsfId=int(self.gsfid,16)) success = True return success, error
def get(self, device=None): if device and device not in config.getDevicesCodenames(): self.write("Error: no such device %s" % device) return if device is None: device = 'bacon' email, pwd = random.choice(self.credentials_list) print("Using", email) need_login = False if device in self.apis[email]: # check token not expired and return api = self.apis[email][device] try: api.search('drv') except Exception: need_login = True elif device not in self.apis[email] or need_login: # need fresh login api = GooglePlayAPI(device_codename = device) api.login(email, pwd) self.apis[email][device] = api # wait for login to propagate time.sleep(5) self.write("%s %s" % (api.authSubToken, hex(api.gsfId)[2:]))
class Play(object): def __init__(self, debug=True, fdroid=False): self.currentSet = [] self.totalNumOfApps = 0 self.debug = debug self.fdroid = fdroid self.firstRun = True self.loggedIn = False self._email = None self._passwd = None self._last_fdroid_update = None # configuring download folder if self.fdroid: self.download_path = os.path.join(os.getcwd(), 'repo') else: self.download_path = os.getcwd() # configuring fdroid data if self.fdroid: self.fdroid_exe = 'fdroid' self.fdroid_path = os.getcwd() self.fdroid_init() self.service = GooglePlayAPI(self.debug) def fdroid_init(self): found = False for path in os.environ['PATH'].split(':'): exe = os.path.join(path, self.fdroid_exe) if os.path.isfile(exe): found = True break if not found: print('Please install fdroid') sys.exit(1) elif os.path.isfile('./config.py'): print('Repo already initalized, skipping') else: p = Popen([self.fdroid_exe, 'init', '-v'], stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate() if p.returncode != 0: sys.stderr.write("error initializing fdroid repository " + stderr.decode('utf-8')) sys.exit(1) # backup config.py if self.debug: print('Backing up config.py') move('./config.py', './config-backup.py') with open('./config-backup.py') as f1: content = f1.readlines() # copy all content of backup in the main config.py # if the file was not modified with custom values, do it with open('./config.py', 'w') as f: modified = False for line in content: if '# playmaker' in line: modified = True f.write(line) if not modified: if self.debug: print('Appending playmaker data to config.py') f.write('\n# playmaker\nrepo_name = "playmaker"\n' 'repo_description = "repository managed with ' 'playmaker https://github.com/NoMore201/playmaker"\n') os.chmod('./config.py', 0o600) # ensure all folder and files are setup p = Popen([self.fdroid_exe, 'update', '--create-key', '-v'], stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate() if p.returncode != 0: sys.stderr.write("error initializing fdroid repository " + stderr.decode('utf-8')) else: print('Fdroid repo initialized successfully') def get_last_fdroid_update(self): return {'status': 'SUCCESS', 'message': str(self._last_fdroid_update)} def fdroid_update(self): if not self.loggedIn: return {'status': 'UNAUTHORIZED'} if self.fdroid: try: p = Popen([self.fdroid_exe, 'update', '-c', '--clean'], stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate() if p.returncode != 0: sys.stderr.write("error updating fdroid repository " + stderr.decode('utf-8')) return makeError(FDROID_ERR) else: print('Fdroid repo updated successfully') self._last_fdroid_update = dt.today().replace( microsecond=0) return {'status': 'SUCCESS'} except Exception as e: return makeError(FDROID_ERR) else: return {'status': 'SUCCESS'} def get_apps(self): if not self.loggedIn: return {'status': 'UNAUTHORIZED'} if self.firstRun: return { 'status': 'PENDING', 'total': self.totalNumOfApps, 'current': len(self.currentSet) } return { 'status': 'SUCCESS', 'message': sorted(self.currentSet, key=lambda k: k['title']) } def login(self, email=None, password=None): def unpad(s): return s[:-ord(s[len(s) - 1:])] try: if email is not None and password is not None: self._email = base64.b64decode(email).decode('utf-8') self._passwd = base64.b64decode(password).decode('utf-8') self.service.login(self._email, self._passwd, None, None) else: # otherwise we need only to refresh auth token encrypted = self.service.encrypt_password( self._email, self._passwd).decode('utf-8') self.service.getAuthSubToken(self._email, encrypted) self.loggedIn = True return {'status': 'SUCCESS', 'message': 'OK'} except LoginError as e: print('Wrong credentials: {0}'.format(e)) return {'status': 'ERROR', 'message': 'Wrong credentials'} except RequestError as e: # probably tokens are invalid, so it is better to # invalidate them print(e) return { 'status': 'ERROR', 'message': 'Request error, probably invalid token' } def update_state(self): print('Updating cache') with concurrent.futures.ProcessPoolExecutor() as executor: # get application ids from apk files apkFiles = [ apk for apk in os.listdir(self.download_path) if os.path.splitext(apk)[1] == '.apk' ] self.totalNumOfApps = len(apkFiles) if self.totalNumOfApps != 0: future_to_app = [ executor.submit(get_details_from_apk, a, self.download_path, self.service) for a in apkFiles ] for future in concurrent.futures.as_completed(future_to_app): app = future.result() if app is not None: self.currentSet.append(app) print('Cache correctly initialized') self.firstRun = False def insert_app_into_state(self, newApp): found = False result = list( filter(lambda x: x['docId'] == newApp['docId'], self.currentSet)) if len(result) > 0: found = True if self.debug: print('%s is already cached, updating..' % newApp['docId']) i = self.currentSet.index(result[0]) self.currentSet[i] = newApp if not found: if self.debug: print('Adding %s into cache..' % newApp['docId']) self.currentSet.append(newApp) def search(self, appName, numItems=15): if not self.loggedIn: return {'status': 'UNAUTHORIZED'} try: apps = self.service.search(appName, numItems, None) except RequestError as e: print(e) self.loggedIn = False return {'status': 'ERROR', 'message': SESSION_EXPIRED_ERR} except LoginError as e: print(SESSION_EXPIRED_ERR) self.loggedIn = False except IndexError as e: print(SESSION_EXPIRED_ERR) self.loggedIn = False return {'status': 'SUCCESS', 'message': apps} def details(self, app): try: details = self.service.details(app) except RequestError: details = None return details def get_bulk_details(self, apksList): if not self.loggedIn: return {'status': 'UNAUTHORIZED'} try: apps = [self.details(a) for a in apksList] except LoginError as e: print(e) self.loggedIn = False return apps def download_selection(self, appNames): if not self.loggedIn: return {'status': 'UNAUTHORIZED'} success = [] failed = [] unavail = [] for app in appNames: details = self.details(app) if details is None: print('Package %s does not exits' % app) unavail.append(app) continue print('Downloading %s' % app) try: if details['offer'][0]['formattedAmount'] == 'Free': data = self.service.download(app, details['versionCode']) else: data = self.service.delivery(app, details['versionCode']) except IndexError as exc: print(exc) print('Package %s does not exists' % app) unavail.append(app) except Exception as exc: print(exc) print('Failed to download %s' % app) failed.append(app) else: filename = app + '.apk' filepath = os.path.join(self.download_path, filename) try: open(filepath, 'wb').write(data['data']) except IOError as exc: print('Error while writing %s: %s' % (filename, exc)) failed.append(app) details['filename'] = filename success.append(details) for x in success: self.insert_app_into_state(x) return { 'status': 'SUCCESS', 'message': { 'success': success, 'failed': failed, 'unavail': unavail } } def check_local_apks(self): if not self.loggedIn: return {'status': 'UNAUTHORIZED'} if len(self.currentSet) == 0: print('There is no package') return {'status': 'SUCCESS', 'message': []} else: toUpdate = [] for app in self.currentSet: details = self.details(app['docId']) if details is None: print('%s not available in Play Store' % app['docId']) continue if self.debug: print('Checking %s' % app['docId']) print('%d == %d ?' % (app['versionCode'], details['versionCode'])) if app['versionCode'] != details['versionCode']: toUpdate.append(details['docId']) return {'status': 'SUCCESS', 'message': toUpdate} def remove_local_app(self, docId): if not self.loggedIn: return {'status': 'UNAUTHORIZED'} # get app from cache app = list(filter(lambda x: x['docId'] == docId, self.currentSet)) if len(app) < 1: return {'status': 'ERROR'} apkPath = os.path.join(self.download_path, app[0]['filename']) if os.path.isfile(apkPath): os.remove(apkPath) self.currentSet.remove(app[0]) return {'status': 'SUCCESS'} return {'status': 'ERROR'}
from gpapi.googleplay import GooglePlayAPI server = GooglePlayAPI("it_IT", "Europe/Rome") mail = "*****@*****.**" passwd = "mypasswd" server.login(mail, passwd, None, None) gsfId = server.gsfId authSubToken = server.authSubToken print(gsfId) print(authSubToken)
class Downloader: def __init__(self, socketio): self.gid = "" self.gwd = "" self.authSubToken = "" self.options = Options() self.options.headless = False self.chrome_driver = "./chromedriver" if platform.system() == "Windows": self.chrome_driver = "./chromedriver.exe" self.request_url = "https://accounts.google.com/b/0/DisplayUnlockCaptcha" self.apkfile_path = os.path.join("./apk/") if os.path.exists(self.apkfile_path) == False: os.mkdir(self.apkfile_path) self.locale = "en_US" #'ko_KR' self.timezone = None #'Asia/Seoul' self.devices_codenames = GooglePlayAPI.getDevicesCodenames() self.devices_codenames.reverse() self.proxy = {} self.socketio = socketio self.namespace = "/apk_download" def emit(self, t, data): self.socketio.emit(t, data, namespace=self.namespace) def set_locale(self, locale): self.locale = locale def set_proxy(self, proxy): self.proxy = proxy # LOGIN def firstlogin(self, gid, gpw): self.server = GooglePlayAPI(self.locale, self.timezone, proxies_config=self.proxy) self.gid = gid self.gpw = gpw for i in range(10): try: print("try : " + str(i + 1) + "/10") print('\nLogging in with email and password\n') self.server.login(self.gid, self.gpw, None, None) self.gsfId = self.server.gsfId self.authSubToken = self.server.authSubToken return self.secondlogin() except SecurityCheckError: #traceback.print_exc() print("SecurityCheckError") return False except Exception: time.sleep(0.5) pass print("UNKNOWNERROR") return False def secondlogin(self): print( '\nNow trying secondary login with ac2dm token and gsfId saved\n') self.server = GooglePlayAPI(self.locale, self.timezone, proxies_config=self.proxy) self.server.login(None, None, self.gsfId, self.authSubToken) return True def download_packages(self, package_list): for package in package_list: self.startdownload(package) def startdownload(self, pkgid): self.pkgid = pkgid self.asset = Assets() self.asset.update_status(self.pkgid, "downloading") print('\nAttempting to download %s\n' % self.pkgid) self.emit("download_step", {"step": "start", "package": self.pkgid}) try: fl = "" for codename in self.devices_codenames: self.server = GooglePlayAPI(self.locale, self.timezone, device_codename=codename, proxies_config=self.proxy) self.server.login(None, None, self.gsfId, self.authSubToken) try: fl = self.server.download(self.pkgid) if fl == "": raise Exception("No Device") with open(self.apkfile_path + self.pkgid + '.apk', 'wb') as apk_file: for chunk in fl.get('file').get('data'): apk_file.write(chunk) print('\n[+] Download successful\n') self.emit("download_step", { "step": "finish", "package": self.pkgid }) self.asset.update_status(self.pkgid, "downloaded") self.emit("download_step", { "step": "check", "package": self.pkgid }) self.check_aws_sdk_common(self.pkgid) break except requests.exceptions.SSLError: self.emit("download_step", { "step": "error", "msg": "sslerror", "package": self.pkgid }) print("requests.exceptions.SSLError") break except Exception: traceback.print_exc() continue except: print("Unexpected error:", sys.exc_info()[0]) traceback.print_exc() #time.sleep(3) pass # time.sleep(1) def check_aws_sdk(self, pkgid): print('[+] Checking AWS_SDK') apkfinal_path = self.apkfile_path + pkgid + '.apk' s = os.popen('/usr/bin/grep -i "aws-android-sdk" {0}'.format( apkfinal_path)).read() if 'matches' in s: print("[!] This Application use AWS_SDK") pass else: print("[!] NO AWS_SDK FOUND") os.remove(apkfinal_path) def check_aws_sdk_common(self, pkgid): print('[+] Checking AWS_SDK') apkfinal_path = self.apkfile_path + pkgid + '.apk' if re.search(b'(?i)aws-android-sdk', open(apkfinal_path, "rb").read()): print("[!] This Application use AWS_SDK") self.asset.exist_sdk(pkgid, True) self.emit("download_step", { "step": "result", "package": self.pkgid, "sdk": True }) pass else: print("[!] NO AWS_SDK FOUND") self.asset.exist_sdk(pkgid, False) self.emit("download_step", { "step": "result", "package": self.pkgid, "sdk": False })
class Play(object): def __init__(self, debug=True, fdroid=False): self.currentSet = [] self.debug = debug self.fdroid = fdroid self.loggedIn = False self.firstRun = True # configuring download folder if self.fdroid: self.download_path = os.path.join(os.getcwd(), 'repo') else: self.download_path = os.getcwd() # configuring fdroid data if self.fdroid: self.fdroid_exe = 'fdroid' self.fdroid_path = os.getcwd() self.fdroid_init() self.service = GooglePlayAPI(self.debug) def fdroid_init(self): found = False for path in ['/usr/bin', '/usr/local/bin']: exe = os.path.join(path, self.fdroid_exe) if os.path.isfile(exe): found = True if not found: print('Please install fdroid') sys.exit(1) elif os.path.isfile('./config.py'): print('Repo already initalized, skipping') else: p = Popen([self.fdroid_exe, 'init'], stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate() if p.returncode != 0: sys.stderr.write("error initializing fdroid repository " + stderr.decode('utf-8')) sys.exit(1) # ensure all folder and files are setup p = Popen([self.fdroid_exe, 'update', '--create-key'], stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate() if p.returncode != 0: sys.stderr.write("error initializing fdroid repository " + stderr.decode('utf-8')) else: print('Fdroid repo initialized successfully') def fdroid_update(self): if not self.loggedIn: return makeError(NOT_LOGGED_IN_ERR) if self.fdroid: try: p = Popen([self.fdroid_exe, 'update', '-c', '--clean'], stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate() if p.returncode != 0: sys.stderr.write("error updating fdroid repository " + stderr.decode('utf-8')) return makeError(FDROID_ERR) else: print('Fdroid repo updated successfully') return {'status': 'SUCCESS'} except: return makeError(FDROID_ERR) else: return {'status': 'SUCCESS'} def get_apps(self): if self.firstRun: return {'status': 'PENDING'} return { 'status': 'SUCCESS', 'message': sorted(self.currentSet, key=lambda k: k['title']) } def login(self, ciphertext, hashToB64): def unpad(s): return s[:-ord(s[len(s) - 1:])] try: cipher = base64.b64decode(ciphertext) passwd = base64.b64decode(hashToB64) # first 16 bytes corresponds to the init vector iv = cipher[0:16] cipher = cipher[16:] aes = AES.new(passwd, AES.MODE_CBC, iv) result = unpad(aes.decrypt(cipher)).split(b'\x00') email = result[0].decode('utf-8') password = result[1].decode('utf-8') self.service.login(email, password, None, None) self.loggedIn = True return {'status': 'SUCCESS', 'message': 'OK'} except LoginError as e: print('Wrong credentials') self.loggedIn = False return {'status': 'ERROR', 'message': 'Wrong credentials'} except RequestError as e: # probably tokens are invalid, so it is better to # invalidate them print('Request error, probably invalid token') self.loggedIn = False return { 'status': 'ERROR', 'message': 'Request error, probably invalid token' } def update_state(self): def get_details_from_apk(details): filepath = os.path.join(self.download_path, details['docId'] + '.apk') a = APK(filepath) details['versionCode'] = int(a.version_code) return details def fetch_details_for_local_apps(): # get application ids from apk files appList = [ os.path.splitext(apk)[0] for apk in os.listdir(self.download_path) if os.path.splitext(apk)[1] == '.apk' ] toReturn = [] if len(appList) > 0: details = self.get_bulk_details(appList) executor = concurrent.futures.ThreadPoolExecutor(max_workers=2) futures = { executor.submit(get_details_from_apk, app): app for app in details } for future in concurrent.futures.as_completed(futures): app = future.result() toReturn.append(app) if self.debug: print('Added %s to cache' % app['docId']) return toReturn print('Updating cache') self.currentSet = fetch_details_for_local_apps() self.firstRun = False def insert_app_into_state(self, newApp): found = False result = filter(lambda x: x['docId'] == newApp['docId'], self.currentSet) result = list(result) if len(result) > 0: found = True if self.debug: print('%s is already cached, updating..' % newApp['docId']) i = self.currentSet.index(result[0]) self.currentSet[i] = newApp if not found: if self.debug: print('Adding %s into cache..' % newApp['docId']) self.currentSet.append(newApp) def search(self, appName, numItems=15): if not self.loggedIn: return {'status': 'ERROR', 'message': NOT_LOGGED_IN_ERR} try: apps = self.service.search(appName, numItems, None) except RequestError as e: print(SESSION_EXPIRED_ERR) self.loggedIn = False return {'status': 'ERROR', 'message': SESSION_EXPIRED_ERR} return {'status': 'SUCCESS', 'message': apps} def get_bulk_details(self, apksList): if not self.loggedIn: return {'status': 'ERROR', 'message': NOT_LOGGED_IN_ERR} try: apps = self.service.bulkDetails(apksList) except RequestError as e: print(SESSION_EXPIRED_ERR) self.loggedIn = False return {'status': 'ERROR', 'message': SESSION_EXPIRED_ERR} return apps def download_selection(self, appNames): if not self.loggedIn: return {'status': 'ERROR', 'error': NOT_LOGGED_IN_ERR} success = [] failed = [] unavail = [] details = self.get_bulk_details(appNames) for appname, appdetails in zip(appNames, details): if appdetails['docId'] == '': print('Package does not exits') unavail.append(appname) continue print('Downloading %s' % appname) try: if appdetails['offer'][0]['formattedAmount'] == 'Free': data = self.service.download(appname, appdetails['versionCode']) else: data = self.service.delivery(appname, appdetails['versionCode']) success.append(appdetails) print('Done!') except IndexError as exc: print(exc) print('Package %s does not exists' % appname) unavail.append(appname) except Exception as exc: print(exc) print('Failed to download %s' % appname) failed.append(appname) else: filename = appname + '.apk' filepath = os.path.join(self.download_path, filename) try: open(filepath, 'wb').write(data) except IOError as exc: print('Error while writing %s: %s' % (filename, exc)) failed.append(appname) for x in success: self.insert_app_into_state(x) return { 'status': 'SUCCESS', 'message': { 'success': success, 'failed': failed, 'unavail': unavail } } def check_local_apks(self): if not self.loggedIn: return {'status': 'ERROR', 'error': NOT_LOGGED_IN_ERR} localDetails = self.currentSet onlineDetails = self.get_bulk_details( [app['docId'] for app in localDetails]) if len(localDetails) == 0 or len(onlineDetails) == 0: print('There is no package locally') return {'status': 'SUCCESS', 'message': []} else: toUpdate = [] for local, online in zip(localDetails, onlineDetails): if self.debug: print('Checking %s' % local['docId']) print('%d == %d ?' % (local['versionCode'], online['versionCode'])) if local['versionCode'] != online['versionCode']: toUpdate.append(online['docId']) return {'status': 'SUCCESS', 'message': toUpdate} def remove_local_app(self, appName): apkName = appName + '.apk' apkPath = os.path.join(self.download_path, apkName) if os.path.isfile(apkPath): os.remove(apkPath) for pos, app in enumerate(self.currentSet): if app['docId'] == appName: del self.currentSet[pos] return {'status': 'SUCCESS'} return {'status': 'ERROR'}
class GPlaycli(object): def __init__(self, args=None, credentials=None): # no config file given, look for one if credentials is None: # default local user configs cred_paths_list = [ 'gplaycli.conf', os.path.expanduser("~") + '/.config/gplaycli/gplaycli.conf', '/etc/gplaycli/gplaycli.conf' ] tmp_list = list(cred_paths_list) while not os.path.isfile(tmp_list[0]): tmp_list.pop(0) if not tmp_list: raise OSError("No configuration file found at %s" % cred_paths_list) credentials = tmp_list[0] default_values = dict() self.configparser = configparser.ConfigParser(default_values) self.configparser.read(credentials) self.config = {key: value for key, value in self.configparser.items("Credentials")} self.tokencachefile = os.path.expanduser(self.configparser.get("Cache", "token")) self.playstore_api = None # default settings, ie for API calls if args is None: self.yes = False self.verbose = False self.progress_bar = False self.logging_enable = False self.device_codename = 'bacon' self.addfiles_enable = False # if args are passed else: self.yes = args.yes_to_all self.verbose = args.verbose if self.verbose: logger.setLevel(logging.INFO) handler = logging.StreamHandler() formatter = logging.Formatter("[%(levelname)s] %(message)s") handler.setFormatter(formatter) logger.addHandler(handler) logger.propagate = False logger.info('GPlayCli version %s', __version__) logger.info('Configuration file is %s', credentials) self.progress_bar = args.progress_bar self.set_download_folder(args.update_folder) self.logging_enable = args.logging_enable self.device_codename = args.device_codename self.addfiles_enable = args.addfiles_enable if args.token_enable is None: self.token_enable = self.configparser.getboolean('Credentials', 'token') else: self.token_enable = args.token_enable if self.token_enable: if args.token_url is None: self.token_url = self.configparser.get('Credentials', 'token_url') else: self.token_url = args.token_url self.token, self.gsfid = self.retrieve_token() if self.logging_enable: self.success_logfile = "apps_downloaded.log" self.failed_logfile = "apps_failed.log" self.unavail_logfile = "apps_not_available.log" def get_cached_token(self): try: with open(self.tokencachefile, 'r') as tcf: token, gsfid = tcf.readline().split() if not token: token = None gsfid = None except (IOError, ValueError): # cache file does not exists or is corrupted token = None gsfid = None logger.error('cache file does not exists or is corrupted') return token, gsfid def write_cached_token(self, token, gsfid): try: # creates cachedir if not exists cachedir = os.path.dirname(self.tokencachefile) if not os.path.exists(cachedir): os.mkdir(cachedir) with open(self.tokencachefile, 'w') as tcf: tcf.write("%s %s" % (token, gsfid)) except IOError as e: err_str = "Failed to write token to cache file: %s %s" % (self.tokencachefile, e.strerror) logger.error(err_str) raise IOError(err_str) def retrieve_token(self, force_new=False): token, gsfid = self.get_cached_token() if token is not None and not force_new: logger.info("Using cached token.") return token, gsfid logger.info("Retrieving token ...") r = requests.get(self.token_url) if r.text == 'Auth error': print('Token dispenser auth error, probably too many connections') sys.exit(ERRORS.TOKEN_DISPENSER_AUTH_ERROR) elif r.text == "Server error": print('Token dispenser server error') sys.exit(ERRORS.TOKEN_DISPENSER_SERVER_ERROR) token, gsfid = r.text.split(" ") logger.info("Token: %s", token) logger.info("GSFId: %s", gsfid) self.token = token self.gsfid = gsfid self.write_cached_token(token, gsfid) return token, gsfid def set_download_folder(self, folder): self.config["download_folder_path"] = folder def connect_to_googleplay_api(self): self.playstore_api = GooglePlayAPI(device_codename=self.device_codename) error = None email = None password = None authSubToken = None gsfId = None if self.token_enable is False: logger.info("Using credentials to connect to API") email = self.config["gmail_address"] if self.config["gmail_password"]: logger.info("Using plaintext password") password = self.config["gmail_password"] elif self.config["keyring_service"] and HAVE_KEYRING is True: password = keyring.get_password(self.config["keyring_service"], email) elif self.config["keyring_service"] and HAVE_KEYRING is False: print("You asked for keyring service but keyring package is not installed") sys.exit(ERRORS.KEYRING_NOT_INSTALLED) else: logger.info("Using token to connect to API") authSubToken = self.token gsfId = int(self.gsfid, 16) try: self.playstore_api.login(email=email, password=password, authSubToken=authSubToken, gsfId=gsfId) except (ValueError, IndexError, LoginError, DecodeError) as ve: # invalid token or expired logger.info("Token has expired or is invalid. Retrieving a new one...") self.retrieve_token(force_new=True) self.playstore_api.login(authSubToken=self.token, gsfId=int(self.gsfid, 16)) success = True return success, error def list_folder_apks(self, folder): list_of_apks = [filename for filename in os.listdir(folder) if filename.endswith(".apk")] return list_of_apks def prepare_analyse_apks(self): download_folder_path = self.config["download_folder_path"] list_of_apks = [filename for filename in os.listdir(download_folder_path) if os.path.splitext(filename)[1] == ".apk"] if list_of_apks: logger.info("Checking apks ...") self.analyse_local_apks(list_of_apks, self.playstore_api, download_folder_path, self.prepare_download_updates) def analyse_local_apks(self, list_of_apks, playstore_api, download_folder_path, return_function): list_apks_to_update = [] package_bunch = [] version_codes = [] for position, filename in enumerate(list_of_apks): filepath = os.path.join(download_folder_path, filename) logger.info("Analyzing %s", filepath) a = APK(filepath) packagename = a.package package_bunch.append(packagename) version_codes.append(a.version_code) # BulkDetails requires only one HTTP request # Get APK info from store details = playstore_api.bulkDetails(package_bunch) for detail, packagename, filename, apk_version_code in zip(details, package_bunch, list_of_apks, version_codes): store_version_code = detail['versionCode'] # Compare if apk_version_code != "" and int(apk_version_code) < int(store_version_code) and int( store_version_code) != 0: # Add to the download list list_apks_to_update.append([packagename, filename, int(apk_version_code), int(store_version_code)]) return_function(list_apks_to_update) def prepare_download_updates(self, list_apks_to_update): if list_apks_to_update: list_of_packages_to_download = [] # Ask confirmation before downloading message = "The following applications will be updated :" for packagename, filename, apk_version_code, store_version_code in list_apks_to_update: message += "\n%s Version : %s -> %s" % (filename, apk_version_code, store_version_code) list_of_packages_to_download.append([packagename, filename]) message += "\n" print(message) if not self.yes: print("\nDo you agree?") return_value = input('y/n ?') if self.yes or return_value == 'y': logger.info("Downloading ...") downloaded_packages = self.download_selection(self.playstore_api, list_of_packages_to_download, self.after_download) return_string = str() for package in downloaded_packages: return_string += package + " " print("Updated: " + return_string[:-1]) else: print("Everything is up to date !") sys.exit(ERRORS.OK) def download_selection(self, playstore_api, list_of_packages_to_download, return_function): success_downloads = list() failed_downloads = list() unavail_downloads = list() # BulkDetails requires only one HTTP request # Get APK info from store details = playstore_api.bulkDetails([pkg[0] for pkg in list_of_packages_to_download]) position = 1 for detail, item in zip(details, list_of_packages_to_download): packagename, filename = item logger.info("%s / %s %s", position, len(list_of_packages_to_download), packagename) # Check for download folder download_folder_path = self.config["download_folder_path"] if not os.path.isdir(download_folder_path): os.mkdir(download_folder_path) # Get the version code and the offer type from the app details # m = playstore_api.details(packagename) vc = detail['versionCode'] # Download try: data_dict = playstore_api.download(packagename, vc, progress_bar=self.progress_bar, expansion_files=self.addfiles_enable) success_downloads.append(packagename) except IndexError as exc: logger.error("Error while downloading %s : %s" % (packagename, "this package does not exist, " "try to search it via --search before")) unavail_downloads.append((item, exc)) except Exception as exc: logger.error("Error while downloading %s : %s" % (packagename, exc)) failed_downloads.append((item, exc)) else: if filename is None: filename = packagename + ".apk" filepath = os.path.join(download_folder_path, filename) data = data_dict['data'] additional_data = data_dict['additionalData'] try: open(filepath, "wb").write(data) if additional_data: for obb_file in additional_data: obb_filename = "%s.%s.%s.obb" % (obb_file["type"], obb_file["versionCode"], data_dict["docId"]) obb_filename = os.path.join(download_folder_path, obb_filename) open(obb_filename, "wb").write(obb_file["data"]) except IOError as exc: logger.error("Error while writing %s : %s" % (packagename, exc)) failed_downloads.append((item, exc)) position += 1 success_items = set(success_downloads) failed_items = set([item[0] for item, error in failed_downloads]) unavail_items = set([item[0] for item, error in unavail_downloads]) to_download_items = set([item[0] for item in list_of_packages_to_download]) if self.logging_enable: self.write_logfiles(success_items, failed_items, unavail_items) return_function(failed_downloads + unavail_downloads) return to_download_items - failed_items def after_download(self, failed_downloads): # Info message if not failed_downloads: message = "Download complete" else: message = "A few packages could not be downloaded :" for item, exception in failed_downloads: package_name, filename = item if filename is not None: message += "\n%s : %s" % (filename, package_name) else: message += "\n%s" % package_name message += "\n%s\n" % exception print(message) def raw_search(self, results_list, search_string, nb_results): # Query results return self.playstore_api.search(search_string, nb_result=nb_results) def search(self, results_list, search_string, nb_results, free_only=True, include_headers=True): try: results = self.raw_search(results_list, search_string, nb_results) except IndexError: results = list() if not results: print("No result") return all_results = list() if include_headers: # Name of the columns col_names = ["Title", "Creator", "Size", "Downloads", "Last Update", "AppID", "Version", "Rating"] all_results.append(col_names) # Compute results values for result in results: # skip that app if it not free # or if it's beta (pre-registration) if (len(result['offer']) == 0 # beta apps (pre-registration) or free_only and result['offer'][0]['checkoutFlowRequired'] # not free to download ): continue l = [result['title'], result['author'], util.sizeof_fmt(result['installationSize']), result['numDownloads'], result['uploadDate'], result['docId'], result['versionCode'], "%.2f" % result["aggregateRating"]["starRating"] ] if len(all_results) < int(nb_results) + 1: all_results.append(l) if self.verbose: # Print a nice table col_width = list() for column_indice in range(len(all_results[0])): col_length = max([len("%s" % row[column_indice]) for row in all_results]) col_width.append(col_length + 2) for result in all_results: print("".join(str("%s" % item).strip().ljust(col_width[indice]) for indice, item in enumerate(result))) return all_results def download_packages(self, list_of_packages_to_download): self.download_selection(self.playstore_api, [(pkg, None) for pkg in list_of_packages_to_download], self.after_download) def write_logfiles(self, success, failed, unavail): for result, logfile in [(success, self.success_logfile), (failed, self.failed_logfile), (unavail, self.unavail_logfile) ]: if result: with open(logfile, 'w') as _buffer: for package in result: print(package, file=_buffer)
class GPlaycli: """ Object which handles Google Play connection search and download. GPlaycli can be used as an API with parameters token_enable, token_url, config and with methods retrieve_token(), connect(), download(), search(). """ def __init__(self, args=None, config_file=None): # no config file given, look for one if config_file is None: # default local user configs cred_paths_list = [ 'gplaycli.conf', os.path.expanduser("~") + '/.config/gplaycli/gplaycli.conf', '/etc/gplaycli/gplaycli.conf' ] tmp_list = list(cred_paths_list) while not os.path.isfile(tmp_list[0]): tmp_list.pop(0) if not tmp_list: raise OSError("No configuration file found at %s" % cred_paths_list) config_file = tmp_list[0] default_values = {} self.configparser = configparser.ConfigParser(default_values) self.configparser.read(config_file) self.creds = { key: value for key, value in self.configparser.items("Credentials") } self.tokencachefile = os.path.expanduser( self.configparser.get("Cache", "token", fallback="token.cache")) self.api = None self.token_passed = False self.locale = self.configparser.get("Locale", "locale", fallback="en_GB") self.timezone = self.configparser.get("Locale", "timezone", fallback="CEST") # default settings, ie for API calls if args is None: self.yes = False self.verbose = False self.append_version = False self.progress_bar = False self.logging_enable = False self.device_codename = 'bacon' self.addfiles_enable = False # if args are passed else: self.yes = args.yes_to_all self.verbose = args.verbose if self.verbose: logger.setLevel(logging.INFO) handler = logging.StreamHandler() formatter = logging.Formatter("[%(levelname)s] %(message)s") handler.setFormatter(formatter) logger.addHandler(handler) logger.propagate = False logger.info('GPlayCli version %s', __version__) logger.info('Configuration file is %s', config_file) self.append_version = args.append_version self.progress_bar = args.progress_bar self.set_download_folder(args.update_folder) self.logging_enable = args.logging_enable self.device_codename = args.device_codename logger.info('Device is %s', self.device_codename) self.addfiles_enable = args.addfiles_enable if args.locale is not None: self.locale = args.locale if args.timezone is not None: self.timezone = args.timezone if args.token_enable is None: self.token_enable = self.configparser.getboolean( 'Credentials', 'token') else: self.token_enable = args.token_enable if self.token_enable: if args.token_url is None: self.token_url = self.configparser.get( 'Credentials', 'token_url') else: self.token_url = args.token_url if (args.token_str is None) and (args.gsf_id is None): self.token, self.gsfid = self.retrieve_token() elif (args.token_str is not None) and (args.gsf_id is not None): self.token = args.token_str self.gsfid = args.gsf_id self.token_passed = True else: # Either args.token_str or args.gsf_id is None raise TypeError( "Token string and GSFID have to be passed at the same time." ) if self.logging_enable: self.success_logfile = "apps_downloaded.log" self.failed_logfile = "apps_failed.log" self.unavail_logfile = "apps_not_available.log" ########## Public methods ########## def retrieve_token(self, force_new=False): """ Return a token. If a cached token exists, it will be used. Else, or if force_new=True, a new token is fetched from the token-dispenser server located at self.token_url. """ token, gsfid, device = self.get_cached_token() self.retrieve_time = time.time() if (token is not None and not force_new and device == self.device_codename): logger.info("Using cached token.") return token, gsfid logger.info("Retrieving token ...") url = '/'.join([self.token_url, self.device_codename]) logger.info("Token URL is %s", url) response = requests.get(url) if response.text == 'Auth error': logger.error( 'Token dispenser auth error, probably too many connections') sys.exit(ERRORS.TOKEN_DISPENSER_AUTH_ERROR) elif response.text == "Server error": logger.error('Token dispenser server error') sys.exit(ERRORS.TOKEN_DISPENSER_SERVER_ERROR) elif len(response.text) != 88: # other kinds of errors logger.error('Unknowned error: %s', response.text) sys.exit(ERRORS.TOKEN_DISPENSER_SERVER_ERROR) token, gsfid = response.text.split(" ") logger.info("Token: %s", token) logger.info("GSFId: %s", gsfid) self.token = token self.gsfid = gsfid self.write_cached_token(token, gsfid, self.device_codename) return token, gsfid @hooks.connected def download(self, pkg_todownload): """ Download apks from the pkg_todownload list pkg_todownload -- list either of app names or of tuple of app names and filepath to write them Example: ['org.mozilla.focus','org.mozilla.firefox'] or [('org.mozilla.focus', 'org.mozilla.focus.apk'), ('org.mozilla.firefox', 'download/org.mozilla.firefox.apk')] """ success_downloads = [] failed_downloads = [] unavail_downloads = [] # case where no filenames have been provided for index, pkg in enumerate(pkg_todownload): if isinstance(pkg, str): pkg_todownload[index] = [pkg, None] # remove whitespaces before and after package name pkg_todownload[index][0] = pkg_todownload[index][0].strip('\r\n ') # BulkDetails requires only one HTTP request # Get APK info from store details = list() for pkg in pkg_todownload: try: detail = self.api.details(pkg[0]) details.append(detail) except RequestError as request_error: failed_downloads.append((pkg, request_error)) if any([d is None for d in details]): logger.info( "Token has expired while downloading. Retrieving a new one.") self.refresh_token() details = self.api.bulkDetails([pkg[0] for pkg in pkg_todownload]) position = 1 for detail, item in zip(details, pkg_todownload): packagename, filename = item if filename is None: if self.append_version: filename = detail['docId'] + "-v." + detail[ 'versionString'] + ".apk" else: filename = detail['docId'] + ".apk" logger.info("%s / %s %s", position, len(pkg_todownload), packagename) # Check for download folder download_folder = self.download_folder if not os.path.isdir(download_folder): os.makedirs(download_folder, exist_ok=True) # Download try: if detail['offer'][0]['checkoutFlowRequired']: method = self.api.delivery else: method = self.api.download data_iter = method(packagename, expansion_files=self.addfiles_enable) success_downloads.append(packagename) except IndexError as exc: logger.error( "Error while downloading %s : this package does not exist, " "try to search it via --search before", packagename) unavail_downloads.append((item, exc)) except Exception as exc: logger.error("Error while downloading %s : %s", packagename, exc) failed_downloads.append((item, exc)) else: filepath = os.path.join(download_folder, filename) #if file exists, continue if self.append_version and os.path.isfile(filepath): logger.info("File %s already exists, skipping.", filename) position += 1 continue additional_data = data_iter['additionalData'] total_size = int(data_iter['file']['total_size']) chunk_size = int(data_iter['file']['chunk_size']) try: with open(filepath, "wb") as fbuffer: bar = util.progressbar(expected_size=total_size, hide=not self.progress_bar) for index, chunk in enumerate( data_iter['file']['data']): fbuffer.write(chunk) bar.show(index * chunk_size) bar.done() if additional_data: for obb_file in additional_data: obb_filename = "%s.%s.%s.obb" % ( obb_file["type"], obb_file["versionCode"], data_iter["docId"]) obb_filename = os.path.join( download_folder, obb_filename) obb_total_size = int( obb_file['file']['total_size']) obb_chunk_size = int( obb_file['file']['chunk_size']) with open(obb_filename, "wb") as fbuffer: bar = util.progressbar( expected_size=obb_total_size, hide=not self.progress_bar) for index, chunk in enumerate( obb_file["file"]["data"]): fbuffer.write(chunk) bar.show(index * obb_chunk_size) bar.done() except IOError as exc: logger.error("Error while writing %s : %s", packagename, exc) failed_downloads.append((item, exc)) position += 1 success_items = set(success_downloads) failed_items = set([item[0] for item, error in failed_downloads]) unavail_items = set([item[0] for item, error in unavail_downloads]) to_download_items = set([item[0] for item in pkg_todownload]) if self.logging_enable: self.write_logfiles(success_items, failed_items, unavail_items) self.print_failed(failed_downloads + unavail_downloads) return to_download_items - failed_items @hooks.connected def search(self, search_string, nb_results, free_only=True, include_headers=True): """ Search the given string search_string on the Play Store. search_string -- the string to search on the Play Store nb_results -- the number of results to print free_only -- True if only costless apps should be searched for include_headers -- True if the result table should show column names """ try: results = self.api.search(search_string, nb_result=nb_results) except IndexError: results = [] if not results: logger.info("No result") return all_results = [] if include_headers: # Name of the columns col_names = [ "Title", "Creator", "Size", "Downloads", "Last Update", "AppID", "Version", "Rating" ] all_results.append(col_names) # Compute results values for result in results: # skip that app if it not free # or if it's beta (pre-registration) if (len(result['offer']) == 0 # beta apps (pre-registration) or free_only and result['offer'][0][ 'checkoutFlowRequired'] # not free to download ): continue detail = [ result['title'], result['author'], util.sizeof_fmt(result['installationSize']) if result['installationSize'] > 0 else 'N/A', result['numDownloads'], result['uploadDate'], result['docId'], result['versionCode'], "%.2f" % result["aggregateRating"]["starRating"] ] if len(all_results) < int(nb_results) + 1: all_results.append(detail) if self.verbose: # Print a nice table col_width = [] for column_indice in range(len(all_results[0])): col_length = max( [len("%s" % row[column_indice]) for row in all_results]) col_width.append(col_length + 2) for result in all_results: for indice, item in enumerate(result): out = str(item) out = out.strip() out = out.ljust(col_width[indice]) out = "".join(out) try: print(out, end='') except UnicodeEncodeError: out = out.encode('utf-8', errors='replace') print(out, end='') print() return all_results ########## End public methods ########## ########## Internal methods ########## def connect(self): """ Connect GplayCli to the Google Play API. If self.token_enable=True, the token from self.retrieve_token is used. Else, classical credentials are used. They might be stored into the keyring if the keyring package is installed. """ self.api = GooglePlayAPI(locale=self.locale, timezone=self.timezone, device_codename=self.device_codename) error = None email = None password = None authsub_token = None gsfid = None if self.token_enable is False: logger.info("Using credentials to connect to API") email = self.creds["gmail_address"] if self.creds["gmail_password"]: logger.info("Using plaintext password") password = self.creds["gmail_password"] elif self.creds["keyring_service"] and HAVE_KEYRING is True: password = keyring.get_password(self.creds["keyring_service"], email) elif self.creds["keyring_service"] and HAVE_KEYRING is False: logger.error( "You asked for keyring service but keyring package is not installed" ) sys.exit(ERRORS.KEYRING_NOT_INSTALLED) else: if self.token_passed: logger.info("Using passed token to connect to API") else: logger.info("Using auto retrieved token to connect to API") authsub_token = self.token gsfid = int(self.gsfid, 16) with warnings.catch_warnings(): warnings.simplefilter('error') try: if self.token_enable: now = time.time() if now - self.retrieve_time < 5: # Need to wait a bit before loging in with this token time.sleep(5) self.api.login(email=email, password=password, authSubToken=authsub_token, gsfId=gsfid) except LoginError as login_error: logger.error( "Bad authentication, login or password incorrect (%s)", login_error) return False, ERRORS.CANNOT_LOGIN_GPLAY # invalid token or expired except (ValueError, IndexError, LoginError, DecodeError, SystemError): logger.info( "Token has expired or is invalid. Retrieving a new one...") self.refresh_token() success = True return success, error def get_cached_token(self): """ Retrieve a cached token, gsfid and device if exist. Otherwise return None. """ try: cache_dic = json.loads(open(self.tokencachefile).read()) token = cache_dic['token'] gsfid = cache_dic['gsfid'] device = cache_dic['device'] except (IOError, ValueError): # cache file does not exists or is corrupted token = None gsfid = None device = None logger.error('cache file does not exists or is corrupted') return token, gsfid, device def write_cached_token(self, token, gsfid, device): """ Write the given token, gsfid and device to the self.tokencachefile file. Path and file are created if missing. """ try: # creates cachedir if not exists cachedir = os.path.dirname(self.tokencachefile) if not os.path.exists(cachedir): os.makedirs(cachedir, exist_ok=True) with open(self.tokencachefile, 'w') as tcf: tcf.write( json.dumps({ 'token': token, 'gsfid': gsfid, 'device': device })) except IOError as io_error: err_str = "Failed to write token to cache file: %s %s" % ( self.tokencachefile, io_error.strerror) logger.error(err_str) raise IOError(err_str) def set_download_folder(self, folder): """ Set the download folder for apk to folder. """ self.download_folder = folder def refresh_token(self): """ Get a new token from token-dispenser instance and re-connect to the play-store. """ self.retrieve_token(force_new=True) self.api.login(authSubToken=self.token, gsfId=int(self.gsfid, 16)) def prepare_analyse_apks(self): """ Gather apks to further check for update """ download_folder = self.download_folder list_of_apks = util.list_folder_apks(download_folder) if list_of_apks: logger.info("Checking apks ...") to_update = self.analyse_local_apks(list_of_apks, download_folder) self.prepare_download_updates(to_update) @hooks.connected def analyse_local_apks(self, list_of_apks, download_folder): """ Analyse apks in the list list_of_apks to check for updates and download updates in the download_folder folder. """ list_apks_to_update = [] package_bunch = [] version_codes = [] unavail_items = [] UNAVAIL = "This app is not available in the Play Store" for filename in list_of_apks: filepath = os.path.join(download_folder, filename) logger.info("Analyzing %s", filepath) apk = APK(filepath) packagename = apk.package package_bunch.append(packagename) version_codes.append(util.vcode(apk.version_code)) # BulkDetails requires only one HTTP request # Get APK info from store details = self.api.bulkDetails(package_bunch) for detail, packagename, filename, apk_version_code in zip( details, package_bunch, list_of_apks, version_codes): # this app is not in the play store if not detail: unavail_items.append(((packagename, filename), UNAVAIL)) continue store_version_code = detail['versionCode'] # Compare if apk_version_code < store_version_code: # Add to the download list list_apks_to_update.append([ packagename, filename, apk_version_code, store_version_code ]) if self.logging_enable: self.write_logfiles(None, None, [item[0][0] for item in unavail_items]) self.print_failed(unavail_items) return list_apks_to_update def prepare_download_updates(self, list_apks_to_update): """ Ask confirmation before updating apks """ if list_apks_to_update: pkg_todownload = [] # Ask confirmation before downloading message = "The following applications will be updated :" for packagename, filename, apk_version_code, store_version_code in list_apks_to_update: message += "\n%s Version : %s -> %s" % ( filename, apk_version_code, store_version_code) pkg_todownload.append([packagename, filename]) message += "\n" print(message) if not self.yes: print("\nDo you agree?") return_value = input('y/n ?') if self.yes or return_value == 'y': logger.info("Downloading ...") downloaded_packages = self.download(pkg_todownload) return_string = ' '.join(downloaded_packages) print("Updated: " + return_string) else: print("Everything is up to date !") sys.exit(ERRORS.SUCCESS) @staticmethod def print_failed(failed_downloads): """ Print/log failed downloads from failed_downloads """ # Info message if not failed_downloads: logger.info("Download complete") else: message = "A few packages could not be downloaded :" for pkg, exception in failed_downloads: package_name, filename = pkg if filename is not None: message += "\n%s : %s" % (filename, package_name) else: message += "\n%s" % package_name message += "\n%s\n" % exception logger.error(message) def write_logfiles(self, success, failed, unavail): """ Write success failed and unavail list to logfiles """ for result, logfile in [(success, self.success_logfile), (failed, self.failed_logfile), (unavail, self.unavail_logfile)]: if result: with open(logfile, 'w') as _buffer: for package in result: print(package, file=_buffer)
class GPlaycli(object): def __init__(self, credentials=None, proxies=None, device='bacon', locale=None): # no config file given, look for one if credentials is None: # default local user configs cred_paths_list = [ 'gplaycli.conf', os.path.expanduser("~") + '/.config/gplaycli/gplaycli.conf', '/etc/gplaycli/gplaycli.conf' ] tmp_list = list(cred_paths_list) while not os.path.isfile(tmp_list[0]): tmp_list.pop(0) if not tmp_list: raise OSError("No configuration file found at %s" % cred_paths_list) credentials = tmp_list[0] self.proxies = proxies default_values = dict() self.configparser = configparser.ConfigParser(default_values) self.configparser.read(credentials) self.config = {key: value for key, value in self.configparser.items("Credentials")} self.tokencachefile = os.path.expanduser(self.configparser.get("Cache", "token")) self.playstore_api = None self.token_enable = True self.token_url = self.configparser.get('Credentials', 'token_url') self.token, self.gsfid = self.retrieve_token() # default settings, ie for API calls self.yes = False self.verbose = False logging.basicConfig() self.progress_bar = False self.logging_enable = False self.device_codename = device self.locale = locale self.addfiles_enable = False def get_cached_token(self): try: with open(self.tokencachefile, 'r') as tcf: token, gsfid = tcf.readline().split() if not token: token = None gsfid = None except (IOError, ValueError): # cache file does not exists or is corrupted token = None gsfid = None return token, gsfid def write_cached_token(self, token, gsfid): try: # creates cachedir if not exists cachedir = os.path.dirname(self.tokencachefile) if not os.path.exists(cachedir): os.mkdir(cachedir) with open(self.tokencachefile, 'w') as tcf: tcf.write("%s %s" % (token, gsfid)) except IOError as error: raise IOError("Failed to write token to cache file: %s %s" % (self.tokencachefile, error.strerror)) def retrieve_token(self, force_new=False): token, gsfid = self.get_cached_token() if token is not None and not force_new: logging.info("Using cached token.") return token, gsfid logging.info("Retrieving token ...") resp = requests.get(self.token_url) if resp.text == 'Auth error': print('Token dispenser auth error, probably too many connections') sys.exit(ERRORS.TOKEN_DISPENSER_AUTH_ERROR) elif resp.text == "Server error": print('Token dispenser server error') sys.exit(ERRORS.TOKEN_DISPENSER_SERVER_ERROR) token, gsfid = resp.text.split(" ") self.token = token self.gsfid = gsfid self.write_cached_token(token, gsfid) return token, gsfid def set_download_folder(self, folder): self.config["download_folder_path"] = folder def connect_to_googleplay_api(self): if self.locale: self.playstore_api = GooglePlayAPI( device_codename=self.device_codename, proxies_config=self.proxies, locale=self.locale, timezone=None ) else: self.playstore_api = GooglePlayAPI( device_codename=self.device_codename, proxies_config=self.proxies, timezone=None ) error = None email = None password = None auth_sub_token = None gsf_id = None if self.token_enable is False: logging.info("Using credentials to connect to API") email = self.config["gmail_address"] if self.config["gmail_password"]: logging.info("Using plaintext password") password = self.config["gmail_password"] elif self.config["keyring_service"] and HAVE_KEYRING is True: password = keyring.get_password(self.config["keyring_service"], email) elif self.config["keyring_service"] and HAVE_KEYRING is False: print("You asked for keyring service but keyring package is not installed") sys.exit(ERRORS.KEYRING_NOT_INSTALLED) else: logging.info("Using token to connect to API") auth_sub_token = self.token gsf_id = int(self.gsfid, 16) try: self.playstore_api.login(email=email, password=password, authSubToken=auth_sub_token, gsfId=gsf_id) except (ValueError, IndexError, LoginError, GoogleDecodeError): # invalid token or expired logging.info("Token has expired or is invalid. Retrieving a new one...") self.retrieve_token(force_new=True) self.playstore_api.login(authSubToken=self.token, gsfId=int(self.gsfid, 16)) success = True return success, error def download_pkg(self, pkg, version): # Check for download folder download_folder_path = self.config["download_folder_path"] if not os.path.isdir(download_folder_path): os.mkdir(download_folder_path) #Download try: data_dict = self.playstore_api.download(pkg, version) except IndexError as exc: print("Error while downloading %s : %s" % (pkg, "this package does not exist, " "try to search it via --search before")) return False, None except LoginError: self.retrieve_token(force_new=True) self.playstore_api.login(authSubToken=self.token, gsfId=int(self.gsfid, 16)) try: data_dict = self.playstore_api.download(pkg, version) except IndexError as exc: print("Error while downloading %s : %s" % (pkg, "this package does not exist, " "try to search it via --search before")) return False, None except Exception as exc: print("Error while downloading %s : %s" % (pkg, exc)) return False, None except Exception as exc: print("Error while downloading %s : %s" % (pkg, exc)) return False, None else: filename = pkg + ".apk" filepath = os.path.join(download_folder_path, filename) data = data_dict['data'] additional_data = data_dict['additionalData'] try: open(filepath, "wb").write(data) if additional_data: for obb_file in additional_data: obb_filename = "%s.%s.%s.obb" % (obb_file["type"], obb_file["versionCode"], data_dict["docId"]) obb_filename = os.path.join(download_folder_path, obb_filename) open(obb_filename, "wb").write(obb_file["data"]) except IOError as exc: print("Error while writing %s : %s" % (pkg, exc)) return True, filepath def raw_search(self, search_string, nb_results): # Query results return self.playstore_api.search(search_string, nb_result=nb_results) def search(self, search_string, nb_results=1, free_only=True): try: results = self.raw_search(search_string, nb_results) except IndexError: results = list() except LoginError: self.retrieve_token(force_new=True) self.playstore_api.login(authSubToken=self.token, gsfId=int(self.gsfid, 16)) try: results = self.raw_search(search_string, nb_results) except IndexError: results = list() logging.info(results) if not results: print("No result") return all_results = list() # Compute results values for result in results: if free_only and result['offer'][0]['checkoutFlowRequired']: # if not Free to download continue entry = {"title": result["title"], "creator": result['author'], "size": util.sizeof_fmt(result['installationSize']), "downloads": result['numDownloads'], "last_update": result['uploadDate'], "app_id": result['docId'], "version": result['versionCode'], "rating": "%.2f" % result["aggregateRating"]["starRating"], "paid": result['offer'][0]['checkoutFlowRequired'], "stable": not result["unstable"]} all_results.append(entry) for result in all_results: if result["app_id"] == search_string: return result return "NOT_AT_PLAY"
import sys import os import pymysql import urllib.request from num2words import num2words #database connection # connection = pymysql.connect(host="localhost",user="******",passwd="",database="gapps" ) # cursor = connection.cursor() # gsfId = int(os.environ["GPAPI_GSFID"]) # authSubToken = os.environ["GPAPI_TOKEN"] server = GooglePlayAPI("en_US", "UTC") server.login(None,None,4372181884517069262,'4Qez-s1eEJI4Myt1P_nw20zOpwj7209IFUWyVB76-Pyjb2ZIla5RGnatalrXTVDqi1IZ1A.') ''' gsfId=server.gsfId AuthToken = server.authSubToken # print(gsfId, "\n", AuthToken) # LOGIN print("\nLogin with ac2dm token and gsfId saved\n") # SEARCH print("\nSearch suggestion for \"fir\"\n") print(server.searchSuggest("fir")) result = server.search("firefox") i=0
import sys import argparse ap = argparse.ArgumentParser(description='Test download of expansion files') ap.add_argument('-e', '--email', dest='email', help='google username') ap.add_argument('-p', '--password', dest='password', help='google password') args = ap.parse_args() server = GooglePlayAPI('it_IT', 'Europe/Rome') # LOGIN print('\nLogging in with email and password\n') server.login(args.email, args.password, None, None) gsfId = server.gsfId authSubToken = server.authSubToken print('\nNow trying secondary login with ac2dm token and gsfId saved\n') server = GooglePlayAPI('it_IT', 'Europe/Rome') server.login(None, None, gsfId, authSubToken) # SEARCH apps = server.search('telegram', 34, None) print('\nSearch suggestion for "fir"\n') print(server.searchSuggest('fir')) print('nb_result: 34')
class Play(object): def __init__(self, debug=True, fdroid=False): self.currentSet = [] self.totalNumOfApps = 0 self.debug = debug self.fdroid = fdroid self.firstRun = True self.loggedIn = False self._email = None self._passwd = None self._gsfId = None self._token = None self._last_fdroid_update = None # configuring download folder if self.fdroid: self.download_path = os.path.join(os.getcwd(), 'repo') else: self.download_path = os.getcwd() # configuring fdroid data if self.fdroid: self.fdroid_exe = 'fdroid' self.fdroid_path = os.getcwd() self.fdroid_init() # language settings locale = os.environ.get('LANG_LOCALE') if locale is None: locale = locale_service.getdefaultlocale()[0] timezone = os.environ.get('LANG_TIMEZONE') if timezone is None: timezone = 'Europe/Berlin' device = os.environ.get('DEVICE_CODE') if device is None: self.service = GooglePlayAPI(locale, timezone) else: self.service = GooglePlayAPI(locale, timezone, device_codename=device) def fdroid_init(self): found = False for path in os.environ['PATH'].split(':'): exe = os.path.join(path, self.fdroid_exe) if os.path.isfile(exe): found = True break if not found: print('Please install fdroid') sys.exit(1) elif os.path.isfile('config.py'): print('Repo already initalized, skipping init') else: p = Popen([self.fdroid_exe, 'init', '-v'], stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate() if p.returncode != 0: sys.stderr.write("error initializing fdroid repository " + stderr.decode('utf-8')) sys.exit(1) # backup config.py if self.debug: print('Checking config.py file') with open('config.py', 'r') as config_file: content = config_file.readlines() with open('config.py', 'w') as config_file: # copy all the original content of config.py # if the file was not modified with custom values, do it modified = False for line in content: if '# playmaker' in line: modified = True config_file.write(line) if not modified: if self.debug: print('Appending playmaker data to config.py') config_file.write( '\n# playmaker\nrepo_name = "playmaker"\n' 'repo_description = "repository managed with ' 'playmaker https://github.com/NoMore201/playmaker"\n') # ensure all folder and files are setup p = Popen([self.fdroid_exe, 'update', '--create-key', '-v'], stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate() if p.returncode != 0: print('Skipping fdroid update') else: print('Fdroid repo initialized successfully') def get_last_fdroid_update(self): if not self.loggedIn: return {'status': 'UNAUTHORIZED'} return {'status': 'SUCCESS', 'message': str(self._last_fdroid_update)} def fdroid_update(self): if not self.loggedIn: return {'status': 'UNAUTHORIZED'} if self.fdroid: try: p = Popen([self.fdroid_exe, 'update', '-c', '--clean'], stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate() if p.returncode != 0: sys.stderr.write("error updating fdroid repository " + stderr.decode('utf-8')) return makeError(FDROID_ERR) else: print('Fdroid repo updated successfully') self._last_fdroid_update = dt.today().replace( microsecond=0) return {'status': 'SUCCESS'} except Exception as e: return makeError(FDROID_ERR) else: return {'status': 'SUCCESS'} def get_apps(self): if not self.loggedIn: return {'status': 'UNAUTHORIZED'} if self.firstRun: return { 'status': 'PENDING', 'total': self.totalNumOfApps, 'current': len(self.currentSet) } return { 'status': 'SUCCESS', 'message': sorted(self.currentSet, key=lambda k: k['title']) } def set_encoded_credentials(self, email, password): self._email = base64.b64decode(email).decode('utf-8') self._passwd = base64.b64decode(password).decode('utf-8') def set_credentials(self, email, password): self._email = email self._passwd = password def set_token_credentials(self, gsfId, token): self._gsfId = int(gsfId, 16) self._token = token def has_credentials(self): passwd_credentials = self._email is not None and self._passwd is not None token_credentials = self._gsfId is not None and self._token is not None return passwd_credentials or token_credentials def login(self): if self.loggedIn: return { 'status': 'SUCCESS', 'securityCheck': False, 'message': 'OK' } try: if not self.has_credentials(): raise LoginError("missing credentials") self.service.login(self._email, self._passwd, self._gsfId, self._token) self.loggedIn = True return { 'status': 'SUCCESS', 'securityCheck': False, 'message': 'OK' } except LoginError as e: print('LoginError: {0}'.format(e)) self.loggedIn = False return { 'status': 'ERROR', 'securityCheck': False, 'message': 'Wrong credentials' } except SecurityCheckError as e: print('SecurityCheckError: {0}'.format(e)) self.loggedIn = False return { 'status': 'ERROR', 'securityCheck': True, 'message': 'Need security check' } except RequestError as e: # probably tokens are invalid, so it is better to # invalidate them print('RequestError: {0}'.format(e)) self.loggedIn = False return { 'status': 'ERROR', 'securityCheck': False, 'message': 'Request error, probably invalid token' } def update_state(self): if not self.loggedIn: return {'status': 'UNAUTHORIZED'} print('Updating cache') with concurrent.futures.ProcessPoolExecutor() as executor: # get application ids from apk files apkFiles = [ apk for apk in os.listdir(self.download_path) if os.path.splitext(apk)[1] == '.apk' ] self.totalNumOfApps = len(apkFiles) if self.totalNumOfApps != 0: future_to_app = [ executor.submit(get_details_from_apk, a, self.download_path, self.service) for a in apkFiles ] for future in concurrent.futures.as_completed(future_to_app): app = future.result() if app is not None: self.currentSet.append(app) print('Cache correctly initialized') self.firstRun = False def insert_app_into_state(self, newApp): found = False result = list( filter(lambda x: x['docid'] == newApp['docid'], self.currentSet)) if len(result) > 0: found = True if self.debug: print('%s is already cached, updating..' % newApp['docid']) i = self.currentSet.index(result[0]) self.currentSet[i] = newApp if not found: if self.debug: print('Adding %s into cache..' % newApp['docid']) self.currentSet.append(newApp) def search(self, appName, numItems=15): if not self.loggedIn: return {'status': 'UNAUTHORIZED'} try: apps = self.service.search(appName) except RequestError as e: print(e) self.loggedIn = False return {'status': 'ERROR', 'message': SESSION_EXPIRED_ERR} except LoginError as e: print(SESSION_EXPIRED_ERR) self.loggedIn = False except IndexError as e: print(SESSION_EXPIRED_ERR) self.loggedIn = False return {'status': 'SUCCESS', 'message': apps} def details(self, app): try: details = self.service.details(app) except RequestError: details = None return details def get_bulk_details(self, apksList): if not self.loggedIn: return {'status': 'UNAUTHORIZED'} try: apps = [self.details(a) for a in apksList] except LoginError as e: print(e) self.loggedIn = False return apps def download_selection(self, apps): if not self.loggedIn: return {'status': 'UNAUTHORIZED'} success = [] failed = [] unavail = [] for app in apps: docid = app.get('docid') details = self.details(docid) filename = app.get('filename') if filename is None: filename = details.get('docid') + '.apk' if details is None: print('Package %s does not exits' % docid) unavail.append(docid) continue print('Downloading %s' % docid) try: if details.get('offer')[0].get('micros') == 0: data_gen = self.service.download( docid, details.get('details').get('appDetails') ['versionCode']) else: data_gen = self.service.delivery( docid, details.get('details').get('appDetails') ['versionCode']) data_gen = data_gen.get('file').get('data') except IndexError as exc: print(exc) print('Package %s does not exists' % docid) unavail.append(docid) except Exception as exc: print(exc) print('Failed to download %s' % docid) failed.append(docid) else: filepath = os.path.join(self.download_path, filename) try: with open(filepath, 'wb') as apk_file: for chunk in data_gen: apk_file.write(chunk) except IOError as exc: print('Error while writing %s: %s' % (filename, exc)) failed.append(docid) details['filename'] = filename success.append(details) for x in success: self.insert_app_into_state(x) return { 'status': 'SUCCESS', 'message': { 'success': success, 'failed': failed, 'unavail': unavail } } def check_local_apks(self): if not self.loggedIn: return {'status': 'UNAUTHORIZED'} if len(self.currentSet) == 0: print('There is no package') return {'status': 'SUCCESS', 'message': []} else: toUpdate = [] for app in self.currentSet: details = self.details(app.get('docid')) #print(details) if details is None: print('%s not available in Play Store' % app['docid']) continue details['filename'] = app.get('filename') if self.debug: print('Checking %s' % app['docid']) print('%d == %d ?' % (app.get('details').get('appDetails')['versionCode'], details.get('details').get('appDetails') ['versionCode'])) if app.get('details').get( 'appDetails')['versionCode'] != details.get( 'details').get('appDetails')['versionCode']: toUpdate.append(details) return {'status': 'SUCCESS', 'message': toUpdate} def remove_local_app(self, docid): if not self.loggedIn: return {'status': 'UNAUTHORIZED'} # get app from cache app = list(filter(lambda x: x['docid'] == docid, self.currentSet)) if len(app) < 1: return {'status': 'ERROR'} apkPath = os.path.join(self.download_path, app[0]['filename']) if os.path.isfile(apkPath): os.remove(apkPath) self.currentSet.remove(app[0]) return {'status': 'SUCCESS'} return {'status': 'ERROR'}
from gpapi.googleplay import GooglePlayAPI, RequestError import sys import os # gsfId = int(os.environ["GPAPI_GSFID"]) # authSubToken = os.environ["GPAPI_TOKEN"] # gsfId = 3650034961473215264 # authSubToken = '4Qez-goRRJVqq9CA2BTkCmchsjEJBQA9382fYb9FDG7RTXQuI1h6w_rEY5OvgInZWNQcrQ' server = GooglePlayAPI("it_IT", "Europe/Rome") server.login("*****@*****.**", "google9090", None, None) gsfId = server.gsfId AuthToken = server.authSubToken print(gsfId, " ", AuthToken) # LOGIN print("\nLogin with ac2dm token and gsfId saved\n") # server.login(None, None,gsfId,authSubToken) # print(authSubToken, " ",gsfId ) # SEARCH print("\nSearch suggestion for \"fir\"\n") print(server.searchSuggest("fir")) result = server.search("firefox") for doc in result:
from gpapi.googleplay import GooglePlayAPI, RequestError from time import sleep import json, re, os, pymongo, threading mail = "*****@*****.**" passwd = "devdevdev@GG" api = GooglePlayAPI(locale="en_US", timezone="UTC", device_codename="hero2lte") api.login(email=mail, password=passwd) cat = "MUSIC_AND_AUDIO" catList = [ 'apps_topselling_free', 'apps_topgrossing', 'apps_movers_shakers', 'apps_topselling_paid' ] #api.list(cat) # print(catList) #for c in catList: # print(c) # limit = 100 cats = [ "Art & Design", "Auto & Vehicles", "Beauty", "Books & Reference", "Business", "Comics", "Communication", "Dating",
class aabchecker: def __init__(self, args=None, config_file=None): if config_file is None: config_file_paths = [ 'aabc.conf', os.path.expanduser('~') + '/.config/aabc/aabc.conf', '/etc/aabc/aabc.conf' ] for path in config_file_paths: if os.path.isfile(path): config_file = path break if config_file is None: logger.warn( 'No configuration files found at %s, using default values') self.gpapi = None self.reporter = None config = configparser.ConfigParser() if config_file: config.read(config_file) self.gmail_address = config.get('Credentials', 'gmail_address', fallback=None) self.gmail_password = config.get('Credentials', 'gmail_password', fallback=None) self.keyring_service = config.get('Credentials', 'keyring_service', fallback=None) self.device_codename = config.get('Device', 'codename', fallback='bacon') self.locale = config.get('Locale', 'locale', fallback='en_US') self.timezone = config.get('Locale', 'timezone', fallback='UTC') if not args: return if args.report_file is not None: self.reporter = CSVReporter(args.report_file) if args.verbose is not None: self.verbose = args.verbose if self.verbose: logger.setLevel(logging.INFO) logger.info('aabc version %s', __version__) logger.info('Configuration file is %s', config_file) if args.device_codename is not None: self.device_codename = args.device_codename @hooks.connected def check_aab(self, apps): '''Check whether list of apps use Android App Bundles. Queries Google Play Store for each app's number of files and interprets multiple files as Android App Bundle usage. When reporting is enabled results are written to the configured report. Args: apps (list): list of Android app IDs to check ''' # List of apps that gpapi failed to find details for apps_not_found = [] # A list of 2-tuples holding app ID and AAB usage boolean # (list of tuples is convenient for CSV writer) results = [] # Query Google Play store for details on apps app_details = self.gpapi.bulkDetails(apps) # Reduce app_details to dictionary of app IDs to their file lists app_details = zip(apps, app_details) app_files = dict( map(lambda app: (app[0], get_files_from_details(app[1])), app_details)) # Check file counts for apps to determine if they use AAB for app, files in app_files.items(): if files is None: logger.info('Failed to get query ' + app + ' on Google Play. Skipping check.') apps_not_found.append(app) continue num_files = len(files) results.append((app, num_files > 1)) if self.verbose: files_list = pprint.pformat(files) logger.info( str(num_files) + ' files found for ' + app + ':\n' + files_list) if self.reporter is not None: self.reporter.write(results) # TODO: Log summary of results. def connect(self): ''' Connect aabc to the Google Play API. Credentials might be stored into the keyring if the keyring package is installed. ''' self.gpapi = GooglePlayAPI(locale=self.locale, timezone=self.timezone, device_codename=self.device_codename) ok, err = self.connect_credentials() if ok: self.token = self.gpapi.authSubToken self.gsfid = self.gpapi.gsfId return ok, err def connect_credentials(self): logger.info('Using credentials to connect to API') if self.gmail_password: logger.info('Using plaintext password') password = self.gmail_password elif self.keyring_service and HAVE_KEYRING: password = keyring.get_password(self.keyring_service, self.gmail_address) elif self.keyring_service and not HAVE_KEYRING: logger.error( 'You asked for keyring service but keyring package is not installed' ) return False, ERRORS.KEYRING_NOT_INSTALLED else: logger.error('No password found. Check your configuration file.') return False, ERRORS.CANNOT_LOGIN_GPLAY try: self.gpapi.login(email=self.gmail_address, password=password) except LoginError as e: logger.error( 'Bad authentication, login or password incorrect (%s)', e) return False, ERRORS.CANNOT_LOGIN_GPLAY return True, None
try: server = GooglePlayAPI(locale=locale, timezone='CET', device_codename=device_codename) except Exception: message = 'Device not recognized, you can see valid codenames in device_codenames file.' print('\033[91m'+message+'\033[0m') sys.exit() config = configparser.ConfigParser() config.read('4pk.conf') gsfid = token = None if config['Credentials']['id']: gsfid = int(config['Credentials']['id']) if config['Credentials']['token']: token = config['Credentials']['token'] try: server.login(gsfId=gsfid,authSubToken=token) except OSError: print('\033[93m'+'Connection error, please check your internet connection.'+'\033[0m') sys.exit() except LoginError: print('\033[93m'+'Not possible to login with id and token.'+'\033[0m') email = input('Enter Gmail address: ') password = getpass.getpass('Password: '******'Login failed.' print('\033[91m'+message+'\033[0m') sys.exit() except SecurityCheckError: message = 'Security error, unlock your account here: https://accounts.google.com/DisplayUnlockCaptcha'
help='batch download randomly top #n free in each category') args = ap.parse_args() api = GooglePlayAPI('it_IT', 'Europe/Rome') ######## LOGIN ############ if args.cookie is not None: try: with open(args.cookie, "rb") as f: api = pickle.load(f) except: print("Can't login by cookie") exit(1) else: try: api.login(args.email, args.password, None, None) except Exception as e: print("Can't login by email/password") exit(1) with open("login_cookie", "wb") as f: pickle.dump(api, f) ####### DOWNLOAD 1 APK ###### if args.package is not None: download(api, args.package) exit(0) ###### DOWNLOAD IN BATCH TOP N FREE IN EACH CATEGORY ##### if args.batch is not None: cat = ["Business", "Dating", "Education"] pkg = []
class MarketSession(object): HOST_API_REQUEST = "https://android.clients.google.com/fdfe/" URL_REVIEWS = HOST_API_REQUEST + "rev" def __init__(self, gp_login, gp_password, android_id=None, auth_sub_token=None): self.resetAuthData() self.gp_login = gp_login self.gp_password = gp_password self.android_id = android_id self.auth_sub_token = auth_sub_token self.server = GooglePlayAPI(locale.getdefaultlocale()[0], None) def resetAuthData(self): self.logged_in = False self.android_id = None self.auth_sub_token = None def login(self): try: if self.android_id and self.auth_sub_token: self.server.login(gsfId=int(self.android_id), authSubToken=self.auth_sub_token) else: self.server.login(self.gp_login, self.gp_password) except GpRequestError as gpe: raise RequestError(gpe, 401) self.logged_in = True self.android_id = str(self.server.gsfId) self.auth_sub_token = self.server.authSubToken @defer.inlineCallbacks def execute(self, url, params, lang): try: headers = self.server.getHeaders() headers["Accept-Language"] = lang.encode("ascii") resp = yield treq.get(url, params=params, headers=headers) if resp.code == http.OK: data = yield treq.content(resp) response = googleplay_pb2.ResponseWrapper.FromString(data) # @UndefinedVariable defer.returnValue(response) else: err_data = yield treq.content(resp) err_msg = str(err_data) raise RequestError(err_msg, resp.code) except RequestError as e: if isinstance(e, RequestError): raise e raise RequestError(e) @defer.inlineCallbacks def getReviews(self, appid, startIndex=0, entriesCount=10, lang='en'): response = yield self.execute(self.URL_REVIEWS, {"doc": appid, "o": startIndex, "n": entriesCount, "sort": 0}, lang) result = [] rp = response.payload if rp.HasField("reviewResponse"): for review in rp.reviewResponse.getResponse.review: result.append(self._toDict(review)) defer.returnValue(result) def _toDict(self, protoObj): msg = dict() for fielddesc, value in protoObj.ListFields(): msg[fielddesc.name] = value return msg
import os.path import re import smtplib # email from time import sleep from random import randint from gpapi.googleplay import GooglePlayAPI, LoginError import cred # credential file # set locale & timezone server = GooglePlayAPI(cred.locale, cred.timezone) # LOGIN try: # Login with prestored token server.login(None, None, cred.token, cred.gsfId) except: # Fallback to manual login server.login(cred.email, cred.password, None, None) # Replace old token with open("cred.py","r") as f: content = f.read() content = re.sub('[0123456789]{15,}', str(server.gsfId), content) content = re.sub('[^ "]{50,}', str(server.authSubToken), content) with open('cred.py', 'w') as f: f.write(content) # LIST Apps # get categories browse = server.browse() categories = [x['catId'] for x in browse]
class GPlaycli: """ Object which handles Google Play connection search and download. GPlaycli can be used as an API with parameters token_enable, token_url, config and with methods retrieve_token(), connect(), download(), search(). """ def __init__(self, args=None, config_file=None): # no config file given, look for one if config_file is None: # default local user configs cred_paths_list = [ os.getcwd(), os.path.join(site.USER_BASE, 'etc', 'gplaycli'), os.path.join(sys.prefix, 'local', 'etc', 'gplaycli'), os.path.join('/etc', 'gplaycli') ] for filepath in cred_paths_list: if os.path.isfile(os.path.join(filepath, 'gplaycli.conf')): config_file = os.path.join(filepath, 'gplaycli.conf') break if config_file is None: logger.warn( "No configuration file gplaycli.conf found at %s, using default values" % cred_paths_list) self.api = None self.token_passed = False config = configparser.ConfigParser() if config_file: config.read(config_file) self.gmail_address = config.get('Credentials', 'gmail_address', fallback=None) self.gmail_password = config.get('Credentials', 'gmail_password', fallback=None) self.token_enable = config.getboolean('Credentials', 'token', fallback=True) self.token_url = config.get( 'Credentials', 'token_url', fallback='https://matlink.fr/token/email/gsfid') self.keyring_service = config.get('Credentials', 'keyring_service', fallback=None) self.tokencachefile = os.path.expanduser( config.get("Cache", "token", fallback="token.cache")) self.yes = config.getboolean('Misc', 'accept_all', fallback=False) self.verbose = config.getboolean('Misc', 'verbose', fallback=False) self.append_version = config.getboolean('Misc', 'append_version', fallback=False) self.progress_bar = config.getboolean('Misc', 'progress', fallback=False) self.logging_enable = config.getboolean('Misc', 'enable_logging', fallback=False) self.addfiles_enable = config.getboolean('Misc', 'enable_addfiles', fallback=False) self.device_codename = config.get('Device', 'codename', fallback='bacon') self.locale = config.get("Locale", "locale", fallback="en_GB") self.timezone = config.get("Locale", "timezone", fallback="CEST") if not args: return # if args are passed, override defaults if args.yes is not None: self.yes = args.yes if args.verbose is not None: self.verbose = args.verbose if self.verbose: logger.setLevel(logging.INFO) logger.info('GPlayCli version %s', __version__) logger.info('Configuration file is %s', config_file) if args.append_version is not None: self.append_version = args.append_version if args.progress is not None: self.progress_bar = args.progress if args.update is not None: self.download_folder = args.update if args.log is not None: self.logging_enable = args.log if args.device_codename is not None: self.device_codename = args.device_codename logger.info('Device is %s', self.device_codename) if args.additional_files is not None: self.addfiles_enable = args.additional_files if args.token is not None: self.token_enable = args.token if self.token_enable is not None: if args.token_url is not None: self.token_url = args.token_url if (args.token_str is not None) and (args.gsfid is not None): self.token = args.token_str self.gsfid = args.gsfid self.token_passed = True elif args.token_str is None and args.gsfid is None: pass else: raise TypeError( "Token string and GSFID have to be passed at the same time." ) if self.logging_enable: self.success_logfile = "apps_downloaded.log" self.failed_logfile = "apps_failed.log" self.unavail_logfile = "apps_not_available.log" ########## Public methods ########## def retrieve_token(self, force_new=False): """ Return a token. If a cached token exists, it will be used. Else, or if force_new=True, a new token is fetched from the token-dispenser server located at self.token_url. """ self.token, self.gsfid, self.gmail_address = self.get_cached_token() if (self.token is not None and not force_new): logger.info("Using cached token.") self.gsfid = hex(self.api.checkin(self.gmail_address, self.token))[2:] return logger.info("Retrieving token ...") logger.info("Token URL is %s", self.token_url) email_url = '/'.join([self.token_url, 'email']) response = requests.get(email_url) if response.status_code == 200: self.gmail_address = response.text else: logger.error("Cannot retrieve email address from token dispenser") raise ERRORS.TOKEN_DISPENSER_SERVER_ERROR token_url = '/'.join( [self.token_url, 'token/email', self.gmail_address]) response = requests.get(token_url) if response.status_code == 200: self.token = response.text self.gsfid = hex(self.api.checkin(self.gmail_address, self.token))[2:] logger.info("Email: %s", self.gmail_address) logger.info("Token: %s", self.token) logger.info("GsfId: %s", self.gsfid) self.write_cached_token(self.token, self.gsfid, self.gmail_address) else: logger.error("Token dispenser server error: %s", response.status_code) raise ERRORS.TOKEN_DISPENSER_SERVER_ERROR @hooks.connected def download(self, pkg_todownload): """ Download apks from the pkg_todownload list pkg_todownload -- list either of app names or of tuple of app names and filepath to write them Example: ['org.mozilla.focus','org.mozilla.firefox'] or [('org.mozilla.focus', 'org.mozilla.focus.apk'), ('org.mozilla.firefox', 'download/org.mozilla.firefox.apk')] """ success_downloads = [] failed_downloads = [] unavail_downloads = [] # case where no filenames have been provided for index, pkg in enumerate(pkg_todownload): if isinstance(pkg, str): pkg_todownload[index] = [pkg, None] # remove whitespaces before and after package name pkg_todownload[index][0] = pkg_todownload[index][0].strip() # Check for download folder download_folder = self.download_folder if not os.path.isdir(download_folder): os.makedirs(download_folder, exist_ok=True) # BulkDetails requires only one HTTP request # Get APK info from store details = list() for pkg in pkg_todownload: try: detail = self.api.details(pkg[0]) details.append(detail) except RequestError as request_error: failed_downloads.append((pkg, request_error)) if any([d is None for d in details]): logger.info( "Token has expired while downloading. Retrieving a new one.") self.refresh_token() details = self.api.bulkDetails([pkg[0] for pkg in pkg_todownload]) for position, (detail, item) in enumerate(zip(details, pkg_todownload)): packagename, filename = item if filename is None: if self.append_version: filename = "%s-v.%s.apk" % ( detail['docid'], detail['details']['appDetails']['versionString']) else: filename = "%s.apk" % detail['docid'] logger.info("%s / %s %s", 1 + position, len(pkg_todownload), packagename) # Download try: if detail['offer'][0]['checkoutFlowRequired']: method = self.api.delivery else: method = self.api.download data_iter = method(packagename, expansion_files=self.addfiles_enable) success_downloads.append(packagename) except IndexError as exc: logger.error( "Error while downloading %s : this package does not exist, " "try to search it via --search before", packagename) unavail_downloads.append((item, exc)) continue except Exception as exc: logger.error("Error while downloading %s : %s", packagename, exc) failed_downloads.append((item, exc)) continue filepath = os.path.join(download_folder, filename) #if file exists, continue if self.append_version and os.path.isfile(filepath): logger.info("File %s already exists, skipping.", filename) continue additional_data = data_iter['additionalData'] splits = data_iter['splits'] total_size = int(data_iter['file']['total_size']) chunk_size = int(data_iter['file']['chunk_size']) filenames = [] try: with open(filepath, "wb") as fbuffer: bar = util.progressbar(expected_size=total_size, hide=not self.progress_bar) for index, chunk in enumerate(data_iter['file']['data']): fbuffer.write(chunk) bar.show(index * chunk_size) bar.done() filenames.append(filepath) if additional_data: for obb_file in additional_data: obb_filename = "%s.%s.%s.obb" % ( obb_file["type"], obb_file["versionCode"], data_iter["docId"]) obb_filename = os.path.join(download_folder, obb_filename) obb_total_size = int(obb_file['file']['total_size']) obb_chunk_size = int(obb_file['file']['chunk_size']) with open(obb_filename, "wb") as fbuffer: bar = util.progressbar( expected_size=obb_total_size, hide=not self.progress_bar) for index, chunk in enumerate( obb_file["file"]["data"]): fbuffer.write(chunk) bar.show(index * obb_chunk_size) bar.done() if splits: for split in splits: split_total_size = int(split['file']['total_size']) split_chunk_size = int(split['file']['chunk_size']) splitfilename = "%s-%s.apk" % (detail['docid'], split['name']) splitfilename = os.path.join(download_folder, splitfilename) filenames.append(splitfilename) with open(splitfilename, "wb") as fbuffer: bar = util.progressbar( expected_size=split_total_size, hide=not self.progress_bar) for index, chunk in enumerate( split["file"]["data"]): fbuffer.write(chunk) bar.show(index * split_chunk_size) bar.done() print(filenames) except IOError as exc: logger.error("Error while writing %s : %s", packagename, exc) failed_downloads.append((item, exc)) success_items = set(success_downloads) failed_items = set([item[0] for item, error in failed_downloads]) unavail_items = set([item[0] for item, error in unavail_downloads]) to_download_items = set([item[0] for item in pkg_todownload]) self.write_logfiles(success_items, failed_items, unavail_items) self.print_failed(failed_downloads + unavail_downloads) return to_download_items - failed_items @hooks.connected def bulkdetails(self, pkg_todetail): """ Get deatils for apks from the pkg_todetail list pkg_todetail -- list of app names Example: ['org.mozilla.focus','org.mozilla.firefox'] """ # BulkDetails requires only one HTTP request # Get APK info from store try: details = self.api.bulkDetails(pkg_todetail) json.dump(details, open("details.json", "w")) except RequestError as request_error: logger.error("Error getting bulk details") return @hooks.connected def details(self, pkg_todetail): """ Get deatils for apks from the pkg_todetail list pkg_todetail -- a single app name Example: 'org.mozilla.focus' """ # BulkDetails requires only one HTTP request # Get APK info from store try: details = self.api.details(str(pkg_todetail)) json.dump(details, open("details.json", "w")) except RequestError as request_error: logger.error("Error getting details") pprint.pprint(request_error) return @hooks.connected def search(self, search_string, free_only=True, include_headers=True): """ Search the given string search_string on the Play Store. search_string -- the string to search on the Play Store free_only -- True if only costless apps should be searched for include_headers -- True if the result table should show column names """ try: results = self.api.search(search_string) except IndexError: results = [] if not results: logger.info("No result") return all_results = [] if include_headers: # Name of the columns col_names = [ "Title", "Creator", "Size", "Downloads", "Last Update", "AppID", "Version", "Rating" ] all_results.append(col_names) # Compute results values for doc in results: for cluster in doc["child"]: for app in cluster["child"]: # skip that app if it not free # or if it's beta (pre-registration) if ('offer' not in app # beta apps (pre-registration) or free_only and app['offer'][0][ 'checkoutFlowRequired'] # not free to download ): continue details = app['details']['appDetails'] detail = [ app['title'], app['creator'], util.sizeof_fmt(int(details['installationSize'])) if int(details['installationSize']) > 0 else 'N/A', details['numDownloads'], details['uploadDate'], app['docid'], details['versionCode'], "%.2f" % app["aggregateRating"]["starRating"] ] all_results.append(detail) # Print a nice table col_width = [] for column_indice in range(len(all_results[0])): col_length = max( [len("%s" % row[column_indice]) for row in all_results]) col_width.append(col_length + 2) for result in all_results: for indice, item in enumerate(result): out = "".join(str(item).strip().ljust(col_width[indice])) try: print(out, end='') except UnicodeEncodeError: out = out.encode('utf-8', errors='replace') print(out, end='') print() return all_results ########## End public methods ########## ########## Internal methods ########## def connect(self): """ Connect GplayCli to the Google Play API. If self.token_enable=True, the token from self.retrieve_token is used. Else, classical credentials are used. They might be stored into the keyring if the keyring package is installed. """ self.api = GooglePlayAPI(locale=self.locale, timezone=self.timezone, device_codename=self.device_codename) if self.token_enable: self.retrieve_token() return self.connect_token() else: ok, err = self.connect_credentials() if ok: self.token = self.api.authSubToken self.gsfid = self.api.gsfId return ok, err def connect_token(self): if self.token_passed: logger.info("Using passed token to connect to API") else: logger.info("Using auto retrieved token to connect to API") try: self.api.login(authSubToken=self.token, gsfId=int(self.gsfid, 16)) except (ValueError, IndexError, LoginError, DecodeError, SystemError, RequestError): logger.info( "Token has expired or is invalid. Retrieving a new one...") self.retrieve_token(force_new=True) self.connect() return True, None def connect_credentials(self): logger.info("Using credentials to connect to API") if self.gmail_password: logger.info("Using plaintext password") password = self.gmail_password elif self.keyring_service and HAVE_KEYRING: password = keyring.get_password(self.keyring_service, self.gmail_address) elif self.keyring_service and not HAVE_KEYRING: logger.error( "You asked for keyring service but keyring package is not installed" ) return False, ERRORS.KEYRING_NOT_INSTALLED else: logger.error("No password found. Check your configuration file.") return False, ERRORS.CANNOT_LOGIN_GPLAY try: self.api.login(email=self.gmail_address, password=password) except LoginError as e: logger.error( "Bad authentication, login or password incorrect (%s)", e) return False, ERRORS.CANNOT_LOGIN_GPLAY return True, None def get_cached_token(self): """ Retrieve a cached token, gsfid and device if exist. Otherwise return None. """ try: cache_dic = json.loads(open(self.tokencachefile).read()) token = cache_dic['token'] gsfid = cache_dic['gsfid'] address = cache_dic['address'] except (IOError, ValueError, KeyError) as e: # cache file does not exists or is corrupted print(e) token = None gsfid = None address = None logger.info('Cache file does not exists or is corrupted') return token, gsfid, address def write_cached_token(self, token, gsfid, address): """ Write the given token, gsfid and device to the self.tokencachefile file. Path and file are created if missing. """ cachedir = os.path.dirname(self.tokencachefile) if not cachedir: cachedir = os.getcwd() # creates cachedir if not exists if not os.path.exists(cachedir): os.makedirs(cachedir, exist_ok=True) with open(self.tokencachefile, 'w') as tcf: tcf.write( json.dumps({ 'token': token, 'gsfid': gsfid, 'address': address })) def prepare_analyse_apks(self): """ Gather apks to further check for update """ list_of_apks = util.list_folder_apks(self.download_folder) if not list_of_apks: return logger.info("Checking apks ...") to_update = self.analyse_local_apks(list_of_apks, self.download_folder) return self.prepare_download_updates(to_update) @hooks.connected def analyse_local_apks(self, list_of_apks, download_folder): """ Analyse apks in the list list_of_apks to check for updates and download updates in the download_folder folder. """ list_apks_to_update = [] package_bunch = [] version_codes = [] unavail_items = [] UNAVAIL = "This app is not available in the Play Store" for filename in list_of_apks: filepath = os.path.join(download_folder, filename) logger.info("Analyzing %s", filepath) apk = APK(filepath) packagename = apk.package package_bunch.append(packagename) version_codes.append(util.vcode(apk.version_code)) # BulkDetails requires only one HTTP request # Get APK info from store details = self.api.bulkDetails(package_bunch) for detail, packagename, filename, apk_version_code in zip( details, package_bunch, list_of_apks, version_codes): # this app is not in the play store if not detail: unavail_items.append(((packagename, filename), UNAVAIL)) continue store_version_code = detail['details']['appDetails']['versionCode'] # Compare if apk_version_code < store_version_code: # Add to the download list list_apks_to_update.append([ packagename, filename, apk_version_code, store_version_code ]) self.write_logfiles(None, None, [item[0][0] for item in unavail_items]) self.print_failed(unavail_items) return list_apks_to_update def prepare_download_updates(self, list_apks_to_update): """ Ask confirmation before updating apks """ if not list_apks_to_update: print("Everything is up to date !") return False pkg_todownload = [] # Ask confirmation before downloading print("The following applications will be updated :") for packagename, filename, apk_version_code, store_version_code in list_apks_to_update: print("%s Version : %s -> %s" % (filename, apk_version_code, store_version_code)) pkg_todownload.append([packagename, filename]) if not self.yes: print("Do you agree?") return_value = input('y/n ?') if self.yes or return_value == 'y': logger.info("Downloading ...") downloaded_packages = self.download(pkg_todownload) return_string = ' '.join(downloaded_packages) print("Updated: %s" % return_string) return True @staticmethod def print_failed(failed_downloads): """ Print/log failed downloads from failed_downloads """ # Info message if not failed_downloads: return else: message = "A few packages could not be downloaded :\n" for pkg, exception in failed_downloads: package_name, filename = pkg if filename is not None: message += "%s : %s\n" % (filename, package_name) else: message += "%s\n" % package_name message += "%s\n" % exception logger.error(message) def write_logfiles(self, success, failed, unavail): """ Write success failed and unavail list to logfiles """ if not self.logging_enable: return for result, logfile in [(success, self.success_logfile), (failed, self.failed_logfile), (unavail, self.unavail_logfile)]: if not result: continue with open(logfile, 'w') as _buffer: for package in result: print(package, file=_buffer)
from gpapi.googleplay import GooglePlayAPI, RequestError import sys import os gsfId = int(os.environ["GPAPI_GSFID"]) authSubToken = os.environ["GPAPI_TOKEN"] server = GooglePlayAPI("it_IT", "Europe/Rome") # LOGIN print("\nLogin with ac2dm token and gsfId saved\n") server.login(None, None, gsfId, authSubToken) # SEARCH print("\nSearch suggestion for \"fir\"\n") print(server.searchSuggest("fir")) result = server.search("firefox") for doc in result: if 'docid' in doc: print("doc: {}".format(doc["docid"])) for cluster in doc["child"]: print("\tcluster: {}".format(cluster["docid"])) for app in cluster["child"]: print("\t\tapp: {}".format(app["docid"])) # HOME APPS print("\nFetching apps from play store home\n") result = server.home() for cluster in result:
class Downloader: def __init__(self): self.gid = "" self.gwd = "" self.authSubToken = "" self.options = Options() self.options.headless = False self.chrome_driver = "./chromedriver" if platform.system() == "Windows": self.chrome_driver = "./chromedriver.exe" self.request_url = "https://accounts.google.com/b/0/DisplayUnlockCaptcha" self.apkfile_path = os.path.join("./tmp/") if os.path.exists(self.apkfile_path) == False: os.mkdir(self.apkfile_path) self.server = GooglePlayAPI('ko_KR', 'Asia/Seoul') self.devices_codenames = GooglePlayAPI.getDevicesCodenames() self.devices_codenames.reverse() # LOGIN def firstlogin(self, gid, gpw): self.gid = gid self.gpw = gpw #print(self.gid+" "+self.gpw) try: print('\nLogging in with email and password\n') self.server.login(self.gid, self.gpw, None, None) self.gsfId = self.server.gsfId self.authSubToken = self.server.authSubToken return self.secondlogin() except: #traceback.print_exc() return False def secondlogin(self): print( '\nNow trying secondary login with ac2dm token and gsfId saved\n') self.server = GooglePlayAPI('ko_KR', 'Asia/Seoul') self.server.login(None, None, self.gsfId, self.authSubToken) # call DOWNLOAD self.startdownload() return True def download_packages(self, package_list, logger=""): for package in package_list: self.startdownload(package, logger) logger.info(json.dumps({"step": "complete"})) def startdownload(self, pkgid="", logger=""): self.pkgid = pkgid if self.pkgid == "": return self.asset = Assets() self.asset.update_status(self.pkgid, "downloading") print('\nAttempting to download %s\n' % self.pkgid) logger.info(json.dumps({"step": "start", "package": self.pkgid})) try: fl = "" for codename in self.devices_codenames: self.server = GooglePlayAPI('ko_KR', 'Asia/Seoul', device_codename=codename) self.server.login(None, None, self.gsfId, self.authSubToken) try: fl = self.server.download(self.pkgid) break except Exception as e: continue if fl == "": raise Exception("No Device") with open(self.apkfile_path + self.pkgid + '.apk', 'wb') as apk_file: for chunk in fl.get('file').get('data'): apk_file.write(chunk) print('\n[+] Download successful\n') logger.info(json.dumps({"step": "finish", "package": self.pkgid})) self.asset.update_status(self.pkgid, "downloaded") logger.info(json.dumps({"step": "check", "package": self.pkgid})) self.check_aws_sdk_common(self.pkgid, logger) except: print("Unexpected error:", sys.exc_info()[0]) traceback.print_exc() #time.sleep(3) pass # time.sleep(1) def check_aws_sdk(self, pkgid): print('[+] Checking AWS_SDK') apkfinal_path = self.apkfile_path + pkgid + '.apk' s = os.popen('/usr/bin/grep -i "aws-android-sdk" {0}'.format( apkfinal_path)).read() if 'matches' in s: print("[!] This Application use AWS_SDK") pass else: print("[!] NO AWS_SDK FOUND") os.remove(apkfinal_path) def check_aws_sdk_common(self, pkgid, logger=""): print('[+] Checking AWS_SDK') apkfinal_path = self.apkfile_path + pkgid + '.apk' if re.search(b'(?i)aws-android-sdk', open(apkfinal_path, "rb").read()): print("[!] This Application use AWS_SDK") self.asset.exist_sdk(pkgid, True) logger.info( json.dumps({ "step": "result", "package": self.pkgid, "sdk": True })) pass else: print("[!] NO AWS_SDK FOUND") self.asset.exist_sdk(pkgid, False) logger.info( json.dumps({ "step": "result", "package": self.pkgid, "sdk": False }))
server = GooglePlayAPI('en_US', 'America/New_York') if (len(sys.argv) < 2): print "Usage: %s packagename [filename]" print "Download an app." print "If filename is not present, will write to packagename.apk." sys.exit(0) packagename = sys.argv[1] if (len(sys.argv) == 3): filename = sys.argv[2] else: filename = packagename + ".apk" server.login(GOOGLE_LOGIN, GOOGLE_PASSWORD, None, None) fl = server.download(packagename) with open(filename, 'wb') as apk_file: for chunk in fl.get('file').get('data'): apk_file.write(chunk) if fl['additionalData'] != []: print('\nDownloading additional files\n') for obb in fl['additionalData']: name = obb['type'] + '.' + str( obb['versionCode']) + '.' + fl['docId'] + '.obb' with open(name, 'wb') as second: for chunk in obb.get('file').get('data'): second.write(chunk) print('\nDownload successful\n')
from gpapi.googleplay import GooglePlayAPI import argparse ap = argparse.ArgumentParser(description='Test download of expansion files') ap.add_argument('-e', '--email', dest='email', help='google username') ap.add_argument('-p', '--password', dest='password', help='google password') args = ap.parse_args() server = GooglePlayAPI('it_IT', 'Europe/Rome') # LOGIN print('\nLogging in with email and password\n') server.login(args.email, args.password, None, None) docid = 'com.pixel.gun3d' print('\nDownloading apk\n') download = server.download(docid, expansion_files=True) with open(download['docId'] + '.apk', 'wb') as first: for chunk in download.get('file').get('data'): first.write(chunk) print('\nDownloading additional files\n') for obb in download['additionalData']: name = obb['type'] + '.' + str( obb['versionCode']) + '.' + download['docId'] + '.obb' with open(name, 'wb') as second: for chunk in obb.get('file').get('data'): second.write(chunk)
base_path = os.path.abspath("") return os.path.join(base_path, relative_path) input("Press Enter To Start") with open(resource_path("login.json")) as logins: device_log_ins = json.load(logins) current_log_in = device_log_ins['pixel_2'] # Change this to change device server = GooglePlayAPI("en_US", "America/Toronto", current_log_in['deviceName']) print("Logging in...") server.login(gsfId=current_log_in['gsfId'], authSubToken=current_log_in['authSubToken']) print("Complete!") # Replace these with your package and version code names docid = "com.package.name" versionCode = 1234 details = server.details(docid, versionCode) title = details['title'].replace("\n", "-") print(f"""App Details Below: Name: {title} Package: {details['docid']} Version Code: {details['details']['appDetails']['versionCode']} App Version: {details['details']['appDetails']['versionString']}
import sys import argparse ap = argparse.ArgumentParser(description='Test') ap.add_argument('-e', '--email', dest='email', help='google username') ap.add_argument('-p', '--password', dest='password', help='google password') args = ap.parse_args() server = GooglePlayAPI('en_EN', 'Usa/Chicago') # LOGIN print('\nLogging in with email and password\n') server.login(args.email, args.password, None, None) gsfId = server.gsfId authSubToken = server.authSubToken print('\nNow trying secondary login with ac2dm token and gsfId saved\n') # server = GooglePlayAPI('it_IT', 'Europe/Rome') server.login(None, None, gsfId, authSubToken) # SEARCH apps = server.search('telegram', 34, None) print('\nSearch suggestion for "fir"\n') print(server.searchSuggest('fir')) print('nb_result: 34')
from gpapi.googleplay import GooglePlayAPI import argparse ap = argparse.ArgumentParser(description='Test download of expansion files') ap.add_argument('-e', '--email', dest='email', help='google username') ap.add_argument('-p', '--password', dest='password', help='google password') args = ap.parse_args() server = GooglePlayAPI('it_IT', 'Europe/Rome') # LOGIN print('\nLogging in with email and password\n') server.login(args.email, args.password, None, None) docid = 'com.pixel.gun3d' print('\nDownloading apk\n') download = server.download(docid, expansion_files=True) with open(download['docId'] + '.apk', 'wb') as first: for chunk in download.get('file').get('data'): first.write(chunk) print('\nDownloading additional files\n') for obb in download['additionalData']: name = obb['type'] + '.' + str(obb['versionCode']) + '.' + download['docId'] + '.obb' with open(name, 'wb') as second: for chunk in obb.get('file').get('data'): second.write(chunk)
import random import time import multiprocessing from gpapi.googleplay import GooglePlayAPI # https://github.com/krnick/GooglePlay-API mail = None passwd = None # The gsfId and authSubToken are retrieved from the mail and passwd. # call api.gsfId and api.authSubToken gsfId = 4132308869485558804 authSubToken = '0gc-qsIOLGFubtuKlPD9UE-24mcMCanGWWFBFLBF1fiCjNdIAPmFoQDBfQKNDHYJSAesxw.' api = GooglePlayAPI(locale="en_US", timezone="UTC", device_codename="hero2lte") # api.login(email=mail, password=passwd) # do this the first time!!! api.login(gsfId=gsfId, authSubToken=authSubToken) print("Login is ", api.authSubToken != None) import os files_present = os.listdir("../outapks") files_present = set(files_present) def searchApps(query): search_res = api.search(query) # region search results print apps = [] for doc in search_res: if 'docid' in doc:
with open(args.configfile, 'r+') as configfile: config = configparser.SafeConfigParser() config.readfp(configfile) try: gsfId = config.get(CONFIG_SECTION, CONFIG_GSFID) authSubToken = config.get(CONFIG_SECTION, CONFIG_AUTHSUBTOKEN) except (configparser.NoOptionError, configparser.NoSectionError): print("Missing one of gsfId or authSubToken, fetching new...") email = input("Enter email: ") password = getpass.getpass("Enter password: ") server = GooglePlayAPI(SERVER_LOCALE, SERVER_TZ, device_codename=args.devicecode) server.login(email, password, None, None) gsfId = str(server.gsfId) authSubToken = server.authSubToken if not config.has_section(CONFIG_SECTION): config.add_section(CONFIG_SECTION) # write fetched values to config config.set(CONFIG_SECTION, CONFIG_GSFID, gsfId) config.set(CONFIG_SECTION, CONFIG_AUTHSUBTOKEN, str(authSubToken)) config.write(configfile) server = GooglePlayAPI(SERVER_LOCALE, SERVER_TZ, device_codename=args.devicecode) server.login(None, None, int(gsfId), authSubToken)