"""OS Modules environ method to get the setup vars from the Environment"""
# import built-in & third-party modules
import time
import random
import os
import csv
import json
import requests
import unicodedata
import logging
import logging.handlers

from datetime import datetime
from datetime import timedelta
from math import ceil
from sys import platform
from platform import python_version
from selenium import webdriver
from selenium.webdriver import DesiredCapabilities
from logging.handlers import RotatingFileHandler
from contextlib import contextmanager
from copy import deepcopy

try:
    from pyvirtualdisplay import Display
except ModuleNotFoundError:
    pass

# import InstaPy modules
from . import __version__
from .constants import MEDIA_PHOTO
from .constants import MEDIA_VIDEO
from .clarifai_util import check_image
from .comment_util import comment_image
from .comment_util import get_comments_on_post
from .comment_util import process_comments
from .like_util import check_link
from .like_util import verify_liking
from .like_util import get_links_for_tag
from .like_util import get_links_from_feed
from .like_util import get_tags
from .like_util import get_links_for_location
from .like_util import like_image
from .like_util import get_links_for_username
from .like_util import like_comment
from .story_util import watch_story
from .login_util import login_user
from .settings import Settings
from .settings import localize_path
from .print_log_writer import log_follower_num
from .print_log_writer import log_following_num
from .time_util import sleep
from .time_util import set_sleep_percentage
from .util import get_active_users
from .util import validate_username
from .util import web_address_navigator
from .util import interruption_handler
from .util import highlight_print
from .util import dump_record_activity
from .util import truncate_float
from .util import save_account_progress
from .util import parse_cli_args
from .util import get_cord_location
from .util import get_bounding_box
from .util import file_handling
from .util import scroll_down
from .unfollow_util import get_given_user_followers
from .unfollow_util import get_given_user_following
from .unfollow_util import unfollow
from .unfollow_util import unfollow_user
from .unfollow_util import follow_user
from .unfollow_util import follow_restriction
from .unfollow_util import dump_follow_restriction
from .unfollow_util import set_automated_followed_pool
from .unfollow_util import get_follow_requests
from .commenters_util import extract_information
from .commenters_util import users_liked
from .commenters_util import get_photo_urls_from_profile
from .relationship_tools import get_following
from .relationship_tools import get_followers
from .relationship_tools import get_unfollowers
from .relationship_tools import get_nonfollowers
from .relationship_tools import get_fans
from .relationship_tools import get_mutual_following
from .database_engine import get_database
from .text_analytics import text_analysis
from .text_analytics import yandex_supported_languages
from .browser import set_selenium_local_session
from .browser import close_browser
from .file_manager import get_workspace
from .file_manager import get_logfolder
from .pods_util import group_posts
from .pods_util import get_recent_posts_from_pods
from .pods_util import share_my_post_with_pods
from .pods_util import share_with_pods_restriction
from .pods_util import comment_restriction
from .xpath import read_xpath

# import exceptions
from selenium.common.exceptions import NoSuchElementException
from .exceptions import InstaPyError


class InstaPy:
    """Class to be instantiated to use the script"""

    def __init__(
        self,
        username: str = None,
        password: str = None,
        nogui: bool = False,
        selenium_local_session: bool = True,
        browser_profile_path: str = None,
        page_delay: int = 25,
        show_logs: bool = True,
        headless_browser: bool = False,
        proxy_username: str = None,
        proxy_password: str = None,
        proxy_address: str = None,
        proxy_port: str = None,
        disable_image_load: bool = False,
        multi_logs: bool = True,
        log_handler=None,  # TODO function type ?
        geckodriver_path: str = None,
        split_db: bool = False,
        bypass_security_challenge_using: str = "email",
        security_codes: int = 0000,
        want_check_browser: bool = True,
        browser_executable_path: str = None,
        geckodriver_log_level: str = "info",  # "info" by default
    ):
        print("InstaPy Version: {}".format(__version__))
        cli_args = parse_cli_args()
        username = cli_args.username or username
        password = cli_args.password or password
        page_delay = cli_args.page_delay or page_delay
        headless_browser = cli_args.headless_browser or headless_browser
        proxy_address = cli_args.proxy_address or proxy_address
        proxy_port = cli_args.proxy_port or proxy_port
        disable_image_load = cli_args.disable_image_load or disable_image_load
        split_db = cli_args.split_db or split_db
        want_check_browser = cli_args.want_check_browser or want_check_browser

        Settings.InstaPy_is_running = True
        # workspace must be ready before anything
        if not get_workspace():
            raise InstaPyError("Oh no! I don't have a workspace to work at :'(")

        # virtual display to hide browser (not supported on Windows)
        self.nogui = nogui
        if self.nogui:
            if not platform.startswith("win32"):
                self.display = Display(visible=0, size=(800, 600))
                self.display.start()
            else:
                raise InstaPyError("The 'nogui' parameter isn't supported on Windows.")

        self.browser = None
        self.page_delay = page_delay
        self.disable_image_load = disable_image_load
        self.bypass_security_challenge_using = bypass_security_challenge_using
        self.security_codes = security_codes

        # choose environment over static typed credentials
        self.username = os.environ.get("INSTA_USER") or username
        self.password = os.environ.get("INSTA_PW") or password

        Settings.profile["name"] = self.username

        self.split_db = split_db
        if self.split_db:
            Settings.database_location = localize_path(
                "db", "instapy_{}.db".format(self.username)
            )

        self.want_check_browser = want_check_browser

        self.do_comment = False
        self.comment_percentage = 0
        self.comments = ["Cool!", "Nice!", "Looks good!"]
        self.photo_comments = []
        self.video_comments = []

        self.do_reply_to_comments = False
        self.reply_to_comments_percent = 0
        self.comment_replies = []
        self.photo_comment_replies = []
        self.video_comment_replies = []

        self.liked_img = 0
        self.already_liked = 0
        self.liked_comments = 0
        self.commented = 0
        self.replied_to_comments = 0
        self.followed = 0
        self.already_followed = 0
        self.unfollowed = 0
        self.followed_by = 0
        self.following_num = 0
        self.inap_img = 0
        self.not_valid_users = 0
        self.video_played = 0
        self.already_Visited = 0
        self.stories_watched = 0
        self.reels_watched = 0

        self.follow_times = 1
        self.share_times = 1
        self.comment_times = 1
        self.do_follow = False
        self.follow_percentage = 0
        self.dont_include = set()
        self.white_list = set()
        self.blacklist = {"enabled": "True", "campaign": ""}
        self.automatedFollowedPool = {"all": [], "eligible": []}
        self.do_like = False
        self.like_percentage = 0
        self.do_comment_liked_photo = False
        self.do_story = False
        self.story_percentage = 0
        self.story_simulate = False
        self.smart_hashtags = []
        self.smart_location_hashtags = []

        self.dont_like = ["sex", "nsfw"]
        self.mandatory_words = []
        self.ignore_if_contains = []
        self.ignore_users = []

        self.user_interact_amount = 0
        self.user_interact_media = None
        self.user_interact_percentage = 0
        self.user_interact_random = False
        self.dont_follow_inap_post = True

        self.use_clarifai = False
        self.clarifai_api_key = None
        self.clarifai_models = []
        self.clarifai_workflow = []
        self.clarifai_probability = 0.50
        self.clarifai_img_tags = []
        self.clarifai_img_tags_skip = []
        self.clarifai_full_match = False
        self.clarifai_check_video = False
        self.clarifai_proxy = None

        self.potency_ratio = None  # 1.3466
        self.delimit_by_numbers = None

        self.max_followers = None  # 90000
        self.max_following = None  # 66834
        self.min_followers = None  # 35
        self.min_following = None  # 27

        self.delimit_liking = False
        self.liking_approved = True
        self.max_likes = 1000
        self.min_likes = 0

        self.delimit_commenting = False
        self.commenting_approved = True
        self.max_comments = 35
        self.min_comments = 0
        self.comments_mandatory_words = []
        self.max_posts = None
        self.min_posts = None
        self.skip_business_categories = []
        self.skip_bio_keyword = []
        self.mandatory_bio_keywords = []
        self.dont_skip_business_categories = []
        self.skip_business = False
        self.skip_non_business = False
        self.skip_no_profile_pic = False
        self.skip_private = True
        self.skip_business_percentage = 100
        self.skip_no_profile_pic_percentage = 100
        self.skip_private_percentage = 100
        self.relationship_data = {username: {"all_following": [], "all_followers": []}}

        self.simulation = {"enabled": True, "percentage": 100}

        self.mandatory_language = False
        self.mandatory_character = []
        self.check_letters = {}

        # use this variable to terminate the nested loops after quotient
        # reaches
        self.quotient_breach = False
        # hold the consecutive jumps and set max of it used with QS to break
        # loops
        self.jumps = {
            "consequent": {"likes": 0, "comments": 0, "follows": 0, "unfollows": 0},
            "limit": {"likes": 7, "comments": 3, "follows": 5, "unfollows": 4},
        }

        self.allowed_pod_topics = [
            "general",
            "fashion",
            "food",
            "travel",
            "sports",
            "entertainment",
        ]
        self.allowed_pod_engagement_modes = ["no_comments", "light", "normal", "heavy"]

        # stores the features' name which are being used by other features
        self.internal_usage = {}

        self.aborting = False
        self.start_time = time.time()

        # proxy address
        self.proxy_address = proxy_address

        # assign logger
        self.show_logs = show_logs
        Settings.show_logs = show_logs or None
        self.multi_logs = multi_logs
        self.logfolder = get_logfolder(self.username, self.multi_logs)
        self.logger = self.get_instapy_logger(self.show_logs, log_handler)

        get_database(make=True)  # IMPORTANT: think twice before relocating

        if selenium_local_session:
            self.browser, err_msg = set_selenium_local_session(
                proxy_address,
                proxy_port,
                proxy_username,
                proxy_password,
                headless_browser,
                browser_profile_path,
                disable_image_load,
                page_delay,
                geckodriver_path,
                browser_executable_path,
                self.logfolder,
                self.logger,
                geckodriver_log_level,
            )
            if len(err_msg) > 0:
                raise InstaPyError(err_msg)

    def get_instapy_logger(self, show_logs: bool, log_handler=None):
        """
        Handles the creation and retrieval of loggers to avoid
        re-instantiation.
        """

        existing_logger = Settings.loggers.get(self.username)
        if existing_logger is not None:
            return existing_logger
        else:
            # initialize and setup logging system for the InstaPy object
            logger = logging.getLogger(self.username)
            logger.setLevel(logging.DEBUG)
            # log name and format
            general_log = "{}general.log".format(self.logfolder)
            file_handler = logging.FileHandler(general_log)
            # log rotation, 5 logs with 10MB size each one
            file_handler = RotatingFileHandler(
                general_log, maxBytes=10 * 1024 * 1024, backupCount=5
            )
            file_handler.setLevel(logging.DEBUG)
            extra = {"username": self.username}
            logger_formatter = logging.Formatter(
                "%(levelname)s [%(asctime)s] [%(username)s]  %(message)s",
                datefmt="%Y-%m-%d %H:%M:%S",
            )
            file_handler.setFormatter(logger_formatter)
            logger.addHandler(file_handler)

            # add custom user handler if given
            if log_handler:
                logger.addHandler(log_handler)

            if show_logs is True:
                console_handler = logging.StreamHandler()
                console_handler.setLevel(logging.DEBUG)
                console_handler.setFormatter(logger_formatter)
                logger.addHandler(console_handler)

            logger = logging.LoggerAdapter(logger, extra)

            Settings.loggers[self.username] = logger
            Settings.logger = logger
            return logger

    def set_selenium_remote_session(
        self, logger, selenium_url: str = "", selenium_driver=None
    ):
        """
        Starts remote session for a selenium server.
        Creates a new selenium driver instance for remote session or uses
        provided one. Useful for docker setup.

        :param selenium_url: string
        :param selenium_driver: selenium WebDriver
        :return: self
        """
        if self.aborting:
            return self

        if selenium_driver:
            self.browser = selenium_driver
        else:
            self.browser = webdriver.Remote(
                command_executor=selenium_url,
                desired_capabilities=DesiredCapabilities.FIREFOX,
            )

        # convert_selenium_browser(selenium_driver)
        message = "Session started!"
        highlight_print("browser", message, "initialization", "info", logger)

        return self

    def login(self):
        """Used to login the user either with the username and password"""
        # InstaPy uses page_delay speed to implicit wait for elements,
        # here we're decreasing it to 5 seconds instead of the default 25
        # seconds to speed up the login process.
        #
        # In short: default page_delay speed took 25 seconds trying to locate
        # every element, now it's taking 5 seconds.
        temporary_page_delay = 5
        self.browser.implicitly_wait(temporary_page_delay)

        if not login_user(
            self.browser,
            self.username,
            self.password,
            self.logger,
            self.logfolder,
            self.proxy_address,
            self.bypass_security_challenge_using,
            self.security_codes,
            self.want_check_browser,
        ):
            message = (
                "Unable to login to Instagram! "
                "You will find more information in the logs above."
            )
            highlight_print(self.username, message, "login", "critical", self.logger)

            self.aborting = True
            return self

        # back the page_delay to default, or the value set by the user
        self.browser.implicitly_wait(self.page_delay)
        message = "Logged in successfully!"
        highlight_print(self.username, message, "login", "info", self.logger)
        # try to save account progress
        try:
            save_account_progress(self.browser, self.username, self.logger)
        except Exception as e:
            # Comment:
            # Saw this error:
            # "TypeError: window._sharedData.entry_data.ProfilePage is undefined"
            # when IG deleted a pic or video due community guidelines, then the
            # FF session shows a different page that interrupts the normal flow.
            self.logger.warning(
                "Unable to save account progress, skipping data update \n\t{}".format(
                    str(e).encode("utf-8")
                )
            )

        # logs only followers/following numbers when able to login,
        # to speed up the login process and avoid loading profile
        # page (meaning less server calls)
        self.followed_by = log_follower_num(self.browser, self.username, self.logfolder)
        self.following_num = log_following_num(
            self.browser, self.username, self.logfolder
        )

        return self

    def set_sleep_reduce(self, percentage: int):
        set_sleep_percentage(percentage)
        return self

    def set_action_delays(
        self,
        enabled: bool = False,
        like: int = None,
        comment: int = None,
        follow: int = None,
        unfollow: int = None,
        story: int = None,
        randomize: bool = False,
        random_range_from: int = None,
        random_range_to: int = None,
        safety_match: bool = True,
    ):
        """Set custom sleep delay after actions"""
        Settings.action_delays.update(
            {
                "enabled": enabled,
                "like": like,
                "comment": comment,
                "follow": follow,
                "unfollow": unfollow,
                "story": story,
                "randomize": randomize,
                "random_range": (random_range_from, random_range_to),
                "safety_match": safety_match,
            }
        )

    def set_do_comment(
        self,
        enabled: bool = False,
        comment_liked_photo: bool = False,
        percentage: int = 0,
    ):
        """
        Defines if images should be commented or not.
        E.g. percentage=25 means every ~4th picture will be commented.
        """

        if self.aborting:
            return self

        self.do_comment = enabled
        self.do_comment_liked_photo = comment_liked_photo  # Comment a liked photo
        self.comment_percentage = percentage

        return self

    def set_comments(self, comments: list = [], media: str = None):
        """
        Sets the possible posted comments.
        'What an amazing shot :heart_eyes: !' is an example for using emojis.
        """
        if self.aborting:
            return self

        if media not in [None, MEDIA_PHOTO, MEDIA_VIDEO]:
            self.logger.warning('Unkown media type! Treating as "any".')
            media = None

        self.comments = comments

        if media is None:
            self.comments = comments
        else:
            attr = "{}_comments".format(media.lower())
            setattr(self, attr, comments)

        return self

    def set_do_follow(self, enabled: bool = False, percentage: int = 0, times: int = 1):
        """Defines if the user of the liked image should be followed"""
        if self.aborting:
            return self

        self.follow_times = times
        self.do_follow = enabled
        self.follow_percentage = percentage

        return self

    def set_do_like(self, enabled: bool = False, percentage: int = 0):
        if self.aborting:
            return self

        self.do_like = enabled
        self.like_percentage = min(percentage, 100)

        return self

    def set_do_story(
        self, enabled: bool = False, percentage: int = 0, simulate: bool = False
    ):
        """
        configure stories
        enabled: to add story to interact
        percentage: how much to watch
        simulate: if True, we will simulate watching (faster),
                  but nothing will be seen on the browser window
        """
        if self.aborting:
            return self

        self.do_story = enabled
        self.story_percentage = min(percentage, 100)
        self.story_simulate = simulate

        return self

    def set_dont_like(self, tags: list = []):
        """Changes the possible restriction tags, if one of this
        words is in the description, the image won't be liked but user
        still might be unfollowed"""
        if self.aborting:
            return self

        if not isinstance(tags, list):
            self.logger.warning("Unable to use your set_dont_like configuration!")
            self.aborting = True

        self.dont_like = tags

        return self

    def set_mandatory_words(self, tags: list = []):
        """Changes the possible restriction tags, if all of this
        hashtags is in the description, the image will be liked"""
        if self.aborting:
            return self

        if not isinstance(tags, list):
            self.logger.warning("Unable to use your set_mandatory_words configuration!")
            self.aborting = True

        self.mandatory_words = tags

        return self

    def set_user_interact(
        self,
        amount: int = 10,
        percentage: int = 100,
        randomize: bool = False,
        media: str = None,
    ):
        """Define if posts of given user should be interacted"""
        if self.aborting:
            return self

        self.user_interact_amount = amount
        self.user_interact_random = randomize
        self.user_interact_percentage = percentage
        self.user_interact_media = media

        return self

    def set_ignore_users(self, users: list = []):
        """Changes the possible restriction to users, if a user who posts
        is one of these, the image won't be liked"""
        if self.aborting:
            return self

        self.ignore_users = users

        return self

    def set_ignore_if_contains(self, words: list = []):
        """Ignores the don't likes if the description contains
        one of the given words"""
        if self.aborting:
            return self

        self.ignore_if_contains = words

        return self

    def set_dont_include(self, friends: list = None):
        """Defines which accounts should not be unfollowed"""
        if self.aborting:
            return self

        self.dont_include = set(friends) or set()
        self.white_list = set(friends) or set()

        return self

    def set_switch_language(self, option: bool = True):
        self.switch_language = option
        return self

    def set_use_clarifai(
        self,
        enabled: bool = False,
        api_key: str = None,
        models: list = ["general"],
        workflow: list = [],
        probability: float = 0.50,
        full_match: bool = False,
        check_video: bool = False,
        proxy: str = None,
    ):
        """
        Defines if the clarifai img api should be used
        Which 'project' will be used (only 5000 calls per month)

        Raises:
            InstaPyError if os is windows
        """
        if self.aborting:
            return self

        # if os.name == 'nt':
        #    raise InstaPyError('Clarifai is not supported on Windows')

        self.use_clarifai = enabled

        if api_key is None and self.clarifai_api_key is None:
            self.clarifai_api_key = os.environ.get("CLARIFAI_API_KEY")
        elif api_key is not None:
            self.clarifai_api_key = api_key

        self.clarifai_models = models
        self.clarifai_workflow = workflow
        self.clarifai_probability = probability
        self.clarifai_full_match = full_match
        self.clarifai_check_video = check_video

        if proxy is not None:
            self.clarifai_proxy = "https://" + proxy

        return self

    def set_smart_hashtags(
        self,
        tags: list = None,
        limit: int = 3,
        sort: str = "top",
        log_tags: bool = True,
    ):
        """Generate smart hashtags based on https://displaypurposes.com/"""
        """ranking, banned and spammy tags are filtered out."""

        if tags is None:
            print("set_smart_hashtags is misconfigured")
            return

        for tag in tags:
            if python_version() > "3.5":
                # CI Travis alert for Python3.5 and apidisplaypurposes
                from apidisplaypurposes import displaypurposes

                myToken = displaypurposes.generate_api_token(tag, Settings.user_agent)
                head = {"User-Agent": Settings.user_agent, "api-token": myToken}
                req = requests.get(
                    "https://apidisplaypurposes.com/tag/{}".format(tag), headers=head
                )
            else:
                # Old fashion request, must fail in Python <= 3.5
                req = requests.get("https://apidisplaypurposes.com/tag/{}".format(tag))

            data = json.loads(req.text)

            if data["tagExists"] is True:
                if sort == "top":
                    # sort by ranking
                    ordered_tags_by_rank = sorted(
                        data["results"], key=lambda d: d["rank"], reverse=True
                    )
                    ranked_tags = ordered_tags_by_rank[:limit]
                    for item in ranked_tags:
                        # add smart hashtag to like list
                        self.smart_hashtags.append(item["tag"])

                elif sort == "random":
                    if len(data["results"]) < limit:
                        random_tags = random.sample(
                            data["results"], len(data["results"])
                        )
                    else:
                        random_tags = random.sample(data["results"], limit)
                    for item in random_tags:
                        self.smart_hashtags.append(item["tag"])

                if log_tags is True:
                    for item in self.smart_hashtags:
                        print("[smart hashtag generated: {}]".format(item))
            else:
                print("Too few results for #{} tag".format(tag))

        # delete duplicated tags
        self.smart_hashtags = list(set(self.smart_hashtags))
        return self

    def set_smart_location_hashtags(
        self, locations: list, radius: int = 10, limit: int = 3, log_tags: bool = True
    ):
        """Generate smart hashtags based on https://apidisplaypurposes.com/map"""
        if locations is None:
            self.logger.error("set_smart_location_hashtags is misconfigured")
            return self

        for location in locations:
            lat, lon = get_cord_location(self.browser, location)

            bbox = get_bounding_box(
                lat, lon, logger=self.logger, half_side_in_miles=radius
            )
            bbox_url = "{},{},{},{}&zoom={}".format(
                bbox["lon_min"],
                bbox["lat_min"],
                bbox["lon_max"],
                bbox["lat_max"],
                radius,
            )
            url = "https://apidisplaypurposes.com/local/?bbox={}".format(bbox_url)

            req = requests.get(url)
            data = json.loads(req.text)
            if int(data["count"]) == 0:
                self.logger.warning("Too few results for {} location".format(location))
                continue

            count = limit if limit < data["count"] else data["count"]
            i = 0
            while i < count:
                self.smart_location_hashtags.append(data["tags"][i]["tag"])
                i += 1

        self.smart_location_hashtags = list(set(self.smart_location_hashtags))

        if log_tags is True:
            self.logger.info(
                "[smart location hashtag generated: {}]\n".format(
                    self.smart_location_hashtags
                )
            )

        return self

    def set_mandatory_language(
        self, enabled: bool = False, character_set: list = ["LATIN"]
    ):
        """Restrict the description of the image to a character set"""

        if self.aborting:
            return self

        char_set = []

        if not isinstance(character_set, list):
            character_set = [character_set]

        for chr_set in character_set:
            if chr_set not in [
                "LATIN",
                "GREEK",
                "CYRILLIC",
                "ARABIC",
                "HEBREW",
                "CJK",
                "HANGUL",
                "HIRAGANA",
                "KATAKANA",
                "THAI",
                "MATHEMATICAL",
            ]:
                self.logger.warning('Unkown character set! Treating as "LATIN".')
                ch_set_name = "LATIN"
            else:
                ch_set_name = chr_set

            if ch_set_name not in char_set:
                char_set.append(ch_set_name)

        self.mandatory_language = enabled
        self.mandatory_character = char_set

        return self

    def clarifai_check_img_for(
        self,
        tags: list = None,
        tags_skip: list = None,
        comment: bool = False,
        comments: list = None,
    ):
        """Defines the tags the images should be checked for"""
        if self.aborting:
            return self

        if tags is None and not self.clarifai_img_tags:
            self.use_clarifai = False
        elif tags:
            self.clarifai_img_tags.append((tags, comment, comments))
            self.clarifai_img_tags_skip = tags_skip or []

        return self

    def query_clarifai(self):
        """Method for querying Clarifai using parameters set in
        clarifai_check_img_for"""
        return check_image(
            self.browser,
            self.clarifai_api_key,
            self.clarifai_img_tags,
            self.clarifai_img_tags_skip,
            self.logger,
            self.clarifai_models,
            self.clarifai_workflow,
            self.clarifai_probability,
            self.clarifai_full_match,
            self.clarifai_check_video,
            proxy=self.clarifai_proxy,
        )

    def follow_commenters(
        self,
        usernames: list,
        amount: int = 10,
        daysold: int = 365,
        max_pic: int = 50,
        sleep_delay: int = 600,
        interact: bool = False,
    ):
        """Follows users' commenters"""

        if self.aborting:
            return self

        message = "Starting to follow commenters.."
        highlight_print(self.username, message, "feature", "info", self.logger)

        if not isinstance(usernames, list):
            usernames = [usernames]

        followed_all = 0
        followed_new = 0

        # hold the current global values for differentiating at the end
        already_followed_init = self.already_followed
        not_valid_users_init = self.not_valid_users
        liked_init = self.liked_img
        already_liked_init = self.already_liked
        commented_init = self.commented
        inap_img_init = self.inap_img

        relax_point = random.randint(7, 14)  # you can use some plain value
        # `10` instead of this quitely randomized score
        self.quotient_breach = False

        for username in usernames:
            if self.quotient_breach:
                break

            self.logger.info(
                "Following commenters of '{}' from {} pictures in last {} "
                "days...\nScrapping wall..".format(username, max_pic, daysold)
            )
            commenters = extract_information(
                self.browser, username, daysold, max_pic, self.logger
            )

            if len(commenters) > 0:
                self.logger.info("Going to follow top {} users.\n".format(amount))
                sleep(1)
                # This way of iterating will prevent sleep interference
                # between functions
                random.shuffle(commenters)
                for commenter in commenters[:amount]:
                    if self.quotient_breach:
                        self.logger.warning(
                            "--> Follow quotient reached its peak!"
                            "\t~leaving Follow-Commenters activity\n"
                        )
                        break

                    with self.feature_in_feature("follow_by_list", True):
                        followed = self.follow_by_list(
                            commenter, self.follow_times, sleep_delay, interact
                        )
                    if followed > 0:
                        followed_all += 1
                        followed_new += 1
                        self.logger.info("Total Follow: {}\n".format(str(followed_all)))
                        # Take a break after a good following
                        if followed_new >= relax_point:
                            delay_random = random.randint(
                                ceil(sleep_delay * 0.85), ceil(sleep_delay * 1.14)
                            )
                            sleep_time = (
                                "{} seconds".format(delay_random)
                                if delay_random < 60
                                else "{} minutes".format(
                                    truncate_float(delay_random / 60, 2)
                                )
                            )
                            self.logger.info(
                                "------=>  Followed {} new users ~sleeping "
                                "about {}".format(followed_new, sleep_time)
                            )
                            sleep(delay_random)
                            relax_point = random.randint(7, 14)
                            followed_new = 0
                            pass

            else:
                self.logger.info("Noone commented, noone to follow.\n")

            sleep(1)

        self.logger.info("Finished following Commenters!\n")

        # find the feature-wide action sizes by taking a difference
        already_followed = self.already_followed - already_followed_init
        not_valid_users = self.not_valid_users - not_valid_users_init
        liked = self.liked_img - liked_init
        already_liked = self.already_liked - already_liked_init
        commented = self.commented - commented_init
        inap_img = self.inap_img - inap_img_init

        # print results
        self.logger.info("Followed: {}".format(followed_all))
        self.logger.info("Already followed: {}".format(already_followed))
        self.logger.info("Not valid users: {}".format(not_valid_users))

        if interact is True:
            print("")
            # print results out of interactions
            self.logger.info("Liked: {}".format(liked))
            self.logger.info("Already Liked: {}".format(already_liked))
            self.logger.info("Commented: {}".format(commented))
            self.logger.info("Inappropriate: {}".format(inap_img))

        return self

    def follow_likers(
        self,
        usernames: list,
        photos_grab_amount: int = 3,
        follow_likers_per_photo: int = 3,
        randomize: bool = True,
        sleep_delay: int = 600,
        interact: bool = False,
    ):
        """Follows users' likers"""
        if self.aborting:
            return self

        message = "Starting to follow likers.."
        highlight_print(self.username, message, "feature", "info", self.logger)

        if not isinstance(usernames, list):
            usernames = [usernames]

        if photos_grab_amount > 12:
            self.logger.info(
                "Sorry, you can only grab likers from first 12 photos for "
                "given username now.\n"
            )
            photos_grab_amount = 12

        followed_all = 0
        followed_new = 0

        # hold the current global values for differentiating at the end
        already_followed_init = self.already_followed
        not_valid_users_init = self.not_valid_users
        liked_init = self.liked_img
        already_liked_init = self.already_liked
        commented_init = self.commented
        inap_img_init = self.inap_img

        relax_point = random.randint(7, 14)  # you can use some plain value
        # `10` instead of this quitely randomized score
        self.quotient_breach = False

        for username in usernames:
            if self.quotient_breach:
                break

            photo_urls = get_photo_urls_from_profile(
                self.browser, username, photos_grab_amount, randomize, self.logger
            )
            sleep(1)
            if not isinstance(photo_urls, list):
                photo_urls = [photo_urls]

            for photo_url in photo_urls:
                if self.quotient_breach:
                    break

                likers = users_liked(
                    self.browser, photo_url, follow_likers_per_photo, self.logger
                )
                # This way of iterating will prevent sleep interference
                # between functions
                random.shuffle(likers)

                for liker in likers[:follow_likers_per_photo]:
                    if self.quotient_breach:
                        self.logger.warning(
                            "--> Follow quotient reached its peak!"
                            "\t~leaving Follow-Likers activity\n"
                        )
                        break

                    with self.feature_in_feature("follow_by_list", True):
                        followed = self.follow_by_list(
                            liker, self.follow_times, sleep_delay, interact
                        )
                    if followed > 0:
                        followed_all += 1
                        followed_new += 1
                        self.logger.info("Total Follow: {}\n".format(str(followed_all)))
                        # Take a break after a good following
                        if followed_new >= relax_point:
                            delay_random = random.randint(
                                ceil(sleep_delay * 0.85), ceil(sleep_delay * 1.14)
                            )
                            sleep_time = (
                                "{} seconds".format(delay_random)
                                if delay_random < 60
                                else "{} minutes".format(
                                    truncate_float(delay_random / 60, 2)
                                )
                            )
                            self.logger.info(
                                "------=>  Followed {} new users ~sleeping "
                                "about {}".format(followed_new, sleep_time)
                            )
                            sleep(delay_random)
                            relax_point = random.randint(7, 14)
                            followed_new = 0
                            pass

        self.logger.info("Finished following Likers!\n")

        # find the feature-wide action sizes by taking a difference
        already_followed = self.already_followed - already_followed_init
        not_valid_users = self.not_valid_users - not_valid_users_init
        liked = self.liked_img - liked_init
        already_liked = self.already_liked - already_liked_init
        commented = self.commented - commented_init
        inap_img = self.inap_img - inap_img_init

        # print results
        self.logger.info("Followed: {}".format(followed_all))
        self.logger.info("Already followed: {}".format(already_followed))
        self.logger.info("Not valid users: {}".format(not_valid_users))

        if interact is True:
            print("")
            # print results out of interactions
            self.logger.info("Liked: {}".format(liked))
            self.logger.info("Already Liked: {}".format(already_liked))
            self.logger.info("Commented: {}".format(commented))
            self.logger.info("Inappropriate: {}".format(inap_img))

        return self

    def follow_by_list(
        self,
        followlist: list,
        times: int = 1,
        sleep_delay: int = 600,
        interact: bool = False,
    ):
        """Allows to follow by any scrapped list"""
        if not isinstance(followlist, list):
            followlist = [followlist]

        if self.aborting:
            self.logger.info(">>> self aborting prevented")
            # return self

        # standalone means this feature is started by the user
        standalone = (
            True if "follow_by_list" not in self.internal_usage.keys() else False
        )
        # skip validation in case of it is already accomplished
        users_validated = (
            True
            if not standalone and not self.internal_usage["follow_by_list"]["validate"]
            else False
        )

        self.follow_times = times or 0

        followed_all = 0
        followed_new = 0
        already_followed = 0
        not_valid_users = 0

        # hold the current global values for differentiating at the end
        liked_init = self.liked_img
        already_liked_init = self.already_liked
        commented_init = self.commented
        inap_img_init = self.inap_img

        relax_point = random.randint(7, 14)  # you can use some plain value
        # `10` instead of this quitely randomized score
        self.quotient_breach = False

        for acc_to_follow in followlist:
            if self.jumps["consequent"]["follows"] >= self.jumps["limit"]["follows"]:
                self.logger.warning(
                    "--> Follow quotient reached its peak!\t~leaving "
                    "Follow-By-Tags activity\n"
                )
                # reset jump counter before breaking the loop
                self.jumps["consequent"]["follows"] = 0
                # turn on `quotient_breach` to break the internal iterators
                # of the caller
                self.quotient_breach = True if not standalone else False
                break

            if follow_restriction(
                "read", acc_to_follow, self.follow_times, self.logger
            ):
                print("")
                continue

            if not users_validated:
                # Verify if the user should be followed
                validation, details = self.validate_user_call(acc_to_follow)
                if validation is not True or acc_to_follow == self.username:
                    self.logger.info("--> Not a valid user: {}".format(details))
                    not_valid_users += 1
                    continue

            # Take a break after a good following
            if followed_new >= relax_point:
                delay_random = random.randint(
                    ceil(sleep_delay * 0.85), ceil(sleep_delay * 1.14)
                )
                sleep_time = (
                    "{} seconds".format(delay_random)
                    if delay_random < 60
                    else "{} minutes".format(truncate_float(delay_random / 60, 2))
                )
                self.logger.info(
                    "Followed {} new users  ~sleeping about {}\n".format(
                        followed_new, sleep_time
                    )
                )
                sleep(delay_random)
                followed_new = 0
                relax_point = random.randint(7, 14)
                pass

            if not follow_restriction(
                "read", acc_to_follow, self.follow_times, self.logger
            ):
                follow_state, msg = follow_user(
                    self.browser,
                    "profile",
                    self.username,
                    acc_to_follow,
                    None,
                    self.blacklist,
                    self.logger,
                    self.logfolder,
                )
                sleep(random.randint(1, 3))

                if follow_state is True:
                    followed_all += 1
                    followed_new += 1
                    # reset jump counter after a successful follow
                    self.jumps["consequent"]["follows"] = 0

                    if standalone:  # print only for external usage (
                        # internal callers have their printers)
                        self.logger.info("Total Follow: {}\n".format(str(followed_all)))

                    # Check if interaction is expected
                    if interact and self.do_like:
                        do_interact = (
                            random.randint(0, 100) <= self.user_interact_percentage
                        )
                        # Do interactions if any
                        if do_interact and self.user_interact_amount > 0:
                            # store original value of `self.do_follow`
                            original_do_follow = self.do_follow
                            # disable following temporarily
                            # cos the user is already followed
                            self.do_follow = False

                            # disable revalidating user in interact_by_users
                            with self.feature_in_feature("interact_by_users", False):
                                self.interact_by_users(
                                    acc_to_follow,
                                    self.user_interact_amount,
                                    self.user_interact_random,
                                    self.user_interact_media,
                                )

                            # revert back to original `self.do_follow` value
                            self.do_follow = original_do_follow

                elif msg == "already followed":
                    already_followed += 1

                elif msg == "jumped":
                    # will break the loop after certain consecutive jumps
                    self.jumps["consequent"]["follows"] += 1

                sleep(1)

        if standalone:  # print only for external usage (internal callers
            # have their printers)
            self.logger.info("Finished following by List!\n")
            # print summary
            self.logger.info("Followed: {}".format(followed_all))
            self.logger.info("Already followed: {}".format(already_followed))
            self.logger.info("Not valid users: {}".format(not_valid_users))

            if interact is True:
                print("")
                # find the feature-wide action sizes by taking a difference
                liked = self.liked_img - liked_init
                already_liked = self.already_liked - already_liked_init
                commented = self.commented - commented_init
                inap_img = self.inap_img - inap_img_init

                # print the summary out of interactions
                self.logger.info("Liked: {}".format(liked))
                self.logger.info("Already Liked: {}".format(already_liked))
                self.logger.info("Commented: {}".format(commented))
                self.logger.info("Inappropriate: {}".format(inap_img))

        # always sum up general objects regardless of the request size
        self.followed += followed_all
        self.already_followed += already_followed
        self.not_valid_users += not_valid_users

        return followed_all

    def set_relationship_bounds(
        self,
        enabled: bool = False,
        potency_ratio: float = None,
        delimit_by_numbers: bool = None,
        min_posts: int = None,
        max_posts: int = None,
        max_followers: int = None,
        max_following: int = None,
        min_followers: int = None,
        min_following: int = None,
    ):
        """Sets the potency ratio and limits to the provide an efficient
        activity between the targeted masses"""

        self.potency_ratio = potency_ratio if enabled is True else None
        self.delimit_by_numbers = delimit_by_numbers if enabled is True else None

        self.max_followers = max_followers
        self.min_followers = min_followers

        self.max_following = max_following
        self.min_following = min_following

        self.min_posts = min_posts if enabled is True else None
        self.max_posts = max_posts if enabled is True else None

    def validate_user_call(self, user_name: str):
        """Short call of validate_username() function"""
        validation, details = validate_username(
            self.browser,
            user_name,
            self.username,
            self.ignore_users,
            self.blacklist,
            self.potency_ratio,
            self.delimit_by_numbers,
            self.max_followers,
            self.max_following,
            self.min_followers,
            self.min_following,
            self.min_posts,
            self.max_posts,
            self.skip_private,
            self.skip_private_percentage,
            self.skip_no_profile_pic,
            self.skip_no_profile_pic_percentage,
            self.skip_business,
            self.skip_non_business,
            self.skip_business_percentage,
            self.skip_business_categories,
            self.dont_skip_business_categories,
            self.skip_bio_keyword,
            self.mandatory_bio_keywords,
            self.logger,
            self.logfolder,
        )
        return validation, details

    def fetch_smart_comments(self, is_video: bool, temp_comments: list):
        if temp_comments:
            # Use clarifai related comments only!
            comments = temp_comments
        elif is_video:
            comments = self.comments + self.video_comments
        else:
            comments = self.comments + self.photo_comments

        return comments

    def set_skip_users(
        self,
        skip_private: bool = True,
        private_percentage: int = 100,
        skip_no_profile_pic: bool = False,
        no_profile_pic_percentage: int = 100,
        skip_business: bool = False,
        business_percentage: int = 100,
        skip_business_categories: list = [],
        dont_skip_business_categories: list = [],
        skip_non_business: bool = False,
        skip_bio_keyword: list = [],
        mandatory_bio_keywords: list = [],
    ):

        self.skip_business = skip_business
        self.skip_private = skip_private
        self.skip_no_profile_pic = skip_no_profile_pic
        self.skip_business_percentage = business_percentage
        self.skip_no_profile_pic_percentage = no_profile_pic_percentage
        self.skip_private_percentage = private_percentage
        self.skip_non_business = skip_non_business
        self.skip_bio_keyword = skip_bio_keyword
        self.mandatory_bio_keywords = mandatory_bio_keywords
        if skip_business:
            self.skip_business_categories = skip_business_categories
            if len(skip_business_categories) == 0:
                self.dont_skip_business_categories = dont_skip_business_categories
            else:
                if len(dont_skip_business_categories) != 0:
                    self.logger.warning(
                        "Both skip_business_categories and "
                        "dont_skip_business categories provided in "
                        "skip_business feature,"
                        + "will skip only the categories listed in "
                        "skip_business_categories parameter"
                    )
                    # dont_skip_business_categories = [] Setted by default
                    # in init

    def set_delimit_liking(
        self, enabled: bool = False, max_likes: int = None, min_likes: int = None
    ):

        self.delimit_liking = True if enabled is True else False
        self.max_likes = max_likes
        self.min_likes = min_likes

    def set_delimit_commenting(
        self,
        enabled: bool = False,
        max_comments: int = None,
        min_comments: int = None,
        comments_mandatory_words: list = [],
    ):

        self.delimit_commenting = True if enabled is True else False
        self.max_comments = max_comments
        self.min_comments = min_comments

        # comment only when the image description contain at least one of
        # those words
        self.comments_mandatory_words = comments_mandatory_words

    def set_simulation(self, enabled: bool = True, percentage: int = 100):
        """Sets aside simulation parameters"""
        if enabled not in [True, False]:
            self.logger.info(
                "Invalid simulation parameter! Please use correct syntax "
                "with accepted values."
            )

        elif enabled is False:
            self.simulation["enabled"] = False

        else:
            percentage = 0 if percentage is None else percentage
            self.simulation = {"enabled": True, "percentage": percentage}

    def like_by_locations(
        self,
        locations: list = None,
        amount: int = 50,
        media: str = None,
        skip_top_posts: bool = True,
        randomize: bool = False,
    ):
        """Likes (default) 50 images per given locations"""
        if self.aborting:
            return self

        liked_img = 0
        already_liked = 0
        inap_img = 0
        commented = 0
        followed = 0
        not_valid_users = 0

        locations = locations or []
        self.quotient_breach = False

        if randomize is True:
            random.shuffle(locations)

        for index, location in enumerate(locations):
            if self.quotient_breach:
                break

            self.logger.info("Location [{}/{}]".format(index + 1, len(locations)))
            self.logger.info("--> {}".format(location.encode("utf-8")))

            try:
                links = get_links_for_location(
                    self.browser, location, amount, self.logger, media, skip_top_posts
                )
            except NoSuchElementException as exc:
                self.logger.warning(
                    "Error occurred while getting images from location: {}  "
                    "~maybe too few images exist\n\t{}\n".format(
                        location, str(exc).encode("utf-8")
                    )
                )
                continue

            for i, link in enumerate(links):
                if self.jumps["consequent"]["likes"] >= self.jumps["limit"]["likes"]:
                    self.logger.warning(
                        "--> Like quotient reached its peak!\t~leaving "
                        "Like-By-Locations activity\n"
                    )
                    self.quotient_breach = True
                    # reset jump counter after a breach report
                    self.jumps["consequent"]["likes"] = 0
                    break

                self.logger.info("Like# [{}/{}]".format(i + 1, len(links)))
                self.logger.info(link)

                try:
                    inappropriate, user_name, is_video, reason, scope = check_link(
                        self.browser,
                        link,
                        self.dont_like,
                        self.mandatory_words,
                        self.mandatory_language,
                        self.is_mandatory_character,
                        self.mandatory_character,
                        self.check_character_set,
                        self.ignore_if_contains,
                        self.logger,
                    )

                    if not inappropriate and self.delimit_liking:
                        self.liking_approved = verify_liking(
                            self.browser, self.max_likes, self.min_likes, self.logger
                        )

                    if not inappropriate and self.liking_approved:
                        # validate user
                        validation, details = self.validate_user_call(user_name)

                        if validation is not True:
                            self.logger.info("--> Not a valid user: {}".format(details))
                            not_valid_users += 1
                            continue
                        else:
                            web_address_navigator(self.browser, link)

                        # try to like
                        like_state, msg = like_image(
                            self.browser,
                            user_name,
                            self.blacklist,
                            self.logger,
                            self.logfolder,
                            liked_img,
                        )

                        if like_state is True or self.do_comment_liked_photo:
                            if self.do_comment_liked_photo:
                                already_liked += 1
                            else:
                                liked_img += 1
                                # reset jump counter after a successful like
                                self.jumps["consequent"]["likes"] = 0

                            checked_img = True
                            temp_comments = []

                            commenting = (
                                random.randint(0, 100) <= self.comment_percentage
                            )
                            following = random.randint(0, 100) <= self.follow_percentage

                            if self.use_clarifai and (following or commenting):
                                try:
                                    (
                                        checked_img,
                                        temp_comments,
                                        clarifai_tags,
                                    ) = self.query_clarifai()

                                except Exception as err:
                                    self.logger.error(
                                        "Image check error: {}".format(err)
                                    )

                            # comments
                            if (
                                self.do_comment
                                and user_name not in self.dont_include
                                and checked_img
                                and commenting
                            ):
                                comments = self.comments + (
                                    self.video_comments
                                    if is_video
                                    else self.photo_comments
                                )
                                success = process_comments(
                                    comments,
                                    temp_comments,
                                    self.delimit_commenting,
                                    self.max_comments,
                                    self.min_comments,
                                    self.comments_mandatory_words,
                                    self.username,
                                    user_name,  # Comments with target user
                                    self.blacklist,
                                    self.browser,
                                    link,
                                    self.logger,
                                    self.logfolder,
                                )

                                if success:
                                    commented += 1
                            else:
                                self.logger.info("--> Not commented")
                                sleep(1)

                            # following
                            if (
                                self.do_follow
                                and user_name not in self.dont_include
                                and checked_img
                                and following
                                and not follow_restriction(
                                    "read", user_name, self.follow_times, self.logger
                                )
                            ):

                                follow_state, msg = follow_user(
                                    self.browser,
                                    "post",
                                    self.username,
                                    user_name,
                                    None,
                                    self.blacklist,
                                    self.logger,
                                    self.logfolder,
                                )
                                if follow_state is True:
                                    followed += 1

                            else:
                                self.logger.info("--> Not following")
                                sleep(1)

                        elif msg == "already liked":
                            already_liked += 1

                        elif msg == "block on likes":
                            break

                        elif msg == "jumped":
                            # will break the loop after certain consecutive
                            # jumps
                            self.jumps["consequent"]["likes"] += 1

                    else:
                        self.logger.info(
                            "--> Image not liked: {}".format(reason.encode("utf-8"))
                        )
                        inap_img += 1

                except NoSuchElementException as err:
                    self.logger.error("Invalid Page: {}".format(err))

            self.logger.info("Location: {}".format(location.encode("utf-8")))
            self.logger.info("Liked: {}".format(liked_img))
            self.logger.info("Already Liked: {}".format(already_liked))
            self.logger.info("Commented: {}".format(commented))
            self.logger.info("Followed: {}".format(followed))
            self.logger.info("Inappropriate: {}".format(inap_img))
            self.logger.info("Not valid users: {}\n".format(not_valid_users))

        self.followed += followed
        self.liked_img += liked_img
        self.already_liked += already_liked
        self.commented += commented
        self.inap_img += inap_img
        self.not_valid_users += not_valid_users

        return self

    def comment_by_locations(
        self,
        locations: list = None,
        amount: int = 50,
        media: str = None,
        skip_top_posts: bool = True,
    ):
        """Likes (default) 50 images per given locations"""
        if self.aborting:
            return self

        commented = 0
        followed = 0
        inap_img = 0
        not_valid_users = 0
        msg = None
        location = None

        locations = locations or []
        self.quotient_breach = False

        for index, location in enumerate(locations):
            if self.quotient_breach:
                break

            self.logger.info("Location [{}/{}]".format(index + 1, len(locations)))
            self.logger.info("--> {}".format(location.encode("utf-8")))

            try:
                links = get_links_for_location(
                    self.browser, location, amount, self.logger, media, skip_top_posts
                )
            except NoSuchElementException:
                self.logger.warning("Too few images, skipping this location")
                continue

            for i, link in enumerate(links):
                if (
                    self.jumps["consequent"]["comments"]
                    >= self.jumps["limit"]["comments"]
                ):
                    self.logger.warning(
                        "--> Comment quotient reached its peak!\t~leaving "
                        "Comment-By-Locations activity\n"
                    )
                    self.quotient_breach = True
                    # reset jump counter after a breach report
                    self.jumps["consequent"]["comments"] = 0
                    break

                self.logger.info("Comment# [{}/{}]".format(i + 1, len(links)))
                self.logger.info(link)

                try:
                    inappropriate, user_name, is_video, reason, scope = check_link(
                        self.browser,
                        link,
                        self.dont_like,
                        self.mandatory_words,
                        self.mandatory_language,
                        self.is_mandatory_character,
                        self.mandatory_character,
                        self.check_character_set,
                        self.ignore_if_contains,
                        self.logger,
                    )
                    if not inappropriate:
                        # validate user
                        validation, details = self.validate_user_call(user_name)
                        if validation is not True:
                            self.logger.info(details)
                            not_valid_users += 1
                            continue
                        else:
                            web_address_navigator(self.browser, link)

                        # try to comment
                        self.logger.info(
                            "--> Image not liked: Likes are disabled for the "
                            "'Comment-By-Locations' feature"
                        )

                        checked_img = True
                        temp_comments = []
                        commenting = random.randint(0, 100) <= self.comment_percentage
                        following = random.randint(0, 100) <= self.follow_percentage

                        if not commenting:
                            self.logger.info(
                                "--> Image not commented: skipping out of "
                                "given comment percentage"
                            )
                            continue

                        if self.use_clarifai:
                            try:
                                (
                                    checked_img,
                                    temp_comments,
                                    clarifai_tags,
                                ) = self.query_clarifai()

                            except Exception as err:
                                self.logger.error("Image check error: {}".format(err))

                        # comments
                        if (
                            self.do_comment
                            and user_name not in self.dont_include
                            and checked_img
                            and commenting
                        ):
                            comments = self.comments + (
                                self.video_comments if is_video else self.photo_comments
                            )

                            success = process_comments(
                                comments,
                                temp_comments,
                                self.delimit_commenting,
                                self.max_comments,
                                self.min_comments,
                                self.comments_mandatory_words,
                                self.username,
                                user_name,  # Comments with target user
                                self.blacklist,
                                self.browser,
                                link,
                                self.logger,
                                self.logfolder,
                            )
                            if success:
                                commented += 1
                                # reset jump counter after a
                                # successful comment
                                self.jumps["consequent"]["comments"] = 0
                                # try to follow
                                if (
                                    self.do_follow
                                    and user_name not in self.dont_include
                                    and checked_img
                                    and following
                                    and not follow_restriction(
                                        "read",
                                        user_name,
                                        self.follow_times,
                                        self.logger,
                                    )
                                ):
                                    follow_state, msg = follow_user(
                                        self.browser,
                                        "post",
                                        self.username,
                                        user_name,
                                        None,
                                        self.blacklist,
                                        self.logger,
                                        self.logfolder,
                                    )
                                    if follow_state is True:
                                        followed += 1
                                else:
                                    self.logger.info("--> Not following")
                                    sleep(1)

                            elif msg == "jumped":
                                # will break the loop after certain
                                # consecutive jumps
                                self.jumps["consequent"]["comments"] += 1

                        else:
                            self.logger.info("--> Not commented")
                            sleep(1)

                    else:
                        self.logger.info(
                            "--> Image not commented: {}".format(reason.encode("utf-8"))
                        )
                        inap_img += 1

                except NoSuchElementException as err:
                    self.logger.error("Invalid Page: {}".format(err))

        self.logger.info("Location: {}".format(location.encode("utf-8")))
        self.logger.info("Commented: {}".format(commented))
        self.logger.info("Followed: {}".format(followed))
        self.logger.info("Inappropriate: {}".format(inap_img))
        self.logger.info("Not valid users: {}\n".format(not_valid_users))

        self.followed += followed
        self.not_valid_users += not_valid_users

        return self

    def like_by_tags(
        self,
        tags: list = None,
        use_random_tags: bool = False,
        amount: int = 50,
        skip_top_posts: bool = True,
        use_smart_hashtags: bool = False,
        use_smart_location_hashtags: bool = False,
        interact: bool = False,
        randomize: bool = False,
        media: str = None,
    ):
        """Likes (default) 50 images per given tag"""
        if self.aborting:
            return self

        liked_img = 0
        already_liked = 0
        inap_img = 0
        commented = 0
        followed = 0
        not_valid_users = 0

        # 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
        else:
            # deletes white spaces in tags
            tags = [tag.strip() for tag in tags]
            tags = tags or []
            self.quotient_breach = False

        # if session includes like_by_tags, then randomize the tag list
        if use_random_tags is True:
            random.shuffle(tags)
            for i, tag in enumerate(tags):
                self.logger.info(
                    "Tag list randomized: [{}/{}/{}]".format(i + 1, len(tags), tag)
                )

        for index, tag in enumerate(tags):
            if self.quotient_breach:
                break

            self.logger.info("Tag [{}/{}]".format(index + 1, len(tags)))
            self.logger.info("--> {}".format(tag.encode("utf-8")))

            try:
                links = get_links_for_tag(
                    self.browser,
                    tag,
                    amount,
                    skip_top_posts,
                    randomize,
                    media,
                    self.logger,
                )
            except NoSuchElementException:
                self.logger.info("Too few images, skipping this tag")
                continue

            for i, link in enumerate(links):
                if self.jumps["consequent"]["likes"] >= self.jumps["limit"]["likes"]:
                    self.logger.warning(
                        "--> Like quotient reached its peak!\t~leaving "
                        "Like-By-Tags activity\n"
                    )
                    self.quotient_breach = True
                    # reset jump counter after a breach report
                    self.jumps["consequent"]["likes"] = 0
                    break

                self.logger.info("Like# [{}/{}]".format(i + 1, len(links)))
                self.logger.info(link)

                try:
                    inappropriate, user_name, is_video, reason, scope = check_link(
                        self.browser,
                        link,
                        self.dont_like,
                        self.mandatory_words,
                        self.mandatory_language,
                        self.is_mandatory_character,
                        self.mandatory_character,
                        self.check_character_set,
                        self.ignore_if_contains,
                        self.logger,
                    )

                    if not inappropriate and self.delimit_liking:
                        self.liking_approved = verify_liking(
                            self.browser, self.max_likes, self.min_likes, self.logger
                        )

                    if not inappropriate and self.liking_approved:
                        # validate user
                        validation, details = self.validate_user_call(user_name)
                        if validation is not True:
                            self.logger.info(details)
                            not_valid_users += 1
                            continue
                        else:
                            web_address_navigator(self.browser, link)

                        # try to like
                        like_state, msg = like_image(
                            self.browser,
                            user_name,
                            self.blacklist,
                            self.logger,
                            self.logfolder,
                            liked_img,
                        )

                        if like_state is True or self.do_comment_liked_photo:
                            if self.do_comment_liked_photo:
                                already_liked += 1
                            else:
                                liked_img += 1
                                # reset jump counter after a successful like
                                self.jumps["consequent"]["likes"] = 0

                            checked_img = True
                            temp_comments = []

                            commenting = (
                                random.randint(0, 100) <= self.comment_percentage
                            )
                            following = random.randint(0, 100) <= self.follow_percentage

                            if self.use_clarifai and (following or commenting):
                                try:
                                    (
                                        checked_img,
                                        temp_comments,
                                        clarifai_tags,
                                    ) = self.query_clarifai()

                                except Exception as err:
                                    self.logger.error(
                                        "Image check error: {}".format(err)
                                    )

                            # comments
                            if (
                                self.do_comment
                                and user_name not in self.dont_include
                                and checked_img
                                and commenting
                            ):
                                comments = self.comments + (
                                    self.video_comments
                                    if is_video
                                    else self.photo_comments
                                )
                                success = process_comments(
                                    comments,
                                    temp_comments,
                                    self.delimit_commenting,
                                    self.max_comments,
                                    self.min_comments,
                                    self.comments_mandatory_words,
                                    self.username,
                                    user_name,  # Comments with target user
                                    self.blacklist,
                                    self.browser,
                                    link,
                                    self.logger,
                                    self.logfolder,
                                )

                                if success:
                                    commented += 1
                            else:
                                self.logger.info("--> Not commented")
                                sleep(1)

                            # following
                            if (
                                self.do_follow
                                and user_name not in self.dont_include
                                and checked_img
                                and following
                                and not follow_restriction(
                                    "read", user_name, self.follow_times, self.logger
                                )
                            ):

                                follow_state, msg = follow_user(
                                    self.browser,
                                    "post",
                                    self.username,
                                    user_name,
                                    None,
                                    self.blacklist,
                                    self.logger,
                                    self.logfolder,
                                )
                                if follow_state is True:
                                    followed += 1
                            else:
                                self.logger.info("--> Not following")
                                sleep(1)

                            # interactions (if any)
                            if interact:
                                self.logger.info(
                                    "--> User gonna be interacted: '{}'".format(
                                        user_name
                                    )
                                )

                                # disable revalidating user in like_by_users
                                with self.feature_in_feature("like_by_users", False):
                                    self.like_by_users(
                                        user_name,
                                        self.user_interact_amount,
                                        self.user_interact_random,
                                        self.user_interact_media,
                                    )

                        elif msg == "already liked":
                            already_liked += 1

                        elif msg == "block on likes":
                            break

                        elif msg == "jumped":
                            # will break the loop after certain consecutive
                            # jumps
                            self.jumps["consequent"]["likes"] += 1

                    else:
                        self.logger.info(
                            "--> Image not liked: {}".format(reason.encode("utf-8"))
                        )
                        inap_img += 1

                except NoSuchElementException as err:
                    self.logger.error("Invalid Page: {}".format(err))

            self.logger.info("Tag: {}".format(tag.encode("utf-8")))

        self.logger.info("Liked: {}".format(liked_img))
        self.logger.info("Already Liked: {}".format(already_liked))
        self.logger.info("Commented: {}".format(commented))
        self.logger.info("Followed: {}".format(followed))
        self.logger.info("Inappropriate: {}".format(inap_img))
        self.logger.info("Not valid users: {}\n".format(not_valid_users))

        self.liked_img += liked_img
        self.already_liked += already_liked
        self.commented += commented
        self.followed += followed
        self.inap_img += inap_img
        self.not_valid_users += not_valid_users

        return self

    def like_by_users(
        self,
        usernames: list,
        amount: int = 10,
        randomize: bool = False,
        media: str = None,
    ):
        """Likes some amounts of images for each usernames"""
        if self.aborting:
            return self

        if not isinstance(usernames, list):
            usernames = [usernames]

        # standalone means this feature is started by the user
        standalone = (
            True if "like_by_users" not in self.internal_usage.keys() else False
        )
        # skip validation in case of it is already accomplished
        users_validated = (
            True
            if not standalone and not self.internal_usage["like_by_users"]["validate"]
            else False
        )

        username = None
        liked_img = 0
        total_liked_img = 0
        already_liked = 0
        inap_img = 0
        commented = 0
        followed = 0
        not_valid_users = 0

        usernames = usernames or []
        self.quotient_breach = False

        for index, username in enumerate(usernames):
            if self.quotient_breach:
                break

            self.logger.info("Username [{}/{}]".format(index + 1, len(usernames)))
            self.logger.info("--> {}".format(username.encode("utf-8")))

            following = random.randint(0, 100) <= self.follow_percentage

            if not users_validated:
                validation, details = self.validate_user_call(username)
                if not validation:
                    self.logger.info("--> Not a valid user: {}".format(details))
                    not_valid_users += 1
                    continue

            try:
                links = get_links_for_username(
                    self.browser,
                    self.username,
                    username,
                    amount,
                    self.logger,
                    self.logfolder,
                    randomize,
                    media,
                )

            except NoSuchElementException:
                self.logger.error("Element not found, skipping this username")
                continue

            if (
                self.do_follow
                and username not in self.dont_include
                and following
                and not follow_restriction(
                    "read", username, self.follow_times, self.logger
                )
            ):
                follow_state, msg = follow_user(
                    self.browser,
                    "profile",
                    self.username,
                    username,
                    None,
                    self.blacklist,
                    self.logger,
                    self.logfolder,
                )
                if follow_state is True:
                    followed += 1
            else:
                self.logger.info("--> Not following")
                sleep(1)

            if links is False:
                continue

            # Reset like counter for every username
            liked_img = 0

            for i, link in enumerate(links):
                # Check if target has reached
                if liked_img >= amount:
                    self.logger.info("-------------")
                    self.logger.info(
                        "--> Total liked image reached it's "
                        "amount given: {}".format(liked_img)
                    )
                    break

                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

                self.logger.info("Post [{}/{}]".format(liked_img + 1, amount))
                self.logger.info(link)

                try:
                    inappropriate, user_name, is_video, reason, scope = check_link(
                        self.browser,
                        link,
                        self.dont_like,
                        self.mandatory_words,
                        self.mandatory_language,
                        self.is_mandatory_character,
                        self.mandatory_character,
                        self.check_character_set,
                        self.ignore_if_contains,
                        self.logger,
                    )

                    if not inappropriate and self.delimit_liking:
                        self.liking_approved = verify_liking(
                            self.browser, self.max_likes, self.min_likes, self.logger
                        )

                    if not inappropriate and self.liking_approved:
                        like_state, msg = like_image(
                            self.browser,
                            user_name,
                            self.blacklist,
                            self.logger,
                            self.logfolder,
                            total_liked_img,
                        )
                        if like_state is True or self.do_comment_liked_photo:
                            if self.do_comment_liked_photo:
                                already_liked += 1
                            else:
                                total_liked_img += 1
                                liked_img += 1
                                # reset jump counter after a successful like
                                self.jumps["consequent"]["likes"] = 0

                            checked_img = True
                            temp_comments = []

                            commenting = (
                                random.randint(0, 100) <= self.comment_percentage
                            )

                            if self.use_clarifai and (following or commenting):
                                try:
                                    (
                                        checked_img,
                                        temp_comments,
                                        clarifai_tags,
                                    ) = self.query_clarifai()

                                except Exception as err:
                                    self.logger.error(
                                        "Image check error: {}".format(err)
                                    )

                            # comments
                            if (
                                self.do_comment
                                and user_name not in self.dont_include
                                and checked_img
                                and commenting
                            ):
                                comments = self.comments + (
                                    self.video_comments
                                    if is_video
                                    else self.photo_comments
                                )
                                success = process_comments(
                                    comments,
                                    temp_comments,
                                    self.delimit_commenting,
                                    self.max_comments,
                                    self.min_comments,
                                    self.comments_mandatory_words,
                                    self.username,
                                    user_name,  # Comments with target user
                                    self.blacklist,
                                    self.browser,
                                    link,
                                    self.logger,
                                    self.logfolder,
                                )

                                if success:
                                    commented += 1
                            else:
                                self.logger.info("--> Not commented")
                                sleep(1)

                        elif msg == "already liked":
                            already_liked += 1

                        elif msg == "block on likes":
                            break

                        elif msg == "jumped":
                            # will break the loop after certain consecutive
                            # jumps
                            self.jumps["consequent"]["likes"] += 1

                    else:
                        self.logger.info(
                            "--> Image not liked: {}".format(reason.encode("utf-8"))
                        )
                        inap_img += 1

                except NoSuchElementException as err:
                    self.logger.error("Invalid Page: {}".format(err))

            if liked_img < amount:
                self.logger.info("-------------")
                self.logger.info(
                    "--> Given amount not fullfilled, image pool reached its end\n"
                )

        self.logger.info("User: {}".format(username.encode("utf-8")))
        self.logger.info("Liked: {}".format(total_liked_img))
        self.logger.info("Already Liked: {}".format(already_liked))
        self.logger.info("Commented: {}".format(commented))
        self.logger.info("Inappropriate: {}".format(inap_img))
        self.logger.info("Not valid users: {}\n".format(not_valid_users))

        self.liked_img += liked_img
        self.already_liked += already_liked
        self.commented += commented
        self.inap_img += inap_img
        self.not_valid_users += not_valid_users

        return self

    def interact_by_users(
        self,
        usernames: list,
        amount: int = 10,
        randomize: bool = False,
        media: str = None,
    ):
        """Likes some amounts of images for each usernames"""
        if self.aborting:
            return self

        message = "Starting to interact by users.."
        highlight_print(self.username, message, "feature", "info", self.logger)

        if not isinstance(usernames, list):
            usernames = [usernames]

        # standalone means this feature is started by the user
        standalone = (
            True if "interact_by_users" not in self.internal_usage.keys() else False
        )
        # skip validation in case of it is already accomplished
        users_validated = (
            True
            if not standalone
            and not self.internal_usage["interact_by_users"]["validate"]
            else False
        )

        total_liked_img = 0
        already_liked = 0
        inap_img = 0
        commented = 0
        followed = 0
        already_followed = 0
        not_valid_users = 0

        self.quotient_breach = False

        for index, username in enumerate(usernames):
            if self.quotient_breach:
                # keep `quotient_breach` active to break the internal
                # iterators of the caller
                self.quotient_breach = True if not standalone else False
                break

            self.logger.info("Username [{}/{}]".format(index + 1, len(usernames)))
            self.logger.info("--> {}".format(username.encode("utf-8")))

            if not users_validated:
                validation, details = self.validate_user_call(username)
                if not validation:
                    self.logger.info("--> not a valid user: {}".format(details))
                    not_valid_users += 1
                    continue

            track = "profile"
            # decision making
            # static conditions
            not_dont_include = username not in self.dont_include
            follow_restricted = follow_restriction(
                "read", username, self.follow_times, self.logger
            )
            counter = 0
            while True:
                following = (
                    random.randint(0, 100) <= self.follow_percentage
                    and self.do_follow
                    and not_dont_include
                    and not follow_restricted
                )
                commenting = (
                    random.randint(0, 100) <= self.comment_percentage
                    and self.do_comment
                    and not_dont_include
                )
                liking = random.randint(0, 100) <= self.like_percentage

                story = (
                    random.randint(0, 100) <= self.story_percentage and self.do_story
                )

                counter += 1

                # if we have only one image to like/comment
                if commenting and not liking and amount == 1:
                    continue

                if following or commenting or liking or story:
                    self.logger.info(
                        "username actions: following={} commenting={} "
                        "liking={} story={}".format(
                            following, commenting, liking, story
                        )
                    )
                    break

                # if for some reason we have no actions on this user
                if counter > 5:
                    self.logger.info(
                        "username={} could not get interacted".format(username)
                    )
                    break

            try:
                links = get_links_for_username(
                    self.browser,
                    self.username,
                    username,
                    amount,
                    self.logger,
                    self.logfolder,
                    randomize,
                    media,
                )
            except NoSuchElementException:
                self.logger.error("Element not found, skipping this username")
                continue

            if links is False:
                continue

            # Reset like counter for every username
            liked_img = 0

            for i, link in enumerate(links[:amount]):
                if self.jumps["consequent"]["likes"] >= self.jumps["limit"]["likes"]:
                    self.logger.warning(
                        "--> Like quotient reached its peak!\t~leaving "
                        "Interact-By-Users activity\n"
                    )
                    self.quotient_breach = True
                    # reset jump counter after a breach report
                    self.jumps["consequent"]["likes"] = 0
                    break

                # Check if target has reached
                if liked_img >= amount:
                    self.logger.info("-------------")
                    self.logger.info(
                        "--> Total liked image reached it's "
                        "amount given: {}".format(liked_img)
                    )
                    break

                self.logger.info("Post [{}/{}]".format(i + 1, len(links[:amount])))
                self.logger.info(link)

                try:
                    inappropriate, user_name, is_video, reason, scope = check_link(
                        self.browser,
                        link,
                        self.dont_like,
                        self.mandatory_words,
                        self.mandatory_language,
                        self.is_mandatory_character,
                        self.mandatory_character,
                        self.check_character_set,
                        self.ignore_if_contains,
                        self.logger,
                    )
                    track = "post"

                    if not inappropriate:
                        # after first image we roll again
                        if i > 0:
                            liking = random.randint(0, 100) <= self.like_percentage
                            commenting = (
                                random.randint(0, 100) <= self.comment_percentage
                                and self.do_comment
                                and not_dont_include
                            )
                            story = (
                                random.randint(0, 100) <= self.story_percentage
                                and self.do_story
                            )

                        # like
                        if self.do_like and liking and self.delimit_liking:
                            self.liking_approved = verify_liking(
                                self.browser,
                                self.max_likes,
                                self.min_likes,
                                self.logger,
                            )

                        if self.do_like and liking and self.liking_approved:
                            like_state, msg = like_image(
                                self.browser,
                                user_name,
                                self.blacklist,
                                self.logger,
                                self.logfolder,
                                total_liked_img,
                            )
                            if like_state is True or self.do_comment_liked_photo:
                                if self.do_comment_liked_photo:
                                    already_liked += 1
                                else:
                                    total_liked_img += 1
                                    liked_img += 1
                                    # reset jump counter after a successful like
                                    self.jumps["consequent"]["likes"] = 0

                                # comment
                                checked_img = True
                                temp_comments = []

                                if self.use_clarifai and commenting:
                                    try:
                                        (
                                            checked_img,
                                            temp_comments,
                                            clarifai_tags,
                                        ) = self.query_clarifai()

                                    except Exception as err:
                                        self.logger.error(
                                            "Image check error: {}".format(err)
                                        )

                                if commenting and checked_img:
                                    comments = self.comments + (
                                        self.video_comments
                                        if is_video
                                        else self.photo_comments
                                    )
                                    success = process_comments(
                                        comments,
                                        temp_comments,
                                        self.delimit_commenting,
                                        self.max_comments,
                                        self.min_comments,
                                        self.comments_mandatory_words,
                                        self.username,
                                        user_name,  # Comments with target user
                                        self.blacklist,
                                        self.browser,
                                        link,
                                        self.logger,
                                        self.logfolder,
                                    )

                                    if success:
                                        commented += 1
                                else:
                                    self.logger.info("--> Not commented")
                                    sleep(1)

                            elif msg == "already liked":
                                already_liked += 1

                            elif msg == "block on likes":
                                break

                            elif msg == "jumped":
                                # will break the loop after certain
                                # consecutive jumps
                                self.jumps["consequent"]["likes"] += 1

                    else:
                        self.logger.info(
                            "--> Image not liked: {}".format(reason.encode("utf-8"))
                        )
                        inap_img += 1

                except NoSuchElementException as err:
                    self.logger.info("Invalid Page: {}".format(err))

            # follow
            if following and not (self.dont_follow_inap_post and inap_img > 0):

                follow_state, msg = follow_user(
                    self.browser,
                    track,
                    self.username,
                    username,
                    None,
                    self.blacklist,
                    self.logger,
                    self.logfolder,
                )

                if follow_state is True:
                    followed += 1

                elif msg == "already followed":
                    already_followed += 1

            else:
                self.logger.info("--> Not following")
                sleep(1)

            # watch story if present
            if story:
                self.story_by_users([username])

            if liked_img < amount:
                self.logger.info("-------------")
                self.logger.info(
                    "--> Given amount not fullfilled, image pool reached its end\n"
                )

        if len(usernames) > 1:
            # final words
            interacted_media_size = len(usernames) * amount - inap_img
            self.logger.info(
                "Finished interacting on total of {} images from {} users! xD\n".format(
                    interacted_media_size, len(usernames)
                )
            )

            # print results
            self.logger.info("Liked: {}".format(total_liked_img))
            self.logger.info("Already Liked: {}".format(already_liked))
            self.logger.info("Commented: {}".format(commented))
            self.logger.info("Followed: {}".format(followed))
            self.logger.info("Already Followed: {}".format(already_followed))
            self.logger.info("Inappropriate: {}".format(inap_img))
            self.logger.info("Not valid users: {}\n".format(not_valid_users))

        self.liked_img += total_liked_img
        self.already_liked += already_liked
        self.commented += commented
        self.followed += followed
        self.already_followed += already_followed
        self.inap_img += inap_img
        self.not_valid_users += not_valid_users

        return self

    def interact_by_users_tagged_posts(
        self,
        usernames: list,
        amount: int = 10,
        randomize: bool = False,
        media: str = None,
    ):
        """Likes some amounts of tagged images for each usernames"""
        if self.aborting:
            return self

        if not isinstance(usernames, list):
            usernames = [usernames]

        # standalone means this feature is started by the user
        standalone = (
            True if "interact_by_users" not in self.internal_usage.keys() else False
        )
        # skip validation in case of it is already accomplished
        users_validated = (
            True
            if not standalone
            and not self.internal_usage["interact_by_users"]["validate"]
            else False
        )

        total_liked_img = 0
        already_liked = 0
        inap_img = 0
        commented = 0
        followed = 0
        already_followed = 0
        not_valid_users = 0

        self.quotient_breach = False

        for index, username in enumerate(usernames):
            if self.quotient_breach:
                # keep `quotient_breach` active to break the internal
                # iterators of the caller
                self.quotient_breach = True if not standalone else False
                break

            self.logger.info("Username [{}/{}]".format(index + 1, len(usernames)))
            self.logger.info("--> {}".format(username.encode("utf-8")))

            if not users_validated and username != self.username:
                validation, details = self.validate_user_call(username)
                if not validation:
                    self.logger.info("--> not a valid user: {}".format(details))
                    not_valid_users += 1
                    continue

            # decision making
            # static conditions
            not_dont_include = username not in self.dont_include
            follow_restricted = follow_restriction(
                "read", username, self.follow_times, self.logger
            )
            counter = 0
            while True:
                following = (
                    random.randint(0, 100) <= self.follow_percentage
                    and self.do_follow
                    and not_dont_include
                    and not follow_restricted
                )
                commenting = (
                    random.randint(0, 100) <= self.comment_percentage
                    and self.do_comment
                    and not_dont_include
                )
                liking = random.randint(0, 100) <= self.like_percentage

                counter += 1

                # if we have only one image to like/comment
                if commenting and not liking and amount == 1:
                    continue
                if following or commenting or liking:
                    self.logger.info(
                        "username actions: following={} commenting={} "
                        "liking={}".format(following, commenting, liking)
                    )
                    break
                # if for some reason we have no actions on this user
                if counter > 5:
                    self.logger.info(
                        "username={} could not get interacted".format(username)
                    )
                    break

            try:
                links = get_links_for_username(
                    self.browser,
                    self.username,
                    username,
                    amount,
                    self.logger,
                    self.logfolder,
                    randomize,
                    media,
                    taggedImages=True,
                )
            except NoSuchElementException:
                self.logger.error("Element not found, skipping this username")
                continue

            if links is False:
                continue

            # Reset like counter for every username
            liked_img = 0

            for i, link in enumerate(links[:amount]):
                if self.jumps["consequent"]["likes"] >= self.jumps["limit"]["likes"]:
                    self.logger.warning(
                        "--> Like quotient reached its peak!\t~leaving "
                        "Interact-By-Users activity\n"
                    )
                    self.quotient_breach = True
                    # reset jump counter after a breach report
                    self.jumps["consequent"]["likes"] = 0
                    break

                # Check if target has reached
                if liked_img >= amount:
                    self.logger.info("-------------")
                    self.logger.info(
                        "--> Total liked image reached it's "
                        "amount given: {}".format(liked_img)
                    )
                    break

                self.logger.info(
                    "Post [{}/{}]".format(liked_img + 1, len(links[:amount]))
                )
                self.logger.info(link)

                try:
                    inappropriate, user_name, is_video, reason, scope = check_link(
                        self.browser,
                        link,
                        self.dont_like,
                        self.mandatory_words,
                        self.mandatory_language,
                        self.is_mandatory_character,
                        self.mandatory_character,
                        self.check_character_set,
                        self.ignore_if_contains,
                        self.logger,
                    )

                    if not inappropriate:
                        # after first image we roll again
                        if i > 0:
                            liking = random.randint(0, 100) <= self.like_percentage
                            commenting = (
                                random.randint(0, 100) <= self.comment_percentage
                                and self.do_comment
                                and not_dont_include
                            )

                        # like
                        if self.do_like and liking and self.delimit_liking:
                            self.liking_approved = verify_liking(
                                self.browser,
                                self.max_likes,
                                self.min_likes,
                                self.logger,
                            )

                        if self.do_like and liking and self.liking_approved:
                            like_state, msg = like_image(
                                self.browser,
                                user_name,
                                self.blacklist,
                                self.logger,
                                self.logfolder,
                                total_liked_img,
                            )
                            if like_state is True or self.do_comment_liked_photo:
                                if self.do_comment_liked_photo:
                                    already_liked += 1
                                else:
                                    total_liked_img += 1
                                    liked_img += 1
                                    # reset jump counter after a successful like
                                    self.jumps["consequent"]["likes"] = 0

                                # comment
                                checked_img = True
                                temp_comments = []

                                if self.use_clarifai and commenting:
                                    try:
                                        (
                                            checked_img,
                                            temp_comments,
                                            clarifai_tags,
                                        ) = self.query_clarifai()

                                    except Exception as err:
                                        self.logger.error(
                                            "Image check error: {}".format(err)
                                        )

                                if commenting and checked_img:
                                    comments = self.comments + (
                                        self.video_comments
                                        if is_video
                                        else self.photo_comments
                                    )
                                    success = process_comments(
                                        comments,
                                        temp_comments,
                                        self.delimit_commenting,
                                        self.max_comments,
                                        self.min_comments,
                                        self.comments_mandatory_words,
                                        self.username,
                                        user_name,  # Comments with target user
                                        self.blacklist,
                                        self.browser,
                                        link,
                                        self.logger,
                                        self.logfolder,
                                    )

                                    if success:
                                        commented += 1
                                else:
                                    self.logger.info("--> Not commented")
                                    sleep(1)

                            elif msg == "already liked":
                                already_liked += 1

                            elif msg == "block on likes":
                                break

                            elif msg == "jumped":
                                # will break the loop after certain
                                # consecutive jumps
                                self.jumps["consequent"]["likes"] += 1

                    else:
                        self.logger.info(
                            "--> Image not liked: {}".format(reason.encode("utf-8"))
                        )
                        inap_img += 1

                except NoSuchElementException as err:
                    self.logger.info("Invalid Page: {}".format(err))

            # follow
            if following and not (self.dont_follow_inap_post and inap_img > 0):

                follow_state, msg = follow_user(
                    self.browser,
                    "profile",
                    self.username,
                    username,
                    None,
                    self.blacklist,
                    self.logger,
                    self.logfolder,
                )
                if follow_state is True:
                    followed += 1

                elif msg == "already followed":
                    already_followed += 1

            else:
                self.logger.info("--> Not following")
                sleep(1)

            if liked_img < amount:
                self.logger.info("-------------")
                self.logger.info(
                    "--> Given amount not fullfilled, image pool reached its end\n"
                )

        # final words
        interacted_media_size = len(usernames) * amount - inap_img
        self.logger.info(
            "Finished interacting on total of {} images from {} users! xD\n".format(
                interacted_media_size, len(usernames)
            )
        )

        # print results
        self.logger.info("Liked: {}".format(total_liked_img))
        self.logger.info("Already Liked: {}".format(already_liked))
        self.logger.info("Commented: {}".format(commented))
        self.logger.info("Followed: {}".format(followed))
        self.logger.info("Already Followed: {}".format(already_followed))
        self.logger.info("Inappropriate: {}".format(inap_img))
        self.logger.info("Not valid users: {}\n".format(not_valid_users))

        self.liked_img += total_liked_img
        self.already_liked += already_liked
        self.commented += commented
        self.followed += followed
        self.already_followed += already_followed
        self.inap_img += inap_img
        self.not_valid_users += not_valid_users

        return self

    def like_from_image(self, url: str, amount: int = 50, media: str = None):
        """Gets the tags from an image and likes 50 images for each tag"""
        if self.aborting:
            return self

        try:
            if not url:
                urls = self.browser.find_elements_by_xpath(
                    read_xpath(self.__class__.__name__, "main_article")
                )
                url = urls[0].get_attribute("href")
                self.logger.info("new url {}".format(url))
            tags = get_tags(self.browser, url)
            self.logger.info(tags)
            self.like_by_tags(tags, amount, media)
        except TypeError as err:
            self.logger.error("Sorry, an error occurred: {}".format(err))
            self.aborting = True
            return self

        return self

    def interact_user_followers(
        self, usernames: list, amount: int = 10, randomize: bool = False
    ):
        """
        Interact with the people that a given user is followed by.

        set_do_comment, set_do_follow and set_do_like are applicable.

        :param usernames: List of users to interact with their followers.
        :param amount: Amount of followers to interact with.
        :param randomize: If followers should be chosen randomly.
        """

        if self.aborting:
            return self

        if self.do_follow is not True and self.do_like is not True:
            self.logger.info(
                "Please enable following or liking in settings in order to "
                "do interactions."
            )
            return self

        elif self.user_interact_amount <= 0:
            self.logger.info(
                "Please choose an amount higher than zero in "
                "`set_user_interact` in order to do interactions."
            )
            return self

        if not isinstance(usernames, list):
            usernames = [usernames]

        interacted_all = 0
        not_valid_users = 0
        simulated_unfollow = 0

        # hold the current global values for differentiating at the end
        liked_init = self.liked_img
        already_liked_init = self.already_liked
        commented_init = self.commented
        followed_init = self.followed
        inap_img_init = self.inap_img

        self.quotient_breach = False

        for index, user in enumerate(usernames):
            if self.quotient_breach:
                break

            self.logger.info(
                "User '{}' [{}/{}]".format((user), index + 1, len(usernames))
            )
            try:
                person_list, simulated_list = get_given_user_followers(
                    self.browser,
                    self.username,
                    user,
                    amount,
                    self.dont_include,
                    randomize,
                    self.blacklist,
                    self.follow_times,
                    self.simulation,
                    self.jumps,
                    self.logger,
                    self.logfolder,
                )

            except (TypeError, RuntimeWarning) as err:
                if isinstance(err, RuntimeWarning):
                    self.logger.warning(
                        "Warning: {} , skipping to next user".format(err)
                    )
                    continue

                else:
                    self.logger.error("Sorry, an error occurred: {}".format(err))
                    self.aborting = True
                    return self

            print("")
            self.logger.info(
                "Grabbed {} usernames from '{}'s `Followers` to do "
                "interaction.".format(len(person_list), user)
            )

            interacted_personal = 0

            for index, person in enumerate(person_list):
                if self.quotient_breach:
                    self.logger.warning(
                        "--> Like quotient reached its peak!"
                        "\t~leaving Interact-User-Followers activity\n"
                    )
                    break

                self.logger.info(
                    "User '{}' [{}/{}]".format((person), index + 1, len(person_list))
                )

                validation, details = self.validate_user_call(person)
                if not validation:
                    self.logger.info(details)
                    not_valid_users += 1

                    if person in simulated_list:
                        self.logger.warning(
                            "--> Simulated Unfollow {}:"
                            " unfollowing '{}' due to mismatching "
                            "validation...".format(simulated_unfollow + 1, person)
                        )
                        unfollow_state, msg = unfollow_user(
                            self.browser,
                            "profile",
                            self.username,
                            person,
                            None,
                            None,
                            self.relationship_data,
                            self.logger,
                            self.logfolder,
                        )
                        if unfollow_state:
                            simulated_unfollow += 1

                    continue

                # Do interactions if any
                do_interact = random.randint(0, 100) <= self.user_interact_percentage

                if not do_interact:
                    self.logger.info(
                        "Skipping user '{}' due to the interaction "
                        "percentage of {}".format(person, self.user_interact_percentage)
                    )
                    continue

                else:
                    interacted_all += 1
                    interacted_personal += 1

                    self.logger.info(
                        "Interaction [{}/{}]  |  Total Interaction: {}".format(
                            interacted_personal, len(person_list), interacted_all
                        )
                    )

                    with self.feature_in_feature("interact_by_users", False):
                        self.interact_by_users(
                            person,
                            self.user_interact_amount,
                            self.user_interact_random,
                            self.user_interact_media,
                        )
                    if self.aborting:
                        return self
                    sleep(1)

        # final words
        self.logger.info(
            "Finished interacting {} people from {} users' `Followers`! xD\n".format(
                interacted_all, len(usernames)
            )
        )

        # find the feature-wide action sizes by taking a difference
        liked = self.liked_img - liked_init
        already_liked = self.already_liked - already_liked_init
        commented = self.commented - commented_init
        followed = self.followed - followed_init
        inap_img = self.inap_img - inap_img_init

        # print results
        self.logger.info("Liked: {}".format(liked))
        self.logger.info("Already Liked: {}".format(already_liked))
        self.logger.info("Commented: {}".format(commented))
        self.logger.info("Followed: {}".format(followed))
        self.logger.info("Inappropriate: {}".format(inap_img))
        self.logger.info("Not valid users: {}\n".format(not_valid_users))

        self.not_valid_users += not_valid_users

        return self

    def interact_user_likers(
        self,
        usernames: list,
        posts_grab_amount: int = 3,
        interact_likers_per_post: int = 3,
        randomize: bool = False,
    ):
        """
        Interact with the likers of given user's posts.

        set_do_comment, set_do_follow and set_do_like are applicable.

        :param usernames: List of users with whose likers to interact.
        :param posts_grab_amount: Amount of posts to get the likers from per given user.
        :param interact_likers_per_post: Amount of likers to be interacted with per post.
        :param randomize: If followers should be chosen randomly.
        """

        if self.aborting:
            return self

        if self.do_follow is not True and self.do_like is not True:
            self.logger.info(
                "Please enable following or liking in settings in order to "
                "do interactions."
            )
            return self

        elif self.user_interact_amount <= 0:
            self.logger.info(
                "Please choose an amount higher than zero in "
                "`set_user_interact` in order to do interactions."
            )
            return self

        if not isinstance(usernames, list):
            usernames = [usernames]

        if posts_grab_amount > 12:
            self.logger.info(
                "Sorry, you can only grab likers from first 12 posts for "
                "given username now.\n"
            )
            posts_grab_amount = 12

        interacted_all = 0
        not_valid_users = 0
        simulated_unfollow = 0

        # hold the current global values for differentiating at the end
        liked_init = self.liked_img
        already_liked_init = self.already_liked
        commented_init = self.commented
        followed_init = self.followed
        inap_img_init = self.inap_img

        self.quotient_breach = False

        for index, username in enumerate(usernames):
            if self.quotient_breach:
                break

            self.logger.info(
                "User '{}' [{}/{}]".format((username), index + 1, len(usernames))
            )
            try:
                post_urls = get_photo_urls_from_profile(
                    self.browser, username, posts_grab_amount, randomize, self.logger
                )

                if not isinstance(post_urls, list):
                    post_urls = [post_urls]

            except (TypeError, RuntimeWarning) as err:
                if isinstance(err, RuntimeWarning):
                    self.logger.warning(
                        "Warning: {} , skipping to next user".format(err)
                    )
                    continue

                else:
                    self.logger.error("Sorry, an error occurred: {}".format(err))
                    self.aborting = True
                    return self

            print("")
            self.logger.info(
                "Grabbed {} posts from '{}'s profile to do "
                "interaction.".format(len(post_urls), username)
            )

            interacted_personal = 0

            for post_index, post_url in enumerate(post_urls):
                if self.quotient_breach:
                    break

                likers = users_liked(
                    self.browser, post_url, interact_likers_per_post, self.logger
                )
                # This way of iterating will prevent sleep interference
                # between functions
                random.shuffle(likers)

                self.logger.info(
                    "Post '{}' [{}/{}]".format(
                        (post_url), post_index + 1, len(post_urls)
                    )
                )

                for liker_index, person in enumerate(likers):
                    if self.quotient_breach:
                        self.logger.warning(
                            "--> Like quotient reached its peak!"
                            "\t~leaving Interact-Likers activity\n"
                        )
                        break

                    self.logger.info(
                        "Liker '{}' [{}/{}]".format(
                            (person), liker_index + 1, len(likers)
                        )
                    )

                    validation, details = self.validate_user_call(person)
                    if not validation:
                        self.logger.info(details)
                        not_valid_users += 1

                        continue

                    # Do interactions if any
                    do_interact = (
                        random.randint(0, 100) <= self.user_interact_percentage
                    )

                    if not do_interact:
                        self.logger.info(
                            "Skipping user '{}' due to the interaction "
                            "percentage of {}".format(
                                person, self.user_interact_percentage
                            )
                        )
                        continue

                    else:
                        interacted_all += 1
                        interacted_personal += 1

                        self.logger.info(
                            "Interaction [{}/{}]  |  Total Interaction: {}".format(
                                interacted_personal, len(likers), interacted_all
                            )
                        )

                        with self.feature_in_feature("interact_by_users", False):
                            self.interact_by_users(
                                person,
                                self.user_interact_amount,
                                self.user_interact_random,
                                self.user_interact_media,
                            )
                        if self.aborting:
                            return self
                        sleep(1)

        # final words
        self.logger.info(
            "Finished interacting {} people from {} users' `Followers`! xD\n".format(
                interacted_all, len(usernames)
            )
        )

        # find the feature-wide action sizes by taking a difference
        liked = self.liked_img - liked_init
        already_liked = self.already_liked - already_liked_init
        commented = self.commented - commented_init
        followed = self.followed - followed_init
        inap_img = self.inap_img - inap_img_init

        # print results
        self.logger.info("Liked: {}".format(liked))
        self.logger.info("Already Liked: {}".format(already_liked))
        self.logger.info("Commented: {}".format(commented))
        self.logger.info("Followed: {}".format(followed))
        self.logger.info("Inappropriate: {}".format(inap_img))
        self.logger.info("Not valid users: {}\n".format(not_valid_users))

        self.not_valid_users += not_valid_users

        return self

    def interact_user_following(
        self, usernames: list, amount: int = 10, randomize: bool = False
    ):

        if self.aborting:
            return self

        if self.do_follow is not True and self.do_like is not True:
            self.logger.info(
                "Please enable following or liking in settings"
                " in order to do interactions."
            )
            return self

        elif self.user_interact_amount <= 0:
            self.logger.info(
                "Please choose an amount higher than zero in"
                " `set_user_interact` in order to do interactions."
            )
            return self

        if not isinstance(usernames, list):
            usernames = [usernames]

        interacted_all = 0
        not_valid_users = 0
        simulated_unfollow = 0

        # hold the current global values for differentiating at the end
        liked_init = self.liked_img
        already_liked_init = self.already_liked
        commented_init = self.commented
        followed_init = self.followed
        inap_img_init = self.inap_img

        self.quotient_breach = False

        for index, user in enumerate(usernames):
            if self.quotient_breach:
                break

            self.logger.info(
                "User '{}' [{}/{}]".format((user), index + 1, len(usernames))
            )
            try:
                person_list, simulated_list = get_given_user_following(
                    self.browser,
                    self.username,
                    user,
                    amount,
                    self.dont_include,
                    randomize,
                    self.blacklist,
                    self.follow_times,
                    self.simulation,
                    self.jumps,
                    self.logger,
                    self.logfolder,
                )

            except (TypeError, RuntimeWarning) as err:
                if isinstance(err, RuntimeWarning):
                    self.logger.warning(
                        "Warning: {} , skipping to next user".format(err)
                    )
                    continue

                else:
                    self.logger.error("Sorry, an error occurred: {}".format(err))
                    self.aborting = True
                    return self

            print("")
            self.logger.info(
                "Grabbed {} usernames from '{}'s `Following` to do "
                "interaction.".format(len(person_list), user)
            )
            interacted_personal = 0

            for index, person in enumerate(person_list):
                if self.quotient_breach:
                    self.logger.warning(
                        "--> Like quotient reached its peak!"
                        "\t~leaving Interact-User-Following activity\n"
                    )
                    break

                self.logger.info(
                    "User '{}' [{}/{}]".format((person), index + 1, len(person_list))
                )

                validation, details = self.validate_user_call(person)
                if validation is not True:
                    self.logger.info(details)
                    not_valid_users += 1

                    if person in simulated_list:
                        self.logger.warning(
                            "--> Simulated Unfollow {}:"
                            " unfollowing '{}' due to mismatching "
                            "validation...".format(simulated_unfollow + 1, person)
                        )

                        unfollow_state, msg = unfollow_user(
                            self.browser,
                            "profile",
                            self.username,
                            person,
                            None,
                            None,
                            self.relationship_data,
                            self.logger,
                            self.logfolder,
                        )
                        if unfollow_state is True:
                            simulated_unfollow += 1

                    continue

                # Do interactions if any
                do_interact = random.randint(0, 100) <= self.user_interact_percentage

                if do_interact is False:
                    self.logger.info(
                        "Skipping user '{}' due to"
                        " the interaction percentage of {}".format(
                            person, self.user_interact_percentage
                        )
                    )
                    continue

                else:
                    interacted_all += 1
                    interacted_personal += 1

                    self.logger.info(
                        "Interaction [{}/{}]  |  Total Interaction: {}".format(
                            interacted_personal, len(person_list), interacted_all
                        )
                    )

                    with self.feature_in_feature("interact_by_users", False):
                        self.interact_by_users(
                            person,
                            self.user_interact_amount,
                            self.user_interact_random,
                            self.user_interact_media,
                        )
                    if self.aborting:
                        return self
                    sleep(1)

        # final words
        self.logger.info(
            "Finished interacting {} people"
            " from {} users' `Following`! xD\n".format(interacted_all, len(usernames))
        )

        # find the feature-wide action sizes by taking a difference
        liked = self.liked_img - liked_init
        already_liked = self.already_liked - already_liked_init
        commented = self.commented - commented_init
        followed = self.followed - followed_init
        inap_img = self.inap_img - inap_img_init

        # print results
        self.logger.info("Liked: {}".format(liked))
        self.logger.info("Already Liked: {}".format(already_liked))
        self.logger.info("Commented: {}".format(commented))
        self.logger.info("Followed: {}".format(followed))
        self.logger.info("Inappropriate: {}".format(inap_img))
        self.logger.info("Not valid users: {}\n".format(not_valid_users))

        self.not_valid_users += not_valid_users

        return self

    def follow_user_followers(
        self,
        usernames: list,
        amount: int = 10,
        randomize: bool = False,
        interact: bool = False,
        sleep_delay: int = 600,
    ):
        """Follow the `Followers` of given users"""
        if self.aborting:
            return self

        message = "Starting to follow user `Followers`.."
        highlight_print(self.username, message, "feature", "info", self.logger)

        if not isinstance(usernames, list):
            usernames = [usernames]

        followed_all = 0
        followed_new = 0
        not_valid_users = 0

        # below, you can use some static value `10` instead of random ones..
        relax_point = random.randint(7, 14)

        # hold the current global values for differentiating at the end
        already_followed_init = self.already_followed
        liked_init = self.liked_img
        already_liked_init = self.already_liked
        commented_init = self.commented
        inap_img_init = self.inap_img

        self.quotient_breach = False

        for index, user in enumerate(usernames):
            if self.quotient_breach:
                break

            self.logger.info(
                "User '{}' [{}/{}]".format((user), index + 1, len(usernames))
            )

            try:
                person_list, simulated_list = get_given_user_followers(
                    self.browser,
                    self.username,
                    user,
                    amount,
                    self.dont_include,
                    randomize,
                    self.blacklist,
                    self.follow_times,
                    self.simulation,
                    self.jumps,
                    self.logger,
                    self.logfolder,
                )

            except (TypeError, RuntimeWarning) as err:
                if isinstance(err, RuntimeWarning):
                    self.logger.warning(
                        "Warning: {} , skipping to next user".format(err)
                    )
                    continue

                else:
                    self.logger.error("Sorry, an error occurred: {}".format(err))
                    self.aborting = True
                    return self

            print("")
            self.logger.info(
                "Grabbed {} usernames from '{}'s `Followers` to do following\n".format(
                    len(person_list), user
                )
            )

            followed_personal = 0
            simulated_unfollow = 0

            for index, person in enumerate(person_list):
                if self.quotient_breach:
                    self.logger.warning(
                        "--> Follow quotient reached its peak!"
                        "\t~leaving Follow-User-Followers activity\n"
                    )
                    break

                self.logger.info(
                    "Ongoing Follow [{}/{}]: now following '{}'...".format(
                        index + 1, len(person_list), person
                    )
                )

                validation, details = self.validate_user_call(person)
                if validation is not True:
                    self.logger.info(details)
                    not_valid_users += 1

                    if person in simulated_list:
                        self.logger.warning(
                            "--> Simulated Unfollow {}: unfollowing"
                            " '{}' due to mismatching validation...\n".format(
                                simulated_unfollow + 1, person
                            )
                        )

                        unfollow_state, msg = unfollow_user(
                            self.browser,
                            "profile",
                            self.username,
                            person,
                            None,
                            None,
                            self.relationship_data,
                            self.logger,
                            self.logfolder,
                        )
                        if unfollow_state is True:
                            simulated_unfollow += 1
                    # skip this [non-validated] user
                    continue

                # go ahead and follow, then interact (if any)
                with self.feature_in_feature("follow_by_list", False):
                    followed = self.follow_by_list(
                        person, self.follow_times, sleep_delay, interact
                    )
                sleep(1)

                if followed > 0:
                    followed_all += 1
                    followed_new += 1
                    followed_personal += 1

                self.logger.info(
                    "Follow per user: {}  |  Total Follow: {}\n".format(
                        followed_personal, followed_all
                    )
                )

                # take a break after a good following
                if followed_new >= relax_point:
                    delay_random = random.randint(
                        ceil(sleep_delay * 0.85), ceil(sleep_delay * 1.14)
                    )
                    sleep_time = (
                        "{} seconds".format(delay_random)
                        if delay_random < 60
                        else "{} minutes".format(truncate_float(delay_random / 60, 2))
                    )
                    self.logger.info(
                        "------=>  Followed {} new users ~sleeping about {}\n".format(
                            followed_new, sleep_time
                        )
                    )
                    sleep(delay_random)
                    relax_point = random.randint(7, 14)
                    followed_new = 0

        # final words
        self.logger.info(
            "Finished following {} users' `Followers`! xD\n".format(len(usernames))
        )
        # find the feature-wide action sizes by taking a difference
        already_followed = self.already_followed - already_followed_init
        inap_img = self.inap_img - inap_img_init
        liked = self.liked_img - liked_init
        already_liked = self.already_liked - already_liked_init
        commented = self.commented - commented_init

        # print results
        self.logger.info("Followed: {}".format(followed_all))
        self.logger.info("Already followed: {}".format(already_followed))
        self.logger.info("Not valid users: {}".format(not_valid_users))

        if interact is True:
            print("")
            # print results out of interactions
            self.logger.info("Liked: {}".format(liked))
            self.logger.info("Already Liked: {}".format(already_liked))
            self.logger.info("Commented: {}".format(commented))
            self.logger.info("Inappropriate: {}".format(inap_img))

        self.not_valid_users += not_valid_users

        return self

    def follow_user_following(
        self,
        usernames: list,
        amount: int = 10,
        randomize: bool = False,
        interact: bool = False,
        sleep_delay: int = 600,
    ):
        """Follow the `Following` of given users"""
        if self.aborting:
            return self

        message = "Starting to follow user `Following`.."
        highlight_print(self.username, message, "feature", "info", self.logger)

        if not isinstance(usernames, list):
            usernames = [usernames]

        followed_all = 0
        followed_new = 0
        not_valid_users = 0

        # hold the current global values for differentiating at the end
        already_followed_init = self.already_followed
        liked_init = self.liked_img
        already_liked_init = self.already_liked
        commented_init = self.commented
        inap_img_init = self.inap_img

        # below, can use a static value instead of from random range..
        relax_point = random.randint(7, 14)
        self.quotient_breach = False

        for index, user in enumerate(usernames):
            if self.quotient_breach:
                break

            self.logger.info(
                "User '{}' [{}/{}]".format((user), index + 1, len(usernames))
            )
            try:
                person_list, simulated_list = get_given_user_following(
                    self.browser,
                    self.username,
                    user,
                    amount,
                    self.dont_include,
                    randomize,
                    self.blacklist,
                    self.follow_times,
                    self.simulation,
                    self.jumps,
                    self.logger,
                    self.logfolder,
                )

            except (TypeError, RuntimeWarning) as err:
                if isinstance(err, RuntimeWarning):
                    self.logger.warning(
                        "Warning: {} , skipping to next user".format(err)
                    )
                    continue

                else:
                    self.logger.error("Sorry, an error occurred: {}".format(err))
                    self.aborting = True
                    return self

            print("")
            self.logger.info(
                "Grabbed {} usernames from '{}'s `Following` to do following\n".format(
                    len(person_list), user
                )
            )

            followed_personal = 0
            simulated_unfollow = 0

            for index, person in enumerate(person_list):
                if self.quotient_breach:
                    self.logger.warning(
                        "--> Follow quotient reached its peak!"
                        "\t~leaving Follow-User-Following activity\n"
                    )
                    break

                self.logger.info(
                    "Ongoing Follow [{}/{}]: now following '{}'...".format(
                        index + 1, len(person_list), person
                    )
                )

                validation, details = self.validate_user_call(person)
                if validation is not True:
                    self.logger.info(details)
                    not_valid_users += 1

                    if person in simulated_list:
                        self.logger.warning(
                            "--> Simulated Unfollow {}:"
                            " unfollowing '{}' due to mismatching "
                            "validation...\n".format(simulated_unfollow + 1, person)
                        )

                        unfollow_state, msg = unfollow_user(
                            self.browser,
                            "profile",
                            self.username,
                            person,
                            None,
                            None,
                            self.relationship_data,
                            self.logger,
                            self.logfolder,
                        )
                        if unfollow_state is True:
                            simulated_unfollow += 1
                    # skip the [non-validated] user
                    continue

                # go ahead and follow, then interact (if any)
                with self.feature_in_feature("follow_by_list", False):
                    followed = self.follow_by_list(
                        person, self.follow_times, sleep_delay, interact
                    )
                sleep(1)

                if followed > 0:
                    followed_all += 1
                    followed_new += 1
                    followed_personal += 1

                self.logger.info(
                    "Follow per user: {}  |  Total Follow: {}\n".format(
                        followed_personal, followed_all
                    )
                )

                # take a break after a good following
                if followed_new >= relax_point:
                    delay_random = random.randint(
                        ceil(sleep_delay * 0.85), ceil(sleep_delay * 1.14)
                    )
                    sleep_time = (
                        "{} seconds".format(delay_random)
                        if delay_random < 60
                        else "{} minutes".format(truncate_float(delay_random / 60, 2))
                    )
                    self.logger.info(
                        "------=>  Followed {} new users ~sleeping about {}\n".format(
                            followed_new, sleep_time
                        )
                    )
                    sleep(delay_random)
                    relax_point = random.randint(7, 14)
                    followed_new = 0

        # final words
        self.logger.info(
            "Finished following {} users' `Following`! xD\n".format(len(usernames))
        )

        # find the feature-wide action sizes by taking a difference
        already_followed = self.already_followed - already_followed_init
        inap_img = self.inap_img - inap_img_init
        liked = self.liked_img - liked_init
        already_liked = self.already_liked - already_liked_init
        commented = self.commented - commented_init

        # print results
        self.logger.info("Followed: {}".format(followed_all))
        self.logger.info("Already followed: {}".format(already_followed))
        self.logger.info("Not valid users: {}".format(not_valid_users))

        if interact is True:
            print("")
            # print results out of interactions
            self.logger.info("Liked: {}".format(liked))
            self.logger.info("Already Liked: {}".format(already_liked))
            self.logger.info("Commented: {}".format(commented))
            self.logger.info("Inappropriate: {}".format(inap_img))

        self.not_valid_users += not_valid_users

        return self

    def unfollow_users(
        self,
        amount: int = 10,
        custom_list_enabled: bool = False,
        custom_list: list = [],
        custom_list_param: str = "all",
        instapy_followed_enabled: bool = False,
        instapy_followed_param: str = "all",
        nonFollowers: bool = False,
        allFollowing: bool = False,
        style: str = "FIFO",
        unfollow_after: int = None,
        delay_followbackers: int = 0,  # 864000 = 10 days, 0 = don't delay
        sleep_delay: int = 600,
    ):
        """Unfollows (default) 10 users from your following list"""

        if self.aborting:
            return self

        message = "Starting to unfollow users.."
        highlight_print(self.username, message, "feature", "info", self.logger)

        if unfollow_after is not None:
            if not python_version().startswith(("2.7", "3")):
                self.logger.warning(
                    "`unfollow_after` parameter is not"
                    " available for Python versions below 2.7"
                )
                unfollow_after = None

        self.automatedFollowedPool = set_automated_followed_pool(
            self.username,
            unfollow_after,
            self.logger,
            self.logfolder,
            delay_followbackers,
        )

        try:
            unfollowed = unfollow(
                self.browser,
                self.username,
                amount,
                (custom_list_enabled, custom_list, custom_list_param),
                (instapy_followed_enabled, instapy_followed_param),
                nonFollowers,
                allFollowing,
                style,
                self.automatedFollowedPool,
                self.relationship_data,
                self.dont_include,
                self.white_list,
                sleep_delay,
                self.jumps,
                delay_followbackers,
                self.logger,
                self.logfolder,
            )
            self.logger.info("--> Total people unfollowed : {}\n".format(unfollowed))
            self.unfollowed += unfollowed

        except Exception as exc:
            if isinstance(exc, RuntimeWarning):
                self.logger.warning("Warning: {} , stopping unfollow_users".format(exc))
                return self

            else:
                self.logger.error("Sorry, an error occurred: {}".format(exc))
                self.aborting = True
                return self

        return self

    def remove_follow_requests(self, amount: int = 200, sleep_delay: int = 600):
        """Remove user unaccepted follow requests"""

        if self.aborting:
            return self

        message = "Starting to get follow requests.."
        highlight_print(self.username, message, "feature", "info", self.logger)

        follow_requests = get_follow_requests(
            self.browser, amount, sleep_delay, self.logger, self.logfolder
        )

        unfollow_count = 0

        for person in follow_requests:
            self.logger.warning(
                "--> Unfollow {}/{}:"
                " Removing request for: '{}' ".format(
                    unfollow_count + 1, len(follow_requests), person
                )
            )

            unfollow_state, msg = unfollow_user(
                self.browser,
                "profile",
                self.username,
                person,
                None,
                None,
                self.relationship_data,
                self.logger,
                self.logfolder,
            )

            if unfollow_state is True:
                unfollow_count += 1
                self.unfollowed += 1

        return self

    def like_by_feed(self, amount, randomize, unfollow, interact):
        """
        Like the users feed

        :param amount: Specifies how many total likes you want to perform
        :param randomize: randomly skips posts to be liked on your feed
        :param unfollow: unfollows the author of a post which was considered inappropriate
        :param interact: visits the author's profile page of a
        """

        if self.aborting:
            return self

        for _ in self.like_by_feed_generator(amount, randomize, unfollow, interact):
            pass

        return self

    def like_by_feed_generator(
        self,
        amount: int = 50,
        randomize: bool = False,
        unfollow: bool = False,
        interact: bool = False,
    ):
        """
        Like the users feed

        :param amount: Specifies how many total likes you want to perform
        :param randomize: randomly skips posts to be liked on your feed
        :param unfollow: unfollows the author of a post which was considered inappropriate
        :param interact: visits the author's profile page of a
        """

        if self.aborting:
            return

        liked_img = 0
        already_liked = 0
        inap_img = 0
        inap_unfollow = 0
        commented = 0
        followed = 0
        skipped_img = 0
        num_of_search = 0
        not_valid_users = 0
        link_not_found_loop_error = 0

        history = []
        self.quotient_breach = False

        while liked_img < amount:
            if self.quotient_breach:
                break

            try:
                # Gets another load of links to be tested
                links = get_links_from_feed(
                    self.browser, amount, num_of_search, self.logger
                )

                if len(links) > 0:
                    link_not_found_loop_error = 0

                if len(links) == 0:
                    link_not_found_loop_error += 1
                    if link_not_found_loop_error >= 10:
                        self.logger.warning(
                            "Loop error, 0 links"
                            " for 10 times consecutively, exit loop"
                        )
                        break

            except NoSuchElementException:
                self.logger.warning("Too few images, aborting")
                self.aborting = True
                return

            num_of_search += 1

            for _, link in enumerate(links):
                if liked_img == amount:
                    break

                if self.jumps["consequent"]["likes"] >= self.jumps["limit"]["likes"]:
                    self.logger.warning(
                        "--> Like quotient reached its peak!"
                        "\t~leaving Like-By-Feed activity\n"
                    )
                    self.quotient_breach = True
                    # reset jump counter after a breach report
                    self.jumps["consequent"]["likes"] = 0
                    break

                if randomize and random.choice([True, False]):
                    # Just inform that this post is randomly skipped
                    self.logger.info("Post randomly skipped...")
                    skipped_img += 1
                    continue
                else:
                    if link in history:
                        self.logger.info(
                            "This link has already been visited: {}".format(link)
                        )
                        continue
                    else:
                        self.logger.info("New link found...")
                        history.append(link)
                        self.logger.info(
                            "[{} posts liked /{} amount]".format(liked_img, amount)
                        )
                        self.logger.info(link)

                        try:
                            (
                                inappropriate,
                                user_name,
                                is_video,
                                reason,
                                scope,
                            ) = check_link(
                                self.browser,
                                link,
                                self.dont_like,
                                self.mandatory_words,
                                self.mandatory_language,
                                self.is_mandatory_character,
                                self.mandatory_character,
                                self.check_character_set,
                                self.ignore_if_contains,
                                self.logger,
                            )

                            if not inappropriate and self.delimit_liking:
                                self.liking_approved = verify_liking(
                                    self.browser,
                                    self.max_likes,
                                    self.min_likes,
                                    self.logger,
                                )
                            if not inappropriate and self.liking_approved:
                                # validate user
                                validation, details = self.validate_user_call(user_name)
                                if validation is not True:
                                    self.logger.info(details)
                                    not_valid_users += 1
                                    continue
                                else:
                                    web_address_navigator(self.browser, link)

                                # try to like
                                like_state, msg = like_image(
                                    self.browser,
                                    user_name,
                                    self.blacklist,
                                    self.logger,
                                    self.logfolder,
                                    liked_img,
                                )

                                if like_state is True or self.do_comment_liked_photo:
                                    if self.do_comment_liked_photo:
                                        already_liked += 1
                                    else:
                                        liked_img += 1
                                        # reset jump counter after a successful like
                                        self.jumps["consequent"]["likes"] = 0

                                    checked_img = True
                                    temp_comments = []

                                    commenting = (
                                        random.randint(0, 100)
                                        <= self.comment_percentage
                                    )
                                    following = (
                                        random.randint(0, 100) <= self.follow_percentage
                                    )

                                    if self.use_clarifai and (following or commenting):
                                        try:
                                            (
                                                checked_img,
                                                temp_comments,
                                                clarifai_tags,
                                            ) = self.query_clarifai()

                                        except Exception as err:
                                            self.logger.error(
                                                "Image check error: {}".format(err)
                                            )

                                    # comments
                                    if (
                                        self.do_comment
                                        and user_name not in self.dont_include
                                        and checked_img
                                        and commenting
                                    ):
                                        comments = self.comments + (
                                            self.video_comments
                                            if is_video
                                            else self.photo_comments
                                        )
                                        success = process_comments(
                                            comments,
                                            temp_comments,
                                            self.delimit_commenting,
                                            self.max_comments,
                                            self.min_comments,
                                            self.comments_mandatory_words,
                                            self.username,
                                            user_name,  # Comments with target user
                                            self.blacklist,
                                            self.browser,
                                            link,
                                            self.logger,
                                            self.logfolder,
                                        )

                                        if success:
                                            commented += 1
                                    else:
                                        self.logger.info("--> Not commented")
                                        sleep(1)

                                    # following
                                    if (
                                        self.do_follow
                                        and user_name not in self.dont_include
                                        and checked_img
                                        and following
                                        and not follow_restriction(
                                            "read",
                                            user_name,
                                            self.follow_times,
                                            self.logger,
                                        )
                                    ):
                                        follow_state, msg = follow_user(
                                            self.browser,
                                            "post",
                                            self.username,
                                            user_name,
                                            None,
                                            self.blacklist,
                                            self.logger,
                                            self.logfolder,
                                        )
                                        if follow_state is True:
                                            followed += 1
                                    else:
                                        self.logger.info("--> Not following")
                                        sleep(1)

                                    # interactions (if any)
                                    if interact:
                                        self.logger.info(
                                            "--> User gonna be interacted: "
                                            "'{}'".format(user_name)
                                        )

                                        self.like_by_users(
                                            user_name,
                                            self.user_interact_amount,
                                            self.user_interact_random,
                                            self.user_interact_media,
                                        )
                                    # :P
                                    yield self

                                elif msg == "already liked":
                                    already_liked += 1

                                elif msg == "block on likes":
                                    break

                                elif msg == "jumped":
                                    # will break the loop after
                                    # certain consecutive jumps
                                    self.jumps["consequent"]["likes"] += 1

                            elif inappropriate:
                                inap_img += 1
                                self.logger.info(
                                    "--> Image not liked: {}".format(
                                        reason.encode("utf-8")
                                    )
                                )

                                if "Inappropriate" in reason and unfollow:
                                    # example of unfollowing
                                    # directly from a post page (faster)
                                    self.logger.warning(
                                        "--> Ongoing Unfollow {}: unfollowing"
                                        " '{}' due to inappropriate content...".format(
                                            inap_unfollow + 1, user_name
                                        )
                                    )

                                    unfollow_state, msg = unfollow_user(
                                        self.browser,
                                        "post",
                                        self.username,
                                        user_name,
                                        None,
                                        None,
                                        self.relationship_data,
                                        self.logger,
                                        self.logfolder,
                                    )

                                    if unfollow_state is True:
                                        inap_unfollow += 1

                        except NoSuchElementException as err:
                            self.logger.error("Invalid Page: {}".format(err))

        self.logger.info("Liked: {}".format(liked_img))
        self.logger.info("Already Liked: {}".format(already_liked))
        self.logger.info("Commented: {}".format(commented))
        self.logger.info("Followed: {}".format(followed))
        self.logger.info("Inappropriate: {}".format(inap_img))
        self.logger.info("Not valid users: {}".format(not_valid_users))
        self.logger.info("Randomly Skipped: {}\n".format(skipped_img))

        self.liked_img += liked_img
        self.already_liked += already_liked
        self.commented += commented
        self.followed += followed
        self.inap_img += inap_img
        self.not_valid_users += not_valid_users

        return

    def set_dont_unfollow_active_users(
        self, enabled: bool = False, posts: int = 4, boundary: int = 500
    ):
        """Prevents unfollow followers who have liked one of
        your latest X posts"""

        if self.aborting:
            return

        # do nothing
        if not enabled:
            return

        # list of users who liked our media
        active_users = get_active_users(
            self.browser, self.username, posts, boundary, self.logger
        )

        # include active user to not unfollow list
        self.dont_include.update(active_users)

    def set_blacklist(self, enabled: bool, campaign: str):
        """
         Enable/disable blacklist. If enabled, adds users to a blacklist
        after interact with and adds users to dont_include list
        """

        if enabled is False:
            self.dont_include = self.white_list
            return

        self.blacklist["enabled"] = True
        self.blacklist["campaign"] = campaign

        try:
            with open("{}blacklist.csv".format(self.logfolder), "r") as blacklist:
                reader = csv.DictReader(blacklist)
                for row in reader:
                    if row["campaign"] == campaign:
                        self.dont_include.add(row["username"])
        # except:
        except Exception:
            self.logger.info("Campaign {} first run".format(campaign))

    def grab_followers(
        self,
        username: str = None,
        amount: int = None,
        live_match: bool = False,
        store_locally: bool = True,
        verified_only: bool = False,
    ):
        """
         Gets and returns `followers` information of given user
        in desired amount, also, saves locally
        """

        message = "Starting to get the `Followers` data.."
        highlight_print(self.username, message, "feature", "info", self.logger)

        if username is None:
            self.logger.warning(
                "Please provide a username to grab `Followers` data"
                "  ~e.g. your own username or somebody else's"
            )
            return self

        elif amount is None:
            self.logger.warning("Please put amount to grab `Followers` data")
            return self

        elif amount != "full" and (
            type(amount) != int or ((type(amount) == int and amount <= 0))
        ):
            self.logger.info(
                "Please provide a valid amount bigger than"
                " zero (0) to grab `Followers` data"
            )
            return self

        # Get `followers` data
        grabbed_followers = get_followers(
            self.browser,
            self.username,
            username,
            amount,
            self.relationship_data,
            live_match,
            store_locally,
            self.logger,
            self.logfolder,
            verified_only,
        )
        return grabbed_followers

    def grab_following(
        self,
        username: str = None,
        amount: int = None,
        live_match: bool = False,
        store_locally: bool = True,
    ):
        """
         Gets and returns `following` information of given user
        in desired amount, also, saves locally
        """

        message = "Starting to get the `Following` data.."
        highlight_print(self.username, message, "feature", "info", self.logger)

        if username is None:
            self.logger.warning(
                "Please provide a username to grab `Following` data"
                "  ~e.g. your own username or somebody else's"
            )
            return self

        elif amount is None:
            self.logger.warning("Please put amount to grab `Following` data")
            return self

        elif amount != "full" and (
            type(amount) != int or ((type(amount) == int and amount <= 0))
        ):
            self.logger.info(
                "Please provide a valid amount bigger than"
                " zero (0) to grab `Following` data"
            )
            return self

        # get `following` data
        grabbed_following = get_following(
            self.browser,
            self.username,
            username,
            amount,
            self.relationship_data,
            live_match,
            store_locally,
            self.logger,
            self.logfolder,
        )
        return grabbed_following

    def pick_unfollowers(
        self,
        username: str = None,
        compare_by: str = "latest",
        compare_track: str = "first",
        live_match: bool = False,
        store_locally: bool = True,
        print_out: bool = True,
    ):
        """
         Compares the `followers` stored in a latest local
        copy against either lively generated data or previous
        local copy and returns absent followers
        """

        message = "Starting to pick Unfollowers of {}..".format(username)
        highlight_print(self.username, message, "feature", "info", self.logger)

        # get all and active Unfollowers
        all_unfollowers, active_unfollowers = get_unfollowers(
            self.browser,
            self.username,
            username,
            compare_by,
            compare_track,
            self.relationship_data,
            live_match,
            store_locally,
            print_out,
            self.logger,
            self.logfolder,
        )

        return all_unfollowers, active_unfollowers

    def pick_nonfollowers(
        self, username: str = None, live_match: bool = False, store_locally: bool = True
    ):
        """Returns Nonfollowers data of a given user"""

        message = "Starting to pick Nonfollowers of {}..".format(username)
        highlight_print(self.username, message, "feature", "info", self.logger)

        # get Nonfollowers
        nonfollowers = get_nonfollowers(
            self.browser,
            self.username,
            username,
            self.relationship_data,
            live_match,
            store_locally,
            self.logger,
            self.logfolder,
        )

        return nonfollowers

    def pick_fans(
        self, username: str = None, live_match: bool = False, store_locally: bool = True
    ):
        """
         Returns Fans data- all of the usernames who do follow
        the user WHOM user itself do not follow back
        """

        message = "Starting to pick Fans of {}..".format(username)
        highlight_print(self.username, message, "feature", "info", self.logger)

        # get Fans
        fans = get_fans(
            self.browser,
            self.username,
            username,
            self.relationship_data,
            live_match,
            store_locally,
            self.logger,
            self.logfolder,
        )

        return fans

    def pick_mutual_following(
        self, username: str = None, live_match: bool = False, store_locally: bool = True
    ):
        """
         Returns Mutual Following data- all of the usernames who
        do follow the user WHOM user itself also do follow back
        """

        message = "Starting to pick Mutual Following of {}..".format(username)
        highlight_print(self.username, message, "feature", "info", self.logger)

        # get Mutual Following
        mutual_following = get_mutual_following(
            self.browser,
            self.username,
            username,
            self.relationship_data,
            live_match,
            store_locally,
            self.logger,
            self.logfolder,
        )

        return mutual_following

    def end(self, threaded_session: bool = False):
        """Closes the current session"""

        Settings.InstaPy_is_running = False
        close_browser(self.browser, threaded_session, self.logger)

        with interruption_handler(threaded=threaded_session):
            # close virtual display
            if self.nogui:
                self.display.stop()

            # write useful information
            dump_follow_restriction(self.username, self.logger, self.logfolder)
            dump_record_activity(self.username, self.logger, self.logfolder)

            with open("{}followed.txt".format(self.logfolder), "a") as followFile:
                followFile.write(
                    "{:%Y-%m-%d %H:%M} {}\n".format(datetime.now(), self.followed or 0)
                )

            # output live stats before leaving
            self.live_report()

            message = "Session ended!"
            highlight_print(self.username, message, "end", "info", self.logger)
            print("\n\n")

    def follow_by_locations(
        self,
        locations: list = [],
        amount: int = 50,
        media: str = None,
        skip_top_posts: bool = True,
    ):
        if self.aborting:
            return self

        inap_img = 0
        followed = 0
        not_valid_users = 0

        locations = locations
        self.quotient_breach = False

        for index, location in enumerate(locations):
            if self.quotient_breach:
                break

            self.logger.info("Location [{}/{}]".format(index + 1, len(locations)))
            self.logger.info("--> {}".format(location.encode("utf-8")))

            try:
                links = get_links_for_location(
                    self.browser, location, amount, self.logger, media, skip_top_posts
                )
            except NoSuchElementException:
                self.logger.warning("Too few images, skipping this location")
                continue

            for i, link in enumerate(links):
                if (
                    self.jumps["consequent"]["follows"]
                    >= self.jumps["limit"]["follows"]
                ):
                    self.logger.warning(
                        "--> Follow quotient reached its peak!"
                        "\t~leaving Follow-By-Locations activity\n"
                    )
                    self.quotient_breach = True
                    # reset jump counter after a breach report
                    self.jumps["consequent"]["follows"] = 0
                    break

                self.logger.info("Follow# [{}/{}]".format(i + 1, len(links)))
                self.logger.info(link)

                try:
                    inappropriate, user_name, is_video, reason, scope = check_link(
                        self.browser,
                        link,
                        self.dont_like,
                        self.mandatory_words,
                        self.mandatory_language,
                        self.is_mandatory_character,
                        self.mandatory_character,
                        self.check_character_set,
                        self.ignore_if_contains,
                        self.logger,
                    )

                    if not inappropriate:
                        # validate user
                        validation, details = self.validate_user_call(user_name)
                        if validation is not True:
                            self.logger.info(details)
                            not_valid_users += 1
                            continue
                        else:
                            web_address_navigator(self.browser, link)

                        # try to follow
                        follow_state, msg = follow_user(
                            self.browser,
                            "post",
                            self.username,
                            user_name,
                            None,
                            self.blacklist,
                            self.logger,
                            self.logfolder,
                        )
                        if follow_state is True:
                            followed += 1
                            # reset jump counter after a successful follow
                            self.jumps["consequent"]["follows"] = 0

                        elif msg == "jumped":
                            # will break the loop after certain consecutive
                            # jumps
                            self.jumps["consequent"]["follows"] += 1

                    else:
                        self.logger.info("--> User not followed: {}".format(reason))
                        inap_img += 1

                except NoSuchElementException as err:
                    self.logger.error("Invalid Page: {}".format(err))

        self.logger.info("Followed: {}".format(followed))
        self.logger.info("Inappropriate: {}".format(inap_img))
        self.logger.info("Not valid users: {}\n".format(not_valid_users))

        self.followed += followed
        self.inap_img += inap_img
        self.not_valid_users += not_valid_users

        return self

    def follow_by_tags(
        self,
        tags: list = [],
        amount: int = 50,
        skip_top_posts: bool = True,
        use_smart_hashtags: bool = False,
        use_smart_location_hashtags: bool = False,
        randomize: bool = False,
        media: str = None,
        interact: bool = False,
    ):

        if self.aborting:
            return self

        inap_img = 0
        followed = 0
        not_valid_users = 0

        # 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
        self.quotient_breach = False

        for index, tag in enumerate(tags):
            if self.quotient_breach:
                break

            self.logger.info("Tag [{}/{}]".format(index + 1, len(tags)))
            self.logger.info("--> {}".format(tag.encode("utf-8")))

            try:
                links = get_links_for_tag(
                    self.browser,
                    tag,
                    amount,
                    skip_top_posts,
                    randomize,
                    media,
                    self.logger,
                )
            except NoSuchElementException:
                self.logger.info("Too few images, skipping this tag")
                continue

            for i, link in enumerate(links):
                if (
                    self.jumps["consequent"]["follows"]
                    >= self.jumps["limit"]["follows"]
                ):
                    self.logger.warning(
                        "--> Follow quotient reached its peak!"
                        "\t~leaving Follow-By-Tags activity\n"
                    )
                    self.quotient_breach = True
                    # reset jump counter after a breach report
                    self.jumps["consequent"]["follows"] = 0
                    break

                self.logger.info("Follow# [{}/{}]".format(i + 1, len(links)))
                self.logger.info(link)

                try:
                    inappropriate, user_name, is_video, reason, scope = check_link(
                        self.browser,
                        link,
                        self.dont_like,
                        self.mandatory_words,
                        self.mandatory_language,
                        self.is_mandatory_character,
                        self.mandatory_character,
                        self.check_character_set,
                        self.ignore_if_contains,
                        self.logger,
                    )

                    if not inappropriate:
                        # validate user
                        validation, details = self.validate_user_call(user_name)
                        if validation is not True:
                            self.logger.info(details)
                            not_valid_users += 1
                            continue
                        else:
                            web_address_navigator(self.browser, link)

                        # try to follow
                        follow_state, msg = follow_user(
                            self.browser,
                            "post",
                            self.username,
                            user_name,
                            None,
                            self.blacklist,
                            self.logger,
                            self.logfolder,
                        )
                        if follow_state is True:
                            followed += 1
                            # reset jump counter after a successful follow
                            self.jumps["consequent"]["follows"] = 0

                            # Check if interaction is expected
                            if interact and self.do_like:
                                do_interact = (
                                    random.randint(0, 100)
                                    <= self.user_interact_percentage
                                )
                                # Do interactions if any
                                if do_interact and self.user_interact_amount > 0:
                                    # store the original value
                                    original_do_follow = self.do_follow
                                    # disable following temporarily
                                    self.do_follow = False
                                    self.interact_by_users(
                                        user_name,
                                        self.user_interact_amount,
                                        self.user_interact_random,
                                        self.user_interact_media,
                                    )
                                    # back original `self.do_follow` value
                                    self.do_follow = original_do_follow
                        elif msg == "jumped":
                            # will break the loop after certain consecutive
                            # jumps
                            self.jumps["consequent"]["follows"] += 1

                    else:
                        self.logger.info("--> User not followed: {}".format(reason))
                        inap_img += 1

                except NoSuchElementException as err:
                    self.logger.error("Invalid Page: {}".format(err))

        self.logger.info("Followed: {}".format(followed))
        self.logger.info("Inappropriate: {}".format(inap_img))
        self.logger.info("Not valid users: {}\n".format(not_valid_users))

        self.followed += followed
        self.inap_img += inap_img
        self.not_valid_users += not_valid_users

        return self

    def interact_by_URL(
        self, urls: list = [], randomize: bool = False, interact: bool = False
    ):
        """Interact on posts at given URLs"""

        if self.aborting:
            return self

        message = "Starting to interact by given URLs.."
        highlight_print(self.username, message, "feature", "info", self.logger)

        if not isinstance(urls, list):
            urls = [urls]

        if randomize is True:
            random.shuffle(urls)

        liked_img = 0
        already_liked = 0
        inap_img = 0
        commented = 0
        followed = 0
        not_valid_users = 0

        for index, url in enumerate(urls):
            if self.jumps["consequent"]["likes"] >= self.jumps["limit"]["likes"]:
                self.logger.warning(
                    "--> Like quotient reached its peak!"
                    "\t~leaving Interact-By-URL activity\n"
                )
                # reset jump counter before breaking the loop
                self.jumps["consequent"]["likes"] = 0
                # we have not used `quotient_breach` here
                # cos this method has just one iterator
                break

            if "https://www.instagram.com/p/" not in url:
                url = "https://www.instagram.com/p/" + url

            self.logger.info("URL [{}/{}]".format(index + 1, len(urls)))
            self.logger.info("--> {}".format(url.encode("utf-8")))

            try:
                inappropriate, user_name, is_video, reason, scope = check_link(
                    self.browser,
                    url,
                    self.dont_like,
                    self.mandatory_words,
                    self.mandatory_language,
                    self.is_mandatory_character,
                    self.mandatory_character,
                    self.check_character_set,
                    self.ignore_if_contains,
                    self.logger,
                )

                if not inappropriate and self.delimit_liking:
                    self.liking_approved = verify_liking(
                        self.browser, self.max_likes, self.min_likes, self.logger
                    )

                if not inappropriate and self.liking_approved:
                    # validate user
                    validation, details = self.validate_user_call(user_name)
                    if validation is not True:
                        self.logger.info(details)
                        not_valid_users += 1
                        continue
                    else:
                        web_address_navigator(self.browser, url)

                    # try to like
                    like_state, msg = like_image(
                        self.browser,
                        user_name,
                        self.blacklist,
                        self.logger,
                        self.logfolder,
                        liked_img,
                    )

                    if like_state is True or self.do_comment_liked_photo:
                        if self.do_comment_liked_photo:
                            already_liked += 1
                        else:
                            liked_img += 1
                            # reset jump counter after a successful like
                            self.jumps["consequent"]["likes"] = 0

                        checked_img = True
                        temp_comments = []

                        commenting = random.randint(0, 100) <= self.comment_percentage
                        following = random.randint(0, 100) <= self.follow_percentage

                        if self.use_clarifai and (following or commenting):
                            try:
                                (
                                    checked_img,
                                    temp_comments,
                                    clarifai_tags,
                                ) = self.query_clarifai()

                            except Exception as err:
                                self.logger.error("Image check error: {}".format(err))

                        # comments
                        if (
                            self.do_comment
                            and user_name not in self.dont_include
                            and checked_img
                            and commenting
                        ):
                            comments = self.comments + (
                                self.video_comments if is_video else self.photo_comments
                            )
                            success = process_comments(
                                comments,
                                temp_comments,
                                self.delimit_commenting,
                                self.max_comments,
                                self.min_comments,
                                self.comments_mandatory_words,
                                self.username,
                                user_name,  # Comments with target user
                                self.blacklist,
                                self.browser,
                                url,
                                self.logger,
                                self.logfolder,
                            )

                            if success:
                                commented += 1
                        else:
                            self.logger.info("--> Not commented")
                            sleep(1)

                        if (
                            self.do_follow
                            and user_name not in self.dont_include
                            and checked_img
                            and following
                            and not follow_restriction(
                                "read", user_name, self.follow_times, self.logger
                            )
                        ):
                            follow_state, msg = follow_user(
                                self.browser,
                                "post",
                                self.username,
                                user_name,
                                None,
                                self.blacklist,
                                self.logger,
                                self.logfolder,
                            )

                            if follow_state is True:
                                followed += 1
                        else:
                            self.logger.info("--> Not following")
                            sleep(1)

                        # check if interaction is expected
                        if interact is True:
                            do_interact = (
                                random.randint(0, 100) <= self.user_interact_percentage
                            )
                            # do interactions if any
                            if do_interact and self.user_interact_amount > 0:
                                self.interact_by_users(
                                    user_name,
                                    self.user_interact_amount,
                                    self.user_interact_random,
                                    self.user_interact_media,
                                )

                    elif msg == "already liked":
                        already_liked += 1

                    elif msg == "block on likes":
                        break

                    elif msg == "jumped":
                        # will break the loop after certain consecutive jumps
                        self.jumps["consequent"]["likes"] += 1

                else:
                    self.logger.info(
                        "--> Image not liked: {}".format(reason.encode("utf-8"))
                    )
                    inap_img += 1

            except NoSuchElementException as err:
                self.logger.error("Invalid Page: {}".format(err))

        self.logger.info("Liked: {}".format(liked_img))
        self.logger.info("Already Liked: {}".format(already_liked))
        self.logger.info("Commented: {}".format(commented))
        self.logger.info("Followed: {}".format(followed))
        self.logger.info("Inappropriate: {}".format(inap_img))
        self.logger.info("Not valid users: {}\n".format(not_valid_users))

        self.liked_img += liked_img
        self.already_liked += already_liked
        self.commented += commented
        self.followed += followed
        self.inap_img += inap_img
        self.not_valid_users += not_valid_users

        return self

    def set_quota_supervisor(
        self,
        enabled: bool = False,
        sleep_after: list = [],
        sleepyhead: bool = False,
        stochastic_flow: bool = False,
        notify_me: bool = False,
        peak_likes_hourly: int = None,
        peak_likes_daily: int = None,
        peak_comments_hourly: int = None,
        peak_comments_daily: int = None,
        peak_follows_hourly: int = None,
        peak_follows_daily: int = None,
        peak_unfollows_hourly: int = None,
        peak_unfollows_daily: int = None,
        peak_server_calls_hourly: int = None,
        peak_server_calls_daily: int = None,
    ):
        """
        Sets aside QS configuration ANY time in a session
        """

        # take a reference of the global configuration
        configuration = Settings.QS_config

        peaks = {
            "likes": {"hourly": peak_likes_hourly, "daily": peak_likes_daily},
            "comments": {"hourly": peak_comments_hourly, "daily": peak_comments_daily},
            "follows": {"hourly": peak_follows_hourly, "daily": peak_follows_daily},
            "unfollows": {
                "hourly": peak_unfollows_hourly,
                "daily": peak_unfollows_daily,
            },
            "server_calls": {
                "hourly": peak_server_calls_hourly,
                "daily": peak_server_calls_daily,
            },
        }

        if not isinstance(sleep_after, list):
            sleep_after = [sleep_after]

        rt = time.time()
        latesttime = {"hourly": rt, "daily": rt}
        orig_peaks = deepcopy(peaks)  # original peaks always remain static
        stochasticity = {
            "enabled": stochastic_flow,
            "latesttime": latesttime,
            "original_peaks": orig_peaks,
        }

        if platform.startswith("win32") and python_version() < "2.7.15":
            # UPDATE ME: remove this block once plyer is
            # verified to work on [very] old versions of Python 2
            notify_me = False

        # update QS configuration with the fresh settings
        configuration.update(
            {
                "state": enabled,
                "sleep_after": sleep_after,
                "sleepyhead": sleepyhead,
                "stochasticity": stochasticity,
                "notify": notify_me,
                "peaks": peaks,
            }
        )

    @contextmanager
    def feature_in_feature(self, feature, validate_users):
        """
         Use once a host feature calls a guest
        feature WHERE guest needs special behaviour(s)
        """

        try:
            # add the guest which is gonna be used by the host :)
            self.internal_usage[feature] = {"validate": validate_users}
            yield

        finally:
            # remove the guest just after using it
            self.internal_usage.pop(feature)

    def live_report(self):
        """Report live sessional statistics"""

        print("")

        stats = [
            self.liked_img,
            self.already_liked,
            self.commented,
            self.followed,
            self.already_followed,
            self.unfollowed,
            self.stories_watched,
            self.reels_watched,
            self.inap_img,
            self.not_valid_users,
        ]

        if self.following_num and self.followed_by:
            owner_relationship_info = (
                "On session start was FOLLOWING {} users"
                " & had {} FOLLOWERS".format(self.following_num, self.followed_by)
            )
        else:
            owner_relationship_info = ""

        sessional_run_time = self.run_time()
        run_time_info = (
            "{} seconds".format(sessional_run_time)
            if sessional_run_time < 60
            else "{} minutes".format(truncate_float(sessional_run_time / 60, 2))
            if sessional_run_time < 3600
            else "{} hours".format(truncate_float(sessional_run_time / 60 / 60, 2))
        )
        run_time_msg = "[Session lasted {}]".format(run_time_info)

        if any(stat for stat in stats):
            self.logger.info(
                "Sessional Live Report:\n"
                "\t|> LIKED {} images  |  ALREADY LIKED: {}\n"
                "\t|> COMMENTED on {} images\n"
                "\t|> FOLLOWED {} users  |  ALREADY FOLLOWED: {}\n"
                "\t|> UNFOLLOWED {} users\n"
                "\t|> LIKED {} comments\n"
                "\t|> REPLIED to {} comments\n"
                "\t|> INAPPROPRIATE images: {}\n"
                "\t|> NOT VALID users: {}\n"
                "\t|> WATCHED {} story(ies)  |  WATCHED {} reel(s)\n"
                "\n{}\n{}".format(
                    self.liked_img,
                    self.already_liked,
                    self.commented,
                    self.followed,
                    self.already_followed,
                    self.unfollowed,
                    self.liked_comments,
                    self.replied_to_comments,
                    self.inap_img,
                    self.not_valid_users,
                    self.stories_watched,
                    self.reels_watched,
                    owner_relationship_info,
                    run_time_msg,
                )
            )
        else:
            self.logger.info(
                "Sessional Live Report:\n"
                "\t|> No any statistics to show\n"
                "\n{}\n{}".format(owner_relationship_info, run_time_msg)
            )

    def set_do_reply_to_comments(self, enabled: bool = False, percentage: int = 0):
        """Define if the comments on posts should be replied"""

        self.do_reply_to_comments = enabled
        self.reply_to_comments_percent = percentage

        return self

    def set_comment_replies(self, replies: list = [], media: str = None):
        """Set the replies to comments"""

        if not replies:
            self.logger.info("Please, provide some comment replies for use next time.")
            self.comment_replies = None
            self.photo_comment_replies = None
            self.video_comment_replies = None

            return self

        if media in [MEDIA_PHOTO, MEDIA_VIDEO]:
            attr = "{}_comment_replies".format(media.lower())
            setattr(self, attr, replies)

        else:
            if media is not None:
                self.logger.warning(
                    "Unkown media type set at comment replies! Treating as 'any'."
                )

            self.comment_replies = replies

    def set_use_meaningcloud(
        self,
        enabled: bool = False,
        license_key: str = None,
        polarity: str = "P",
        agreement: str = None,
        subjectivity: str = None,
        confidence: bool = 100,
    ):
        """Set MeaningCloud Sentiment Analysis API configuration"""

        if license_key is None:
            license_key = os.environ.get("MEANINGCLOUD_LIC_KEY")

        if polarity.upper() not in ["P", "P+", "NEU", "N", "N+"]:
            self.logger.info(
                "Oh no! Please provide a valid polarity "
                "score tag for MeaningCloud"
                "\t~service will not operate"
            )
            polarity = None

        if enabled and license_key and polarity:
            Settings.meaningcloud_config.update(
                enabled=enabled,
                license_key=license_key,
                score_tag=polarity.upper(),
                agreement=agreement.upper() if agreement else None,
                subjectivity=subjectivity.upper() if subjectivity else None,
                confidence=int(confidence) if confidence else None,
            )

        else:
            # turn off MeaningCloud service if not enabled or wrongly
            # configured
            Settings.meaningcloud_config.update(enabled=False)

    def set_use_yandex(
        self,
        enabled: bool = False,
        API_key: str = None,
        match_language: bool = False,
        language_code: str = "en",
    ):
        """Set Yandex Translate API configuration"""

        if API_key is None:
            API_key = os.environ.get("YANDEX_API_KEY")

        if enabled and API_key:
            Settings.yandex_config.update(enabled=enabled, API_key=API_key)

            if match_language is True and language_code:
                supported_langs = yandex_supported_languages()

                if not supported_langs or language_code.lower() not in supported_langs:
                    msg = (
                        "Oh no! Failed to get the list of supported"
                        " languages by Yandex Translate :("
                        if not supported_langs
                        else "Oh no! The language with '{}' code is not"
                        " supported by Yandex Translate :/".format(language_code)
                    )
                    self.logger.info("{}\t~text language won't be matched".format(msg))
                    match_language = False

            Settings.yandex_config.update(
                match_language=match_language if language_code else False,
                language_code=language_code.lower() if language_code else None,
            )

        else:
            # turn off Yandex service if not enabled or wrongly configured
            Settings.yandex_config.update(enabled=False)

    def interact_by_comments(
        self,
        usernames: list = None,
        posts_amount: int = 10,
        comments_per_post: int = 1,
        reply: bool = False,
        interact: bool = False,
        randomize: bool = False,
        media: str = None,
    ):
        """
         Like comments of people on posts, reply to them
        and also interact with those commenters
        """
        if self.aborting:
            return self
        message = "Starting to interact by comments.."
        highlight_print(self.username, message, "feature", "info", self.logger)

        if not isinstance(usernames, list):
            usernames = [usernames]

        if media not in [MEDIA_PHOTO, MEDIA_VIDEO, None]:
            self.logger.warning(
                "Unkown media type- '{}' set at"
                " Interact-By-Comments!\t~treating as any..".format(media)
            )
            media = None

        # hold the current global values for differentiating at the end
        liked_init = self.liked_img
        already_liked_init = self.already_liked
        liked_comments_init = self.liked_comments
        commented_init = self.commented
        replied_to_comments_init = self.replied_to_comments
        followed_init = self.followed
        already_followed_init = self.already_followed
        inap_img_init = self.inap_img
        not_valid_users_init = self.not_valid_users

        overall_posts_count = 0
        self.quotient_breach = False
        like_failures_tracker = {
            "consequent": {"post_likes": 0, "comment_likes": 0},
            "limit": {"post_likes": 5, "comment_likes": 10},
        }

        leave_msg = "\t~leaving Interact-By-Comments activity\n"

        # start the interaction!
        for s, username in enumerate(usernames):
            if self.quotient_breach:
                break

            message = "User: [{}/{}]".format(s + 1, len(usernames))
            highlight_print(
                self.username, message, "user iteration", "info", self.logger
            )

            if username != self.username:
                validation, details = self.validate_user_call(username)
                if validation is not True:
                    self.logger.info("--> Not a valid user: {}".format(details))
                    self.not_valid_users += 1
                    continue

            per_user_liked_comments = 0
            per_user_replied_to_comments = 0
            per_user_used_replies = []

            try:
                links = get_links_for_username(
                    self.browser,
                    self.username,
                    username,
                    posts_amount,
                    self.logger,
                    self.logfolder,
                    randomize,
                    media,
                )
            except NoSuchElementException:
                self.logger.error("Element not found, skipping this user.")
                continue

            if links is False:
                continue

            else:
                if randomize:
                    random.shuffle(links)
                links = links[:posts_amount]
                overall_posts_count += len(links)

            for i, link in enumerate(links):
                if self.quotient_breach:
                    break

                elif (
                    self.jumps["consequent"]["comments"]
                    >= self.jumps["limit"]["comments"]
                ):
                    self.logger.warning(
                        "--> Comment quotient reached its peak!{}".format(leave_msg)
                    )
                    self.quotient_breach = True
                    # reset jump counter after a breach report
                    self.jumps["consequent"]["comments"] = 0
                    break

                elif (
                    like_failures_tracker["consequent"]["post_likes"]
                    >= like_failures_tracker["limit"]["post_likes"]
                ):
                    self.logger.warning(
                        "--> Too many failures to like posts!{}".format(leave_msg)
                    )
                    # this example shows helpful usage of
                    # quotient breach outside QS needs..
                    self.quotient_breach = True
                    break

                message = "Post: [{}/{}]".format(i + 1, len(links))
                highlight_print(
                    self.username, message, "post iteration", "info", self.logger
                )

                (inappropriate, user_name, is_video, reason, scope) = check_link(
                    self.browser,
                    link,
                    self.dont_like,
                    self.mandatory_words,
                    self.mandatory_language,
                    self.is_mandatory_character,
                    self.mandatory_character,
                    self.check_character_set,
                    self.ignore_if_contains,
                    self.logger,
                )

                if inappropriate:
                    self.logger.info(
                        "--> Post not interacted. {}\n".format(reason.encode("utf-8"))
                    )
                    self.inap_img += 1
                    continue

                # go go!
                per_post_liked_comments = 0
                per_post_replied_to_comments = 0
                per_post_interacted_commenters = []

                # get comments (if any)
                comment_data = get_comments_on_post(
                    self.browser,
                    self.username,
                    username,
                    comments_per_post,
                    link,
                    self.ignore_users,
                    randomize,
                    self.logger,
                )
                if not comment_data:
                    self.logger.info("No interaction did happen.\n")
                    continue

                # like the post before interacting on comments
                image_like_state, msg = like_image(
                    self.browser,
                    user_name,
                    self.blacklist,
                    self.logger,
                    self.logfolder,
                    self.liked_img,
                )
                if image_like_state is True:
                    like_failures_tracker["consequent"]["post_likes"] = 0
                    self.liked_img += 1

                elif msg == "already liked":
                    self.already_liked += 1

                elif msg == "block on likes":
                    break

                else:
                    self.logger.info(
                        "Can't interact by comments unless"
                        " the post is liked! :(\t~skipping this post\n"
                    )
                    like_failures_tracker["consequent"]["post_likes"] += 1
                    continue

                # perfect! now going to work with comments...
                for commenter, comment in comment_data:
                    if per_post_liked_comments >= comments_per_post:
                        break

                    elif (
                        like_failures_tracker["consequent"]["comment_likes"]
                        >= like_failures_tracker["limit"]["comment_likes"]
                    ):
                        self.logger.warning(
                            "--> Too many failures to like comments!{}".format(
                                leave_msg
                            )
                        )
                        # this example shows helpful usage of
                        # quotient breach outside QS needs..
                        self.quotient_breach = True
                        break

                    # verify the comment content by sentiment analysis &
                    # language detection if enabled
                    text_analysis_state = text_analysis(comment, "comment", self.logger)
                    if text_analysis_state is False:
                        # comment is inappropriate to be liked [and replied]
                        continue

                    # like the comment
                    comment_like_state, msg = like_comment(
                        self.browser, comment, self.logger
                    )
                    if comment_like_state is not True:
                        like_failures_tracker["consequent"]["comment_likes"] += 1
                        continue

                    else:
                        per_post_interacted_commenters.append(commenter)
                        self.liked_comments += 1
                        per_user_liked_comments += 1
                        per_post_liked_comments += 1
                        like_failures_tracker["consequent"]["comment_likes"] = 0

                        # send a reply to the comment if is appropriate
                        if (
                            self.do_reply_to_comments
                            and reply
                            and text_analysis_state is True
                        ):
                            do_reply_to_comment = (
                                self.reply_to_comments_percent >= random.randint(0, 100)
                            )

                            comment_replies_base = self.comment_replies + (
                                self.video_comment_replies
                                if is_video
                                else self.photo_comment_replies
                            )
                            # dismiss the already used replies per each user
                            comment_replies_base = [
                                reply
                                for reply in comment_replies_base
                                if reply not in per_user_used_replies
                            ]

                            if do_reply_to_comment and comment_replies_base:
                                chosen_reply = random.choice(comment_replies_base)
                                # mention the commenter to make a reply :)
                                reply_msg = ["@{} {}".format(commenter, chosen_reply)]

                                reply_to_comment_state, msg = comment_image(
                                    self.browser,
                                    commenter,
                                    reply_msg,
                                    self.blacklist,
                                    self.logger,
                                    self.logfolder,
                                )

                                if reply_to_comment_state is True:
                                    per_user_used_replies.extend(chosen_reply)
                                    self.replied_to_comments += 1
                                    self.commented += 1
                                    per_user_replied_to_comments += 1
                                    per_post_replied_to_comments += 1
                                    # reset jump counter after a successful
                                    # comment
                                    self.jumps["consequent"]["comments"] = 0

                                elif msg == "jumped":
                                    # will break the loop after
                                    # certain consecutive jumps
                                    self.jumps["consequent"]["comments"] += 1

                post_No_ending = (
                    "st"
                    if str(i + 1).endswith("1")
                    else "nd"
                    if str(i + 1).endswith("2")
                    else "rd"
                    if str(i + 1).endswith("3")
                    else "th"
                    if str(i + 1).endswith("4")
                    else ""
                )
                post_No = "{}{}".format(str(i + 1), post_No_ending)

                # quick log after interacting on a post
                print("")
                self.logger.info(
                    "Finished interacting on {} post's comments!".format(post_No)
                )
                self.logger.info("\tLiked comments: {}".format(per_post_liked_comments))
                self.logger.info(
                    "\tReplied to comments: {}\n".format(per_post_replied_to_comments)
                )

                # standalone interaction with commenters whose
                # comment was liked on the post
                if interact and per_post_interacted_commenters:
                    with self.feature_in_feature("interact_by_users", True):
                        self.interact_by_users(
                            per_post_interacted_commenters,
                            self.user_interact_amount,
                            self.user_interact_random,
                            self.user_interact_media,
                        )

            # quick log after interacting with a user
            print("")
            self.logger.info(
                "Finished interacting on {} posts' comments of '{}'!".format(
                    len(links), username
                )
            )
            self.logger.info("\tLiked comments: {}".format(per_user_liked_comments))
            self.logger.info(
                "\tReplied to comments: {}\n".format(per_user_replied_to_comments)
            )

        # full log after finishing whole work
        self.logger.info(
            "Finished interacting on {} posts' comments from {} users!".format(
                overall_posts_count, len(usernames)
            )
        )

        # find the feature-wide action sizes by taking a difference
        liked_img = self.liked_img - liked_init
        already_liked = self.already_liked - already_liked_init
        liked_comments = self.liked_comments - liked_comments_init
        replied_to_comments = self.replied_to_comments - replied_to_comments_init
        commented = (self.commented - commented_init) - replied_to_comments
        followed = self.followed - followed_init
        already_followed = self.already_followed - already_followed_init
        inap_img = self.inap_img - inap_img_init
        not_valid_users = self.not_valid_users - not_valid_users_init

        if self.liked_comments:
            # output results
            self.logger.info("\tLiked comments: {}".format(liked_comments))
            self.logger.info("\tReplied to comments: {}".format(replied_to_comments))
            self.logger.info("\tLiked posts: {}".format(liked_img))
            self.logger.info("\tAlready liked posts: {}".format(already_liked))
            self.logger.info("\tCommented posts: {}".format(commented))
            self.logger.info("\tFollowed users: {}".format(followed))
            self.logger.info("\tAlready followed users: {}".format(already_followed))
            self.logger.info("\tInappropriate posts: {}".format(inap_img))
            self.logger.info("\tNot valid users: {}".format(not_valid_users))

    def is_mandatory_character(self, uchr):
        if self.aborting:
            return self
        try:
            return self.check_letters[uchr]
        except KeyError:
            return self.check_letters.setdefault(
                uchr,
                any(
                    mandatory_char in unicodedata.name(uchr)
                    for mandatory_char in self.mandatory_character
                ),
            )

    def run_time(self):
        """Get the time session lasted in seconds"""

        real_time = time.time()
        run_time = real_time - self.start_time
        run_time = truncate_float(run_time, 2)

        return run_time

    def check_character_set(self, unistr):
        self.check_letters = {}
        if self.aborting:
            return self
        return all(
            self.is_mandatory_character(uchr) for uchr in unistr if uchr.isalpha()
        )

    def accept_follow_requests(self, amount: int = 100, sleep_delay: int = 1):
        """Accept pending follow requests from activity feed"""

        if self.aborting:
            return self

        message = "Starting to get follow requests.."
        highlight_print(self.username, message, "feature", "info", self.logger)

        accepted = 0
        while accepted < amount:

            feed_link = "https://www.instagram.com/accounts/activity/?followRequests=1"
            web_address_navigator(self.browser, feed_link)

            requests_to_confirm = self.browser.find_elements_by_xpath(
                "//button[text()='Confirm']"
            )

            if len(requests_to_confirm) == 0:
                self.logger.info("There are no follow requests in activity feed")
                break

            for request in requests_to_confirm:
                request.click()
                sleep(sleep_delay)
                accepted += 1
                if accepted >= amount:
                    self.logger.info(
                        "Reached accepted accounts limit of {} requests".format(amount)
                    )
                    break
                # catch if the list cannot be accessed, there are more followers under the hood or
                # because another element <a class="gKAyB " href="/accounts/activity/"> obscures it...
                scroll_down(self.browser)

        self.logger.info("Accepted {} follow requests".format(accepted))

        return self

    def join_pods(self, topic: str = "general", engagement_mode: str = "normal"):
        """Join pods"""
        if topic not in self.allowed_pod_topics:
            self.logger.error(
                "You have entered an invalid topic for pods, allowed topics are : {}. Exiting...".format(
                    self.allowed_pod_topics
                )
            )
            return self

        if engagement_mode not in self.allowed_pod_engagement_modes:
            self.logger.error(
                "You have entered an invalid engagement_mode for pods, allowed engagement_modes are : {}. Exiting...".format(
                    self.allowed_pod_engagement_modes
                )
            )
            return self

        if self.comments is not None and len(self.comments) < 10:
            self.logger.error(
                "You have too few comments, please set at least 10 distinct comments to avoid looking suspicious."
            )
            return self

        user_link = "https://www.instagram.com/{}/".format(self.username)
        web_address_navigator(self.browser, user_link)
        try:
            pod_posts = get_recent_posts_from_pods(topic, self.logger)
            self.logger.info("Downloaded pod_posts : {}".format(pod_posts))

            sleep(2)
            post_link_elems = self.browser.find_elements_by_xpath(
                "//a[contains(@href, '/p/')]"
            )
            post_links = []
            post_link = None

            for post_link_elem in post_link_elems:
                try:
                    post_link = post_link_elem.get_attribute("href")
                    post_links.append(post_link)
                except Exception as e:
                    self.logger.error(
                        "Can not get href for {} - {}".format(post_link, e)
                    )
                    continue

            post_links = list(set(post_links))
            my_recent_post_ids = []
            for post_link in post_links:
                try:
                    web_address_navigator(self.browser, post_link)
                    sleep(2)
                    time_element = self.browser.find_element_by_xpath("//div/a/time")
                    post_datetime_str = time_element.get_attribute("datetime")
                    post_datetime = datetime.strptime(
                        post_datetime_str, "%Y-%m-%dT%H:%M:%S.%fZ"
                    )
                    postid = post_link.split("/")[4]
                    self.logger.info(
                        "Post: {}, Instaposted at: {}".format(postid, post_datetime)
                    )
                    share_restricted = share_with_pods_restriction(
                        "read", postid, self.share_times, self.logger
                    )
                    if (
                        datetime.now() - post_datetime < timedelta(hours=12, minutes=30)
                        and not share_restricted
                    ):
                        my_recent_post_ids.append(postid)
                        if share_my_post_with_pods(
                            postid, topic, engagement_mode, self.logger
                        ):
                            share_with_pods_restriction(
                                "write", postid, None, self.logger
                            )
                except Exception as err:
                    self.logger.error(
                        "Failed for {} with Error {}".format(post_link, err)
                    )

            if len(my_recent_post_ids) > 0:
                self.logger.info(
                    "I have recent post(s): {}, so I will now help pod members actively.".format(
                        my_recent_post_ids
                    )
                )
                nposts = 200
            else:
                self.logger.info(
                    "I don't have any recent post, so I will just help a few pod posts and move on."
                )
                nposts = 40

            if len(pod_posts) <= nposts:
                pod_posts = pod_posts
            else:
                pod_posts = random.sample(pod_posts, nposts)

            no_comments_posts, light_posts, normal_posts, heavy_posts = group_posts(
                pod_posts, self.logger
            )

            self.logger.error("no_comments_posts : {} ".format(no_comments_posts))
            self.logger.error("light_posts : {} ".format(light_posts))
            self.logger.error("normal_posts : {} ".format(normal_posts))
            self.logger.error("heavy_posts : {} ".format(heavy_posts))

            self.engage_with_posts(no_comments_posts, 0)
            self.engage_with_posts(light_posts, 10)
            self.engage_with_posts(normal_posts, 30)
            self.engage_with_posts(heavy_posts, 90)

        except Exception as err:
            self.logger.error(err)

        return self

    def engage_with_posts(self, pod_posts, modespecific_comment_percentage):
        for pod_post in pod_posts:
            try:
                pod_post_id = pod_post["postid"]
                post_link = "https://www.instagram.com/p/{}".format(pod_post_id)
                web_address_navigator(self.browser, post_link)

                inappropriate, user_name, is_video, reason, scope = check_link(
                    self.browser,
                    post_link,
                    self.dont_like,
                    self.mandatory_words,
                    self.mandatory_language,
                    self.is_mandatory_character,
                    self.mandatory_character,
                    self.check_character_set,
                    self.ignore_if_contains,
                    self.logger,
                )

                if user_name != self.username:
                    follow_state, msg = follow_user(
                        self.browser,
                        "post",
                        self.username,
                        user_name,
                        None,
                        self.blacklist,
                        self.logger,
                        self.logfolder,
                    )

                    self.dont_include.add(user_name)

                if not inappropriate and user_name != self.username:
                    pods_like_percent = max(90, min(100, self.like_percentage))
                    liking = random.randint(0, 100) <= pods_like_percent
                    commenting = (
                        random.randint(0, 100) <= modespecific_comment_percentage
                    )

                    if liking:
                        like_state, msg = like_image(
                            self.browser,
                            user_name,
                            self.blacklist,
                            self.logger,
                            self.logfolder,
                            self.liked_img,
                        )

                        if like_state is True or self.do_comment_liked_photo:
                            self.liked_img += 1

                        elif msg == "block on likes":
                            break

                    commenting_restricted = comment_restriction(
                        "read", pod_post_id, self.comment_times, self.logger
                    )

                    if commenting and not commenting_restricted:
                        comments = self.fetch_smart_comments(is_video, temp_comments=[])

                        comment_state, msg = comment_image(
                            self.browser,
                            user_name,
                            comments,
                            self.blacklist,
                            self.logger,
                            self.logfolder,
                        )
                        if comment_state:
                            comment_restriction("write", pod_post_id, None, self.logger)

            except Exception as err:
                self.logger.error("Failed for {} with Error {}".format(pod_post, err))

    def story_by_tags(self, tags: list = None):
        """Watch stories for specific tag(s)"""
        if self.aborting:
            return self

        if tags is None:
            self.logger.info("No Tags set")
        else:
            # iterate over available tags
            for index, tag in enumerate(tags):
                # Quota Supervisor peak check
                if self.quotient_breach:
                    break

                # inform user whats happening
                if len(tags) > 1:
                    self.logger.info("Tag [{}/{}]".format(index + 1, len(tags)))
                self.logger.info(
                    "Loading stories with Tag --> {}".format(tag.encode("utf-8"))
                )

                try:
                    reels = watch_story(
                        self.browser, tag, self.logger, "tag", self.story_simulate
                    )
                except NoSuchElementException:
                    self.logger.info("No stories skipping this tag")
                    continue
                if reels > 0:
                    self.stories_watched += 1
                    self.reels_watched += reels

    def story_by_users(self, users: list = None):
        """Watch stories for specific user(s)"""
        if self.aborting:
            return self

        if users is None:
            self.logger.info("No users passed to story_by_users")
        else:
            # iterate over available users
            for index, user in enumerate(users):
                # Quota Supervisor peak check
                if self.quotient_breach:
                    break

                # inform user whats happening
                if len(users) > 1:
                    self.logger.info("User [{}/{}]".format(index + 1, len(users)))
                self.logger.info(
                    "Loading stories with User --> {}".format(user.encode("utf-8"))
                )

                try:
                    reels = watch_story(
                        self.browser, user, self.logger, "user", self.story_simulate
                    )
                except NoSuchElementException:
                    self.logger.info("No stories skipping this user")
                    continue
                if reels > 0:
                    self.stories_watched += 1
                    self.reels_watched += reels

    def target_list(self, file):
        """Extracts target list from text file"""
        target_list = file_handling(file)

        if "FileNotFoundError" in target_list:
            self.logger.warning("No such file or directory: " + file + "\n")
            return []

        return target_list
