Esempio n. 1
0
    def _get_fullname(device):
        profileView = ProfileView(device)
        fullname = ""
        try:
            fullname = profileView.getFullName()
        except Exception as e:
            logger.error("Cannot find full name.")
            logger.debug(f"Error: {e}")

        return fullname
Esempio n. 2
0
    def _get_posts_count(device):
        profileView = ProfileView(device)
        posts_count = 0
        try:
            posts_count = profileView.getPostsCount()
        except Exception as e:
            logger.error("Cannot find posts count. Default is 0.")
            logger.debug(f"Error: {e}")

        return posts_count
Esempio n. 3
0
    def _is_private_account(device):
        private = None
        profileView = ProfileView(device)
        try:
            private = profileView.isPrivateAccount()
        except Exception as e:
            logger.error("Cannot find whether it is private or not")
            logger.debug(f"Error: {e}")

        return private
Esempio n. 4
0
    def _get_followers_and_followings(device):
        followers = 0
        profileView = ProfileView(device)
        try:
            followers = profileView.getFollowersCount()
        except Exception as e:
            logger.error(f"Cannot find followers count view, default is {followers}")
            logger.debug(f"Error: {e}")

        followings = 0
        try:
            followings = profileView.getFollowingCount()
        except Exception as e:
            logger.error(f"Cannot find followings count view, default is {followings}")
            logger.debug(f"Error: {e}")

        return followers, followings
Esempio n. 5
0
def interact_with_user(
    device,
    username,
    my_username,
    likes_count,
    on_like,
    stories_count,
    stories_percentage,
    on_watch,
    can_follow,
    follow_percentage,
    profile_filter,
    args,
    session_state,
) -> Tuple[bool, bool]:
    """
    :return: (whether interaction succeed, whether @username was followed during the interaction)
    """
    if username == my_username:
        logger.info("It's you, skip.")
        return False, False

    random_sleep()

    if not profile_filter.check_profile(device, username):
        return False, False

    likes_value = get_value(likes_count, "Likes count: {}", 2)
    if likes_value > 12:
        logger.error("Max number of likes per user is 12.")
        likes_value = 12

    profile_view = ProfileView(device)
    is_private = profile_view.isPrivateAccount()
    posts_count = profile_view.getPostsCount()
    is_empty = posts_count == 0

    if is_private or is_empty:
        private_empty = "Private" if is_private else "Empty"
        logger.info(f"{private_empty} account.",
                    extra={"color": f"{Fore.GREEN}"})
        if can_follow and profile_filter.can_follow_private_or_empty():
            followed = _follow(device, username, follow_percentage, args,
                               session_state)
        else:
            followed = False
            logger.info("Skip user.", extra={"color": f"{Fore.GREEN}"})
        return False, followed

    _watch_stories(
        device,
        profile_view,
        username,
        stories_count,
        stories_percentage,
        on_watch,
        args,
        session_state,
    )

    ProfileView(device).swipe_to_fit_posts()
    random_sleep()
    start_time = time()
    full_rows, columns_last_row = profile_view.count_photo_in_view()
    end_time = format(time() - start_time, ".2f")
    photos_indices = list(range(0, full_rows * 3 + (columns_last_row)))
    logger.info(
        f"There are {len(photos_indices)} posts fully visible. Calculated in {end_time}s"
    )
    if likes_value > len(photos_indices):
        logger.info(f"Only {photos_indices} photos available")
    else:
        shuffle(photos_indices)
        photos_indices = photos_indices[:likes_value]
        photos_indices = sorted(photos_indices)
    for i in range(0, len(photos_indices)):
        photo_index = photos_indices[i]
        row = photo_index // 3
        column = photo_index - row * 3
        logger.info(f"Open post #{i + 1} ({row + 1} row, {column + 1} column)")
        opened_post_view = PostsGridView(device).navigateToPost(row, column)
        random_sleep()

        like_succeed = False
        if opened_post_view:
            logger.info("Double click post.")

            like_succeed = opened_post_view.likePost()
            if not like_succeed:
                logger.debug("Double click failed. Try the like button.")
                like_succeed = opened_post_view.likePost(click_btn_like=True)

            if like_succeed:
                logger.debug("Like succeed. Check for block.")
                detect_block(device)
                on_like()
            else:
                logger.warning("Fail to like post. Let's continue...")

            logger.info("Back to profile.")
            device.back()

        if not opened_post_view or not like_succeed:
            reason = "open" if not opened_post_view else "like"
            logger.info(
                f"Could not {reason} photo. Posts count: {posts_count}")

            if can_follow and profile_filter.can_follow_private_or_empty():
                followed = _follow(device, username, follow_percentage, args,
                                   session_state)
            else:
                followed = False

            if not followed:
                logger.info("Skip user.", extra={"color": f"{Fore.GREEN}"})
            return False, followed

        random_sleep()
    if can_follow:
        return True, _follow(device, username, follow_percentage, args,
                             session_state)

    return True, False
Esempio n. 6
0
def interact_with_user(
    device,
    username,
    my_username,
    likes_count,
    on_like,
    can_follow,
    follow_percentage,
    profile_filter,
) -> Tuple[bool, bool]:
    """
    :return: (whether interaction succeed, whether @username was followed during the interaction)
    """
    if username == my_username:
        logger.info("It's you, skip.")
        return False, False

    random_sleep()

    if not profile_filter.check_profile(device, username):
        return False, False

    likes_value = get_value(likes_count, "Likes count: {}", 2)
    if likes_value > 12:
        logger.error("Max number of likes per user is 12")
        likes_value = 12

    profile_view = ProfileView(device)
    is_private = profile_view.isPrivateAccount()
    posts_count = profile_view.getPostsCount()
    is_empty = posts_count == 0

    if is_private or is_empty:
        private_empty = "Private" if is_private else "Empty"
        logger.info(
            f"{private_empty} account.",
            extra={"color": f"{Fore.GREEN}"},
        )
        if can_follow and profile_filter.can_follow_private_or_empty():
            followed = _follow(device, username, follow_percentage)
        else:
            followed = False
            logger.info(
                "Skip user.",
                extra={"color": f"{Fore.GREEN}"},
            )
        return False, followed

    posts_tab_view = profile_view.navigateToPostsTab()
    if posts_tab_view.scrollDown():  # scroll down to view all maximum 12 posts
        logger.info("Scrolled down to see more posts.")
    random_sleep()
    number_of_rows_to_use = min((likes_value * 2) // 3 + 1, 4)
    photos_indices = list(range(0, number_of_rows_to_use * 3))
    shuffle(photos_indices)
    photos_indices = photos_indices[:likes_value]
    photos_indices = sorted(photos_indices)
    for i in range(0, likes_value):
        photo_index = photos_indices[i]
        row = photo_index // 3
        column = photo_index - row * 3
        logger.info(f"Open post #{i + 1} ({row + 1} row, {column + 1} column")
        opened_post_view = posts_tab_view.navigateToPost(row, column)
        random_sleep()

        like_succeed = False
        if opened_post_view:
            logger.info("Double click post")
            opened_post_view.likePost()
            random_sleep()
            if not opened_post_view.isPostLiked():
                logger.debug("Double click failed. Try the like button.")
                opened_post_view.likePost(click_btn_like=True)
                random_sleep()

            like_succeed = opened_post_view.isPostLiked()
            if like_succeed:
                detect_block(device)
                on_like()

            logger.info("Back to profile")
            device.back()

        if not opened_post_view or not like_succeed:
            reason = "open" if not opened_post_view else "like"
            logger.info(
                f"Could not {reason} photo. Posts count: {posts_count}")

            if can_follow and profile_filter.can_follow_private_or_empty():
                followed = _follow(device, username, follow_percentage)
            else:
                followed = False

            if not followed:
                logger.info(
                    "Skip user.",
                    extra={"color": f"{Fore.GREEN}"},
                )
            return False, followed

        random_sleep()

    if can_follow:
        return True, _follow(device, username, follow_percentage)

    return True, False
Esempio n. 7
0
def run():
    # Some plugins need config values without being passed
    # through. Because we do a weird config/argparse hybrid,
    # we need to load the configs in a weird way
    load_filter(configs)
    load_interaction(configs)
    load_utils(configs)
    load_views(configs)

    if not configs.args or not check_adb_connection():
        return

    if len(configs.enabled) < 1:
        logger.error("You have to specify one of the actions: " +
                     ", ".join(configs.actions))
        return

    logger.info("Instagram version: " + get_instagram_version())
    device = create_device(configs.device_id, configs.args.uia_version)

    if device is None:
        return

    while True:
        session_state = SessionState(configs)
        sessions.append(session_state)

        device.wake_up()

        logger.info(
            "-------- START: " + str(session_state.startTime) + " --------",
            extra={"color": f"{Style.BRIGHT}{Fore.YELLOW}"},
        )

        if not device.get_info()["screenOn"]:
            device.press_power()
        if device.is_screen_locked():
            device.unlock()
            if device.is_screen_locked():
                logger.error(
                    "Can't unlock your screen. There may be a passcode on it. If you would like your screen to be turned on and unlocked automatically, please remove the passcode."
                )
                exit(0)

        logger.info("Device screen on and unlocked.")

        open_instagram(device, configs.args.screen_record)

        try:
            profileView = TabBarView(device).navigateToProfile()
            random_sleep()
            if configs.args.username is not None:
                success = AccountView(device).changeToUsername(
                    configs.args.username)
                if not success:
                    logger.error(
                        f"Not able to change to {configs.args.username}, abort!"
                    )
                    device.back()
                    break

            (
                session_state.my_username,
                session_state.my_followers_count,
                session_state.my_following_count,
            ) = profileView.getProfileInfo()
        except Exception as e:
            logger.error(f"Exception: {e}")
            save_crash(device)
            switch_to_english(device)
            # Try again on the correct language
            profileView = TabBarView(device).navigateToProfile()
            random_sleep()
            (
                session_state.my_username,
                session_state.my_followers_count,
                session_state.my_following_count,
            ) = profileView.getProfileInfo()

        if (session_state.my_username is None
                or session_state.my_followers_count is None
                or session_state.my_following_count is None):
            logger.critical(
                "Could not get one of the following from your profile: username, # of followers, # of followings. This is typically due to a soft ban. Review the crash screenshot to see if this is the case."
            )
            logger.critical(
                f"Username: {session_state.my_username}, Followers: {session_state.my_followers_count}, Following: {session_state.my_following_count}"
            )
            save_crash(device)
            exit(1)

        if not is_log_file_updated():
            try:
                update_log_file_name(session_state.my_username)
            except Exception as e:
                logger.error(
                    f"Failed to update log file name. Will continue anyway. {e}"
                )
                save_crash(device)

        report_string = f"Hello, @{session_state.my_username}! You have {session_state.my_followers_count} followers and {session_state.my_following_count} followings so far."

        logger.info(report_string, extra={"color": f"{Style.BRIGHT}"})

        storage = Storage(session_state.my_username)
        for plugin in configs.enabled:
            if not session_state.check_limit(
                    configs.args, limit_type=session_state.Limit.ALL,
                    output=False):
                logger.info(f"Current job: {plugin}",
                            extra={"color": f"{Fore.BLUE}"})
                if ProfileView(
                        device).getUsername() != session_state.my_username:
                    logger.debug("Not in your main profile.")
                    TabBarView(device).navigateToProfile()
                configs.actions[plugin].run(device, configs, storage, sessions,
                                            plugin)

            else:
                logger.info(
                    "Successful or Total Interactions limit reached. Ending session."
                )
                break

        close_instagram(device, configs.args.screen_record)
        session_state.finishTime = datetime.now()

        if configs.args.screen_sleep:
            device.screen_off()
            logger.info("Screen turned off for sleeping time")

        logger.info(
            "-------- FINISH: " + str(session_state.finishTime) + " --------",
            extra={"color": f"{Style.BRIGHT}{Fore.YELLOW}"},
        )

        if configs.args.repeat:
            print_full_report(sessions)
            repeat = get_value(configs.args.repeat, "Sleep for {} minutes",
                               180)
            try:
                sleep(60 * repeat)
            except KeyboardInterrupt:
                print_full_report(sessions)
                sessions.persist(directory=session_state.my_username)
                exit(0)
        else:
            break

    print_full_report(sessions)
    sessions.persist(directory=session_state.my_username)
Esempio n. 8
0
 def _get_profile_biography(device):
     profileView = ProfileView(device)
     return profileView.getProfileBiography()
Esempio n. 9
0
    def check_profile(self, device, username):
        """
        This method assumes being on someone's profile already.
        """
        if self.conditions is None:
            return True

        field_skip_business = self.conditions.get(FIELD_SKIP_BUSINESS, False)
        field_skip_non_business = self.conditions.get(FIELD_SKIP_NON_BUSINESS, False)
        field_skip_following = self.conditions.get(FIELD_SKIP_FOLLOWING, False)
        field_skip_follower = self.conditions.get(FIELD_SKIP_FOLLOWER, False)
        field_min_followers = self.conditions.get(FIELD_MIN_FOLLOWERS)
        field_max_followers = self.conditions.get(FIELD_MAX_FOLLOWERS)
        field_min_followings = self.conditions.get(FIELD_MIN_FOLLOWINGS)
        field_max_followings = self.conditions.get(FIELD_MAX_FOLLOWINGS)
        field_min_potency_ratio = self.conditions.get(FIELD_MIN_POTENCY_RATIO, 0)
        field_max_potency_ratio = self.conditions.get(FIELD_MAX_POTENCY_RATIO, 999)
        field_blacklist_words = self.conditions.get(FIELD_BLACKLIST_WORDS, [])
        field_mandatory_words = self.conditions.get(FIELD_MANDATORY_WORDS, [])
        field_interact_only_private = self.conditions.get(
            FIELD_INTERACT_ONLY_PRIVATE, False
        )
        field_specific_alphabet = self.conditions.get(FIELD_SPECIFIC_ALPHABET)
        field_min_posts = self.conditions.get(FIELD_MIN_POSTS)

        if field_skip_following or field_skip_follower:
            profileView = ProfileView(device)
            button, text = profileView.getFollowButton()

            if field_skip_following:
                if text == FollowStatus.FOLLOWING:
                    logger.info(
                        f"You follow @{username}, skip.",
                        extra={"color": f"{Fore.GREEN}"},
                    )
                    return False

            if field_skip_follower:
                if text == FollowStatus.FOLLOW_BACK:
                    logger.info(
                        f"@{username} follows you, skip.",
                        extra={"color": f"{Fore.GREEN}"},
                    )
                    return False

        if field_interact_only_private:
            logger.debug("Checking if account is private...")
            is_private = self._is_private_account(device)

            if field_interact_only_private and is_private is False:

                logger.info(
                    f"@{username} has public account, skip.",
                    extra={"color": f"{Fore.GREEN}"},
                )
                return False

            elif field_interact_only_private and is_private is None:
                logger.info(
                    f"Could not determine if @{username} is public or private, skip.",
                    extra={"color": f"{Fore.GREEN}"},
                )
                return False

        if (
            field_min_followers is not None
            or field_max_followers is not None
            or field_min_followings is not None
            or field_max_followings is not None
            or field_min_potency_ratio is not None
            or field_max_potency_ratio is not None
        ):
            logger.debug(
                "Checking if account is within follower/following parameters..."
            )
            followers, followings = self._get_followers_and_followings(device)
            if followers is not None and followings is not None:
                if field_min_followers is not None and followers < int(
                    field_min_followers
                ):
                    logger.info(
                        f"@{username} has less than {field_min_followers} followers, skip.",
                        extra={"color": f"{Fore.GREEN}"},
                    )
                    return False
                if field_max_followers is not None and followers > int(
                    field_max_followers
                ):
                    logger.info(
                        f"@{username} has has more than {field_max_followers} followers, skip.",
                        extra={"color": f"{Fore.GREEN}"},
                    )
                    return False
                if field_min_followings is not None and followings < int(
                    field_min_followings
                ):
                    logger.info(
                        f"@{username} has less than {field_min_followings} followings, skip.",
                        extra={"color": f"{Fore.GREEN}"},
                    )
                    return False
                if field_max_followings is not None and followings > int(
                    field_max_followings
                ):
                    logger.info(
                        f"@{username} has more than {field_max_followings} followings, skip.",
                        extra={"color": f"{Fore.GREEN}"},
                    )
                    return False

                if field_min_potency_ratio != 0 or field_max_potency_ratio != 999:
                    if (
                        int(followings) == 0
                        or followers / followings < float(field_min_potency_ratio)
                        or followers / followings > float(field_max_potency_ratio)
                    ):
                        logger.info(
                            f"@{username}'s potency ratio is not between {field_min_potency_ratio} and {field_max_potency_ratio}, skip.",
                            extra={"color": f"{Fore.GREEN}"},
                        )
                        return False

            else:
                logger.critical(
                    "Either followers, followings, or possibly both are undefined. Cannot filter."
                )
                return False

        if field_skip_business or field_skip_non_business:
            logger.debug("Checking if account is a business...")
            has_business_category = self._has_business_category(device)
            if field_skip_business and has_business_category is True:
                logger.info(
                    f"@{username} has business account, skip.",
                    extra={"color": f"{Fore.GREEN}"},
                )
                return False
            if field_skip_non_business and has_business_category is False:
                logger.info(
                    f"@{username} has non business account, skip.",
                    extra={"color": f"{Fore.GREEN}"},
                )
                return False

        if field_min_posts is not None:
            posts_count = self._get_posts_count(device)
            if field_min_posts > posts_count:
                logger.info(
                    f"@{username} doesn't have enough posts ({posts_count}), skip.",
                    extra={"color": f"{Fore.GREEN}"},
                )
                return False

        if (
            len(field_blacklist_words) > 0
            or len(field_mandatory_words) > 0
            or field_specific_alphabet is not None
        ):
            logger.debug("Pulling biography...")
            biography = self._get_profile_biography(device)

            if len(field_blacklist_words) > 0:
                logger.debug(
                    "Checking if account has blacklisted words in biography..."
                )
                # If we found a blacklist word return False
                for w in field_blacklist_words:
                    blacklist_words = re.compile(
                        r"\b({0})\b".format(w), flags=re.IGNORECASE
                    ).search(biography)
                    if blacklist_words is not None:
                        logger.info(
                            f"@{username} found a blacklisted word '{w}' in biography, skip.",
                            extra={"color": f"{Fore.GREEN}"},
                        )
                        return False

            if len(field_mandatory_words) > 0:
                logger.debug("Checking if account has mandatory words in biography...")
                mandatory_words = [
                    w
                    for w in field_mandatory_words
                    if re.compile(r"\b({0})\b".format(w), flags=re.IGNORECASE).search(
                        biography
                    )
                    is not None
                ]
                if mandatory_words == []:
                    logger.info(
                        f"@{username} mandatory words not found in biography, skip.",
                        extra={"color": f"{Fore.GREEN}"},
                    )
                    return False

            if field_specific_alphabet is not None:
                if biography != "":
                    logger.debug(
                        "Checking primary character set of account biography..."
                    )
                    biography = biography.replace("\n", "")
                    alphabet = self._find_alphabet(biography, field_specific_alphabet)

                    if alphabet != field_specific_alphabet and alphabet != "":
                        logger.info(
                            f"@{username}'s biography alphabet is not {field_specific_alphabet}. ({alphabet}), skip.",
                            extra={"color": f"{Fore.GREEN}"},
                        )
                        return False
                else:
                    logger.debug("Checking primary character set of name...")
                    fullname = self._get_fullname(device)

                    if fullname != "":
                        alphabet = self._find_alphabet(
                            fullname, field_specific_alphabet
                        )
                        if alphabet != field_specific_alphabet and alphabet != "":
                            logger.info(
                                f"@{username}'s name alphabet is not {field_specific_alphabet}. ({alphabet}), skip.",
                                extra={"color": f"{Fore.GREEN}"},
                            )
                            return False

        # If no filters return false, we are good to proceed
        return True
Esempio n. 10
0
    def handle_hashtag(
        self,
        device,
        hashtag,
        likes_count,
        stories_count,
        stories_percentage,
        follow_percentage,
        follow_limit,
        hashtag_likers_recent,
        # recent_tab,
        storage,
        profile_filter,
        on_like,
        on_watch,
        on_interaction,
    ):
        interaction = partial(
            interact_with_user,
            my_username=self.session_state.my_username,
            likes_count=likes_count,
            stories_count=stories_count,
            stories_percentage=stories_percentage,
            follow_percentage=follow_percentage,
            on_like=on_like,
            on_watch=on_watch,
            profile_filter=profile_filter,
            args=self.args,
            session_state=self.session_state,
        )

        is_follow_limit_reached = partial(
            is_follow_limit_reached_for_source,
            follow_limit=follow_limit,
            source=hashtag,
            session_state=self.session_state,
        )
        search_view = TabBarView(device).navigateToSearch()
        random_sleep()
        if not search_view.navigateToHashtag(hashtag):
            return

        if hashtag_likers_recent != None:
            logger.info("Switching to Recent tab")
            HashTagView(device)._getRecentTab().click()
            random_sleep()
            random_sleep(
            )  # wonder if it possible to check if everything is loaded instead of doing multiple random_sleep..

        logger.info("Opening the first result")

        result_view = HashTagView(device)._getRecyclerView()
        HashTagView(device)._getFistImageView(result_view).click()
        random_sleep()

        posts_list_view = ProfileView(device)._getRecyclerView()
        posts_end_detector = ScrollEndDetector(repeats_to_end=2)
        first_post = True
        post_description = ""
        while True:
            if first_post:
                PostsViewList(device).swipe_to_fit_posts(True)
                first_post = False
            if not OpenedPostView(device).open_likers():
                logger.info("No likes, let's scroll down.",
                            extra={"color": f"{Fore.GREEN}"})
                PostsViewList(device).swipe_to_fit_posts(False)

                flag, post_description = PostsViewList(
                    device).check_if_last_post(post_description)
                if not flag:
                    continue
                else:
                    break

            logger.info("List of likers is opened.")
            posts_end_detector.notify_new_page()
            random_sleep()

            likes_list_view = OpenedPostView(device)._getListViewLikers()
            prev_screen_iterated_likers = []

            while True:
                logger.info("Iterate over visible likers.")
                screen_iterated_likers = []

                try:
                    for item in OpenedPostView(device)._getUserCountainer():
                        username_view = OpenedPostView(device)._getUserName(
                            item)

                        if not username_view.exists(quick=True):
                            logger.info(
                                "Next item not found: probably reached end of the screen.",
                                extra={"color": f"{Fore.GREEN}"},
                            )
                            break

                        username = username_view.get_text()
                        screen_iterated_likers.append(username)
                        posts_end_detector.notify_username_iterated(username)

                        if storage.is_user_in_blacklist(username):
                            logger.info(f"@{username} is in blacklist. Skip.")
                            continue
                        elif storage.check_user_was_interacted(username):
                            logger.info(
                                f"@{username}: already interacted. Skip.")
                            continue
                        else:
                            logger.info(f"@{username}: interact")
                            username_view.click()

                        can_follow = not is_follow_limit_reached() and (
                            storage.get_following_status(username)
                            == FollowingStatus.NONE
                            or storage.get_following_status(username)
                            == FollowingStatus.NOT_IN_LIST)

                        interaction_succeed, followed = interaction(
                            device, username=username, can_follow=can_follow)
                        storage.add_interacted_user(username,
                                                    followed=followed)
                        can_continue = on_interaction(
                            succeed=interaction_succeed, followed=followed)
                        if not can_continue:
                            return

                        logger.info("Back to likers list")
                        device.back()
                        random_sleep()
                except IndexError:
                    logger.info(
                        "Cannot get next item: probably reached end of the screen.",
                        extra={"color": f"{Fore.GREEN}"},
                    )

                if screen_iterated_likers == prev_screen_iterated_likers:
                    logger.info(
                        "Iterated exactly the same likers twice, finish.",
                        extra={"color": f"{Fore.GREEN}"},
                    )
                    logger.info(f"Back to {hashtag}")
                    device.back()
                    break

                prev_screen_iterated_likers.clear()
                prev_screen_iterated_likers += screen_iterated_likers

                logger.info("Need to scroll now",
                            extra={"color": f"{Fore.GREEN}"})
                likes_list_view.scroll(DeviceFacade.Direction.BOTTOM)

            if posts_end_detector.is_the_end():
                break
            else:
                posts_list_view.scroll(DeviceFacade.Direction.BOTTOM)