<gh_stars>0
#!/usr/bin/env python2
# coding=utf-8

"""
Usernames combination generator checker
"""

import argparse
import json
import os
import random
import re
import subprocess
import sys
import time
from datetime import datetime
from itertools import product

import requests
from requests import ConnectTimeout, ConnectionError, RequestException


class Color(object):
    pink = '\033[95m'
    green = '\033[92m'
    red = '\033[91m'
    bold = '\033[1m'
    end = '\033[0m'


class Arguments(object):

    @staticmethod
    def get_all():
        parser = argparse.ArgumentParser()
        parser.add_argument("-c", "--combine", nargs='?', default=False, const=True, help="combine range")
        parser.add_argument("-p", "--pause", nargs='?', default=False, const=True,
                            help="pause range")
        parser.add_argument("-a", "--alphanumeric", nargs='?', default=False, const=True,
                            help="alphanumeric list (split by whitespace)")
        parser.add_argument("-n", "--names", nargs='?', default=False, const=True,
                            help="names list (split by whitespace)")
        parser.add_argument("-i", "--input", nargs='?', default=False, const=True,
                            help="input path (split usernames in file by new line)")
        parser.add_argument("-o", "--output", nargs='?', default=False, const=True,
                            help="output path")
        parser.add_argument("-r", "--regex", nargs='?', default=False, const=True,
                            help="regex match")
        parser.add_argument("-l", "--links", nargs='?', default=False, const=True,
                            help="links list (split by whitespace)")
        parser.add_argument("-t", "--timeout", nargs='?', default=False, const=True,
                            help="timeout limit")
        parser.add_argument("--cookies", nargs='?', default=False, const=True,
                            help="cookies request")
        parser.add_argument("--headers", nargs='?', default=False, const=True,
                            help="headers request")
        parser.add_argument("-g", "--generator", nargs='?', default=False, const=True,
                            help="generator mode")
        parser.add_argument("--github", nargs='?', default=False, const=True,
                            help="github preset")
        parser.add_argument("--gitlab", nargs='?', default=False, const=True,
                            help="gitlab preset")
        return parser.parse_args()

    @staticmethod
    def get_range(argument, default):
        if len(argument.split("-")) >= 1:
            return argument.split("-")
        else:
            return default


class Config(object):
    # system
    arguments = Arguments.get_all()
    caffeinate_pid = str(os.getpid())

    # counters
    check_checked = 0
    check_founded = 0
    check_combined = 0
    check_length = 0
    check_status_code = 0
    check_status_error = 0

    # dynamic
    check_domain = ""
    check_link = ""
    check_username = ""
    check_usernames = []
    check_output_new = ""
    check_output_created = []
    check_output_removed = []

    # defaults
    check_alphanumeric = "a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9"
    check_names = "<NAME>"
    check_input = "./files/input.txt"
    check_output = "./output/usernames.txt"
    check_links = "https://github.com/"
    check_links_headers = '{"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' \
                          'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36"}'
    check_regex = "^(.*)$"
    check_combine = "1-1"
    check_pause = "1-5"
    check_status_code_checked = 200
    check_status_code_founded = 404
    check_status_pause = 30
    check_status_timeout = 5

    # ranges
    combine_min = 0
    combine_max = 0
    pause_min = 0
    pause_max = 0

    # timers
    time_started = datetime.now()

    # Set GitHub preset
    if arguments.github:
        if not arguments.combine:
            arguments.combine = "1"
        if not arguments.pause:
            arguments.pause = "0-1"
        if not arguments.alphanumeric:
            arguments.alphanumeric = True
        if not arguments.names:
            arguments.names = "-"
        if not arguments.regex:
            arguments.regex = "^(?!-)(?!.*--)(?!.*-$)(.*)$"
        if not arguments.links:
            arguments.links = "https://github.com/"

    # Set GitLab preset
    if arguments.gitlab:
        if not arguments.combine:
            arguments.combine = "1"
        if not arguments.pause:
            arguments.pause = "0-2"
        if not arguments.alphanumeric:
            arguments.alphanumeric = True
        if not arguments.names:
            arguments.names = "_"
        if not arguments.regex:
            arguments.regex = "^(?!_)(?!.*__)(?!.*_$)(.*)$"
        if not arguments.links:
            arguments.links = "https://gitlab.com/"

    # Set arguments default and fallback values
    if not isinstance(arguments.combine, str):
        arguments.combine = check_combine
    if not isinstance(arguments.pause, str):
        arguments.pause = check_pause
    if not isinstance(arguments.alphanumeric, str) and arguments.alphanumeric:
        arguments.alphanumeric = check_alphanumeric
    if not isinstance(arguments.names, str) and arguments.names:
        arguments.names = check_names
    if not isinstance(arguments.input, str) and arguments.input:
        arguments.input = check_input
    if not isinstance(arguments.output, str):
        arguments.output = check_output
    if not isinstance(arguments.regex, str):
        arguments.regex = check_regex
    if not isinstance(arguments.links, str):
        arguments.links = check_links
    if not isinstance(arguments.timeout, str):
        arguments.timeout = check_status_timeout
    if not isinstance(arguments.headers, str):
        arguments.headers = check_links_headers

    # Format arguments values
    if arguments.combine:
        combine_min = int(min(Arguments.get_range(arguments.combine, 1)))
        combine_max = int(max(Arguments.get_range(arguments.combine, 1)))
    if arguments.pause:
        pause_min = int(min(Arguments.get_range(arguments.pause, 1)))
        pause_max = int(max(Arguments.get_range(arguments.pause, 5)))
    if arguments.output:
        check_output = arguments.output
    if arguments.regex:
        check_regex = arguments.regex
    if arguments.links:
        check_links = arguments.links.split(" ")
    if arguments.timeout:
        check_status_timeout = int(arguments.timeout)
    if arguments.headers:
        check_links_headers = json.loads(arguments.headers)
    if arguments.alphanumeric or arguments.input or arguments.names:
        if arguments.alphanumeric:
            check_alphanumeric = arguments.alphanumeric.split(" ")
            arguments.alphanumeric = arguments.alphanumeric.split(" ")
            check_usernames += arguments.alphanumeric
        if arguments.input:
            if os.path.isfile(arguments.input):
                check_input = arguments.input
                check_usernames += [line.rstrip() for line in open(arguments.input)]
            else:
                print(">> " + Color.red + "Error!!! Input file not found!" + Color.end)
                print("    -" + Color.red + " check input path: {}".
                      format(arguments.input) + Color.end)
                sys.exit()
        if arguments.names:
            check_names = arguments.names.split(" ")
            arguments.names = arguments.names.split(" ")
            check_usernames += arguments.names
    else:
        check_usernames += check_alphanumeric.split(" ")


class Message(object):

    @staticmethod
    def flush_message(message):
        sys.stdout.write("\r" + message)
        sys.stdout.flush()

    @staticmethod
    def print_header(message):
        print(Color.pink + Color.bold + message + Color.end)

    @staticmethod
    def print_config(message, config):
        print(Color.pink + message + Color.end + config)

    @staticmethod
    def print_exist(message):
        print(Color.red + message + Color.end)

    @staticmethod
    def print_generating(message):
        Message.flush_message(message + Color.end)

    @staticmethod
    def print_created(message):
        print(Color.green + message + Color.end)

    @staticmethod
    def print_empty(message):
        print(message)

    @staticmethod
    def print_ended(message):
        print(Color.green + message + Color.end)

    @staticmethod
    def print_stopped(message):
        print(Color.red + message + Color.end)

    @staticmethod
    def print_check(message):
        if message == "checked":
            check_color = Color.red
        elif message == "founded":
            check_color = Color.green
        else:
            check_color = ""

        usernames = ">> Usernames "
        found = Color.green + "found " + str(Config.check_founded) + Color.end
        of = " of "
        checked = Color.red + "checked " + str(Config.check_checked)
        length = "/" + str(Config.check_length) + Color.end
        domain = " on " + Config.check_domain + ": "
        username = check_color + Config.check_username + Color.end

        Message.flush_message(usernames + found + of + checked + length + domain + username)

    @staticmethod
    def print_output(message):
        if len(Config.check_output_removed) == 0:
            Message.print_empty("")
        else:
            Message.print_exist(Color.end + "\n    –" + Color.red + " removed old usernames file: {}".
                                format(str(Config.check_output_removed)))

        if len(Config.check_output_created) == 0:
            Message.print_stopped(Color.end + "    –" + Color.red + " couldn't {} usernames!!!\n".
                                  format(message))
        else:
            Message.print_ended(Color.end + "    –" + Color.green + " saved usernames to: {}\n".
                                format(str(Config.check_output_created)))


class Parser(object):

    @staticmethod
    def get_domain_name(request_link):
        x = request_link.split("/")

        if x[0] == "https:" or x[0] == "http:":
            x = x[2].split(".")
        else:
            x = x[0].split(".")

        if len(x) == 2:
            return x[0] + "." + x[1]
        else:
            return x[1] + "." + x[2]

    @staticmethod
    def check_error_retry(message):
        Config.check_status_error = Config.check_status_error + 1
        Config.check_status_pause = Config.check_status_pause * Config.check_status_error

        if Config.check_status_error == 1:
            Message.print_empty("")

        Message.print_stopped(Color.end + "    –" + Color.red +
                              " {} {}: will retry request in {} sec".format(
                                  message, str(Config.check_status_code),
                                  str(Config.check_status_pause)))

        time.sleep(Config.check_status_pause)
        Parser.check()

    @staticmethod
    def check_output():
        if os.path.basename(Config.check_output_new) not in Config.check_output_created:
            Config.check_output_created.append(os.path.basename(Config.check_output_new))

            if not os.path.exists(os.path.dirname(Config.check_output_new)):
                os.makedirs(os.path.dirname(Config.check_output_new))

            if os.path.isfile(Config.check_output_new):
                Config.check_output_removed.append(os.path.basename(Config.check_output_new))
                os.remove(Config.check_output_new)

    @staticmethod
    def write_output():
        with open(Config.check_output_new, 'a') as f:
            f.write(Config.check_username + "\n")
            f.close()

    @staticmethod
    def check():
        try:
            request = requests.get(
                Config.check_link,
                headers=Config.check_links_headers,
                timeout=Config.check_status_timeout)

            if Config.arguments.cookies:
                Config.check_links_headers.update({
                    "Cookie": request.headers.get("Set-Cookie")
                })

            Config.check_domain = Parser.get_domain_name(Config.check_link)
            Config.check_status_code = request.status_code
            Config.check_output_new = os.path.abspath("{}/checker_{}_{}_{}".format(
                str(os.path.dirname(Config.check_output)),
                str(Config.check_domain),
                str(Config.check_combined),
                str(os.path.basename(Config.check_output))
            ))

            if Config.check_status_code == Config.check_status_code_checked:
                Config.check_status_error = 0
                Config.check_checked = Config.check_checked + 1

                Message.print_check("checked")

            if Config.check_status_code == Config.check_status_code_founded:
                Config.check_status_error = 0
                Config.check_checked = Config.check_checked + 1
                Config.check_founded = Config.check_founded + 1

                Message.print_check("founded")

                Parser.check_output()
                Parser.write_output()

            if Config.check_status_code != 200 and Config.check_status_code != 404:
                Parser.check_error_retry("RequestError")

        except ConnectTimeout:
            Parser.check_error_retry("ConnectTimeout")

        except ConnectionError:
            Parser.check_error_retry("ConnectionError")

        except RequestException:
            Parser.check_error_retry("RequestException")


class Check(object):

    @staticmethod
    def main():
        if 'darwin' in sys.platform:
            subprocess.Popen(['caffeinate', '-w', Config.caffeinate_pid])

        try:
            if not Config.arguments.generator:
                Message.print_header("===================================")
                Message.print_header("    Starting usernames checker!    ")
                Message.print_header("===================================")
            else:
                Message.print_header("=====================================")
                Message.print_header("    Starting usernames generator!    ")
                Message.print_header("=====================================")

            Message.print_empty("")
            Message.print_config("Combine range: ", str(Config.combine_min) +
                                 "-" + str(Config.combine_max))

            if not Config.arguments.generator:
                Message.print_config("Pause range: ", str(Config.pause_min) +
                                     "-" + str(Config.pause_max))
                Message.print_config("Timeout limit: ", str(Config.check_status_timeout))
                Message.print_config("Links list: ", str(Config.check_links))
                Message.print_config("Links headers: ", str(Config.check_links_headers))

            if Config.arguments.alphanumeric:
                Message.print_config("Names alphanumeric: ", str(Config.arguments.alphanumeric))
            if Config.arguments.names:
                Message.print_config("Names list: ", str(Config.arguments.names))
            if Config.arguments.input:
                Message.print_config("Names input: ", str(Config.arguments.input))

            if not Config.arguments.alphanumeric and not Config.arguments.input and not Config.arguments.names:
                Message.print_config("Names default: ", str(Config.check_usernames))

            Message.print_config("Names match regex: ", str(Config.check_regex))
            Message.print_config("Output directory: ", str(os.path.abspath(os.path.dirname(Config.check_output))))
            Message.print_config("Time started: ", str(Config.time_started))
            Message.print_empty("")

            for combine in range(Config.combine_min, Config.combine_max + 1):
                Config.check_founded = 0
                Config.check_checked = 0
                Config.check_length = 0
                Config.check_combined = 0
                Config.check_output_created = []
                Config.check_output_removed = []

                if Config.arguments.generator:
                    Config.check_output_new = os.path.abspath("{}/generator_{}_{}".format(
                        str(os.path.dirname(Config.check_output)),
                        str(combine),
                        str(os.path.basename(Config.check_output))
                    ))

                for count in product(Config.check_usernames, repeat=combine):
                    if re.search(r'{}'.format(Config.check_regex), str(''.join(count))):
                        Config.check_length = Config.check_length + 1

                if Config.arguments.generator:
                    Message.print_generating(">> Generating {} usernames".
                                             format(Config.check_length))

                for username in product(Config.check_usernames, repeat=combine):
                    Config.check_username = str(''.join(username))
                    Config.check_combined = str(combine)

                    if re.search(r'{}'.format(Config.check_regex), Config.check_username):
                        for link in Config.check_links:
                            Config.check_link = str(''.join(link)) + Config.check_username

                            if not Config.arguments.generator:
                                Parser.check()
                                time.sleep(random.randint(Config.pause_min, Config.pause_max))
                            else:
                                Config.check_founded = Config.check_founded + 1
                                Parser.check_output()
                                Parser.write_output()

                if not Config.arguments.generator:
                    Message.print_output("find")
                else:
                    Message.print_output("generate")

            if not Config.arguments.generator:
                Message.print_ended("Ended checker!!! (duration {})".
                                    format(datetime.now() - Config.time_started))
            else:
                Message.print_ended("Ended generator!!! (duration {})".
                                    format(datetime.now() - Config.time_started))

        except KeyboardInterrupt:
            if not Config.arguments.generator:
                Message.print_stopped("\n\nStopped checker (Ctrl C)!!! (duration {})".
                                      format(datetime.now() - Config.time_started))
            else:
                Message.print_stopped("\n\nStopped generator (Ctrl C)!!! (duration {})".
                                      format(datetime.now() - Config.time_started))

        if 'darwin' in sys.platform:
            subprocess.Popen(['kill', '-9', Config.caffeinate_pid],
                             shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        sys.exit()


if __name__ == "__main__":
    Check.main()
