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 _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 do_like(opened_post_view, device, on_like): 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.info("Like succeeded!") detect_block(device) on_like() else: logger.warning("Fail to like post. Let's continue...") return like_succeed
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
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
def handle_hashtag( self, device, hashtag, likes_count, stories_count, stories_percentage, follow_percentage, follow_limit, interact_percentage, current_job, 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, current_mode=self.current_mode, ) 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() if not search_view.navigateToHashtag(hashtag): return if current_job == "hashtag-posts-recent": logger.info("Switching to Recent tab") HashTagView(device)._getRecentTab().click() random_sleep(5, 10) if HashTagView(device)._check_if_no_posts(): UniversalActions(device)._reload_page() random_sleep(4, 8) logger.info("Opening the first result") result_view = HashTagView(device)._getRecyclerView() HashTagView(device)._getFistImageView(result_view).click() random_sleep() def interact(): 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 False else: return True def random_choice(): from random import randint random_number = randint(1, 100) if interact_percentage > random_number: return True else: return False post_description = "" nr_same_post = 0 nr_same_posts_max = 3 while True: flag, post_description = PostsViewList(device)._check_if_last_post( post_description ) if flag: nr_same_post += 1 logger.info( f"Warning: {nr_same_post}/{nr_same_posts_max} repeated posts." ) if nr_same_post == nr_same_posts_max: logger.info( f"Scrolled through {nr_same_posts_max} posts with same description and author. Finish." ) break else: nr_same_post = 0 if random_choice(): username = PostsViewList(device)._post_owner(Owner.GET_NAME)[:-3] if storage.is_user_in_blacklist(username): logger.info(f"@{username} is in blacklist. Skip.") elif storage.check_user_was_interacted(username): logger.info(f"@{username}: already interacted. Skip.") else: logger.info(f"@{username}: interact") PostsViewList(device)._like_in_post_view(LikeMode.DOUBLE_CLICK) detect_block(device) if not PostsViewList(device)._check_if_liked(): PostsViewList(device)._like_in_post_view(LikeMode.SINGLE_CLICK) detect_block(device) random_sleep(1, 2) if PostsViewList(device)._post_owner(Owner.OPEN): if not interact(): break device.back() PostsViewList(device).swipe_to_fit_posts(SwipeTo.HALF_PHOTO) random_sleep(0, 1) PostsViewList(device).swipe_to_fit_posts(SwipeTo.NEXT_POST) random_sleep() continue
def do_unfollow(self, device: DeviceFacade, username, my_username, check_if_is_follower): """ :return: whether unfollow was successful """ username_view = device.find( resourceId=self.ResourceID.FOLLOW_LIST_USERNAME, className=ClassName.TEXT_VIEW, text=username, ) if not username_view.exists(): logger.error("Cannot find @" + username + ", skip.") return False username_view.click() if check_if_is_follower and self.check_is_follower( device, username, my_username): logger.info(f"Skip @{username}. This user is following you.") logger.info("Back to the followings list.") device.back() return False unfollow_button = device.find( classNameMatches=ClassName.BUTTON, clickable=True, textMatches=FOLLOWING_REGEX, ) # I don't know/remember the origin of this, if someone does - let's document it attempts = 2 for _ in range(attempts): if unfollow_button.exists(): break scrollable = device.find(classNameMatches=ClassName.VIEW_PAGER) if scrollable.exists(): scrollable.scroll(DeviceFacade.Direction.TOP) unfollow_button = device.find( classNameMatches=ClassName.BUTTON, clickable=True, textMatches=FOLLOWING_REGEX, ) if not unfollow_button.exists(): logger.error( "Cannot find Following button. Maybe not English language is set?" ) save_crash(device) switch_to_english(device) raise LanguageNotEnglishException() random_sleep() logger.debug("Unfollow btn click") unfollow_button.click() logger.info(f"Unfollow @{username}.", extra={"color": f"{Fore.YELLOW}"}) # Weirdly enough, this is a fix for after you unfollow someone that follows # you back - the next person you unfollow the button is missing on first find # additional find - finds it. :shrug: confirm_unfollow_button = None attempts = 2 for _ in range(attempts): confirm_unfollow_button = device.find( resourceId=self.ResourceID.FOLLOW_SHEET_UNFOLLOW_ROW) if confirm_unfollow_button.exists(): break if not confirm_unfollow_button or not confirm_unfollow_button.exists(): logger.error("Cannot confirm unfollow.") save_crash(device) device.back() return False logger.debug("Confirm unfollow") confirm_unfollow_button.click() random_sleep(0, 1) # Check if private account confirmation private_unfollow_button = device.find( classNameMatches=ClassName.BUTTON_OR_TEXTVIEW_REGEX, textMatches=UNFOLLOW_REGEX, ) if private_unfollow_button.exists(): logger.debug("Confirm unfollow private account") private_unfollow_button.click() detect_block(device) logger.info("Back to the followings list.") device.back() return True
def do_unfollow(self, device, username, my_username, check_if_is_follower): """ :return: whether unfollow was successful """ username_view = device.find( resourceId="com.instagram.android:id/follow_list_username", className="android.widget.TextView", text=username, ) if not username_view.exists(): logger.error("Cannot find @" + username + ", skip.") return False username_view.click() if check_if_is_follower and self.check_is_follower( device, username, my_username): logger.info(f"Skip @{username}. This user is following you.") logger.info("Back to the followings list.") device.back() return False attempts = 0 while True: unfollow_button = device.find( classNameMatches=BUTTON_REGEX, clickable=True, textMatches=FOLLOWING_REGEX, ) if not unfollow_button.exists() and attempts <= 1: scrollable = device.find( classNameMatches="androidx.viewpager.widget.ViewPager") scrollable.scroll(DeviceFacade.Direction.TOP) attempts += 1 else: break if not unfollow_button.exists(): logger.error( "Cannot find Following button. Maybe not English language is set?" ) save_crash(device) switch_to_english(device) raise LanguageNotEnglishException() unfollow_button.click() confirm_unfollow_button = device.find( resourceId="com.instagram.android:id/follow_sheet_unfollow_row", className="android.widget.TextView", ) if not confirm_unfollow_button.exists(): logger.error("Cannot confirm unfollow.") save_crash(device) device.back() return False confirm_unfollow_button.click() random_sleep() # Check if private account confirmation private_unfollow_button = device.find( classNameMatches=BUTTON_OR_TEXTVIEW_REGEX, textMatches=UNFOLLOW_REGEX, ) if private_unfollow_button.exists(): private_unfollow_button.click() detect_block(device) logger.info("Back to the followings list.") device.back() return True