def _open_user(device, username, open_followers=False, open_followings=False, refresh=False, on_action=None): if refresh: print("Refreshing profile status...") coordinator_layout = device.find(resourceId='com.instagram.android:id/coordinator_root_layout') if coordinator_layout.exists(): coordinator_layout.scroll(DeviceFacade.Direction.TOP) if username is None: if open_followers: print("Open your followers") followers_button = device.find(resourceIdMatches=FOLLOWERS_BUTTON_ID_REGEX) followers_button.click() if open_followings: print("Open your followings") followings_button = device.find(resourceIdMatches=FOLLOWING_BUTTON_ID_REGEX) followings_button.click() else: if not search_for(device, username=username, on_action=on_action): return False if open_followers: print("Open @" + username + " followers") followers_button = device.find(resourceIdMatches=FOLLOWERS_BUTTON_ID_REGEX) followers_button.click() if open_followings: print("Open @" + username + " followings") followings_button = device.find(resourceIdMatches=FOLLOWING_BUTTON_ID_REGEX) followings_button.click() return True
def extract_hashtag_profiles_and_interact(device, hashtag, instructions, iteration_callback, iteration_callback_pre_conditions, on_action): print("Interacting with #{0}-{1}".format(hashtag, instructions.value)) if not search_for(device, hashtag=hashtag, on_action=on_action): return # Switch to Recent tab if instructions == HashtagInteractionType.RECENT_LIKERS: print("Switching to Recent tab") tab_layout = device.find( resourceId='com.instagram.android:id/tab_layout', className='android.widget.LinearLayout') if tab_layout.exists(): tab_layout.child(index=1).click() else: print("Can't Find recent tab. Interacting with Popular.") sleeper.random_sleep() # Open first post print("Opening the first post") # Index 1 is reserved for hot Reels by this tag first_post_index = 2 if instructions == HashtagInteractionType.TOP_LIKERS else 1 first_post_view = device.find( resourceId='com.instagram.android:id/image_button', className='android.widget.ImageView', index=first_post_index) first_post_view.click() sleeper.random_sleep() posts_list_view = device.find( resourceId='android:id/list', className='androidx.recyclerview.widget.RecyclerView') posts_end_detector = ScrollEndDetector(repeats_to_end=2) def pre_conditions(liker_username, liker_username_view): posts_end_detector.notify_username_iterated(liker_username) return iteration_callback_pre_conditions(liker_username, liker_username_view) while True: if not open_likers(device): print(COLOR_OKGREEN + "No likes, let's scroll down." + COLOR_ENDC) posts_list_view.scroll(DeviceFacade.Direction.BOTTOM) continue print("List of likers is opened.") posts_end_detector.notify_new_page() sleeper.random_sleep() iterate_over_likers(device, iteration_callback, pre_conditions) if posts_end_detector.is_the_end(): break else: posts_list_view.scroll(DeviceFacade.Direction.BOTTOM)
def _open_user(device, username, open_followers=False, open_followings=False, refresh=False, deep_link_usage_percentage=0, on_action=None): if refresh: print("Refreshing profile status...") coordinator_layout = device.find( resourceId=f'{device.app_id}:id/coordinator_root_layout') if coordinator_layout.exists(): coordinator_layout.scroll(DeviceFacade.Direction.TOP) if username is None: if open_followers: print("Open your followers") ProfileView(device, is_own_profile=True).navigate_to_followers() if open_followings: print("Open your following") ProfileView(device, is_own_profile=True).navigate_to_following() else: should_open_user_with_search = True deep_link_usage_chance = randint(1, 100) if deep_link_usage_chance <= deep_link_usage_percentage: print(f"Going to open {username} using deeplink") should_open_user_with_search = False should_continue, is_profile_opened = _open_profile_using_deeplink( device, username) if not should_continue: return False if not is_profile_opened: print( f"Failed to open profile using deeplink. Using search instead" ) should_open_user_with_search = True if should_open_user_with_search: if not search_for(device, username=username, on_action=on_action): return False sleeper.random_sleep() is_profile_empty = softban_indicator.detect_empty_profile(device) if is_profile_empty: return False if open_followers: print("Open @" + username + " followers") ProfileView(device, is_own_profile=True).navigate_to_followers() if open_followings: print("Open @" + username + " following") ProfileView(device, is_own_profile=True).navigate_to_following() return True
def extract_hashtag_profiles_and_interact(device, hashtag, instructions, iteration_callback, iteration_callback_pre_conditions, on_action): print("Interacting with #{0}-{1}".format(hashtag, instructions.value)) if not search_for(device, hashtag=hashtag, on_action=on_action): return # Switch to Recent tab if instructions == HashtagInteractionType.RECENT_LIKERS: print("Switching to Recent tab") tab_layout = device.find(resourceId=f'{device.app_id}:id/tab_layout', className='android.widget.LinearLayout') if tab_layout.exists(): tab_layout.child(index=1).click() else: print("Can't Find recent tab. Interacting with Popular.") # Sleep longer because posts loading takes time sleeper.random_sleep(multiplier=2.0) # Open post posts_view_list = PostsGridView(device).open_random_post() if posts_view_list is None: return posts_end_detector = ScrollEndDetector(repeats_to_end=2) def pre_conditions(liker_username, liker_username_view): posts_end_detector.notify_username_iterated(liker_username) return iteration_callback_pre_conditions(liker_username, liker_username_view) while True: if not open_likers(device): print(COLOR_OKGREEN + "No likes, let's scroll down." + COLOR_ENDC) posts_view_list.scroll_down() continue print("List of likers is opened.") posts_end_detector.notify_new_page() sleeper.random_sleep() should_continue_using_source = iterate_over_likers(device, iteration_callback, pre_conditions) if not should_continue_using_source: break if posts_end_detector.is_the_end(): break else: posts_view_list.scroll_down()
def _open_user(device, username, open_followers=False, open_followings=False, refresh=False, on_action=None): if refresh: print("Refreshing profile status...") coordinator_layout = device.find( resourceId=f'{device.app_id}:id/coordinator_root_layout') if coordinator_layout.exists(): coordinator_layout.scroll(DeviceFacade.Direction.TOP) if username is None: if open_followers: print("Open your followers") followers_button = device.find( resourceIdMatches=FOLLOWERS_BUTTON_ID_REGEX.format( device.app_id, device.app_id)) followers_button.click() if open_followings: print("Open your followings") followings_button = device.find( resourceIdMatches=FOLLOWING_BUTTON_ID_REGEX.format( device.app_id, device.app_id)) followings_button.click() else: if not search_for(device, username=username, on_action=on_action): return False sleeper.random_sleep() is_profile_empty = softban_indicator.detect_empty_profile(device) if is_profile_empty: return False if open_followers: print("Open @" + username + " followers") followers_button = device.find( resourceIdMatches=FOLLOWERS_BUTTON_ID_REGEX.format( device.app_id, device.app_id)) followers_button.click() if open_followings: print("Open @" + username + " followings") followings_button = device.find( resourceIdMatches=FOLLOWING_BUTTON_ID_REGEX.format( device.app_id, device.app_id)) followings_button.click() return True
def extract_hashtag_likers_and_interact(device, hashtag, iteration_callback, iteration_callback_pre_conditions, on_action): print("Interacting with #{0} recent-likers".format(hashtag)) if not search_for(device, hashtag=hashtag, on_action=on_action): return # Switch to Recent tab print("Switching to Recent tab") tab_layout = device.find(resourceId='com.instagram.android:id/tab_layout', className='android.widget.LinearLayout') tab_layout.child(index=1).click() random_sleep() # Open first post print("Opening the first post") first_post_view = device.find( resourceId='com.instagram.android:id/image_button', className='android.widget.ImageView', index=1) first_post_view.click() random_sleep() posts_list_view = device.find( resourceId='android:id/list', className='androidx.recyclerview.widget.RecyclerView') posts_end_detector = ScrollEndDetector(repeats_to_end=2) def pre_conditions(liker_username, liker_username_view): posts_end_detector.notify_username_iterated(liker_username) return iteration_callback_pre_conditions(liker_username, liker_username_view) while True: if not open_likers(device): print(COLOR_OKGREEN + "No likes, let's scroll down." + COLOR_ENDC) posts_list_view.scroll(DeviceFacade.Direction.BOTTOM) continue print("List of likers is opened.") posts_end_detector.notify_new_page() random_sleep() iterate_over_likers(device, iteration_callback, pre_conditions) if posts_end_detector.is_the_end(): break else: posts_list_view.scroll(DeviceFacade.Direction.BOTTOM)
def _open_user_followers(device, username): if username is None: print("Open your followers") followers_button = device.find(resourceIdMatches=FOLLOWERS_BUTTON_ID_REGEX) followers_button.click() else: if not search_for(device, username=username): return False print("Open @" + username + " followers") followers_button = device.find(resourceIdMatches=FOLLOWERS_BUTTON_ID_REGEX) followers_button.click() return True
def navigate_to_feed(): if not search_for(device, hashtag=hashtag, on_action=on_action): return None # Switch to Recent tab if instructions == HashtagInteractionType.RECENT_LIKERS or instructions == HashtagInteractionType.RECENT_POSTS: print("Switching to Recent tab") tab_layout = device.find( resourceId=f'{device.app_id}:id/tab_layout', className='android.widget.LinearLayout') if tab_layout.exists(): tab_layout.child(index=1).click() else: print("Can't Find recent tab. Interacting with Popular.") # Sleep longer because posts loading takes time sleeper.random_sleep(multiplier=2.0) # Open post posts_view_list = PostsGridView(device).open_random_post() if posts_view_list is None: return None return posts_view_list
def handle_place(device, place, instructions, session_state, likes_count, stories_count, follow_percentage, like_percentage, comment_percentage, comments_list, storage, on_action, is_limit_reached, is_passed_filters, action_status): if not search_for(device, place=place, on_action=on_action): return None # Switch to Recent tab if needed if instructions == PlaceInteractionType.RECENT_LIKERS or instructions == PlaceInteractionType.RECENT_POSTS: sleeper.random_sleep() print("Switching to Recent tab") tab_layout = device.find(resourceId=f'{device.app_id}:id/tab_layout', className='android.widget.LinearLayout') if tab_layout.exists(): tab_layout.child(index=1).click() else: print("Can't Find recent tab. Interacting with Popular.") # Sleep longer because posts loading takes time sleeper.random_sleep(multiplier=2.0) source_type = f'{SourceType.PLACE.value}-{instructions.value}' interaction = partial(interact_with_user, device=device, user_source=place, source_type=source_type, my_username=session_state.my_username, on_action=on_action) def pre_conditions(liker_username, liker_username_view): if storage.is_user_in_blacklist(liker_username): print("@" + liker_username + " is in blacklist. Skip.") return False elif storage.check_user_was_filtered(liker_username): print("@" + liker_username + ": already filtered in past. Skip.") return False elif storage.check_user_was_interacted(liker_username): print("@" + liker_username + ": already interacted. Skip.") return False return True def interact_with_profile(liker_username, liker_username_view): """ :return: whether we should continue interaction with other users after this one """ is_interact_limit_reached, interact_reached_source_limit, interact_reached_session_limit = \ is_limit_reached(InteractAction(source_name=place, source_type=source_type, user=liker_username, succeed=True), session_state) if not process_limits( is_interact_limit_reached, interact_reached_session_limit, interact_reached_source_limit, action_status, "Interaction"): return False is_get_profile_limit_reached, get_profile_reached_source_limit, get_profile_reached_session_limit = \ is_limit_reached(GetProfileAction(user=liker_username), session_state) if not process_limits(is_get_profile_limit_reached, get_profile_reached_session_limit, get_profile_reached_source_limit, action_status, "Get-Profile"): return False is_all_filters_satisfied = False if is_passed_filters is not None: print_debug(f"Running filter-ahead on @{liker_username}") should_continue, is_all_filters_satisfied = is_passed_filters( device, liker_username, reset=True, filters_tags=['BEFORE_PROFILE_CLICK']) if not should_continue: on_action(FilterAction(liker_username)) return True if not is_all_filters_satisfied: print_debug( "Not all filters are satisfied with filter-ahead, continue filtering inside the profile-page" ) print("@" + liker_username + ": interact") liker_username_view.click() on_action(GetProfileAction(user=liker_username)) sleeper.random_sleep() is_profile_empty = softban_indicator.detect_empty_profile(device) if is_profile_empty: print("Back to likers list") device.back() return True if is_passed_filters is not None: if not is_all_filters_satisfied: should_continue, _ = is_passed_filters(device, liker_username, reset=False) if not should_continue: on_action(FilterAction(liker_username)) # Continue to next follower print("Back to likers list") device.back() return True is_like_limit_reached, like_reached_source_limit, like_reached_session_limit = \ is_limit_reached(LikeAction(source_name=place, source_type=source_type, user=liker_username), session_state) is_follow_limit_reached, follow_reached_source_limit, follow_reached_session_limit = \ is_limit_reached(FollowAction(source_name=place, source_type=source_type, user=liker_username), session_state) is_watch_limit_reached, watch_reached_source_limit, watch_reached_session_limit = \ is_limit_reached(StoryWatchAction(source_name=place, source_type=source_type, user=liker_username), session_state) is_comment_limit_reached, comment_reached_source_limit, comment_reached_session_limit = \ is_limit_reached(CommentAction(source_name=place, source_type=source_type, user=liker_username, comment=""), session_state) is_private = is_private_account(device) if is_private: if is_passed_filters is None: print(COLOR_OKGREEN + "@" + liker_username + " has private account, won't interact." + COLOR_ENDC) on_action(FilterAction(liker_username)) on_action( InteractAction(source_name=place, source_type=source_type, user=liker_username, succeed=False)) print("Back to likers list") device.back() return True print("@" + liker_username + ": Private account - images wont be liked.") do_have_stories = do_have_story(device) if not do_have_stories: print("@" + liker_username + ": seems there are no stories to be watched.") is_likes_enabled = likes_count != '0' is_stories_enabled = stories_count != '0' is_follow_enabled = follow_percentage != 0 is_comment_enabled = comment_percentage != 0 likes_value = get_value(likes_count, "Likes count: {}", 2, max_count=12) stories_value = get_value(stories_count, "Stories to watch: {}", 1) can_like = not is_like_limit_reached and not is_private and likes_value > 0 can_follow = ( not is_follow_limit_reached ) and storage.get_following_status( liker_username) == FollowingStatus.NONE and follow_percentage > 0 can_watch = (not is_watch_limit_reached ) and do_have_stories and stories_value > 0 can_comment = (not is_comment_limit_reached ) and not is_private and comment_percentage > 0 can_interact = can_like or can_follow or can_watch or can_comment if not can_interact: print( "@" + liker_username + ": Cant be interacted (due to limits / already followed). Skip." ) on_action( InteractAction(source_name=place, source_type=source_type, user=liker_username, succeed=False)) else: print_interaction_types(liker_username, can_like, can_follow, can_watch, can_comment) interaction_strategy = InteractionStrategy( do_like=can_like, do_follow=can_follow, do_story_watch=can_watch, do_comment=can_comment, likes_count=likes_value, follow_percentage=follow_percentage, like_percentage=like_percentage, stories_count=stories_value, comment_percentage=comment_percentage, comments_list=comments_list) is_liked, is_followed, is_watch, is_commented = interaction( username=liker_username, interaction_strategy=interaction_strategy) if is_liked or is_followed or is_watch or is_commented: on_action( InteractAction(source_name=place, source_type=source_type, user=liker_username, succeed=True)) print_short_report(f"P-{place}", session_state) else: on_action( InteractAction(source_name=place, source_type=source_type, user=liker_username, succeed=False)) can_continue = True if ((is_like_limit_reached and is_likes_enabled) or not is_likes_enabled) and \ ((is_follow_limit_reached and is_follow_enabled) or not is_follow_enabled) and \ ((is_comment_limit_reached and is_comment_enabled) or not is_comment_enabled) and \ ((is_watch_limit_reached and is_stories_enabled) or not is_stories_enabled): # If one of the limits reached for source-limit, move to next source if (like_reached_source_limit is not None and like_reached_session_limit is None) or \ (follow_reached_source_limit is not None and follow_reached_session_limit is None): can_continue = False action_status.set_limit(ActionState.SOURCE_LIMIT_REACHED) # If all of the limits reached for session-limit, finish the session if ((like_reached_session_limit is not None and is_likes_enabled) or not is_likes_enabled) and \ ((follow_reached_session_limit is not None and is_follow_enabled) or not is_follow_enabled): can_continue = False action_status.set_limit(ActionState.SESSION_LIMIT_REACHED) print("Back to followers list") device.back() return can_continue def navigate_to_feed(): # Open post posts_view_list = PostsGridView(device).open_random_post() if posts_view_list is None: return None return posts_view_list def should_continue_interact_with_feed(): return True def interact_with_feed_post(posts_views_list): is_interact_limit_reached, interact_reached_source_limit, interact_reached_session_limit = \ is_limit_reached( InteractAction(source_name=place, source_type=source_type, user=None, succeed=True), session_state) if not process_limits( is_interact_limit_reached, interact_reached_session_limit, interact_reached_source_limit, action_status, "Interaction"): return False is_like_limit_reached, like_reached_source_limit, like_reached_session_limit = \ is_limit_reached(LikeAction(source_name=place, source_type=source_type, user=None), session_state) if not process_limits( is_like_limit_reached, like_reached_session_limit, like_reached_source_limit, action_status, "Likes"): return False opened_post_view = posts_views_list.get_current_post() author_name = opened_post_view.get_author_name() like_chance = randint(1, 100) if like_chance > like_percentage: print("Not going to like image due to like-percentage hit") on_action( InteractAction(source_name=place, source_type=source_type, user=author_name, succeed=False)) else: print("Like post") opened_post_view.like() if author_name is not None: on_action( LikeAction(source_name=place, source_type=source_type, user=author_name)) on_action( InteractAction(source_name=place, source_type=source_type, user=author_name, succeed=True)) return True if instructions == PlaceInteractionType.RECENT_LIKERS or instructions == PlaceInteractionType.TOP_LIKERS: extract_place_likers_and_interact(device, place, instructions, navigate_to_feed, interact_with_profile, pre_conditions, on_action) elif instructions == PlaceInteractionType.RECENT_POSTS or instructions == PlaceInteractionType.TOP_POSTS: interact_with_feed(navigate_to_feed, should_continue_interact_with_feed, interact_with_feed_post)
def extract_hashtag_profiles_and_interact(device, hashtag, instructions, iteration_callback, iteration_callback_pre_conditions, on_action): print("Interacting with #{0}-{1}".format(hashtag, instructions.value)) if not search_for(device, hashtag=hashtag, on_action=on_action): return # Switch to Recent tab if instructions == HashtagInteractionType.RECENT_LIKERS: print("Switching to Recent tab") tab_layout = device.find(resourceId=f'{device.app_id}:id/tab_layout', className='android.widget.LinearLayout') if tab_layout.exists(): tab_layout.child(index=1).click() else: print("Can't Find recent tab. Interacting with Popular.") sleeper.random_sleep() # Open post # Scroll down several times to pick random post scroll_times = randint(0, 5) posts_grid = device.find( resourceId=f'{device.app_id}:id/recycler_view', className='androidx.recyclerview.widget.RecyclerView') print(f"Scroll down {scroll_times} times.") for _ in range(0, scroll_times): posts_grid.scroll(DeviceFacade.Direction.BOTTOM) sleeper.random_sleep() # Scan for available posts' coordinates available_posts_coords = [] print("Choosing a random post from those on the screen") for post_view in posts_grid.child( resourceId=f'{device.app_id}:id/image_button', className='android.widget.ImageView'): bounds = post_view.get_bounds() left = bounds["left"] top = bounds["top"] right = bounds["right"] bottom = bounds["bottom"] coords = (left + (right - left) / 2, top + (bottom - top) / 2) available_posts_coords.append(coords) if len(available_posts_coords) == 0: print(COLOR_FAIL + f"No posts for #{hashtag}. Abort." + COLOR_ENDC) return # Pick random post from available ones coords = random.choice(available_posts_coords) print(f"Open the post at {coords}") device.screen_click_by_coordinates(coords[0], coords[1]) sleeper.random_sleep() posts_list_view = device.find( resourceId='android:id/list', className='androidx.recyclerview.widget.RecyclerView') if not posts_list_view.exists(): print("Couldn't open a post, will try again.") device.screen_click_by_coordinates(coords[0], coords[1]) sleeper.random_sleep() posts_end_detector = ScrollEndDetector(repeats_to_end=2) def pre_conditions(liker_username, liker_username_view): posts_end_detector.notify_username_iterated(liker_username) return iteration_callback_pre_conditions(liker_username, liker_username_view) while True: if not open_likers(device): print(COLOR_OKGREEN + "No likes, let's scroll down." + COLOR_ENDC) posts_list_view.scroll(DeviceFacade.Direction.BOTTOM) continue print("List of likers is opened.") posts_end_detector.notify_new_page() sleeper.random_sleep() should_continue_using_source = iterate_over_likers( device, iteration_callback, pre_conditions) if not should_continue_using_source: break if posts_end_detector.is_the_end(): break else: posts_list_view.scroll(DeviceFacade.Direction.BOTTOM)
def extract_place_profiles_and_interact(device, place, instructions, iteration_callback, iteration_callback_pre_conditions, on_action): print("Interacting with place-{0}-{1}".format(place, instructions.value)) if not search_for(device, place=place, on_action=on_action): return # Switch to Recent tab if instructions == PlaceInteractionType.RECENT_LIKERS: print("Switching to Recent tab") tab_layout = device.find(resourceId=f'{device.app_id}:id/tab_layout', className='android.widget.LinearLayout') if tab_layout.exists(): tab_layout.child(index=1).click() else: print("Can't Find recent tab. Interacting with Popular.") sleeper.random_sleep() # Open post first_post_index = 2 post_num = randint(first_post_index, 20) print(f"Opening post #{post_num}") post_view = device.find(resourceId=f'{device.app_id}:id/image_button', className='android.widget.ImageView', index=post_num) for _ in range(0, 10): if post_view.exists(quick=True): break print(f"Cannot find post #{post_num}. Swiping down a bit.") device.swipe(DeviceFacade.Direction.TOP) if not post_view.exists(quick=True): print(f"Cannot find post #{post_num} after 10 swipes. Aborting.") post_view.click() sleeper.random_sleep() posts_list_view = device.find( resourceId='android:id/list', className='androidx.recyclerview.widget.RecyclerView') posts_end_detector = ScrollEndDetector(repeats_to_end=2) def pre_conditions(liker_username, liker_username_view): posts_end_detector.notify_username_iterated(liker_username) return iteration_callback_pre_conditions(liker_username, liker_username_view) while True: if not open_likers(device): print(COLOR_OKGREEN + "No likes, let's scroll down." + COLOR_ENDC) posts_list_view.scroll(DeviceFacade.Direction.BOTTOM) continue print("List of likers is opened.") posts_end_detector.notify_new_page() sleeper.random_sleep() should_continue_using_source = iterate_over_likers( device, iteration_callback, pre_conditions) if not should_continue_using_source: break if posts_end_detector.is_the_end(): break else: posts_list_view.scroll(DeviceFacade.Direction.BOTTOM)
def _open_user(device, username, open_followers=False, open_followings=False, refresh=False, deep_link_usage_percentage=0, on_action=None): if refresh: print("Refreshing profile status...") coordinator_layout = device.find( resourceId=f'{device.app_id}:id/coordinator_root_layout') if coordinator_layout.exists(): coordinator_layout.scroll(DeviceFacade.Direction.TOP) if username is None: if open_followers: print("Open your followers") followers_button = device.find( resourceIdMatches=FOLLOWERS_BUTTON_ID_REGEX.format( device.app_id, device.app_id)) followers_button.click() if open_followings: print("Open your followings") followings_button = device.find( resourceIdMatches=FOLLOWING_BUTTON_ID_REGEX.format( device.app_id, device.app_id)) followings_button.click() sleeper.random_sleep() followings_tab = device.find( classNameMatches=TEXTVIEW_OR_BUTTON_REGEX, clickable=True, textContains=' Following') followings_tab.click() else: should_open_user_with_search = True deep_link_usage_chance = randint(1, 100) if deep_link_usage_chance <= deep_link_usage_percentage: print(f"Going to open {username} using deeplink") should_open_user_with_search = False should_continue, is_profile_opened = _open_profile_using_deeplink( device, username) if not should_continue: return False if not is_profile_opened: print( f"Failed to open profile using deeplink. Using search instead" ) should_open_user_with_search = True if should_open_user_with_search: if not search_for(device, username=username, on_action=on_action): return False sleeper.random_sleep() is_profile_empty = softban_indicator.detect_empty_profile(device) if is_profile_empty: return False if open_followers: print("Open @" + username + " followers") followers_button = device.find( resourceIdMatches=FOLLOWERS_BUTTON_ID_REGEX.format( device.app_id, device.app_id)) followers_button.click() if open_followings: print("Open @" + username + " followings") followings_button = device.find( resourceIdMatches=FOLLOWING_BUTTON_ID_REGEX.format( device.app_id, device.app_id)) followings_button.click() sleeper.random_sleep() followings_tab = device.find( classNameMatches=TEXTVIEW_OR_BUTTON_REGEX, clickable=True, textContains=' Following') followings_tab.click() return True