def like_image(self, username, trying_again=False): """Likes the browser opened image""" # check action availability if self.quota_supervisor.jump_like(): return False, "jumped" # find first for like element like_elem = self.browser.find_elements_by_xpath(XP.LIKE) if len(like_elem) == 1: sleep(1) nf_scroll_into_view(self, like_elem[0]) sleep(1) nf_click_center_of_element(self, like_elem[0]) # check now we have unlike instead of like liked_elem = self.browser.find_elements_by_xpath(XP.UNLIKE) if len(liked_elem) == 1: self.logger.info("Post Liked") add_user_to_blacklist(self, username, self.quota_supervisor.LIKE) self.quota_supervisor.add_like() # after every 10 liked image do checking on the block if self.interactions.liked_img % 10 == 0 and not verify_liked_image( self): return False, "block on likes" return True, "success" elif not trying_again: # if like not seceded wait for 2 min self.logger.info( "Couldn't like post, may be soft-blocked, bot will sleep for 2 minutes and try again" ) sleep(120) like_image(self, username, True) else: return False, "block on likes" else: liked_elem = self.browser.find_elements_by_xpath(XP.UNLIKE) if len(liked_elem) == 1: self.logger.info("Image was already liked") return False, "already liked" self.logger.info("Invalid Like Element") return False, "invalid element"
def like_loop(self, what: str, base_link: str, amount: int, users_validated: False) -> Interactions: try_again = 0 sc_rolled = 0 scroll_nap = 1.5 already_interacted_links = [] interactions = Interactions() likes = 0 try: while likes in range(0, amount): if self.aborting or (self.until_time and datetime.now() > self.until_time): break if self.jumps.check_likes(): self.logger.warning( "Like quotient reached its peak, leaving Like By {} activity" .format(what)) self.quotient_breach = True # reset jump counter after a breach report self.jumps.likes = 0 break if sc_rolled > 100: try_again += 1 if try_again > 2: self.logger.info("'{}' possibly has less images than " "desired ({}), found: {}".format( what, amount, len(already_interacted_links))) break delay_random = random.randint(400, 600) self.logger.info( "Scrolled too much. Sleeping {} minutes and {} seconds". format(int(delay_random / 60), delay_random % 60)) sleep(delay_random) sc_rolled = 0 main_elem = self.browser.find_element_by_tag_name("main") posts = nf_get_all_posts_on_element(main_elem) # Interact with links for post in posts: link = post.get_attribute("href") if link not in already_interacted_links: sleep(1) nf_scroll_into_view(self, post) sleep(1) nf_click_center_of_element(self, post, link) msg, post_interactions = interact_with_post( self, link, users_validated) interactions += post_interactions if post_interactions.liked_img > 0: likes += 1 self.logger.info("[{}] - Like [{}/{}]".format( what, likes, amount)) if msg == "block on likes": raise SoftBlockedException(msg) else: sleep(1) nf_find_and_press_back(self, base_link) already_interacted_links.append(link) break else: # For loop ended means all posts in screen has been interacted with # will scroll the screen a bit and reload for i in range(3): self.browser.execute_script(JS.SCROLL_SCREEN) self.quota_supervisor.add_server_call() sc_rolled += 1 sleep(scroll_nap) except SoftBlockedException: sleep_while_blocked(self) except Exception as err: self.logger.error("Unexpected Exception: {}".format(err)) finally: return interactions
def check_post(self, post_link: str) -> Tuple[bool, str, bool, List[str], str, str]: """Checks if post can be liked according to declared settings Also stores post data in database if appropriate :returns: inappropriate, username, is_video, image_links, reason, scope """ t = perf_counter() username_text = "" caption = "" image_descriptions = [] image_links = [] likes_count = None try: username = self.browser.find_element_by_xpath(XP.POST_USERNAME) username_text = username.text if "\nVerified" in username_text: username_text = username_text.replace("\nVerified", "") # follow_button = self.browser.find_element_by_xpath(POST_FOLLOW_BUTTON) # following = follow_button.text == "Following" locations = self.browser.find_elements_by_xpath(XP.POST_LOCATION) location_text = locations[0].text if locations != [] else None # location_link = locations[0].get_attribute('href') if locations != [] else None images = self.browser.find_elements_by_xpath(XP.POST_IMAGES) """ video_previews = self.browser.find_elements_by_xpath(XP.POST_VIDEO_PREVIEWS) videos = self.browser.find_elements_by_xpath(XP.POST_VIDEOS) if (len(images) + len(videos)) == 1: # single image or video elif len(images) == 2: # carousel """ is_video = len(images) == 0 more_button = self.browser.find_elements_by_xpath( "//button[text()='more']") if more_button: nf_scroll_into_view(self, more_button[0]) more_button[0].click() caption = self.browser.find_elements_by_xpath(XP.POST_CAPTION) caption = caption[0].text if caption else "" caption = caption if caption is not None else "" caption, emoji_less_caption = deform_emojis(caption) for image in images: image_description = image.get_attribute('alt') if image_description is not None and 'Image may contain:' in image_description: image_description = image_description[ image_description.index('Image may contain:') + 19:] else: image_description = None image_descriptions.append(image_description) image_links.append(image.get_attribute('src')) log = "" log += "Post from: {}\n".format(username_text) log += "Link: {}\n".format(post_link) log += "Caption: {}\n".format(caption) for image_description in image_descriptions: if image_description: log += "Description: {}\n".format(image_description) if location_text: log += "Location: {}".format(location_text) self.logger.info(log) # Check if likes_count is between minimum and maximum values defined by user if self.settings.delimit_liking: likes_count = get_like_count(self) if likes_count is None: return (True, username_text, is_video, image_links, "Couldn't get like count", "") elif self.settings.max_likes and likes_count > self.settings.max_likes: return (True, username_text, is_video, image_links, "Delimited by liking", "maximum limit: {}, post has: {}".format( self.settings.max_likes, likes_count)) elif self.settings.min_likes and likes_count < self.settings.min_likes: return (True, username_text, is_video, image_links, "Delimited by liking", "minimum limit: {}, post has: {}".format( self.settings.min_likes, likes_count)) # Check if mandatory character set, before adding the location to the text if self.settings.mandatory_language: if not check_character_set(self, caption): return ( True, username_text, is_video, image_links, "Mandatory language not fulfilled", "Not mandatory language character found", ) # Append location to image_text so we can search through both in one go if location_text: caption = caption + "\n" + location_text if self.settings.mandatory_words: if not any( (word in caption for word in self.settings.mandatory_words)): return ( True, username_text, is_video, image_links, "Mandatory words not fulfilled", "Not mandatory likes", ) image_text_lower = [x.lower() for x in caption] ignore_if_contains_lower = [ x.lower() for x in self.settings.ignore_if_contains ] if any( (word in image_text_lower for word in ignore_if_contains_lower)): return (False, username_text, is_video, image_links, "Contains word in ignore_if_contains list", "Ignore if contains") dont_like_regex = [] for dont_likes in self.settings.dont_like: if dont_likes.startswith("#"): dont_like_regex.append(dont_likes + r"([^\d\w]|$)") elif dont_likes.startswith("["): dont_like_regex.append("#" + dont_likes[1:] + r"[\d\w]+([^\d\w]|$)") elif dont_likes.startswith("]"): dont_like_regex.append(r"#[\d\w]+" + dont_likes[1:] + r"([^\d\w]|$)") else: dont_like_regex.append(r"#[\d\w]*" + dont_likes + r"[\d\w]*([^\d\w]|$)") for dont_likes_regex in dont_like_regex: quash = re.search(dont_likes_regex, caption, re.IGNORECASE) if quash: quashed = ((((quash.group(0)).split("#")[1]).split(" ")[0] ).split("\n")[0].encode("utf-8") ) # dismiss possible space and newlines iffy = ( (re.split(r"\W+", dont_likes_regex))[3] if dont_likes_regex.endswith("*([^\\d\\w]|$)") else (re.split( r"\W+", dont_likes_regex))[1] # 'word' without format if dont_likes_regex.endswith("+([^\\d\\w]|$)") else (re.split(r"\W+", dont_likes_regex))[3] # '[word' if dont_likes_regex.startswith("#[\\d\\w]+") else (re.split(r"\W+", dont_likes_regex))[1] # ']word' ) # '#word' reason = 'Inappropriate: contains "{}"'.format( quashed if iffy == quashed else '" in "'. join([str(iffy), str(quashed)])) return True, username_text, is_video, image_links, reason, "Undesired word" return False, username_text, is_video, image_links, "None", "Success" finally: try: post_date = self.browser.find_element_by_xpath( XP.POST_DATE).get_attribute('datetime') post_date = datetime.fromisoformat(post_date[:-1]) except NoSuchElementException: post_date = datetime.now() self.logger.debug("Storing Post") post = store_post(post_link, username_text, post_date, image_links, caption, likes_count, image_descriptions) self.logger.debug("Storing Comments") # store_comments(self, post) elapsed_time = perf_counter() - t self.logger.info( "Check post elapsed time: {:.0f} seconds".format(elapsed_time))
def follow_user_follow( self, follow: str, usernames: List[str], amount: int = 10, randomize: bool = False ): if self.aborting: return self valid = {"followers", "followings"} if follow not in valid: raise ValueError( "nf_follow_user_follow: follow must be one of %r." % valid) self.logger.info("Starting to follow user {}".format(follow)) for index, username in enumerate(usernames): state = { 'liked_img': 0, 'already_liked': 0, 'inap_img': 0, 'commented': 0, 'followed': 0, 'not_valid_users': 0, } self.logger.info("User [{}/{}]".format(index + 1, len(usernames))) self.logger.info("--> {}".format(username.encode("utf-8"))) nf_go_to_user_page(self, username) sleep(1) # TODO: get follow count follow_count = 10 actual_amount = amount if follow_count < amount: actual_amount = follow_count self.logger.info("About to go to {} page".format(follow)) nf_go_to_follow_page(self, follow, username) sleep(2) sc_rolled = 0 scroll_nap = 1.5 already_interacted_links = [] random_chance = 50 try: while state['followed'] in range(0, actual_amount): if self.quotient_breach: self.logger.warning( "--> Follow quotient reached its peak!" "\t~leaving Follow-User-Follow_ activity\n" ) break if sc_rolled > 100: self.logger.info("Scrolled too much! ~ sleeping 10 minutes") sleep(600) sc_rolled = 0 users = nf_get_all_users_on_element(self) # Interact with links instead of just storing them for user in users: link = user.get_attribute("href") if link not in already_interacted_links: msg = "" try: self.logger.info("about to scroll to user") sleep(1) nf_scroll_into_view(self, user) self.logger.info("about to click to user") sleep(1) nf_click_center_of_element(self, user) sleep(2) valid = False if ( user.text not in self.dont_include and not follow_restriction( "read", user.text, self.follow_times, self.logger and random.randint(0, 100) <= random_chance ) ): valid, details = nf_validate_user_call(self, user.text) self.logger.info("Valid User: {}, details: {}".format(valid, details)) if valid: self.logger.info("about to follow user") follow_state, msg = follow_user( self.browser, "profile", self.username, user.text, None, self.blacklist, self.logger, self.logfolder, ) if follow_state is True: state['followed'] += 1 self.logger.info("user followed") else: self.logger.info("--> Not following") sleep(1) if random.randint(0, 100) <= self.user_interact_percentage: self.logger.info( "--> User gonna be interacted: '{}'".format( user.text ) ) # disable re-validating user in like_by_users like_by_users( self, [user.text], None, True, ) else: state["not_valid_users"] += 1 finally: sleep(5) user_link = "https://www.instagram.com/{}".format(username) follow_link = "https://www.instagram.com/{}/{}".format(username, follow) nf_find_and_press_back(self, follow_link) sleep(3) if check_if_in_correct_page(self, user_link): nf_go_to_follow_page(self, follow, username) already_interacted_links.append(link) if msg == "block on follow": pass # TODO deal with block on follow break else: # For loop ended means all users in screen has been interacted with scrolled_to_bottom = self.browser.execute_script( "return window.scrollMaxY == window.scrollY" ) if scrolled_to_bottom and randomize and random_chance < 100: random_chance += 25 self.browser.execute_script( "window.scrollTo(0, 0);" ) update_activity(self.browser, state=None) sc_rolled += 1 sleep(scroll_nap) elif scrolled_to_bottom: # already followed all possibles users break # will scroll the screen a bit and reload for i in range(3): self.browser.execute_script( "window.scrollTo(0, document.body.scrollHeight);" ) update_activity(self.browser, state=None) sc_rolled += 1 sleep(scroll_nap) except Exception: raise sleep(4) self.logger.info("User [{}/{}]".format(index + 1, len(usernames))) self.logger.info("Liked: {}".format(state['liked_img'])) self.logger.info("Already Liked: {}".format(state['already_liked'])) self.logger.info("Commented: {}".format(state['commented'])) self.logger.info("Followed: {}".format(state['followed'])) self.logger.info("Inappropriate: {}".format(state['inap_img'])) self.logger.info("Not valid users: {}\n".format(state['not_valid_users'])) self.liked_img += state['liked_img'] self.already_liked += state['already_liked'] self.commented += state['commented'] self.followed += state['followed'] self.inap_img += state['inap_img'] self.not_valid_users += state['not_valid_users'] return self
def db_store_comments(self, posts: List[Post], post_link: str): """Stores all comments of open post then goes back to post page""" try: comments_button = self.browser.find_elements_by_xpath( '//article//div[2]/div[1]//a[contains(@href,"comments")]') if comments_button: nf_scroll_into_view(self, comments_button[0]) nf_click_center_of_element(self, comments_button[0]) sleep(2) comments_link = post_link + 'comments/' if not check_if_in_correct_page(self, comments_link): self.logger.error( "Failed to go to comments page, navigating there") # TODO: retry to get there naturally web_address_navigator(self.browser, comments_link) more_comments = self.browser.find_elements_by_xpath( '//span[@aria-label="Load more comments"]') counter = 1 while more_comments and counter <= 10: self.logger.info("Loading comments ({}/10)...".format(counter)) nf_scroll_into_view(self, more_comments[0]) self.browser.execute_script("arguments[0].click();", more_comments[0]) more_comments = self.browser.find_elements_by_xpath( '//span[@aria-label="Load more comments"]') counter += 1 comments = self.browser.find_elements_by_xpath( '/html/body/div[1]/section/main/div/ul/ul[@class="Mr508"]') for comment in comments: inner_container = comment.find_element_by_xpath( './/div[@class="C4VMK"]') username = inner_container.find_element_by_xpath( './/h3/div/a').text text, _ = deform_emojis( inner_container.find_element_by_xpath('.//span').text) post_date = inner_container.find_element_by_xpath( './/time').get_attribute('datetime') post_date = datetime.fromisoformat(post_date[:-1]) user = db_get_or_create_user(self, username) self.db.session.add(user) self.db.session.commit() for post in posts: comment = Comment( date_posted=post_date, text=text, user=user, post=post, ) self.db.session.add(comment) self.db.session.commit() else: self.logger.error("No comments found") except SQLAlchemyError: self.db.session.rollback() raise finally: self.db.session.commit() nf_find_and_press_back(self, post_link)
def like_by_users( self, usernames: List[str], amount: int = None, users_validated: bool = False ): """Likes some amounts of images for each usernames""" if self.aborting: return self amount = amount or self.user_interact_amount usernames = usernames or [] self.quotient_breach = False for index, username in enumerate(usernames): if self.quotient_breach: break state = { 'liked_img': 0, 'already_liked': 0, 'inap_img': 0, 'commented': 0, 'followed': 0, 'not_valid_users': 0, } self.logger.info( "Username [{}/{}]".format(index + 1, len(usernames)) ) self.logger.info("--> {}".format(username.encode("utf-8"))) if len(usernames) == 1 and users_validated: nf_go_from_post_to_profile(self, username) else: nf_go_to_user_page(self, username) if not users_validated: validation, details = nf_validate_user_call(self, username) if not validation: self.logger.info( "--> Not a valid user: {}".format(details) ) state["not_valid_users"] += 1 continue try_again = 0 sc_rolled = 0 scroll_nap = 1.5 already_interacted_links = [] try: while state['liked_img'] in range(0, amount): if self.jumps["consequent"]["likes"] >= self.jumps["limit"]["likes"]: self.logger.warning( "--> Like quotient reached its peak!\t~leaving " "Like-By-Users activity\n" ) self.quotient_breach = True # reset jump counter after a breach report self.jumps["consequent"]["likes"] = 0 break if sc_rolled > 100: try_again += 1 if try_again > 2: # you can try again as much as you want by changing this number self.logger.info( "'{}' user POSSIBLY has less valid images than " "desired:{} found:{}...".format( username, amount, len(already_interacted_links)) ) break self.logger.info( "Scrolled too much! ~ sleeping 10 minutes") sleep(600) sc_rolled = 0 main_elem = self.browser.find_element_by_tag_name("main") # feed = main_elem.find_elements_by_xpath('//div[@class=" _2z6nI"]') posts = nf_get_all_posts_on_element(main_elem) # Interact with links instead of just storing them for post in posts: link = post.get_attribute("href") if link not in already_interacted_links: self.logger.info("about to scroll to post") sleep(1) nf_scroll_into_view(self, post) self.logger.info("about to click to post") sleep(1) nf_click_center_of_element(self, post) success, msg, state = nf_interact_with_post( self, link, amount, state, users_validated, ) self.logger.info( "Returned from liking, should still be in post page") sleep(5) nf_find_and_press_back( self, "https://www.instagram.com/{}/".format(username) ) already_interacted_links.append(link) if success: break if msg == "block on likes": # TODO deal with block on likes break else: # For loop ended means all posts in screen has been interacted with # will scroll the screen a bit and reload for i in range(3): self.browser.execute_script( "window.scrollTo(0, document.body.scrollHeight);" ) update_activity(self.browser, state=None) sc_rolled += 1 sleep(scroll_nap) except Exception: raise sleep(4) self.logger.info("Username [{}/{}]".format(index + 1, len(usernames))) self.logger.info("--> {} ended".format(username.encode("utf-8"))) self.logger.info("Liked: {}".format(state['liked_img'])) self.logger.info("Already Liked: {}".format(state['already_liked'])) self.logger.info("Commented: {}".format(state['commented'])) self.logger.info("Followed: {}".format(state['followed'])) self.logger.info("Inappropriate: {}".format(state['inap_img'])) self.logger.info("Not valid users: {}\n".format(state['not_valid_users'])) self.liked_img += state['liked_img'] self.already_liked += state['already_liked'] self.commented += state['commented'] self.followed += state['followed'] self.inap_img += state['inap_img'] self.not_valid_users += state['not_valid_users'] return self
def like_by_tags( self, tags: List[str] = None, amount: int = 50, skip_top_posts: bool = True, use_smart_hashtags: bool = False, use_smart_location_hashtags: bool = False, ): """Likes (default) 50 images per given tag""" if self.aborting: return self # if smart hashtag is enabled if use_smart_hashtags is True and self.smart_hashtags != []: self.logger.info("Using smart hashtags") tags = self.smart_hashtags elif use_smart_location_hashtags is True and self.smart_location_hashtags != []: self.logger.info("Using smart location hashtags") tags = self.smart_location_hashtags # deletes white spaces in tags tags = [tag.strip() for tag in tags] tags = tags or [] self.quotient_breach = False for index, tag in enumerate(tags): if self.quotient_breach: break state = { 'liked_img': 0, 'already_liked': 0, 'inap_img': 0, 'commented': 0, 'followed': 0, 'not_valid_users': 0, } self.logger.info("Tag [{}/{}]".format(index + 1, len(tags))) self.logger.info("--> {}".format(tag.encode("utf-8"))) tag = tag[1:] if tag[:1] == "#" else tag nf_go_to_tag_page(self, tag) # get amount of post with this hashtag try: possible_posts = self.browser.execute_script( "return window._sharedData.entry_data." "TagPage[0].graphql.hashtag.edge_hashtag_to_media.count" ) except WebDriverException: try: possible_posts = self.browser.find_element_by_xpath( read_xpath("get_links_for_tag", "possible_post") ).text if possible_posts: possible_posts = format_number(possible_posts) else: self.logger.info( "Failed to get the amount of possible posts in '{}' tag " "~empty string".format(tag) ) possible_posts = None except NoSuchElementException: self.logger.info( "Failed to get the amount of possible posts in {} tag".format(tag) ) possible_posts = None self.logger.info( "desired amount: {} | top posts [{}] | possible posts: " "{}".format( amount, "enabled" if not skip_top_posts else "disabled", possible_posts, ) ) if possible_posts is not None: amount = possible_posts if amount > possible_posts else amount # sometimes pages do not have the correct amount of posts as it is # written there, it may be cos of some posts is deleted but still keeps # counted for the tag sleep(1) try_again = 0 sc_rolled = 0 scroll_nap = 1.5 already_interacted_links = [] try: while state['liked_img'] in range(0, amount): if sc_rolled > 100: try_again += 1 if try_again > 2: self.logger.info( "'{}' tag POSSIBLY has less images than " "desired:{} found:{}...".format( tag, amount, len(already_interacted_links) ) ) break self.logger.info("Scrolled too much! ~ sleeping 10 minutes") sleep(600) sc_rolled = 0 main_elem = self.browser.find_element_by_tag_name("main") posts = nf_get_all_posts_on_element(main_elem) # Interact with links instead of just storing them for post in posts: link = post.get_attribute("href") if link not in already_interacted_links: self.logger.info("about to scroll to post") sleep(1) nf_scroll_into_view(self, post) self.logger.info("about to click to post") sleep(1) nf_click_center_of_element(self, post) success, msg, state = nf_interact_with_post( self, link, amount, state, ) self.logger.info("Returned from liking, should still be in post page") sleep(2) nf_find_and_press_back(self, "https://www.instagram.com/explore/tags/{}/".format(tag)) already_interacted_links.append(link) if success: break if msg == "block on likes": # TODO deal with block on likes break else: # For loop ended means all posts in screen has been interacted with # will scroll the screen a bit and reload for i in range(3): self.browser.execute_script( "window.scrollTo(0, document.body.scrollHeight);" ) update_activity(self.browser, state=None) sc_rolled += 1 sleep(scroll_nap) except Exception: raise sleep(2) self.logger.info("Tag [{}/{}]".format(index + 1, len(tags))) self.logger.info("--> {} ended".format(tag.encode("utf-8"))) self.logger.info("Liked: {}".format(state['liked_img'])) self.logger.info("Already Liked: {}".format(state['already_liked'])) self.logger.info("Commented: {}".format(state['commented'])) self.logger.info("Followed: {}".format(state['followed'])) self.logger.info("Inappropriate: {}".format(state['inap_img'])) self.logger.info("Not valid users: {}\n".format(state['not_valid_users'])) self.liked_img += state['liked_img'] self.already_liked += state['already_liked'] self.commented += state['commented'] self.followed += state['followed'] self.inap_img += state['inap_img'] self.not_valid_users += state['not_valid_users'] return self
def follow_user_follow(self, relation: str, usernames: List[str], amount: int = 10, randomize: bool = False, random_chance: int = 50): """ Follows 'amount' users of 'relation' ("following" or "followers") list of each user in usernames :param relation: what list to use, "following" or "followers" :param usernames: list of usernames to follow relations of :param amount: amount of users to follow for each user in 'usernames' :param randomize: if the bot will include a random factor to choose who to follow or follow the first 'amount' of usernames on the list :param random_chance: chance a user will be followed if using 'randomize' """ if self.aborting: return self valid = {"followers", "following"} if relation not in valid: self.logger.info( '{} is not a valid relation, using "followers"'.format( relation)) relation = "followers" self.logger.info("Starting to follow users {}".format(relation)) # for each username for index, username in enumerate(usernames): # if aborting or quota was breached or its past time according to settings break the loop if self.aborting or self.quotient_breach or ( self.until_time and datetime.now() > self.until_time): break interactions = Interactions() self.logger.info("Follow User {} [{}/{}]: {} - started".format( relation, index + 1, len(usernames), username)) user_link = "https://www.instagram.com/{}".format(username) follow_link = "https://www.instagram.com/{}/{}".format( username, relation) # navigate to user page if not check_if_in_correct_page(self, user_link): nf_go_to_user_page(self, username) sleep(1) # get followers & following counts and change amount if less than desired followers_count, following_count = get_relationship_counts( self, username) follow_count = following_count if relation == "following" else followers_count follow_count = follow_count if follow_count else 0 actual_amount = amount if follow_count < amount: actual_amount = follow_count # go to relation page nf_go_to_follow_page(self, relation, username) sleep(2) # follow users sc_rolled = 0 scroll_nap = 1.5 already_interacted_links = [] while interactions.followed in range(actual_amount): # if aborting or quota was breached or its past time according to settings break the loop if self.aborting or (self.until_time and datetime.now() > self.until_time): break # if quotient was breached break the loop if self.jumps.check_follows(): self.logger.warning( "Follow quotient reached its peak, leaving Follow User {} activity" .format(relation)) # reset jump counter before breaking the loop self.jumps.follows = 0 self.quotient_breach = True break # if scrolled too much sleep for 5-10 minutes if sc_rolled > 100: delay_random = random.randint(300, 600) self.logger.info( "Scrolled too much, sleeping {} minutes and {} seconds" .format(int(delay_random / 60), delay_random % 60)) sleep(delay_random) sc_rolled = 0 # get loaded usernames users = nf_get_all_users_on_element(self) # if no users were grabbed try to go back and load the relation page again while len(users) == 0: nf_find_and_press_back(self, user_link) in_user_page = check_if_in_correct_page(self, user_link) if not in_user_page: nf_go_to_user_page(self, username) nf_go_to_follow_page(self, relation, username) # get loaded usernames users = nf_get_all_users_on_element(self) # If after rechecking we are in the correct page there still no are users # the bot is most surely soft blocked from seeing relations, that block doesnt last long usually. # sleep for 5-10 minutes if len(users) == 0: delay_random = random.randint(300, 600) self.logger.info( "Soft block on see followers, " "sleeping {} minutes and {} seconds".format( int(delay_random / 60), delay_random % 60)) sleep(delay_random) self.logger.debug("Grabbed {} usernames".format(len(users))) # first one in the list is un-clickable by bad design on browser instagram, its behind the top bar for user in users[1:]: link = user.get_attribute("href") # try to follow first not already interacted user if link not in already_interacted_links: msg = "" try: user_text = user.text user_link2 = "https://www.instagram.com/{}".format( user_text) self.logger.info("Followed [{}/{}]".format( interactions.followed, actual_amount)) # Go to user page self.logger.info( "Trying user {}".format(user_text)) nf_scroll_into_view(self, user) sleep(1) nf_click_center_of_element(self, user, user_link2) sleep(2) # validate user valid = False if (user_text not in self.settings.dont_include and not is_follow_restricted(self, user_text) and random.randint(0, 100) <= random_chance): valid, details = nf_validate_user_call( self, user_text, self.quota_supervisor.FOLLOW) self.logger.info( "Valid User: {}, details: {}".format( valid, details)) # follow user if valid: follow_state, msg = follow_user( self, "profile", user_text) if follow_state is True: interactions.followed += 1 elif msg == "already followed": interactions.already_followed += 1 elif msg == "jumped": # will break the loop after certain consecutive jumps self.jumps.follows += 1 # interact with user if (self.settings.do_like and random.randint(0, 100) <= self.settings.user_interact_percentage ): self.logger.info( "Interacting with user '{}'".format( user_text)) if not check_if_in_correct_page( self, user_link2): nf_go_from_post_to_profile( self, user_text) interactions += like_loop( self, "Interact with user '{}'".format( user_text), user_link2, self.settings.user_interact_amount, True) else: interactions.not_valid_users += 1 except Exception as e: self.logger.error(e) finally: # go back to relation page and start the loop again sleep(1) nf_find_and_press_back(self, follow_link) in_follow_page = check_if_in_correct_page( self, follow_link) if not in_follow_page: in_user_page = check_if_in_correct_page( self, user_link) if not in_user_page: nf_go_to_user_page(self, username) nf_go_to_follow_page(self, relation, username) already_interacted_links.append(link) if msg == "block on follow": # raise SoftBlockedException(msg) pass # TODO: deal with block on follow break else: # For loop ended means all users in screen has been interacted with scrolled_to_bottom = self.browser.execute_script( JS.SCROLLED_TO_BOTTOM) # even if we are at the bottom if we were using randomize some users were ignored # so the bot can go back and look again with a higher random chance to foollow the users if scrolled_to_bottom and randomize and random_chance < 100: random_chance += 25 self.browser.execute_script(JS.SCROLL_TO_TOP) self.quota_supervisor.add_server_call() sc_rolled += 1 sleep(scroll_nap) elif scrolled_to_bottom: # already followed all possibles users break # if not at the bottom of the list # will scroll the screen a bit and look again for i in range(3): self.browser.execute_script(JS.SCROLL_SCREEN) self.quota_supervisor.add_server_call() sc_rolled += 1 sleep(scroll_nap) sleep(3) self.logger.info("Follow User {} [{}/{}] - ended".format( relation, index + 1, len(usernames))) self.logger.info(str(interactions)) self.interactions += interactions return self
def follow_user( self, track: str, user_name: str, button: Union[WebElement, None] = None ) -> Tuple[bool, str]: # follow_state, msg """ Follow a user either from the profile page or post page or dialog box """ # list of available tracks to follow in: ["profile", "post" "dialog"] # check action availability if self.quota_supervisor.jump_follow(): return False, "jumped" if track in ["profile", "post"]: if track == "profile": # check URL of the webpage, if it already is user's profile # page, then do not navigate to it again user_link = "https://www.instagram.com/{}/".format(user_name) if not check_if_in_correct_page(self, user_link): nf_go_to_user_page(self, user_name) # find out CURRENT following status for _ in range(3): following_status, follow_button = get_following_status( self, track, user_name) if following_status in ["Follow", "Follow Back"]: nf_scroll_into_view(self, follow_button) nf_click_center_of_element(self, follow_button, skip_action_chain=True) sleep(3) following_status, follow_button = get_following_status( self, track, user_name) if following_status in ["Following", "Requested"]: break elif following_status == "Following": self.logger.info("Already following '{}'".format(user_name)) return False, "already followed" elif following_status == "Requested": self.logger.info( "Already requested '{}' to follow".format(user_name)) return False, "already requested" elif following_status == "Unblock": self.logger.info("User '{}' is blocked".format(user_name)) return False, "user is blocked" elif following_status == "UNAVAILABLE": self.logger.info("User '{}' is inaccessible".format(user_name)) return False, "user is inaccessible" elif following_status is None: sirens_wailing, emergency_state = emergency_exit(self) if sirens_wailing is True: return False, emergency_state else: self.logger.warning( "Couldn't follow '{}', unexpected failure".format( user_name)) return False, "unexpected failure" elif track == "dialog": nf_click_center_of_element(self, button) # general tasks after a successful follow self.logger.info("Followed {}".format(user_name)) add_follow_times(self, user_name) add_user_to_blacklist(self, user_name, self.quota_supervisor.FOLLOW) self.quota_supervisor.add_follow() return True, "success"
def nf_check_post( self, post_link: str) -> Tuple[bool, str, bool, List[str], str, str]: """Checks if post can be liked according to declared settings Also stores post data in database if appropriate :returns: inappropriate, username, is_video, image_links, reason, scope """ t = time.perf_counter() username_text = "" caption = "" image_descriptions = [] image_links = [] try: username = self.browser.find_element_by_xpath( '/html/body/div[1]/section/main/div/div/article/header//div[@class="e1e1d"]' ) username_text = username.text # follow_button = self.browser.find_element_by_xpath( # '/html/body/div[1]/section/main/div/div/article/header/div[2]/div[1]/div[2]/button' # ) # following = follow_button.text == "Following" locations = self.browser.find_elements_by_xpath( '/html/body/div[1]/section/main/div/div/article/header//a[contains(@href,"locations")]' ) location_text = locations[0].text if locations != [] else None # location_link = locations[0].get_attribute('href') if locations != [] else None images = self.browser.find_elements_by_xpath( '/html/body/div[1]/section/main/div/div/article//img[@class="FFVAD"]' ) """ video_previews = self.browser.find_elements_by_xpath( '/html/body/div[1]/section/main/div/div/article//img[@class="_8jZFn"]' ) videos = self.browser.find_elements_by_xpath( '/html/body/div[1]/section/main/div/div/article//video[@class="tWeCl"]' ) if (len(images) + len(videos)) == 1: # single image or video elif len(images) == 2: # carousel """ is_video = len(images) == 0 more_button = self.browser.find_elements_by_xpath( "//button[text()='more']") if more_button: nf_scroll_into_view(self, more_button[0]) more_button[0].click() caption = self.browser.find_element_by_xpath( "/html/body/div[1]/section/main/div/div/article//div[2]/div[1]//div/span/span" ).text caption = "" if caption is None else caption for image in images: image_description = image.get_attribute('alt') if image_description is not None and 'Image may contain:' in image_description: image_description = image_description[ image_description.index('Image may contain:') + 19:] else: image_description = None image_descriptions.append(image_description) image_links.append(image.get_attribute('src')) self.logger.info("Image from: {}".format( username_text.encode("utf-8"))) self.logger.info("Link: {}".format(post_link.encode("utf-8"))) self.logger.info("Caption: {}".format(caption.encode("utf-8"))) for image_description in image_descriptions: if image_description: self.logger.info("Description: {}".format( image_description.encode("utf-8"))) # Check if mandatory character set, before adding the location to the text if self.mandatory_language: if not self.check_character_set(caption): return ( True, username_text, is_video, image_links, "Mandatory language not fulfilled", "Not mandatory " "language", ) # Append location to image_text so we can search through both in one go if location_text: self.logger.info("Location: {}".format( location_text.encode("utf-8"))) caption = caption + "\n" + location_text if self.mandatory_words: if not any((word in caption for word in self.mandatory_words)): return ( True, username_text, is_video, image_links, "Mandatory words not fulfilled", "Not mandatory likes", ) image_text_lower = [x.lower() for x in caption] ignore_if_contains_lower = [x.lower() for x in self.ignore_if_contains] if any( (word in image_text_lower for word in ignore_if_contains_lower)): return (False, username_text, is_video, image_links, "Contains word in ignore_if_contains list", "Ignore if contains") dont_like_regex = [] for dont_likes in self.dont_like: if dont_likes.startswith("#"): dont_like_regex.append(dont_likes + r"([^\d\w]|$)") elif dont_likes.startswith("["): dont_like_regex.append("#" + dont_likes[1:] + r"[\d\w]+([^\d\w]|$)") elif dont_likes.startswith("]"): dont_like_regex.append(r"#[\d\w]+" + dont_likes[1:] + r"([^\d\w]|$)") else: dont_like_regex.append(r"#[\d\w]*" + dont_likes + r"[\d\w]*([^\d\w]|$)") for dont_likes_regex in dont_like_regex: quash = re.search(dont_likes_regex, caption, re.IGNORECASE) if quash: quashed = ((((quash.group(0)).split("#")[1]).split(" ")[0] ).split("\n")[0].encode("utf-8") ) # dismiss possible space and newlines iffy = ( (re.split(r"\W+", dont_likes_regex))[3] if dont_likes_regex.endswith("*([^\\d\\w]|$)") else (re.split( r"\W+", dont_likes_regex))[1] # 'word' without format if dont_likes_regex.endswith("+([^\\d\\w]|$)") else (re.split(r"\W+", dont_likes_regex))[3] # '[word' if dont_likes_regex.startswith("#[\\d\\w]+") else (re.split(r"\W+", dont_likes_regex))[1] # ']word' ) # '#word' reason = 'Inappropriate! ~ contains "{}"'.format( quashed if iffy == quashed else '" in "'. join([str(iffy), str(quashed)])) return True, username_text, is_video, image_links, reason, "Undesired word" return False, username_text, is_video, image_links, "None", "Success" finally: if self.store_in_database: try: user = db_get_or_create_user(self, username_text) self.db.session.add(user) self.db.session.commit() db_posts = [] for image_link, image_description in zip( image_links, image_descriptions): try: post_date = self.browser.find_element_by_xpath( '/html/body/div[1]/section/main/div/div/article//a[@class="c-Yi7"]/time' ).get_attribute('datetime') post_date = datetime.fromisoformat(post_date[:-1]) except NoSuchElementException: post_date = datetime.now() post = db_get_or_create_post(self, post_date, image_link, caption, user, image_description) self.db.session.add(post) db_posts.append(post) self.db.session.commit() if db_posts: self.logger.info("About to store comments") db_store_comments(self, db_posts, post_link) except SQLAlchemyError: self.db.session.rollback() finally: self.db.session.commit() elapsed_time = time.perf_counter() - t self.logger.info( "check post elapsed time: {:.0f} seconds".format(elapsed_time))