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 _navigateToTab(self, tab: ProfileTabs): TABS_RES_ID = "com.instagram.android:id/profile_tab_layout" TABS_CLASS_NAME = "android.widget.HorizontalScrollView" tabs_view = self.device.find( resourceIdMatches=case_insensitive_re(TABS_RES_ID), className=TABS_CLASS_NAME, ) TAB_RES_ID = "com.instagram.android:id/profile_tab_icon_view" TAB_CLASS_NAME = "android.widget.ImageView" description = "" if tab == ProfileTabs.POSTS: description = "Grid View" elif tab == ProfileTabs.IGTV: description = "IGTV" elif tab == ProfileTabs.REELS: description = "Reels" elif tab == ProfileTabs.EFFECTS: description = "Effects" elif tab == ProfileTabs.PHOTOS_OF_YOU: description = "Photos of You" button = tabs_view.child( descriptionMatches=case_insensitive_re(description), resourceIdMatches=case_insensitive_re(TAB_RES_ID), className=TAB_CLASS_NAME, ) if not button.exists(): logger.error(f"Cannot navigate to to tab '{description}'") save_crash(self.device) else: button.click()
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 _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 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 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 _navigateToTab(self, tab: TabBarText): tabs_view = self.device.find( resourceIdMatches=case_insensitive_re(ResourceID.PROFILE_TAB_LAYOUT), className=ClassName.HORIZONTAL_SCROLL_VIEW, ) button = tabs_view.child( descriptionMatches=case_insensitive_re(tab), resourceIdMatches=case_insensitive_re(ResourceID.PROFILE_TAB_ICON_VIEW), className=ClassName.IMAGE_VIEW, ) attempts = 0 while not button.exists(): attempts += 1 self.device.swipe(DeviceFacade.Direction.TOP, scale=0.1) if attempts > 2: logger.error(f"Cannot navigate to tab '{tab}'") save_crash(self.device) return button.click()
def _getPostLikeButton(self, scroll_to_find=True): """Find the like button right bellow a post. Note: sometimes the like button from the post above or bellow are dumped as well, so we need handle that situation. scroll_to_find: if the like button is not found, scroll a bit down to try to find it. Default: True """ post_view_area = self.device.find( resourceIdMatches=case_insensitive_re(ResourceID.LIST)) if not post_view_area.exists(): logger.debug("Cannot find post recycler view area") save_crash(self.device) self.device.back() return None post_media_view = self.device.find( resourceIdMatches=case_insensitive_re( ResourceID.CAROUSEL_MEDIA_GROUP_AND_ZOOMABLE_VIEW_CONTAINER)) if not post_media_view.exists(): logger.debug("Cannot find post media view area") save_crash(self.device) self.device.back() return None like_btn_view = post_media_view.down( resourceIdMatches=case_insensitive_re( ResourceID.ROW_FEED_BUTTON_LIKE)) if like_btn_view.exists(): # threshold of 30% of the display height threshold = int((0.3) * self.device.get_info()["displayHeight"]) like_btn_top_bound = like_btn_view.get_bounds()["top"] is_like_btn_in_the_bottom = like_btn_top_bound > threshold if not is_like_btn_in_the_bottom: logger.debug( f"Like button is to high ({like_btn_top_bound} px). Threshold is {threshold} px" ) post_view_area_bottom_bound = post_view_area.get_bounds()["bottom"] is_like_btn_visible = like_btn_top_bound <= post_view_area_bottom_bound if not is_like_btn_visible: logger.debug( f"Like btn out of current clickable area. Like btn top ({like_btn_top_bound}) recycler_view bottom ({post_view_area_bottom_bound})" ) else: logger.debug("Like button not found bellow the post.") if (not like_btn_view.exists(True) or not is_like_btn_in_the_bottom or not is_like_btn_visible): if scroll_to_find: logger.debug("Try to scroll tiny bit down...") # Remember: to scroll down we need to swipe up :) for _ in range(3): self.device.swipe(DeviceFacade.Direction.TOP, scale=0.25) like_btn_view = self.device.find( resourceIdMatches=case_insensitive_re( ResourceID.ROW_FEED_BUTTON_LIKE)) if like_btn_view.exists(True): break if not scroll_to_find or not like_btn_view.exists(True): logger.error("Could not find like button bellow the post") return None return like_btn_view
def wrapper(*args, **kwargs): session_state = sessions[-1] try: func(*args, **kwargs) except KeyboardInterrupt: try: # Catch Ctrl-C and ask if user wants to pause execution logger.info( "CTRL-C detected . . .", extra={"color": f"{Style.BRIGHT}{Fore.YELLOW}"}, ) logger.info( f"-------- PAUSED: {datetime.now().time()} --------", extra={"color": f"{Style.BRIGHT}{Fore.YELLOW}"}, ) logger.info( "NOTE: This is a rudimentary pause. It will restart the action, while retaining session data.", extra={"color": Style.BRIGHT}, ) logger.info( "Press RETURN to resume or CTRL-C again to Quit: ", extra={"color": Style.BRIGHT}, ) input("") logger.info( f"-------- RESUMING: {datetime.now().time()} --------", extra={"color": f"{Style.BRIGHT}{Fore.YELLOW}"}, ) TabBarView(device).navigateToProfile() except KeyboardInterrupt: close_instagram(device, screen_record) 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, UiObjectNotFoundErrorv2, ): 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, screen_record) random_sleep() open_instagram(device, screen_record) 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: logger.error(traceback.format_exc()) save_crash(device) close_instagram(device, screen_record) print_full_report(sessions) sessions.persist(directory=session_state.my_username) raise e
def run(): global device_id global first_run loaded = load_plugins() args = get_args() enabled = [] if not args: return dargs = vars(args) for k in loaded: if dargs[k.replace("-", "_")[2:]] != None: if k == "--interact": logger.warn( 'Using legacy argument "--interact". Please switch to new arguments as this will be deprecated in the near future.' ) if "#" in args.interact[0]: enabled.append("--hashtag-likers") args.hashtag_likers = args.interact else: enabled.append("--blogger-followers") args.blogger_followers = args.interact else: enabled.append(k) enabled = list(dict.fromkeys(enabled)) if len(enabled) < 1: logger.error("You have to specify one of the actions: " + ", ".join(loaded)) return if len(enabled) > 1: logger.error( "Running GramAddict with two or more actions is not supported yet." ) return device_id = args.device if not check_adb_connection(is_device_id_provided=(device_id is not None)): return logger.info("Instagram version: " + get_instagram_version(device_id)) device = create_device(device_id) if device is None: return while True: session_state = SessionState() session_state.args = args.__dict__ sessions.append(session_state) logger.info( "-------- START: " + str(session_state.startTime) + " --------", extra={"color": f"{Style.BRIGHT}{Fore.YELLOW}"}, ) if args.screen_sleep: screen_sleep(device_id, "on") # Turn on the device screen open_instagram(device_id) try: profileView = TabBarView(device).navigateToProfile() ( 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() ( session_state.my_username, session_state.my_followers_count, session_state.my_following_count, ) = profileView.getProfileInfo() if ( session_state.my_username == None or session_state.my_followers_count == None or session_state.my_following_count == 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 first_run: 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) loaded[enabled[0]].run(device, device_id, args, enabled, storage, sessions) close_instagram(device_id) session_state.finishTime = datetime.now() if args.screen_sleep: screen_sleep(device_id, "off") # Turn off the device screen logger.info( "-------- FINISH: " + str(session_state.finishTime) + " --------", extra={"color": f"{Style.BRIGHT}{Fore.YELLOW}"}, ) if args.repeat: print_full_report(sessions) repeat = get_value(args.repeat, "Sleep for {} minutes", 180) try: sleep(60 * repeat) except KeyboardInterrupt: print_full_report(sessions) sessions.persist(directory=session_state.my_username) sys.exit(0) else: break first_run = False print_full_report(sessions) sessions.persist(directory=session_state.my_username)
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 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
def run(): global device_id loaded = load_plugins() args = get_args() enabled = [] if not args: return dargs = vars(args) for item in sys.argv[1:]: if item in loaded: if item != "--interact" and item != "--hashtag-likers": enabled.append(item) for k in loaded: if dargs[k.replace("-", "_")[2:]] != None: if k == "--interact": logger.warn( 'Using legacy argument "--interact". Please switch to new arguments as this will be deprecated in the near future.' ) for source in args.interact: if "@" in source: enabled.append("--blogger-followers") if type(args.blogger_followers) != list: args.blogger_followers = [source] else: args.blogger_followers.append(source) else: enabled.append("--hashtag-likers-top") if type(args.hashtag_likers_top) != list: args.hashtag_likers_top = [source] else: args.hashtag_likers_top.append(source) elif k == "--hashtag-likers": logger.warn( 'Using legacy argument "--hashtag-likers". Please switch to new arguments as this will be deprecated in the near future.' ) for source in args.hashtag_likers: enabled.append("--hashtag-likers-top") if type(args.hashtag_likers_top) != list: args.hashtag_likers_top = [source] else: args.hashtag_likers_top.append(source) enabled = list(dict.fromkeys(enabled)) if len(enabled) < 1: logger.error("You have to specify one of the actions: " + ", ".join(loaded)) return device_id = args.device if not check_adb_connection(is_device_id_provided=(device_id is not None)): return logger.info("Instagram version: " + get_instagram_version(device_id)) device = create_device(device_id) if device is None: return while True: session_state = SessionState() session_state.args = args.__dict__ sessions.append(session_state) device.wake_up() logger.info( "-------- START: " + str(session_state.startTime) + " --------", extra={"color": f"{Style.BRIGHT}{Fore.YELLOW}"}, ) if not DeviceFacade(device_id).get_info()["screenOn"]: DeviceFacade(device_id).press_power() if DeviceFacade(device_id).is_screen_locked(): DeviceFacade(device_id).unlock() if DeviceFacade(device_id).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." ) sys.exit() logger.info("Device screen on and unlocked.") open_instagram(device_id) try: profileView = TabBarView(device).navigateToProfile() random_sleep() ( 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 == None or session_state.my_followers_count == None or session_state.my_following_count == 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 enabled: if not session_state.check_limit( args, limit_type=session_state.Limit.ALL, output=False): loaded[plugin].run(device, device_id, args, enabled, storage, sessions, plugin) else: logger.info( "Successful or Total Interactions limit reached. Ending session." ) break close_instagram(device_id) session_state.finishTime = datetime.now() if args.screen_sleep: DeviceFacade(device_id).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 args.repeat: print_full_report(sessions) repeat = get_value(args.repeat, "Sleep for {} minutes", 180) try: sleep(60 * repeat) except KeyboardInterrupt: print_full_report(sessions) sessions.persist(directory=session_state.my_username) sys.exit(0) else: break print_full_report(sessions) sessions.persist(directory=session_state.my_username)