Exemple #1
0
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'])
Exemple #2
0
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)
Exemple #4
0
 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:]))
Exemple #6
0
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)
Exemple #8
0
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
            })
Exemple #9
0
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'}
Exemple #10
0
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)
Exemple #11
0
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)
Exemple #12
0
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"
Exemple #13
0
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
Exemple #14
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')
Exemple #15
0
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'}
Exemple #16
0
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:
Exemple #17
0
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",
Exemple #18
0
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
Exemple #19
0
	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'
Exemple #20
0
                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 = []
Exemple #21
0
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
Exemple #22
0
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]
Exemple #23
0
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)
Exemple #24
0
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:
Exemple #25
0
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)
Exemple #28
0
        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']}
Exemple #29
0
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)
Exemple #31
0
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:
Exemple #32
0
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)