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
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
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
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
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 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)
def _get_profile_biography(device): profileView = ProfileView(device) return profileView.getProfileBiography()
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
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)