def sort_followings_by_date(self, device): logger.info("Sort followings by date: from oldest to newest.") UniversalActions(device)._swipe_points(direction=Direction.DOWN, ) sort_button = device.find( resourceId=self.ResourceID.SORTING_ENTRY_ROW_ICON, className=ClassName.IMAGE_VIEW, ) if not sort_button.exists(): logger.error( "Cannot find button to sort followings. Continue without sorting." ) return sort_button.click() random_sleep() sort_options_recycler_view = device.find( resourceId=self.ResourceID. FOLLOW_LIST_SORTING_OPTIONS_RECYCLER_VIEW) if not sort_options_recycler_view.exists(): logger.error( "Cannot find options to sort followings. Continue without sorting." ) return sort_options_recycler_view.child(index=2).click()
def getProfileBiography(self): biography = self.device.find( resourceIdMatches=case_insensitive_re( ResourceID.PROFILE_HEADER_BIO_TEXT), className=ClassName.TEXT_VIEW, ) if biography.exists(): biography_text = biography.get_text() # If the biography is very long, blabla text and end with "...more" click the bottom of the text and get the new text is_long_bio = re.compile( r"{0}$".format("… more"), flags=re.IGNORECASE).search(biography_text) if is_long_bio is not None: logger.debug('Found "… more" in bio - trying to expand') username = self.getUsername() for _ in range(2): # Clicking the biography is dangerous. Clicking "bottomright" is safest so we can try to avoid hashtags and tags biography.click(biography.Location.BOTTOMRIGHT) random_sleep() if username == self.getUsername(): return biography.get_text() logger.debug( "We're not in the same page - did we click a hashtag or a tag? Go back." ) self.device.back() logger.info( "Failed to expand biography - checking short view.") return biography.get_text() return biography_text return ""
def likePost(self, click_btn_like=False): MEDIA_GROUP_RE = case_insensitive_re([ "com.instagram.android:id/media_group", "com.instagram.android:id/carousel_media_group", ]) post_media_view = self.device.find( resourceIdMatches=MEDIA_GROUP_RE, className="android.widget.FrameLayout") if click_btn_like: like_btn_view = self._getPostLikeButton() if not like_btn_view: return False like_btn_view.click() else: if post_media_view.exists(): post_media_view.double_click() else: logger.error("Could not find post area to double click") return False random_sleep() return self._isPostLiked()
def navigateToHashtag(self, hashtag): logger.info(f"Navigate to hashtag {hashtag}") search_edit_text = self._getSearchEditText() search_edit_text.click() random_sleep() hashtag_tab = self._getTabTextView(SearchTabs.TAGS) if not hashtag_tab.exists(): logger.debug( "Cannot find tab: Tags. Going to attempt to search for placeholder in all tabs" ) hashtag_tab = self._searchTabWithTextPlaceholder(SearchTabs.TAGS) if hashtag_tab is None: logger.error("Cannot find tab: Tags.") save_crash(self.device) return None hashtag_tab.click() search_edit_text.set_text(hashtag) hashtag_view = self._getHashtagRow(hashtag[1:]) if not hashtag_view.exists(): logger.error(f"Cannot find hashtag {hashtag}, abort.") save_crash(self.device) return None hashtag_view.click() return HashTagView(self.device)
def changeToUsername(self, username): action_bar = self.device.find( resourceId=ResourceID.ACTION_BAR_LARGE_TITLE) current_profile_name = action_bar.get_text().upper() if current_profile_name == username.upper(): logger.info( f"You are already logged as {username}!", extra={"color": f"{Style.BRIGHT}{Fore.BLUE}"}, ) return True if action_bar.exists(): action_bar.click() random_sleep() found_obj = self.device.find( resourceId=ResourceID.ROW_USER_TEXTVIEW, textMatches=case_insensitive_re(username), ) if found_obj.exists(): logger.info( f"Switching to {configs.args.username}...", extra={"color": f"{Style.BRIGHT}{Fore.BLUE}"}, ) found_obj.click() random_sleep() action_bar = self.device.find( resourceId=ResourceID.ACTION_BAR_LARGE_TITLE) current_profile_name = action_bar.get_text().upper() if current_profile_name == username.upper(): return True return False
def process_file(self, current_file, on_like, storage): # TODO: We need to add interactions properly, honor session/source limits, honor filter, # etc. Not going to try to do this now, but adding a note to do it later if path.isfile(current_file): with open(current_file, "r") as f: for line in f: url = line.strip() if validate_url(url) and "instagram.com/p/" in url: if open_instagram_with_url(url) is True: opened_post_view = OpenedPostView(self.device) username = opened_post_view._getUserName like_succeed = do_like(opened_post_view, self.device, on_like) logger.info("Like for: {}, status: {}".format( url, like_succeed)) if like_succeed: logger.info("Back to profile") storage.add_interacted_user(username) self.device.back() random_sleep() else: logger.info("Line in file is blank, skip.") remaining = f.readlines() if self.args.delete_interacted_users: with open(current_file, "w") as f: f.writelines(remaining) else: logger.warning(f"File {current_file} not found.") return
def changeToUsername(self, username): action_bar = self.device.find( resourceId=ResourceID.ACTION_BAR_LARGE_TITLE) current_profile_name = action_bar.get_text().upper() # in private accounts there is little lock which is codec as two spaces (should be \u1F512) if current_profile_name == username.upper( ) or current_profile_name == (" " + username.upper()): logger.info( f"You are already logged as {username}!", extra={"color": f"{Style.BRIGHT}{Fore.BLUE}"}, ) return True if action_bar.exists(): action_bar.click() random_sleep() found_obj = self.device.find( resourceId=ResourceID.ROW_USER_TEXTVIEW, textMatches=case_insensitive_re(username), ) if found_obj.exists(): logger.info( f"Switching to {username}...", extra={"color": f"{Style.BRIGHT}{Fore.BLUE}"}, ) found_obj.click() random_sleep() action_bar = self.device.find( resourceId=ResourceID.ACTION_BAR_LARGE_TITLE) current_profile_name = action_bar.get_text().upper() if current_profile_name == username.upper(): return True return False
def wrapper(*args, **kwargs): session_state = sessions[-1] try: func(*args, **kwargs) except KeyboardInterrupt: close_instagram(device_id) logger.info( f"-------- FINISH: {datetime.now().time()} --------", extra={"color": f"{Style.BRIGHT}{Fore.YELLOW}"}, ) print_full_report(sessions) sessions.persist(directory=session_state.my_username) sys.exit(0) except (DeviceFacade.JsonRpcError, IndexError, HTTPException, timeout): logger.error(traceback.format_exc()) save_crash(device) logger.info("No idea what it was. Let's try again.") # Hack for the case when IGTV was accidentally opened close_instagram(device_id) random_sleep() open_instagram(device_id) TabBarView(device).navigateToProfile() except LanguageNotEnglishException: logger.info( "Language was changed. We'll have to start from the beginning." ) TabBarView(device).navigateToProfile() except Exception as e: save_crash(device) close_instagram(device_id) print_full_report(sessions) sessions.persist(directory=session_state.my_username) raise e
def _follow(device, username, follow_percentage, args, session_state, swipe_amount): if not session_state.check_limit( args, limit_type=session_state.Limit.FOLLOWS, output=False): follow_chance = randint(1, 100) if follow_chance > follow_percentage: return False coordinator_layout = device.find( resourceId=ResourceID.COORDINATOR_ROOT_LAYOUT) if coordinator_layout.exists() and swipe_amount != 0: UniversalActions(device)._swipe_points(direction=Direction.UP, delta_y=swipe_amount) random_sleep() follow_button = device.find( classNameMatches=ClassName.BUTTON, clickable=True, textMatches=FOLLOW_REGEX, ) if not follow_button.exists(): unfollow_button = device.find( classNameMatches=ClassName.BUTTON, clickable=True, textMatches=UNFOLLOW_REGEX, ) followback_button = device.find( classNameMatches=ClassName.BUTTON, clickable=True, textMatches=FOLLOWBACK_REGEX, ) if unfollow_button.exists(): logger.info(f"You already follow @{username}.", extra={"color": f"{Fore.GREEN}"}) return False elif followback_button.exists(): logger.info( f"@{username} already follows you.", extra={"color": f"{Fore.GREEN}"}, ) return False else: logger.error( "Cannot find neither Follow button, Follow Back button, nor Unfollow button. Maybe not English language is set?" ) save_crash(device) switch_to_english(device) raise LanguageNotEnglishException() follow_button.click() detect_block(device) logger.info(f"Followed @{username}", extra={"color": f"{Fore.GREEN}"}) random_sleep() return True else: logger.info("Reached total follows limit, not following.") return False
def unfollow(self, device, count, on_unfollow, storage, unfollow_restriction, my_username): self.open_my_followings(device) random_sleep() self.sort_followings_by_date(device) random_sleep() self.iterate_over_followings(device, count, on_unfollow, storage, unfollow_restriction, my_username)
def open_likers_container(self): likes_view = self.device.find( resourceId=ResourceID.ROW_FEED_TEXTVIEW_LIKES, className=ClassName.TEXT_VIEW, ) logger.info("Opening post likers.") random_sleep() likes_view.click(likes_view.Location.RIGHT)
def _follow(device, username, follow_percentage, args, session_state): if not session_state.check_limit( args, limit_type=session_state.Limit.FOLLOWS, output=False): follow_chance = randint(1, 100) if follow_chance > follow_percentage: return False logger.info("Following...") coordinator_layout = device.find( resourceId="com.instagram.android:id/coordinator_root_layout") if coordinator_layout.exists(): coordinator_layout.scroll(DeviceFacade.Direction.TOP) random_sleep() follow_button = device.find( classNameMatches=BUTTON_REGEX, clickable=True, textMatches=FOLLOW_REGEX, ) if not follow_button.exists(): unfollow_button = device.find( classNameMatches=BUTTON_REGEX, clickable=True, textMatches=UNFOLLOW_REGEX, ) followback_button = device.find( classNameMatches=BUTTON_REGEX, clickable=True, textMatches=FOLLOWBACK_REGEX, ) if unfollow_button.exists(): logger.info(f"You already follow @{username}.", extra={"color": f"{Fore.GREEN}"}) return False elif followback_button.exists(): logger.info( f"@{username} already follows you.", extra={"color": f"{Fore.GREEN}"}, ) return False else: logger.error( "Cannot find neither Follow button, Follow Back button, nor Unfollow button. Maybe not English language is set?" ) save_crash(device) switch_to_english(device) raise LanguageNotEnglishException() follow_button.click() detect_block(device) logger.info(f"Followed @{username}", extra={"color": f"{Fore.GREEN}"}) random_sleep() return True else: logger.info("Reached total follows limit, not following.") return False
def _follow(device, username, follow_percentage): follow_chance = randint(1, 100) if follow_chance > follow_percentage: return False logger.info("Following...") coordinator_layout = device.find( resourceId="com.instagram.android:id/coordinator_root_layout") if coordinator_layout.exists(): coordinator_layout.scroll(DeviceFacade.Direction.TOP) random_sleep() profile_header_actions_layout = device.find( resourceId="com.instagram.android:id/profile_header_actions_top_row", className="android.widget.LinearLayout", ) if not profile_header_actions_layout.exists(): logger.error("Cannot find profile actions.") return False follow_button = profile_header_actions_layout.child( classNameMatches=TEXTVIEW_OR_BUTTON_REGEX, clickable=True, textMatches=FOLLOW_REGEX, ) if not follow_button.exists(): unfollow_button = profile_header_actions_layout.child( classNameMatches=TEXTVIEW_OR_BUTTON_REGEX, clickable=True, textMatches=UNFOLLOW_REGEX, ) if unfollow_button.exists(): logger.info( f"You already follow @{username}.", extra={"color": f"{Fore.GREEN}"}, ) return False else: logger.error( "Cannot find neither Follow button, nor Unfollow button. Maybe not English language is set?" ) save_crash(device) switch_to_english(device) raise LanguageNotEnglishException() follow_button.click() detect_block(device) logger.info( f"Followed @{username}", extra={"color": f"{Fore.GREEN}"}, ) random_sleep() return True
def open_likers(self, device): likes_view = device.find( resourceId="com.instagram.android:id/row_feed_textview_likes", className="android.widget.TextView", ) if likes_view.exists(): logger.info("Opening post likers") random_sleep() likes_view.click("right") return True else: return False
def navigateToUsername(self, username, interact_usernames=False, swipe_to_accounts=True): logger.debug("Search for @" + username) search_edit_text = self._getSearchEditText() search_edit_text.click() random_sleep(1, 2) if swipe_to_accounts: logger.debug("Close the keyboard") DeviceFacade.back(self.device) random_sleep(1, 2) DeviceFacade.swipe(self.device, DeviceFacade.Direction.LEFT, 0.8) random_sleep(1, 2) if interact_usernames: search_edit_text.set_text(username) else: searched_user_recent = self._getUsernameRow(username) if searched_user_recent.exists(True): searched_user_recent.click() return ProfileView(self.device, is_own_profile=False) search_edit_text.set_text(username) logger.debug("Close the keyboard") DeviceFacade.back(self.device) random_sleep(1, 2) username_view = self._getUsernameRow(username) if not username_view.exists(True): logger.error("Cannot find user @" + username + ".") return None username_view.click() return ProfileView(self.device, is_own_profile=False)
def job(): for url in self.urls: url = url.strip().replace("\n", "") if validate_url(url) and "instagram.com/p/" in url: if open_instagram_with_url(self.device_id, url) is True: opened_post_view = OpenedPostView(device) like_succeed = do_like(opened_post_view, device, on_like) logger.info("Like for: {}, status: {}".format( url, like_succeed)) if like_succeed: logger.info("Back to profile") device.back() random_sleep()
def open_user_followers(self, device, username): if username is None: logger.info("Open your followers") profile_view = TabBarView(device).navigateToProfile() profile_view.navigateToFollowers() else: search_view = TabBarView(device).navigateToSearch() profile_view = search_view.navigateToUsername(username) random_sleep() if not profile_view: return False logger.info(f"Open @{username} followers") profile_view.navigateToFollowers() return True
def open_likers(self): while True: likes_view = self.device.find( resourceId="com.instagram.android:id/row_feed_textview_likes", className="android.widget.TextView", ) if likes_view.exists(True): if likes_view.get_text()[-6:].upper() == "OTHERS": logger.info("Opening post likers") random_sleep() likes_view.click(likes_view.Location.RIGHT) return True else: logger.info("This post has only 1 liker, skip") return False else: return False
def check_is_follower(self, device, username, my_username): logger.info(f"Check if @{username} is following you.", extra={"color": f"{Fore.GREEN}"}) following_container = device.find( resourceIdMatches=FOLLOWING_BUTTON_ID_REGEX) following_container.click() random_sleep() my_username_view = device.find( resourceId="com.instagram.android:id/follow_list_username", className="android.widget.TextView", text=my_username, ) result = my_username_view.exists() logger.info("Back to the profile.") device.back() return result
def navigateToHashtag(self, hashtag): logger.info(f"Navigate to hashtag {hashtag}") search_edit_text = self._getSearchEditText() search_edit_text.click() random_sleep() hashtag_tab = self._getTabTextView(SearchTabs.TAGS) if not hashtag_tab.exists(): logger.debug( "Cannot find tab: Tags. Going to attempt to search for placeholder in all tabs" ) hashtag_tab = self._searchTabWithTextPlaceholder(SearchTabs.TAGS) if hashtag_tab is None: logger.error("Cannot find tab: Tags.") save_crash(self.device) return None hashtag_tab.click() random_sleep() DeviceFacade.back(self.device) random_sleep() # check if that hashtag already exists in the recent search list -> act as human hashtag_view_recent = self._getHashtagRow(hashtag[1:]) if hashtag_view_recent.exists(): hashtag_view_recent.click() random_sleep() return HashTagView(self.device) logger.info(f"{hashtag} is not in recent searching hystory..") search_edit_text.set_text(hashtag) hashtag_view = self._getHashtagRow(hashtag[1:]) if not hashtag_view.exists(): logger.error(f"Cannot find hashtag {hashtag}, abort.") save_crash(self.device) return None hashtag_view.click() random_sleep() return HashTagView(self.device)
def open_likers(self, device): attempts = 0 while True: likes_view = device.find( resourceId="com.instagram.android:id/row_feed_textview_likes", className="android.widget.TextView", ) if likes_view.exists(): logger.info("Opening post likers") random_sleep() likes_view.click("right") return True else: if attempts < 1: attempts += 1 logger.info("Can't find likers, trying small swipe") device.swipe(DeviceFacade.Direction.TOP, scale=0.1) continue else: return False
def check_is_follower(self, device, username, my_username): random_sleep() logger.info(f"Check if @{username} is following you.", extra={"color": f"{Fore.GREEN}"}) following_container = device.find( resourceIdMatches=self.ResourceID. ROW_PROFILE_HEADER_FOLLOWING_CONTAINER) following_container.click() random_sleep(4, 6) my_username_view = device.find( resourceId=self.ResourceID.FOLLOW_LIST_USERNAME, className=ClassName.TEXT_VIEW, text=my_username, ) result = my_username_view.exists() logger.info("Back to the profile.") device.back() return result
def likePost(self, click_btn_like=False): post_media_view = self.device.find( resourceIdMatches=case_insensitive_re( ResourceID.CAROUSEL_MEDIA_GROUP_AND_ZOOMABLE_VIEW_CONTAINER)) if click_btn_like: like_btn_view = self._getPostLikeButton() if not like_btn_view: return False like_btn_view.click() else: if post_media_view.exists(True): post_media_view.double_click() else: logger.error("Could not find post area to double click") return False random_sleep() return self._isPostLiked()
def _open_likers(self): while True: likes_view = self.device.find( resourceId=ResourceID.ROW_FEED_TEXTVIEW_LIKES, className=ClassName.TEXT_VIEW, ) if likes_view.exists(True): likes_view_text = likes_view.get_text() if (likes_view_text[-6:].upper() == "OTHERS" or likes_view_text.upper()[-5:] == "LIKES"): logger.info("Opening post likers") random_sleep() PostsViewList( self.device)._if_action_bar_is_over_obj_swipe( likes_view) likes_view.click(likes_view.Location.RIGHT) return True else: logger.info("This post has only 1 liker, skip") return False else: return False
def _navigateTo(self, tab: TabBarTabs): tab_name = tab.name logger.debug(f"Navigate to {tab_name}") button = None if tab == TabBarTabs.HOME: button = self.device.find( className=ClassName.BUTTON, descriptionMatches=case_insensitive_re(TabBarText.HOME_CONTENT_DESC), ) elif tab == TabBarTabs.SEARCH: button = self.device.find( className=ClassName.BUTTON, descriptionMatches=case_insensitive_re(TabBarText.SEARCH_CONTENT_DESC), ) if not button.exists(): # Some accounts display the search btn only in Home -> action bar logger.debug("Didn't find search in the tab bar...") home_view = self.navigateToHome() home_view.navigateToSearch() return elif tab == TabBarTabs.REELS: button = self.device.find( className=ClassName.BUTTON, descriptionMatches=case_insensitive_re(TabBarText.REELS_CONTENT_DESC), ) elif tab == TabBarTabs.ORDERS: button = self.device.find( className=ClassName.BUTTON, descriptionMatches=case_insensitive_re(TabBarText.ORDERS_CONTENT_DESC), ) elif tab == TabBarTabs.ACTIVITY: button = self.device.find( className=ClassName.BUTTON, descriptionMatches=case_insensitive_re( TabBarText.ACTIVITY_CONTENT_DESC ), ) elif tab == TabBarTabs.PROFILE: button = self.device.find( className=ClassName.BUTTON, descriptionMatches=case_insensitive_re(TabBarText.PROFILE_CONTENT_DESC), ) if button.exists(): # Two clicks to reset tab content random_sleep(1, 2) button.click() random_sleep(1, 2) if tab is not TabBarTabs.PROFILE: button.click() random_sleep(1, 2) return logger.error( f"Didn't find tab {tab_name} in the tab bar... Maybe English language is not set!?" ) raise LanguageNotEnglishException()
def navigateToHashtag(self, hashtag): logger.debug(f"Navigate to hashtag #{hashtag}") search_edit_text = self._getSearchEditText() search_edit_text.click() random_sleep() hashtag_tab = self._getTabTextView(SearchTabs.TAGS) if not hashtag_tab.exists(): hashtag_tab = self._getTabTextView(SearchTabs.Tags) if not hashtag_tab.exists(): logger.error("Cannot find tab: TAGS.") return None hashtag_tab.click() search_edit_text.set_text(hashtag) hashtag_view = self._getHashtagRow(hashtag) if not hashtag_view.exists(): logger.error(f"Cannot find hashtag #{hashtag} , abort.") return None hashtag_view.click() return HashTagView(self.device)
def close_confirm_dialog_if_shown(self, device): dialog_root_view = device.find( resourceId="com.instagram.android:id/dialog_root_view", className="android.widget.FrameLayout", ) if not dialog_root_view.exists(): return # Avatar existence is the way to distinguish confirm dialog from block dialog user_avatar_view = device.find( resourceId="com.instagram.android:id/circular_image", className="android.widget.ImageView", ) if not user_avatar_view.exists(): return logger.info("Dialog shown, confirm unfollowing.", extra={"color": f"{Fore.GREEN}"}) random_sleep() unfollow_button = dialog_root_view.child( resourceId="com.instagram.android:id/primary_button", className="android.widget.TextView", ) unfollow_button.click()
def navigateToUsername(self, username, interact_usernames=False, swipe_to_accounts=True): logger.debug("Search for @" + username) search_edit_text = self._getSearchEditText() search_edit_text.click() random_sleep(1, 2) tabbar_container = self.device.find( resourceId=ResourceID.FIXED_TABBAR_TABS_CONTAINER) if tabbar_container.exists(True): delta = tabbar_container.get_bounds()["bottom"] else: delta = 375 if swipe_to_accounts: logger.debug("Swipe up to close the keyboard if present") UniversalActions(self.device)._swipe_points( direction=Direction.UP, start_point_y=randint(delta + 10, delta + 150), delta_y=randint(50, 100), ) random_sleep(1, 2) DeviceFacade.swipe(self.device, DeviceFacade.Direction.LEFT, 0.8) random_sleep(1, 2) if interact_usernames: search_edit_text.set_text(username) else: searched_user_recent = self._getUsernameRow(username) if searched_user_recent.exists(True): searched_user_recent.click() return ProfileView(self.device, is_own_profile=False) search_edit_text.set_text(username) logger.debug("Swipe up to close the keyboard if present") UniversalActions(self.device)._swipe_points( direction=Direction.UP, start_point_y=randint(delta + 10, delta + 150), delta_y=randint(50, 100), ) random_sleep(1, 2) username_view = self._getUsernameRow(username) if not username_view.exists(True): logger.error("Cannot find user @" + username + ".") return None username_view.click() return ProfileView(self.device, is_own_profile=False)
def navigateToHashtag(self, hashtag): logger.info(f"Navigate to hashtag {hashtag}") search_edit_text = self._getSearchEditText() search_edit_text.click() random_sleep(1, 2) hashtag_tab = self._getTabTextView(SearchTabs.TAGS) if not hashtag_tab.exists(): logger.debug( "Cannot find tab: Tags. Going to attempt to search for placeholder in all tabs" ) hashtag_tab = self._searchTabWithTextPlaceholder(SearchTabs.TAGS) if hashtag_tab is None: logger.error("Cannot find tab: Tags.") save_crash(self.device) return None hashtag_tab.click() random_sleep(1, 2) tabbar_container = self.device.find( resourceId=ResourceID.FIXED_TABBAR_TABS_CONTAINER) if tabbar_container.exists(True): delta = tabbar_container.get_bounds()["bottom"] else: delta = 375 logger.debug("Swipe up to close the keyboard if present") UniversalActions(self.device)._swipe_points( direction=Direction.UP, start_point_y=randint(delta + 10, delta + 150), delta_y=randint(50, 100), ) random_sleep(1, 2) # check if that hashtag already exists in the recent search list -> act as human hashtag_view_recent = self._getHashtagRow(hashtag[1:]) if hashtag_view_recent.exists(): hashtag_view_recent.click() random_sleep(5, 10) return HashTagView(self.device) logger.info(f"{hashtag} is not in recent searching history..") search_edit_text.set_text(hashtag) hashtag_view = self._getHashtagRow(hashtag[1:]) random_sleep(4, 8) if not hashtag_view.exists(): logger.error(f"Cannot find hashtag {hashtag}, abort.") save_crash(self.device) return None hashtag_view.click() random_sleep() return HashTagView(self.device)
def iterate_over_followers( self, device, interaction, is_follow_limit_reached, storage, on_interaction, is_myself, ): # Wait until list is rendered device.find( resourceId="com.instagram.android:id/follow_list_container", className="android.widget.LinearLayout", ).wait() def scrolled_to_top(): row_search = device.find( resourceId="com.instagram.android:id/row_search_edit_text", className="android.widget.EditText", ) return row_search.exists() scroll_end_detector = ScrollEndDetector() while True: logger.info("Iterate over visible followers") random_sleep() screen_iterated_followers = [] screen_skipped_followers_count = 0 scroll_end_detector.notify_new_page() try: for item in device.find( resourceId="com.instagram.android:id/follow_list_container", className="android.widget.LinearLayout", ): user_info_view = item.child(index=1) user_name_view = user_info_view.child(index=0).child() if not user_name_view.exists(quick=True): logger.info( "Next item not found: probably reached end of the screen.", extra={"color": f"{Fore.GREEN}"}, ) break username = user_name_view.get_text() screen_iterated_followers.append(username) scroll_end_detector.notify_username_iterated(username) if storage.is_user_in_blacklist(username): logger.info(f"@{username} is in blacklist. Skip.") elif not is_myself and storage.check_user_was_interacted(username): logger.info(f"@{username}: already interacted. Skip.") screen_skipped_followers_count += 1 elif is_myself and storage.check_user_was_interacted_recently( username ): logger.info( f"@{username}: already interacted in the last week. Skip." ) screen_skipped_followers_count += 1 else: logger.info(f"@{username}: interact") user_name_view.click() can_follow = ( not is_myself and not is_follow_limit_reached() and storage.get_following_status(username) == FollowingStatus.NONE ) 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 followers list") device.back() random_sleep() except IndexError: logger.error( "Cannot get next item: probably reached end of the screen." ) if is_myself and scrolled_to_top(): logger.info( "Scrolled to top, finish.", extra={"color": f"{Fore.GREEN}"} ) return elif len(screen_iterated_followers) > 0: load_more_button = device.find( resourceId="com.instagram.android:id/row_load_more_button" ) load_more_button_exists = load_more_button.exists(quick=True) if scroll_end_detector.is_the_end(): return need_swipe = screen_skipped_followers_count == len( screen_iterated_followers ) list_view = device.find( resourceId="android:id/list", className="android.widget.ListView" ) if not list_view.exists(): logger.error( "Cannot find the list of followers. Trying to press back again." ) device.back() list_view = device.find( resourceId="android:id/list", className="android.widget.ListView", ) if is_myself: logger.info("Need to scroll now", extra={"color": f"{Fore.GREEN}"}) list_view.scroll(DeviceFacade.Direction.TOP) else: pressed_retry = False if load_more_button_exists: retry_button = load_more_button.child( className="android.widget.ImageView" ) if retry_button.exists(): logger.info('Press "Load" button') retry_button.click() random_sleep() pressed_retry = True if need_swipe and not pressed_retry: logger.info( "All followers skipped, let's do a swipe", extra={"color": f"{Fore.GREEN}"}, ) list_view.swipe(DeviceFacade.Direction.BOTTOM) else: logger.info( "Need to scroll now", extra={"color": f"{Fore.GREEN}"} ) list_view.scroll(DeviceFacade.Direction.BOTTOM) else: logger.info( "No followers were iterated, finish.", extra={"color": f"{Fore.GREEN}"}, ) return