Exemple #1
0
 def __init__(self, siteName: str, password: str):
     self.name = siteName
     self.client = Client(siteName)
     self.client.login(Config.email, password)
     self.rooms = []
     for room in Config.homes[siteName]:
         self.join(room)
     if os.path.isfile(Config.storageDir + self.name + ".rooms"):
         with open(Config.storageDir + self.name + ".rooms", "r") as f:
             lines = f.readlines()
             for line in lines:
                 try:
                     self.join(int(line))
                 except ValueError:
                     print("Invalid room ID: " + line)
Exemple #2
0
class SESite(object):
    def __init__(self, siteName: str, password: str):
        self.name = siteName
        self.client = Client(siteName)
        self.client.login(Config.email, password)
        self.rooms = []
        for room in Config.homes[siteName]:
            self.join(room)
        if os.path.isfile(Config.storageDir + self.name + ".rooms"):
            with open(Config.storageDir + self.name + ".rooms", "r") as f:
                lines = f.readlines()
                for line in lines:
                    try:
                        self.join(int(line))
                    except ValueError:
                        print("Invalid room ID: " + line)

    def stop(self):
        with open(Config.storageDir + self.name + ".rooms", "w") as f:
            for room in self.rooms:
                f.write(str(room) + "\n")

        for room in self.rooms:
            self.leave(room)
        self.client.logout()

    def join(self, room: int):
        if room in self.rooms:
            return
        r = self.client.get_room(room)
        r.join()
        if not Config.startQuiet:
            r.send_message("Howdy folks!")
        r.watch(onMessage)

        self.rooms.append(room)

    def leave(self, room: int):
        if room not in self.rooms:
            return

        rm = self.client.get_room(room)
        if not Config.leaveQuiet:
            rm.send_message("Adios! I'm off to the tavern")

        rm.leave()
        try:
            self.rooms.remove(room)
        except:
            print(traceback.format_exc())

    def getClient(self):
        return self.client

    def getRoom(self, room: int):
        return self.client.get_room(room)
Exemple #3
0
class GlobalVars:
    false_positives = []
    whitelisted_users = []
    blacklisted_users = []
    ignored_posts = []
    auto_ignored_posts = []
    startup_utc = datetime.utcnow().strftime("%H:%M:%S")
    latest_questions = []
    blockedTime = 0
    api_backoff_time = 0
    charcoal_room_id = "11540"
    meta_tavern_room_id = "89"
    socvr_room_id = "41570"
    site_filename = {
        "electronics.stackexchange.com": "ElectronicsGood.txt",
        "gaming.stackexchange.com": "GamingGood.txt",
        "german.stackexchange.com": "GermanGood.txt",
        "italian.stackexchange.com": "ItalianGood.txt",
        "math.stackexchange.com": "MathematicsGood.txt",
        "spanish.stackexchange.com": "SpanishGood.txt",
        "stats.stackexchange.com": "StatsGood.txt"
    }

    experimental_reasons = []  # Don't widely report these
    non_tavern_reasons = [
        "All-caps title",  # Don't report in the Tavern
        "All-caps body",
        "All-caps answer",
        "All-caps body, all-caps title",
        "Repeating characters in body",
        "Repeating characters in title",
        "Repeating characters in answer",
        "Phone number detected in title",
        "Offensive body detected",
        "Email in answer",
        "Email in title",
        "Link at end of answer"
    ]
    non_tavern_sites = ["stackoverflow.com"]

    parser = HTMLParser.HTMLParser()
    wrap = Client("stackexchange.com")
    wrapm = Client("meta.stackexchange.com")
    wrapso = Client("stackoverflow.com")
    privileged_users = {
        charcoal_room_id: [
            "117490",  # Normal Human
            "66258",  # Andy
            "31768",  # ManishEarth
            "103081",  # hichris123
            "73046",  # Undo
            "88521",  # ProgramFOX
            "59776",  # Doorknob
            "31465",  # Seth
            "88577",  # Santa Claus
            "34124",  # Andrew Leach
            "54229",  # apnorton
            "20459",  # S.L. Barth
            "32436",  # tchrist
            "30477",  # Brock Adams
            "58529",  # ferrybig
            "145208",  # Robert Longson
            "171800"
        ],  # JAL
        meta_tavern_room_id: [
            "259867",  # Normal Human
            "244519",  # CRABOLO
            "244382",  # TGMCians
            "194047",  # Jan Dvorak
            "158100",  # rene
            "178438",  # Manishearth
            "237685",  # hichris123
            "215468",  # Undo
            "229438",  # ProgramFOX
            "180276",  # Doorknob
            "161974",  # Lynn Crumbling
            "186281",  # Andy
            "266094",  # Unihedro
            "245167",  # Infinite Recursion
            "230261",  # Jason C
            "213575",  # Braiam
            "241919",  # Andrew T.
            "203389",  # backwards-Seth
            "202832",  # Mooseman
            "160017",  # bwDraco
            "201151",  # bummi
            "188558",  # Frank
            "229166",  # Santa Claus
            "159034",  # Kevin Brown
            "203972",  # PeterJ
            "188673",  # Alexis King
            "258672",  # AstroCB
            "227577",  # Sam
            "255735",  # cybermonkey
            "279182",  # Ixrec
            "271104",  # James
            "220428",  # Qantas 94 Heavy
            "153355",  # tchrist
            "238426",  # Ed Cottrell
            "166899",  # Second Rikudo
            "287999",  # ASCIIThenANSI
            "208518",  # JNat
            "284141",  # michaelpri
            "260312",  # vaultah
            "244062",  # SouravGhosh
            "152859",  # Shadow Wizard
            "201314",  # apnorton
            "280934",  # M.A.Ramezani
            "200235",  # durron597
            "148310",  # Awesome Poodles / Brock Adams
            "168333",  # S.L. Barth
            "257207",  # Unikitty
            "244282",  # DroidDev
            "163250",  # Cupcake
            "298265",  # BoomsPlus
            "253560",  # josilber
            "244254",  # misterManSam
            "188189",  # Robert Longson
            "174699",  # Ilmari Karonen
            "202362",  # chmod 666 telkitty
            "289717",  # Quill
            "237813",  # bjb568
            "311345",  # Simon Klaver
            "171881",  # rekire
            "260388",  # Pandya
            "242209"
        ],  # JAL
        socvr_room_id: [
            "1849664",  # Undo
            "2581872",  # hichris123
            "1198729",  # Manishearth
            "3717023",  # Normal Human aka 1999
            "2619912",  # ProgramFOX
            "578411",  # rene
            "1043380",  # gunr2171
            "2246344",  # Sam
            "2756409",  # TylerH
            "1768232",  # durron597
            "359284",  # Kevin Brown
            "258400",  # easwee
            "3622940",  # Unihedron
            "3204551",  # Deduplicator
            "4342498",  # NathanOliver
            "4639281",  # Tiny Giant
            "3093387",  # josilber
            "1652962",  # cimmanon
            "1677912",  # Mogsdad
            "656243",  # Lynn Crumbling
            "3933332",  # Rizier123
            "2422013",  # cybermonkey
            "3478852",  # Nisse Engström
            "2302862",  # Siguza
            "1324",  # Paul Roub
            "1743880",  # Tunaki
            "1663001",  # DavidG
            "2415822",  # JAL
            "4174897",  # Kyll
            "5299236",  # Kevin Guan
            "4050842",  # Thaillie
            "1816093",  # Drew
            "874188",  # Triplee
            "880772",  # approxiblue
            "1835379",  # Cerbrus
            "1843331",  # Tim Castelijns
            "3956566",  # JamesENL
            "2357233",  # Ms Yvette
            "3155639",  # AlexanderOMara
            "462627",  # Praveen Kumar
            "4490559",  # intboolstring
            "1364007",  # Wai Ha Lee
            "1699210",  # bummi
            "563532",  # Rob
            "5389107",  # Magisch
            "4099593",  # bhargav-rao
            "1542723",  # Ferrybig
            "2025923",  # Tushar
            "5292302"
        ]  # Petter Friberg
    }
    smokeDetector_user_id = {
        charcoal_room_id: "120914",
        meta_tavern_room_id: "266345",
        socvr_room_id: "3735529"
    }

    censored_committer_names = {"3f4ed0f38df010ce300dba362fa63a62": "Undo1"}

    commit = os.popen('git log --pretty=format:"%h" -n 1').read()
    commit_author = os.popen('git log --pretty=format:"%cn" -n 1').read()

    if md5.new(commit_author).hexdigest() in censored_committer_names:
        commit_author = censored_committer_names[md5.new(
            commit_author).hexdigest()]

    commit_with_author = os.popen('git log --pretty=format:"%h (' +
                                  commit_author + ': *%s*)" -n 1').read()
    on_master = os.popen(
        "git rev-parse --abbrev-ref HEAD").read().strip() == "master"
    charcoal_hq = None
    tavern_on_the_meta = None
    socvr = None
    s = ""
    s_reverted = ""
    specialrooms = []
    apiquota = -1
    bodyfetcher = None
    se_sites = []
    users_chatting = {
        meta_tavern_room_id: [],
        socvr_room_id: [],
        charcoal_room_id: []
    }
    why_data = []
    why_data_allspam = []
    notifications = []
    listen_to_these_if_edited = []
    multiple_reporters = []
    api_calls_per_site = {}

    config = ConfigParser.RawConfigParser()
    config.read('config')

    latest_smokedetector_messages = {
        meta_tavern_room_id: [],
        charcoal_room_id: [],
        socvr_room_id: []
    }

    location = config.get("Config", "location")
    print location

    try:
        metasmoke_host = config.get("Config", "metasmoke_host")
        print metasmoke_host
    except ConfigParser.NoOptionError:
        metasmoke_host = None
        print "metasmoke host not found. Set it as metasmoke_host in the config file. See https://github.com/Charcoal-SE/metasmoke."

    try:
        metasmoke_key = config.get("Config", "metasmoke_key")
    except ConfigParser.NoOptionError:
        metasmoke_key = ""
        print "No metasmoke key found, which is okay if both are running on the same host"
Exemple #4
0
    def main(self, config_data, additional_general_config):
        if "owners" in Config.General:
            self.owners = Config.General["owners"]
        else:
            sys.exit("[Chatbot] FATAL: no owners found. Please update Config.py.")
        if "privileged_users" in config_data:
            self.privileged_users = config_data["privileged_users"]
        if "github" in Config.General:
            self.github = Config.General["github"]
        else:
            self.github = "https://github.com/ProgramFOX/SE-Chatbot"
        if "owner_name" in Config.General:
            self.owner_name = Config.General["owner_name"]
        else:
            sys.exit("[Chatbot] FATAL: no owner name found. Please update Config.py.")
        if "chatbot_name" in Config.General:
            self.chatbot_name = Config.General["chatbot_name"]
        else:
            sys.exit("[Chatbot] FATAL: no chatbot name found. Please update Config.py.")
        # self.setup_logging() # if you want to have logging, un-comment this line

        if "site" in config_data:
            self.site = config_data["site"]
            print("Site: %s" % self.site)
        else:
            self.site = input("Site: ")
        for o in self.owners:
            if self.site in o:
                self.owner_ids.append(o[self.site])
        if len(self.owner_ids) < 1:
            sys.exit("[Chatbot] FATAL: no owners found for this site: %s." % self.site)
        for p in self.privileged_users:
            if self.site in p:
                self.privileged_user_ids.append(p[self.site])
        if "room" in config_data:
            room_number = config_data["room"]
            print("Room number: %i" % room_number)
        else:
            room_number = int(input("Room number: "))
        if "prefix" in config_data:
            self.prefix = config_data["prefix"]
        else:
            self.prefix = '>>'
        print("Prefix: %s" % self.prefix)
        if "email" in Config.General:
            email = Config.General["email"]
        elif "email" in additional_general_config:
            email = additional_general_config["email"]
        else:
            email = input("Email address: ")

        self.client = Client(self.site)

        # Setting the timeout down to 5 fixes random SSL errors when terminating.
        # The bot's timeout on exit is 5; the request timeout is 30 by default. Requests overrun the
        # bot timeout, get force-closed, and cause errors.
        self.client._br.request_timeout = 5.0

        try:
            if "password" in Config.General:  # I would not recommend to store the password in Config.py
                password = Config.General["password"]
                self.client.login(email, password)
            elif "password" in additional_general_config:
                password = additional_general_config["password"]
                self.client.login(email, password)
            else:
                for attempts in range(3):
                    try:
                        password = getpass.getpass("Password: "******"Incorrect password.")
                        else:
                            raise
        except LoginError:
            sys.exit("[Chatbot] FATAL: Incorrect password, shutting down.")

        self.room = self.client.get_room(room_number)
        self.room.join()
        if "message" not in additional_general_config:
            bot_message = "Bot started."
        else:
            bot_message = additional_general_config["message"]
        if bot_message is not None:
            self.room.send_message(bot_message)

        on_loads = self.modules.get_on_load_methods()
        for on_load in on_loads:
            on_load(self)

        self.room.watch_socket(self.on_event)

        while self.running:
            inputted = input("<< ")
            if inputted.strip() == "":
                continue
            if inputted.startswith("$") and len(inputted) > 2:
                command_in = inputted[2:]
                cmd_handler = ConsoleCommandHandler(self, inputted[1] == "+", self.prefix + command_in)
                event_mock = type('MockEvent', (), {})()
                user_mock = type('', (), {})()
                user_mock.id = -1
                event_mock.user = user_mock
                event_mock.message = cmd_handler
                command_out = self.command(command_in, cmd_handler, event_mock, 0)
                if command_out is not False and command_out is not None:
                    cmd_handler.reply(command_out)
            else:
                self.room.send_message(inputted)
Exemple #5
0
class Chatbot:
    def __init__(self):
        self.room = None
        self.client = None
        self.privileged_users = []
        self.owners = []
        self.owner_name = ""
        self.chatbot_name = ""
        self.enabled = True
        self.suspended_until = -1
        self.running = True
        self.site = ""
        self.owner_ids = []
        self.privileged_user_ids = []
        self.save_subdirs = ['main']
        self.modules = MetaModule(ModuleManifest.module_file_names, self, 'all')
        try:
            SaveIO.set_subdirs(self.save_subdirs)
        except DuplicateDirectoryException as e:
            if "-q" not in sys.argv:
                print("[Chatbot] WARNING: there are modules with the same save directory: " + str(e))
        SaveIO.create_if_not_exists(SaveIO.data_dir)
        del self.save_subdirs
        duplicates = self.get_duplicate_commands()
        if duplicates and "-q" not in sys.argv:
            print('[Chatbot] WARNING: there are commands with the same name: ' + str(duplicates))

    def main(self, config_data, additional_general_config):
        if "owners" in Config.General:
            self.owners = Config.General["owners"]
        else:
            sys.exit("[Chatbot] FATAL: no owners found. Please update Config.py.")
        if "privileged_users" in config_data:
            self.privileged_users = config_data["privileged_users"]
        if "github" in Config.General:
            self.github = Config.General["github"]
        else:
            self.github = "https://github.com/ProgramFOX/SE-Chatbot"
        if "owner_name" in Config.General:
            self.owner_name = Config.General["owner_name"]
        else:
            sys.exit("[Chatbot] FATAL: no owner name found. Please update Config.py.")
        if "chatbot_name" in Config.General:
            self.chatbot_name = Config.General["chatbot_name"]
        else:
            sys.exit("[Chatbot] FATAL: no chatbot name found. Please update Config.py.")
        # self.setup_logging() # if you want to have logging, un-comment this line

        if "site" in config_data:
            self.site = config_data["site"]
            print("Site: %s" % self.site)
        else:
            self.site = input("Site: ")
        for o in self.owners:
            if self.site in o:
                self.owner_ids.append(o[self.site])
        if len(self.owner_ids) < 1:
            sys.exit("[Chatbot] FATAL: no owners found for this site: %s." % self.site)
        for p in self.privileged_users:
            if self.site in p:
                self.privileged_user_ids.append(p[self.site])
        if "room" in config_data:
            room_number = config_data["room"]
            print("Room number: %i" % room_number)
        else:
            room_number = int(input("Room number: "))
        if "prefix" in config_data:
            self.prefix = config_data["prefix"]
        else:
            self.prefix = '>>'
        print("Prefix: %s" % self.prefix)
        if "email" in Config.General:
            email = Config.General["email"]
        elif "email" in additional_general_config:
            email = additional_general_config["email"]
        else:
            email = input("Email address: ")

        self.client = Client(self.site)

        # Setting the timeout down to 5 fixes random SSL errors when terminating.
        # The bot's timeout on exit is 5; the request timeout is 30 by default. Requests overrun the
        # bot timeout, get force-closed, and cause errors.
        self.client._br.request_timeout = 5.0

        try:
            if "password" in Config.General:  # I would not recommend to store the password in Config.py
                password = Config.General["password"]
                self.client.login(email, password)
            elif "password" in additional_general_config:
                password = additional_general_config["password"]
                self.client.login(email, password)
            else:
                for attempts in range(3):
                    try:
                        password = getpass.getpass("Password: "******"Incorrect password.")
                        else:
                            raise
        except LoginError:
            sys.exit("[Chatbot] FATAL: Incorrect password, shutting down.")

        self.room = self.client.get_room(room_number)
        self.room.join()
        if "message" not in additional_general_config:
            bot_message = "Bot started."
        else:
            bot_message = additional_general_config["message"]
        if bot_message is not None:
            self.room.send_message(bot_message)

        on_loads = self.modules.get_on_load_methods()
        for on_load in on_loads:
            on_load(self)

        self.room.watch_socket(self.on_event)

        while self.running:
            inputted = input("<< ")
            if inputted.strip() == "":
                continue
            if inputted.startswith("$") and len(inputted) > 2:
                command_in = inputted[2:]
                cmd_handler = ConsoleCommandHandler(self, inputted[1] == "+", self.prefix + command_in)
                event_mock = type('MockEvent', (), {})()
                user_mock = type('', (), {})()
                user_mock.id = -1
                event_mock.user = user_mock
                event_mock.message = cmd_handler
                command_out = self.command(command_in, cmd_handler, event_mock, 0)
                if command_out is not False and command_out is not None:
                    cmd_handler.reply(command_out)
            else:
                self.room.send_message(inputted)

    def get_duplicate_commands(self):
        checked_cmds = []
        dupe_cmds = []
        all_cmds = self.modules.list_commands()
        for command in all_cmds:
            if command.name not in checked_cmds:
                checked_cmds.append(command.name)
            else:
                if command.name not in dupe_cmds:
                    dupe_cmds.append(command.name)
        return dupe_cmds

    def setup_logging(self):  # logging method taken from ChatExchange/examples/chat.py
        logger = logging.getLogger(__name__)
        logging.basicConfig(level=logging.INFO)
        logger.setLevel(logging.DEBUG)

        # In addition to the basic stderr logging configured globally
        # above, we'll use a log file for chatexchange.client.
        wrapper_logger = logging.getLogger('chatexchange.client')
        wrapper_handler = logging.handlers.TimedRotatingFileHandler(
            filename='client.log',
            when='midnight', delay=True, utc=True, backupCount=7,
        )
        wrapper_handler.setFormatter(logging.Formatter(
            "%(asctime)s: %(levelname)s: %(threadName)s: %(message)s"
        ))
        wrapper_logger.addHandler(wrapper_handler)

    def check_existence_and_chars(self, cmd_name, content):
        cmd_list = self.modules.list_commands()
        allowed = -1
        disallowed = -1
        for cmd in cmd_list:
            if cmd.name == cmd_name or (cmd.aliases is not None and cmd_name in cmd.aliases):
                allowed = cmd.allowed_chars
                disallowed = cmd.disallowed_chars
                break
        if allowed == -1:
            return False, False
        for c in content:
            if disallowed is not None and c in disallowed:
                return True, False
            if allowed is not None and c not in allowed:
                return True, False
        return True, True

    def requires_special_arg_parsing(self, cmd_name):
        cmd_list = self.modules.list_commands()
        for cmd in cmd_list:
            if cmd.name == cmd_name:
                return cmd.special_arg_parsing is not None
        return False

    def do_special_arg_parsing(self, cmd_name, full_cmd):
        cmd_list = self.modules.list_commands()
        for cmd in cmd_list:
            if cmd.name == cmd_name and cmd.special_arg_parsing is not None:
                return cmd.special_arg_parsing(full_cmd)
        return False

    def on_event(self, event, client):
        if ((not self.enabled or self.suspended_until > time.time()) and event.user.id not in self.owner_ids) \
                or not self.running:
            return

        watchers = self.modules.get_event_watchers()
        for w in watchers:
            w(event, client, self)
        
        if not (isinstance(event, MessagePosted) or isinstance(event, MessageEdited) or str(type(event)).find('Chatbot.MockEvent') > -1):
            return

        if event.user.id == self.client.get_me().id:
            return

        if isinstance(event, MessageEdited):
            message = Message(event.message.id, client)
        else:
            message = event.message
        content_source = message.content_source
        content = content_source

        fixed_font = is_fixed_font(content)
        if fixed_font:
            fixed_font = True
            content = fixed_font_to_normal(content)
        content = re.sub(r"^%s\s+" % self.prefix, self.prefix, content)
        content = re.sub("(^[^ \r\n]+)(\r?\n)", r"\1 ", content)
        if not fixed_font:
            stripped_content = re.sub(r"\s+", " ", content)
            stripped_content = stripped_content.strip()
        else:
            stripped_content = content
        parts = stripped_content.split(" ")
        if not parts[0].startswith(self.prefix):
            return

        cmd_args = stripped_content[len(self.prefix):]
        if self.requires_special_arg_parsing(cmd_args.split(" ")[0]):
            cmd_args = content[len(self.prefix):]
        output = self.command(cmd_args, message, event, 0)
        if output is not False and output is not None:
            output_with_reply = ":%i %s" % (message.id, output)
            if len(output_with_reply) > 500 and "\n" not in output_with_reply:
                message.reply("Output would be longer than 500 characters (the limit for single-line messages), so only the first 500 characters are posted now.")
                message.reply(output[:500 - (len(message.id) + 2)])
            else:
                message.reply(output, False)

    def command(self, cmd, msg, event, start):
        cmd_args = cmd.split(' ')
        cmd_name = cmd_args[0].lower()
        args = cmd_args[1:]
        exists, allowed = self.check_existence_and_chars(cmd_name, ' '.join(args))
        if not exists:
            return "Command not found."
        if not allowed:
            return "Command contains invalid characters."
        if self.requires_special_arg_parsing(cmd_name):
            args = self.do_special_arg_parsing(cmd_name, cmd)
            if args is False:
                return "Argument parsing failed."
        return self.modules.command(cmd_name, args, msg, event)

    def bot_stopping(self):
        on_stops = self.modules.get_on_stop_methods()
        for on_stop in on_stops:
            on_stop(self)
class GlobalVars:
    false_positives = []
    whitelisted_users = []
    blacklisted_users = []
    ignored_posts = []
    auto_ignored_posts = []
    startup_utc = datetime.utcnow().strftime("%H:%M:%S")
    latest_questions = []
    api_backoff_time = 0
    charcoal_room_id = "11540"
    meta_tavern_room_id = "89"
    socvr_room_id = "41570"
    blockedTime = {
        "all": 0,
        charcoal_room_id: 0,
        meta_tavern_room_id: 0,
        socvr_room_id: 0
    }

    experimental_reasons = []  # Don't widely report these
    non_socvr_reasons = []  # Don't report to SOCVR
    non_tavern_reasons = [  # Don't report in the Tavern
        "all-caps body",
        "all-caps answer",
        "repeating characters in body",
        "repeating characters in title",
        "repeating characters in answer",
        "few unique characters in body",
        "few unique characters in answer",
        "title has only one unique char",
        "phone number detected in title",
        "offensive body detected",
        "no whitespace in body",
        "no whitespace in answer",
    ]
    non_tavern_sites = ["stackoverflow.com"]

    parser = HTMLParser.HTMLParser()
    wrap = Client("stackexchange.com")
    wrapm = Client("meta.stackexchange.com")
    wrapso = Client("stackoverflow.com")
    privileged_users = {
        charcoal_room_id: [
            "117490",  # Normal Human
            "66258",  # Andy
            "31768",  # ManishEarth
            "103081",  # hichris123
            "73046",  # Undo
            "88521",  # ProgramFOX
            "59776",  # Doorknob
            "31465",  # Seth
            "88577",  # Santa Claus
            "34124",  # Andrew Leach
            "54229",  # apnorton
            "20459",  # S.L. Barth
            "32436",  # tchrist
            "30477",  # Brock Adams
            "58529",  # ferrybig
            "145208",  # Robert Longson
            "178825",  # Ms Yvette
            "171800",  # JAL
            "64978",  # PeterJ
            "125141",  # Jeffrey Bosboom
            "54902",  # bummi
            "135450",  # M.A.R.
            "145604",  # Quill
            "60548",  # rene
            "121401",  # michaelpri
            "116218",  # JamesENL
            "82927",  # Braiam
            "11606",  # bwDraco
            "19761",  # Ilmari Karonen
            "108271",  # Andrew T.
            "171054",  # Magisch
            "190011",  # Petter Friberg
            "165661",  # Tunaki
            "145086",  # Wai Ha Lee
            "137665",  # ByteCommander
            "147884",  # wythagoras
            "186395",  # Åna
            "193364",  # Ashish Ahuja
            "163686",  # Gothdo
            "145827",  # angussidney
            "244748",  # Supreme Leader SnokeDetector (angussidney's sock)
            "121520",  # ArtOfCode
            "244382",  # Lt. A. Code (ArtOfCode's sock to test things with)
            "137388",  # QPaysTaxes
            "212311",  # Ryan Bemrose
            "172397",  # Kyll
            "224538",  # FrankerZ
            "61202",  # OldSkool
            "56166",  # Jan Dvorak
            "133966",  # DavidPostill
            "22839",  # djsmiley2k
            "97389",  # Kaz Wolfe
            "144962",  # DJMcMayhem
            "139423",  # NobodyNada
            "62118",  # tripleee
            "130558",  # Registered User
            "128113",  # arda
            "164318",  # Glorfindel
            "175347",  # Floern
            "180274",  # Alexander O'Mara
            "158742"  # Rob
        ],
        meta_tavern_room_id: [
            "315433",  # Normal Human
            "244519",  # CRABOLO
            "244382",  # TGMCians
            "194047",  # Jan Dvorak
            "158100",  # rene
            "178438",  # Manishearth
            "237685",  # hichris123
            "215468",  # Undo
            "229438",  # ProgramFOX
            "180276",  # Doorknob
            "161974",  # Lynn Crumbling
            "186281",  # Andy
            "266094",  # Unihedro
            "245167",  # Infinite Recursion
            "230261",  # Jason C
            "213575",  # Braiam
            "241919",  # Andrew T.
            "203389",  # backwards-Seth
            "202832",  # Mooseman
            "160017",  # bwDraco
            "201151",  # bummi
            "188558",  # Frank
            "229166",  # Santa Claus
            "159034",  # Kevin Brown
            "203972",  # PeterJ
            "188673",  # Alexis King
            "258672",  # AstroCB
            "227577",  # Sam
            "255735",  # cybermonkey
            "279182",  # Ixrec
            "271104",  # James
            "220428",  # Qantas 94 Heavy
            "153355",  # tchrist
            "238426",  # Ed Cottrell
            "166899",  # Second Rikudo
            "287999",  # ASCIIThenANSI
            "208518",  # JNat
            "284141",  # michaelpri
            "260312",  # vaultah
            "244062",  # SouravGhosh
            "152859",  # Shadow Wizard
            "201314",  # apnorton
            "280934",  # M.A.Ramezani
            "200235",  # durron597
            "148310",  # Awesome Poodles / Brock Adams
            "168333",  # S.L. Barth
            "257207",  # Unikitty
            "244282",  # DroidDev
            "163250",  # Cupcake
            "298265",  # BoomsPlus
            "253560",  # josilber
            "244254",  # misterManSam
            "188189",  # Robert Longson
            "174699",  # Ilmari Karonen
            "202362",  # chmod 666 telkitty
            "289717",  # Quill
            "237813",  # bjb568
            "311345",  # Simon Klaver
            "171881",  # rekire
            "260388",  # Pandya
            "310756",  # Ms Yvette
            "262399",  # Jeffrey Bosboom
            "242209",  # JAL
            "280883",  # ByteCommander
            "302251",  # kos
            "262823",  # ArtOfCode
            "215067",  # Ferrybig
            "308386",  # Magisch
            "285368"  # angussidney
        ],
        socvr_room_id: [
            "1849664",  # Undo
            "2581872",  # hichris123
            "1198729",  # Manishearth
            "3717023",  # Normal Human aka 1999
            "2619912",  # ProgramFOX
            "578411",  # rene
            "1043380",  # gunr2171
            "2246344",  # Sam
            "2756409",  # TylerH
            "1768232",  # durron597
            "359284",  # Kevin Brown
            "258400",  # easwee
            "3622940",  # Unihedron
            "3204551",  # Deduplicator
            "4342498",  # NathanOliver
            "4639281",  # Tiny Giant
            "3093387",  # josilber
            "1652962",  # cimmanon
            "1677912",  # Mogsdad
            "656243",  # Lynn Crumbling
            "3933332",  # Rizier123
            "2422013",  # cybermonkey
            "3478852",  # Nisse Engström
            "2302862",  # Siguza
            "1324",  # Paul Roub
            "1743880",  # Tunaki
            "1663001",  # DavidG
            "2415822",  # JAL
            "4174897",  # Kyll
            "5299236",  # Kevin Guan
            "4050842",  # Thaillie
            "1816093",  # Drew
            "874188",  # Triplee
            "880772",  # approxiblue
            "1835379",  # Cerbrus
            "3956566",  # JamesENL
            "2357233",  # Ms Yvette
            "3155639",  # AlexanderOMara
            "462627",  # Praveen Kumar
            "4490559",  # intboolstring
            "1364007",  # Wai Ha Lee
            "1699210",  # bummi
            "563532",  # Rob
            "5389107",  # Magisch
            "4099593",  # bhargav-rao
            "1542723",  # Ferrybig
            "2025923",  # Tushar
            "5292302",  # Petter Friberg
            "792066",  # Braiam
            "5666987",  # Ian
            "3160466",  # ArtOfCode
            "5735775",  # Ashish Ahuja
            "3476191",  # Nobody Nada
            "2227743",  # Eric D
            "821878",  # Ryan Bemrose
            "1413395",  # Panta Rei
            "4875631",  # FrankerZ
            "2958086",  # Compass
            "499214",  # JanDvorak
            "5647260",  # Andrew L.
            "559745"  # Floern
        ]
    }

    code_privileged_users = None

    smokeDetector_user_id = {
        charcoal_room_id: "120914",
        meta_tavern_room_id: "266345",
        socvr_room_id: "3735529"
    }

    censored_committer_names = {"3f4ed0f38df010ce300dba362fa63a62": "Undo1"}

    commit = os.popen('git log --pretty=format:"%h" -n 1').read()
    commit_author = os.popen('git log --pretty=format:"%an" -n 1').read()

    if md5.new(commit_author).hexdigest() in censored_committer_names:
        commit_author = censored_committer_names[md5.new(
            commit_author).hexdigest()]

    commit_with_author = os.popen('git log --pretty=format:"%h (' +
                                  commit_author + ': *%s*)" -n 1').read()
    on_master = os.popen(
        "git rev-parse --abbrev-ref HEAD").read().strip() == "master"
    charcoal_hq = None
    tavern_on_the_meta = None
    socvr = None
    s = ""
    s_reverted = ""
    specialrooms = []
    apiquota = -1
    bodyfetcher = None
    se_sites = []
    users_chatting = {
        meta_tavern_room_id: [],
        charcoal_room_id: [],
        socvr_room_id: []
    }
    why_data = []
    why_data_allspam = []
    notifications = []
    listen_to_these_if_edited = []
    multiple_reporters = []
    api_calls_per_site = {}

    api_request_lock = threading.Lock()

    config = ConfigParser.RawConfigParser()

    if os.path.isfile('config'):
        config.read('config')
    else:
        config.read('config.ci')

    latest_smokedetector_messages = {
        meta_tavern_room_id: [],
        charcoal_room_id: [],
        socvr_room_id: []
    }

    # environ_or_none defined in helpers.py
    bot_name = environ_or_none("SMOKEDETECTOR_NAME") or "SmokeDetector"
    bot_repository = environ_or_none(
        "SMOKEDETECTOR_REPO") or "//github.com/Charcoal-SE/SmokeDetector"
    chatmessage_prefix = "[{}]({})".format(bot_name, bot_repository)

    site_id_dict = {}
    post_site_id_to_question = {}

    location = config.get("Config", "location")
    print location

    metasmoke_ws = None

    try:
        metasmoke_host = config.get("Config", "metasmoke_host")
        print metasmoke_host
    except ConfigParser.NoOptionError:
        metasmoke_host = None
        print "metasmoke host not found. Set it as metasmoke_host in the config file. See https://github.com/Charcoal-SE/metasmoke."

    try:
        metasmoke_key = config.get("Config", "metasmoke_key")
    except ConfigParser.NoOptionError:
        metasmoke_key = ""
        print "No metasmoke key found, which is okay if both are running on the same host"

    try:
        metasmoke_ws_host = config.get("Config", "metasmoke_ws_host")
    except ConfigParser.NoOptionError:
        metasmoke_ws_host = ""
        print "No metasmoke websocket host found, which is okay if you're anti-websocket"

    try:
        github_username = config.get("Config", "github_username")
        github_password = config.get("Config", "github_password")
    except ConfigParser.NoOptionError:
        github_username = None
        github_password = None
class GlobalVars:
    false_positives = []
    whitelisted_users = []
    blacklisted_users = []
    ignored_posts = []
    auto_ignored_posts = []
    startup_utc = datetime.utcnow().strftime("%H:%M:%S")
    latest_questions = []
    api_backoff_time = 0
    charcoal_room_id = "49820"
    meta_tavern_room_id = "89"
    socvr_room_id = "41570"
    blockedTime = {"all": 0, charcoal_room_id: 0, meta_tavern_room_id: 3.2e7, socvr_room_id: 3.2e7}

    experimental_reasons = []  # Don't widely report these
    non_socvr_reasons = []    # Don't report to SOCVR
    non_tavern_reasons = [    # Don't report in the Tavern
        "all-caps body",
        "all-caps answer",
        "repeating characters in body",
        "repeating characters in title",
        "repeating characters in answer",
        "few unique characters in body",
        "few unique characters in answer",
        "title has only one unique char",
        "phone number detected in title",
        "offensive body detected",
        "no whitespace in body",
        "no whitespace in answer",
    ]
    non_tavern_sites = ["stackoverflow.com"]

    parser = HTMLParser.HTMLParser()
    wrap = Client("stackexchange.com")
    wrapm = Client("meta.stackexchange.com")
    wrapso = Client("stackoverflow.com")
    privileged_users = {
        charcoal_room_id: [
            "73046",    # Undo
            "121520",   # ArtOfCode
            "164318",   # Glorfindel
            "56166",    # Jan Dvorak
            "68718",    # Wrzlprmft
            "62118",    # tripleee
            "145827"    # angussidney
        ],
        meta_tavern_room_id: [],
        socvr_room_id: []
    }

    code_privileged_users = None

    smokeDetector_user_id = {charcoal_room_id: "120914", meta_tavern_room_id: "266345",
                             socvr_room_id: "3735529"}

    censored_committer_names = {"3f4ed0f38df010ce300dba362fa63a62": "Undo1"}

    commit = os.popen('git log --pretty=format:"%h" -n 1').read()
    commit_author = os.popen('git log --pretty=format:"%an" -n 1').read()

    if md5.new(commit_author).hexdigest() in censored_committer_names:
        commit_author = censored_committer_names[md5.new(commit_author).hexdigest()]

    commit_with_author = os.popen('git log --pretty=format:"%h (' + commit_author + ': *%s*)" -n 1').read()
    on_master = os.popen("git rev-parse --abbrev-ref HEAD").read().strip() == "master"
    charcoal_hq = None
    tavern_on_the_meta = None
    socvr = None
    s = ""
    s_reverted = ""
    specialrooms = []
    apiquota = -1
    bodyfetcher = None
    se_sites = []
    users_chatting = {meta_tavern_room_id: [], charcoal_room_id: [], socvr_room_id: []}
    why_data = []
    why_data_allspam = []
    notifications = []
    listen_to_these_if_edited = []
    multiple_reporters = []
    api_calls_per_site = {}

    api_request_lock = threading.Lock()

    config = ConfigParser.RawConfigParser()

    if os.path.isfile('config'):
        config.read('config')
    else:
        config.read('config.ci')

    latest_smokedetector_messages = {meta_tavern_room_id: [], "49820": [], socvr_room_id: []}

    # environ_or_none defined in helpers.py
    bot_name = environ_or_none("SMOKEDETECTOR_NAME") or "SmokeDetector"
    bot_repository = environ_or_none("SMOKEDETECTOR_REPO") or "//github.com/Charcoal-SE/SmokeDetector"
    chatmessage_prefix = "[{}]({})".format(bot_name, bot_repository)

    site_id_dict = {}
    post_site_id_to_question = {}

    location = config.get("Config", "location")
    print location

    metasmoke_ws = None

    try:
        metasmoke_host = config.get("Config", "metasmoke_host")
        print metasmoke_host
    except ConfigParser.NoOptionError:
        metasmoke_host = None
        print "metasmoke host not found. Set it as metasmoke_host in the config file. See https://github.com/Charcoal-SE/metasmoke."

    try:
        metasmoke_key = config.get("Config", "metasmoke_key")
    except ConfigParser.NoOptionError:
        metasmoke_key = ""
        print "No metasmoke key found, which is okay if both are running on the same host"

    try:
        metasmoke_ws_host = config.get("Config", "metasmoke_ws_host")
    except ConfigParser.NoOptionError:
        metasmoke_ws_host = ""
        print "No metasmoke websocket host found, which is okay if you're anti-websocket"

    try:
        github_username = config.get("Config", "github_username")
        github_password = config.get("Config", "github_password")
    except ConfigParser.NoOptionError:
        github_username = None
        github_password = None
class GlobalVars:
    false_positives = []
    whitelisted_users = []
    blacklisted_users = []
    ignored_posts = []
    auto_ignored_posts = []
    startup_utc = datetime.utcnow().strftime("%H:%M:%S")
    latest_questions = []
    blockedTime = 0
    charcoal_room_id = "11540"
    meta_tavern_room_id = "89"
    socvr_room_id = "41570"
    site_filename = {"electronics.stackexchange.com": "ElectronicsGood.txt",
                     "gaming.stackexchange.com": "GamingGood.txt", "german.stackexchange.com": "GermanGood.txt",
                     "italian.stackexchange.com": "ItalianGood.txt", "math.stackexchange.com": "MathematicsGood.txt",
                     "spanish.stackexchange.com": "SpanishGood.txt", "stats.stackexchange.com": "StatsGood.txt"}

    experimental_reasons = ["Code block"]  # Don't widely report these

    parser = HTMLParser.HTMLParser()
    wrap = Client("stackexchange.com")
    wrapm = Client("meta.stackexchange.com")
    wrapso = Client("stackoverflow.com")
    privileged_users = {charcoal_room_id: ["117490",  # Normal Human
                                           "66258",  # Andy
                                           "31768",  # ManishEarth
                                           "103081",  # hichris123
                                           "73046",  # Undo
                                           "88521",  # ProgramFOX
                                           "59776",  # Doorknob
                                           "31465",  # Seth
                                           "88577",  # Santa Claus
                                           "34124",  # Andrew Leach
                                           "54229",  # apnorton
                                           "32436"],  # tchrist
                        meta_tavern_room_id: ["259867",  # Normal Human
                                              "244519",  # Roombatron5000
                                              "244382",  # TGMCians
                                              "194047",  # Jan Dvorak
                                              "158100",  # rene
                                              "178438",  # Manishearth
                                              "237685",  # hichris123
                                              "215468",  # Undo
                                              "229438",  # ProgramFOX
                                              "180276",  # Doorknob
                                              "161974",  # Lynn Crumbling
                                              "186281",  # Andy
                                              "266094",  # Unihedro
                                              "245167",  # Infinite Recursion (No.)
                                              "230261",  # Jason C
                                              "213575",  # Braiam
                                              "241919",  # Andrew T.
                                              "203389",  # backwards-Seth
                                              "202832",  # Mooseman
                                              "160017",  # DragonLord the Fiery
                                              "201151",  # bummi
                                              "188558",  # Frank
                                              "229166",  # Santa Claus
                                              "159034",  # Kevin Brown
                                              "203972",  # PeterJ
                                              "188673",  # Alexis King
                                              "258672",  # AstroCB
                                              "227577",  # Sam
                                              "255735",  # cybermonkey
                                              "279182",  # Ixrec
                                              "271104",  # James
                                              "220428",  # Qantas 94 Heavy
                                              "153355",  # tchrist
                                              "238426",  # Ed Cottrell
                                              "166899",  # Second Rikudo
                                              "287999",  # ASCIIThenANSI
                                              "208518",  # JNat
                                              "284141",  # michaelpri
                                              "260312",  # vaultah
                                              "244062",  # SouravGhosh
                                              "152859",  # Shadow Wizard
                                              "201314",  # apnorton
                                              "280934",  # M.A.Ramezani
                                              "200235"],  # durron597
                        socvr_room_id: [
                            "1849664",  # Undo
                            "2581872",  # hichris123
                            "1198729",  # Manishearth
                            "3717023",  # Normal Human aka 1999
                            "2619912",  # ProgramFOX
                            "578411",   # rene
                            "1043380",  # gunr2171
                            "2246344",  # Sam
                            "2756409",  # TylerH
                            "1768232",  # durron597
                            "359284",   # Kevin Brown
                            "258400",   # easwee
                            "3622940",  # Unihedron
                            "3204551",  # Deduplicator
                            "4342498",  # NathanOliver
                            "4639281"]  # Tiny Giant
                        }
    smokeDetector_user_id = {charcoal_room_id: "120914", meta_tavern_room_id: "266345",
                             socvr_room_id: "3735529"}

    censored_committer_names = {"3f4ed0f38df010ce300dba362fa63a62": "Undo1"}

    commit = os.popen('git log --pretty=format:"%h" -n 1').read()
    commit_author = os.popen('git log --pretty=format:"%cn" -n 1').read()

    if md5.new(commit_author).hexdigest() in censored_committer_names:
        commit_author = censored_committer_names[md5.new(commit_author).hexdigest()]

    commit_with_author = os.popen('git log --pretty=format:"%h (' + commit_author + ': *%s*)" -n 1').read()
    on_master = os.popen("git rev-parse --abbrev-ref HEAD").read().strip() == "master"
    charcoal_hq = None
    tavern_on_the_meta = None
    socvr = None
    s = ""
    s_reverted = ""
    specialrooms = []
    bayesian_testroom = None
    apiquota = -1
    bodyfetcher = None
    se_sites = []
    tavern_users_chatting = []
    frequent_sentences = []
    why_data = []

    config = ConfigParser.RawConfigParser()
    config.read('config')

    latest_smokedetector_messages = {meta_tavern_room_id: [], charcoal_room_id: [],
                                     socvr_room_id: []}

    location = config.get("Config", "location")
    print location
Exemple #9
0
class Chatbot:
    def __init__(self):
        self.room = None
        self.client = None
        self.privileged_users = []
        self.owners = []
        self.owner_name = ""
        self.chatbot_name = ""
        self.enabled = True
        self.running = True
        self.waiting_time = -1
        self.latest_word_id = -1
        self.current_word_to_reply_to = ""
        self.latest_words = []
        self.in_shadows_den = False
        self.translation_languages = [
            "auto", "en", "fr", "nl", "de", "he", "ru", "el", "pt", "es", "fi",
            "af", "sq", "ar", "hy", "az", "eu", "be", "bn", "bs", "bg", "ca",
            "ceb", "zh-CN", "hr", "cs", "da", "eo", "et", "tl", "gl", "ka",
            "gu", "ht", "ha", "hi", "hmn", "hu", "is", "ig", "id", "ga", "it",
            "ja", "jw", "kn", "km", "ko", "lo", "la", "lv", "lt", "mk", "ms"
            "mt", "mi", "mr", "mn", "ne", "no", "fa", "pl", "pa", "ro", "sr",
            "sk", "sl", "so", "sw", "sv", "ta", "te", "th", "tr", "uk", "ur",
            "vi", "cy", "yi", "yo", "zu"
        ]
        self.end_lang = None
        self.translation_chain_going_on = False
        self.translation_switch_going_on = False
        self.spell_manager = SpellManager()
        self.links = []
        self.link_explanations = []
        self.banned = {}
        self.site = ""
        self.msg_id_no_reply_found = -1
        self.owner_ids = []
        self.privileged_user_ids = []
        self.commands = {
            'translate': self.command_translate,
            'random': Commands.command_random,
            'randomint': Commands.command_randomint,
            'randomchoice': Commands.command_randomchoice,
            'shuffle': Commands.command_shuffle,
            'listcommands': self.command_listcommands,
            'help': self.command_help,
            'xkcdrandomnumber': Commands.command_xkcdrandomnumber,
            'xkcd': Commands.command_xkcd,
            'alive': Commands.command_alive,
            'utc': Commands.command_utc
        }
        self.shadows_den_specific_commands = {
            'time': self.command_time,
            'viewspells': self.command_viewspells,
            'link': self.command_link,
            'removelink': self.command_removelink,
            'addlinkexplanation': self.command_addlinkexplanation,
            'explainlink': self.command_explainlink,
            'removelinkexplanation': self.command_removelinkexplanation,
            'reply': self.command_reply,
            'showtime': self.command_showtime,
            'islink': self.command_islink,
            'latestword': self.command_latestword,
            'setlatestword': self.command_setlatestword,
            'continue': self.command_continue,
            'retry': self.command_retry,
            'rmword': self.command_rmword,
            'showlatest10': self.command_showlatest10
        }
        self.owner_commands = {
            'stop': self.command_stop,
            'disable': self.command_disable,
            'enable': self.command_enable,
            'award': self.command_award,
            'emptyqueue': self.command_emptyqueue,
            'ban': self.command_ban,
            'unban': self.command_unban,
            'translationchain': self.command_translationchain,
            'translationswitch': self.command_translationswitch,
            'removespell': self.command_removespell
        }
        self.privileged_commands = {'delete': self.command_delete}

    def main(self, config_data, additional_general_config):
        if "owners" in Config.General:
            self.owners = Config.General["owners"]
        else:
            sys.exit("Error: no owners found. Please update Config.py.")
        if "privileged_users" in config_data:
            self.privileged_users = config_data["privileged_users"]
        if "owner_name" in Config.General:
            self.owner_name = Config.General["owner_name"]
        else:
            sys.exit("Error: no owner name found. Please update Config.py.")
        if "chatbot_name" in Config.General:
            self.chatbot_name = Config.General["chatbot_name"]
        else:
            sys.exit("Error: no chatbot name found. Please update Config.py.")
        # self.setup_logging() # if you want to have logging, un-comment this line
        self.spell_manager.init()
        if "in_shadows_den" in config_data:
            self.in_shadows_den = config_data["in_shadows_den"]
            print("In Shadow's Den: %s" % self.in_shadows_den)
        else:
            in_den = raw_input(
                "Does the bot run in Shadow's Den? (y/n) ").lower()
            if in_den == "y":
                self.in_shadows_den = True
            elif in_den == "n":
                self.in_shadows_den = False
            else:
                self.in_shadows_den = False
                print("Invalid input; assumed 'no'")
        if "site" in config_data:
            self.site = config_data["site"]
            print("Site: %s" % self.site)
        else:
            self.site = raw_input("Site: ")
        for o in self.owners:
            if self.site in o:
                self.owner_ids.append(o[self.site])
        if len(self.owner_ids) < 1:
            sys.exit("Error: no owners found for this site: %s." % self.site)
        for p in self.privileged_users:
            if self.site in p:
                self.privileged_user_ids.append(p[self.site])
        if "room" in config_data:
            room_number = config_data["room"]
            print("Room number: %i" % room_number)
        else:
            room_number = int(raw_input("Room number: "))
        if "email" in Config.General:
            email = Config.General["email"]
        elif "email" in additional_general_config:
            email = additional_general_config["email"]
        else:
            email = raw_input("Email address: ")
        if "password" in Config.General:  # I would not recommend to store the password in Config.py
            password = Config.General["password"]
        elif "password" in additional_general_config:
            password = additional_general_config["password"]
        else:
            password = getpass.getpass("Password: "******"config.txt"
        ):  # config.txt is for values that can change at runtime, Config.py is for static data
            f = open("config.txt", "r")
            self.waiting_time = int(f.read())
            f.close()
        else:
            f = open("config.txt", "w")
            f.write("20")
            f.close()

        if os.path.isfile("linkedWords.txt"):
            with open("linkedWords.txt", "r") as f:
                self.links = pickle.load(f)
        if os.path.isfile("bannedUsers.txt"):
            with open("bannedUsers.txt", "r") as f:
                self.banned = pickle.load(f)
        if os.path.isfile("linkExplanations.txt"):
            with open("linkExplanations.txt", "r") as f:
                self.link_explanations = pickle.load(f)

        self.client = Client(self.site)
        self.client.login(email, password)

        self.spell_manager.c = self.client
        self.spell_manager.bot_user_id = self.client.get_me().id

        self.room = self.client.get_room(room_number)
        self.room.join()
        bot_message = "Bot started with waiting time set to %i seconds." % self.waiting_time if self.in_shadows_den else "Bot started."
        self.room.send_message(bot_message)
        self.room.watch_socket(self.on_event)

        thread.start_new_thread(self.scheduled_empty_queue, ())

        while self.running:
            inputted = raw_input("<< ")
            if inputted.strip() == "":
                continue
            if inputted.startswith("$") and len(inputted) > 2:
                command_in = inputted[2:]
                command_out = self.command(command_in, None, None)
                if command_out != False and command_out is not None:
                    print command_out
                    if inputted[1] == "+":
                        self.room.send_message("%s" % command_out)
            else:
                self.room.send_message(inputted)

    def setup_logging(
            self):  # logging method taken from ChatExchange/examples/chat.py
        logger = logging.getLogger(__name__)
        logging.basicConfig(level=logging.INFO)
        logger.setLevel(logging.DEBUG)

        # In addition to the basic stderr logging configured globally
        # above, we'll use a log file for chatexchange.client.
        wrapper_logger = logging.getLogger('chatexchange.client')
        wrapper_handler = logging.handlers.TimedRotatingFileHandler(
            filename='client.log',
            when='midnight',
            delay=True,
            utc=True,
            backupCount=7,
        )
        wrapper_handler.setFormatter(
            logging.Formatter(
                "%(asctime)s: %(levelname)s: %(threadName)s: %(message)s"))
        wrapper_logger.addHandler(wrapper_handler)

    def scheduled_empty_queue(self):
        while self.running:
            time.sleep(15 * 60)
            awarded = self.spell_manager.empty_queue()
            for s in awarded:
                if self.room is not None and s != "This spell was already awarded."\
                        and s is not False:
                    self.room.send_message(s)
                else:
                    print s

    def reply_word(self, message, wait, orig_word):
        if orig_word in self.latest_words:
            message.reply("That word is already said in the latest 10 words. "
                          "Please use another. (In case I'm mistaken, "
                          "run `>>rmword %s` and then `>>reply %s`)" %
                          (orig_word, message.id))
            return
        self.current_word_to_reply_to = orig_word
        if wait and self.waiting_time > 0:
            time.sleep(self.waiting_time)
        if self.current_word_to_reply_to != orig_word:
            return
        word_tuple = self.find_associated_word(orig_word, message)
        word = word_tuple[0]
        word_found = word_tuple[1]
        if word is None and not word_found:
            self.room.send_message("No associated word found for %s." %
                                   orig_word)
            self.msg_id_no_reply_found = message.id
        elif word is None and word_found:
            self.room.send_message(
                "Associated words found for %s, but all of them have been posted in the latest 10 messages."
                % orig_word)
            self.msg_id_no_reply_found = -1
        else:
            self.msg_id_no_reply_found = -1
            message.reply(word)

    def on_event(self, event, client):
        if self.in_shadows_den and self.enabled:
            self.spell_manager.check_spells(event)
        should_return = False
        if not self.enabled:
            should_return = True
        if isinstance(event, MessagePosted) and (
                not self.enabled
        ) and event.user.id in self.owner_ids and event.message.content.startswith(
                "&gt;&gt;"):
            should_return = False
        if not self.running:
            should_return = True
        if not isinstance(event, MessagePosted):
            should_return = True
        if isinstance(event, MessagePosted) and self.site in self.banned \
                and event.user.id in self.banned[self.site]:
            should_return = True
        if should_return:
            return

        message = event.message
        h = HTMLParser()
        content = h.unescape(message.content_source)

        if event.user.id == self.client.get_me().id:
            if self.in_shadows_den and re.compile(
                    r"^:\d+ [a-zA-Z0-9-]+$").search(content):
                self.current_word_to_reply_to = content.split(" ")[1]
                self.latest_word_id = message.id
            return

        content = re.sub(r"^>>\s+", ">>", content)
        if not content.startswith(">>translat") and not content.startswith(
                ">>addlinkexplanation"):
            content = re.sub(r"([:;][-']?[)/(DPdpoO\[\]\\|])", "",
                             content)  # strip smilies
            content = re.sub(r"\[(.+?)\]\(.+?\)", r"\1", content)
            content = re.sub(r"\(.+?\)", "", content)
        content = re.sub(r"\s+", " ", content)
        content = content.strip()
        parts = content.split(" ")
        if (not parts[0].startswith(">>")) and (
                len(parts) != 2
                or not parts[0].startswith(":")) and (event.user.id != -2):
            return

        if len(parts) == 2 and parts[1] == "!delete!" and parts[0].startswith(
                ":"):
            try:
                if event.user.id in self.privileged_user_ids or event.user.id in self.owner_ids:
                    msg_id_to_delete = int(parts[0][1:])
                    self.client.get_message(msg_id_to_delete).delete()
            except:
                pass

        if self.in_shadows_den and parts[0].startswith(":") and re.compile(
                "^:([0-9]+)$").search(parts[0]):
            c = parts[1]
            if re.compile("[^a-zA-Z0-9-]").search(c):
                return
            self.latest_word_id = message.id
            thread.start_new_thread(self.reply_word, (message, True, c))
            return

        if parts[0].startswith(">>"):
            cmd_args = content[2:]
            if (not cmd_args.startswith("translat")) and (
                    not cmd_args.startswith("addlinkexplanation")
            ) and event.user.id not in self.owner_ids and re.compile(
                    "[^a-zA-Z0-9 _-]").search(cmd_args):
                message.reply("Command contains invalid characters.")
                return
            elif cmd_args.startswith(
                    "addlinkexplanation"
            ):  #and event.user.id not in self.owner_ids:
                parts = cmd_args.split(" ")
                if len(parts) != 4:
                    message.reply("3 arguments expected")
                    return
                arg_one = cmd_args.split(" ")[1]
                arg_two = cmd_args.split(" ")[2]
                if re.compile("[^a-zA-Z0-9 _-]").search(arg_one) or re.compile(
                        "[^a-zA-Z0-9 _-]").search(arg_two):
                    message.reply(
                        "Argument one and two contain invalid characters.")
                    return
            output = self.command(cmd_args, message, event)
            if output != False and output is not None:
                if len(output) > 500:
                    message.reply(
                        "Output would be longer than 500 characters (the limit), so only the first 500 characters are posted now."
                    )
                    self.room.send_message(output[:500])
                else:
                    message.reply(output)

    def find_associated_word(self, word, message):
        latest_words_no_save = self.latest_words[:]
        latest_words_no_save.append(word.lower())
        # Create a temp list. Adding the word to the list of the class
        # should only happen if an associated word is found.
        get_word = GetAssociatedWord(word, latest_words_no_save)
        word_to_reply = get_word[0]
        word_found = get_word[1]
        if word_to_reply is None:
            found_links = self.find_links(word)
            valid_found_links = []
            if len(found_links) > 0:
                word_found = True
            for link in found_links:
                if not link in self.latest_words:
                    valid_found_links.append(link)
            if len(valid_found_links) > 0:
                word_to_reply = random.choice(valid_found_links)
        if word_to_reply is not None:
            self.add_word_to_latest_words(word)
            self.add_word_to_latest_words(word_to_reply)
        return (word_to_reply, word_found)

    def add_word_to_latest_words(self, word):
        self.latest_words.insert(0, word.lower())
        if len(self.latest_words) > 10:
            self.latest_words.pop()

    def command(self, cmd, msg, event):
        cmd_args = cmd.split(' ')
        cmd_name = cmd_args[0].lower()
        args = cmd_args[1:]
        if cmd_name == "translationchain" or cmd_name == "translationswitch":
            to_translate = " ".join(args[3:])
            args = args[:3]
            args.append(to_translate)
        elif cmd_name == "translate":
            to_translate = " ".join(args[2:])
            args = args[:2]
            args.append(to_translate)
        commands_to_use = self.commands.copy()
        if self.in_shadows_den:
            commands_to_use.update(self.shadows_den_specific_commands)
        if cmd_name in commands_to_use:
            return commands_to_use[cmd_name](args, msg, event)

        elif cmd_name in self.owner_commands:
            if msg is None or event.user.id in self.owner_ids:
                return self.owner_commands[cmd_name](args, msg, event)
            else:
                return "You don't have the privilege to execute this command."
        elif cmd_name in self.privileged_commands:
            if msg is None or event.user.id in self.privileged_user_ids or event.user.id in self.owner_ids:
                return self.privileged_commands[cmd_name](args, msg, event)
            else:
                return "You don't have the privilege to execute this command."
        else:
            return "Command not found."

    def command_time(self, args, msg, event):
        if len(args) > 0:
            try:
                new_time = int(args[0])
                if new_time > 600:
                    return "Waiting time cannot be greater than 10 minutes (= 600 seconds)."
                if new_time > -1:
                    self.waiting_time = new_time
                    f = open("config.txt", "w")
                    f.write(str(self.waiting_time))
                    f.close()
                    return "Waiting time set to %s %s." % (args[0], (
                        "seconds" if new_time != 1 else "second"))
                else:
                    return "Given argument has to be a positive integer."
            except ValueError:
                return "Given argument is not a valid integer."
        else:
            return "Command does not have enough arguments."

    def command_latestword(self, args, msg, event):
        lwi = self.latest_word_id
        if lwi != -1:
            return "http://chat.meta.stackexchange.com/transcript/message/%s#%s" % (
                lwi, lwi)
        else:
            return "I don't know."

    def command_setlatestword(self, args, msg, event):
        if len(args) != 1:
            return "1 argument expected, %i given" % (len(args), )
        try:
            new_lwi = int(args[0])
            self.latest_word_id = new_lwi
            return "Latest word set."
        except ValueError:
            return "Given argument is not an integer."

    def command_showlatest10(self, args, msg, event):
        l = len(self.latest_words)
        return "Latest %s %s: %s" % (l, "words" if l != 1 else "word",
                                     ", ".join(self.latest_words))

    def command_rmword(self, args, msg, event):
        if len(args) != 1:
            return "1 argument expected, %i given" % (len(args), )
        word = args[0]
        if word in self.latest_words:
            self.latest_words = filter(lambda l: l != word, self.latest_words)
            return "Word removed from latest words."
        else:
            return "Word not in the list of latest words."

    def command_showtime(self, args, msg, event):
        return "Waiting time: %i seconds." % self.waiting_time

    def command_stop(self, args, msg, event):
        self.enabled = False
        self.running = False
        if msg is not None:
            msg.reply("Bot terminated.")
            time.sleep(2)
        self.room.leave()
        self.client.logout()
        time.sleep(5)
        os._exit(0)

    def command_disable(self, args, msg, event):
        self.enabled = False
        return "Bot disabled, run >>enable to enable it again."

    def command_enable(self, args, msg, event):
        self.enabled = True
        return "Bot enabled."

    def command_award(self, args, msg, event):
        if len(args) < 3:
            return "Not enough arguments."
        try:
            spell_id = int(args[0])
            user_id = int(args[1])
        except ValueError:
            return "Not a valid id."
        if args[2] == "-n":
            add_to_queue = False
        elif args[2] == "-q":
            add_to_queue = True
        else:
            return "Invalid arguments."
        return self.spell_manager.award(spell_id, user_id, add_to_queue)

    def command_removespell(self, args, msg, event):
        self.spell_manager.remove(int(args[1]), int(args[0]))
        return "Spell removed (un-awarded)."

    def command_viewspells(self, args, msg, event):
        if len(args) < 1:
            return "Not enough arguments."
        try:
            user_id = int(args[0])
        except ValueError:
            return "Invalid arguments."
        try:
            spells = self.spell_manager.view_spells(user_id)
            return spells
        except:
            return "An error occurred."

    def command_emptyqueue(self, args, msg, event):
        awarded = self.spell_manager.empty_queue()
        for s in awarded:
            if self.room is not None:
                self.room.send_message(s)
            else:
                print s

    def command_ban(self, args, msg, event):
        try:
            banned_user = int(args[0])
        except ValueError:
            return "Invalid arguments."
        try:
            user_name = self.client.get_user(banned_user).name.replace(" ", "")
        except:
            return "Could not fetch user; please check whether the user exists."
        if not self.site in self.banned:
            self.banned[self.site] = []
        if not banned_user in self.banned[self.site]:
            self.banned[self.site].append(banned_user)
        else:
            return "Already banned."
        with open("bannedUsers.txt", "w") as f:
            pickle.dump(self.banned, f)
        return "User @%s has been banne.d." % user_name

    def command_unban(self, args, msg, event):
        try:
            banned_user = int(args[0])
        except ValueError:
            return "Invalid arguments."
        try:
            user_name = self.client.get_user(banned_user).name.replace(" ", "")
        except:
            return "Could not fetch user; please check whether the user exists."
        if not self.site in self.banned:
            return "Not banned."
        if not banned_user in self.banned[self.site]:
            return "Not banned."
        self.banned[self.site].remove(banned_user)
        with open("bannedUsers.txt", "w") as f:
            pickle.dump(self.banned, f)
        return "User @%s has been unbanned." % user_name

    def command_listcommands(self, args, msg, event):
        command_keys = self.commands.keys()
        if self.in_shadows_den:
            command_keys += self.shadows_den_specific_commands.keys()
        command_keys.sort()
        return "Commands: %s" % (", ".join(command_keys), )

    def command_help(self, args, msg, event):
        if len(args) == 0:
            return "I'm %s, %s's chatbot. You can find the source code [on GitHub](https://github.com/ProgramFOX/SE-Chatbot). You can get a list of all commands by running `>>listcommands`, or you can run `>>help command` to learn more about a specific command." % (
                self.chatbot_name, self.owner_name)
        command_to_look_up = args[0]
        if command_to_look_up in CommandHelp:
            return CommandHelp[command_to_look_up]
        elif command_to_look_up in self.commands or command_to_look_up in self.shadows_den_specific_commands or \
             command_to_look_up in self.owner_commands or command_to_look_up in self.privileged_commands:
            return "Command exists, but no help entry found."
        else:
            return "The command you want to look up, does not exist."

    def command_delete(self, args, msg, event):
        if len(args) == 0:
            return "Not enough arguments."
        try:
            message_id = int(args[0])
        except:
            return "Invalid arguments."
        message_to_delete = Message(message_id, self.client)
        try:
            message_to_delete.delete()
        except:
            pass

    def command_link(self, args, msg, event):
        if len(args) != 2:
            return "2 arguments expected, %i given." % len(args)
        if self.links_contain(
            (args[0].replace("_", " "), args[1].replace("_", " "))):
            return "Link is already added."
        self.links.append((args[0].replace("_",
                                           " "), args[1].replace("_", " ")))
        with open("linkedWords.txt", "w") as f:
            pickle.dump(self.links, f)
        return "Link added."

    def command_islink(self, args, msg, event):
        if len(args) != 2:
            return "2 arguments expected, %i given" % len(args)
        if self.links_contain(
            (args[0].replace("_", " "), args[1].replace("_", " "))):
            return "Yes, that's a manually added link."
        else:
            return "No, that's not a link."

    def removelinkexplanation(self, link):
        to_remove = []
        ret = False
        for exp in self.link_explanations:
            l = exp[0]
            if (l[0] == link[0] and l[1] == link[1]) or (l[0] == link[1]
                                                         and l[1] == link[0]):
                to_remove.append(exp)
                ret = True
        for r in to_remove:
            self.link_explanations.remove(r)
        with open("linkExplanations.txt", "w") as f:
            pickle.dump(self.link_explanations, f)
        return ret

    def command_addlinkexplanation(self, args, msg, event):
        if len(args) != 3:
            return "3 arguments expected, %i given" % len(args)
        w1 = args[0].replace("_", " ").lower()
        w2 = args[1].replace("_", " ").lower()
        self.removelinkexplanation((w1, w2))  # remove any older explanations
        if not self.links_contain((w1, w2)):
            return "That link does not exist."
        if re.compile(r"[^a-zA-Z0-9_%*/:.#()\[\]?&=-]").search(args[2]):
            return "Sorry, your explanation can only contain the chars `a-zA-Z_*%/:.#()[]-`."
        self.link_explanations.append(((w1, w2), args[2]))
        with open("linkExplanations.txt", "w") as f:
            pickle.dump(self.link_explanations, f)
        return "Explanation added."

    def command_explainlink(self, args, msg, event):
        if len(args) != 2:
            return "2 arguments expected, %i given" % len(args)
        w1 = args[0].replace("_", " ").lower()
        w2 = args[1].replace("_", " ").lower()
        if not self.links_contain((w1, w2)):
            return "Words not linked."
        for exp in self.link_explanations:
            link = exp[0]
            explanation = exp[1]
            if (link[0] == w1 and link[1] == w2) or (link[1] == w1
                                                     and link[0] == w2):
                return explanation
        return "No explanation found."

    def command_removelinkexplanation(self, args, msg, event):
        if len(args) != 2:
            return "2 argumens expected, %i given" % len(args)
        w1 = args[0].replace("_", " ").lower()
        w2 = args[1].replace("_", " ").lower()
        if self.removelinkexplanation((w1, w2)):
            return "Explanation removed."
        else:
            return "No explanation found to remove."

    def command_reply(self, args, msg, event):
        if len(args) < 1:
            return "Not enough arguments."
        try:
            msg_id_to_reply_to = int(args[0])
        except ValueError:
            if args[0] == "recent":
                msg_id_to_reply_to = self.msg_id_no_reply_found
            else:
                return "Invalid arguments."
            if msg_id_to_reply_to == -1:
                return "'recent' has a value of -1, which is not a valid message ID. Please provide an explicit ID."
        msg_to_reply_to = Message(msg_id_to_reply_to, self.client)
        content = msg_to_reply_to.content_source
        content = re.sub(r"([:;][-']?[)/(DPdpoO\[\]\\|])", "",
                         content)  # strip smilies
        content = re.sub(r"\[(.+?)\]\(.+?\)", r"\1", content)
        content = re.sub(r"\(.+?\)", "", content)
        content = re.sub(r"\s+", " ", content)
        content = content.strip()
        parts = content.split(" ")
        msg_does_not_qualify = "Message does not qualify as a message that belongs to the word association game."
        if len(parts) != 2:
            return msg_does_not_qualify
        if not re.compile("^:([0-9]+)$").search(parts[0]):
            return msg_does_not_qualify
        if re.compile("[^a-zA-Z0-9-]").search(parts[1]):
            return "Word contains invalid characters."
        self.reply_word(msg_to_reply_to, False, parts[1])
        return None

    def command_continue(self, args, msg, event):
        if len(args) != 2:
            return "2 arguments expected, %i given." % (len(args), )
        self.command_link(args, None, None)
        return self.command_reply(["recent"], None, None)

    def command_retry(self, args, msg, event):
        return self.command_reply(["recent"], None, None)

    def command_removelink(self, args, msg, event):
        if len(args) < 2:
            return "Not enough arguments."
        w1 = args[0].replace("_", " ").lower()
        w2 = args[1].replace("_", " ").lower()
        self.removelinkexplanation((w1, w2))
        return self.remove_link(args[0].replace("_", " "),
                                args[1].replace("_", " "))

    def links_contain(self, item):
        for link in self.links:
            lowercase_link = (link[0].lower(), link[1].lower())
            if item[0].lower() in lowercase_link and item[1].lower(
            ) in lowercase_link:
                return True
        return False

    def find_links(self, item):
        results = []
        for link in self.links:
            lowercase_link = (link[0].lower(), link[1].lower())
            lowercase_item = item.lower()
            if lowercase_item in lowercase_link:
                i = lowercase_link.index(lowercase_item)
                associated_index = 0 if i == 1 else 1
                results.append(link[associated_index])
        return results

    def remove_link(self, item0, item1):
        for i, link in enumerate(self.links):
            lowercase_link = (link[0].lower(), link[1].lower())
            if item0.lower() in lowercase_link and item1.lower(
            ) in lowercase_link:
                self.links.pop(i)
                with open("linkedWords.txt", "w") as f:
                    pickle.dump(self.links, f)
                return "Link removed."
        return "No link found."

    def command_translationchain(self, args, msg, event):
        if event.user.id not in self.owner_ids:
            return "The `translationchain` command is a command that posts many messages and it does not post all messages, and causes that some messages that have to be posted after the chain might not be posted, so it is an owner-only command now."
        if len(args) < 4:
            return "Not enough arguments."
        try:
            translation_count = int(args[0])
        except ValueError:
            return "Invalid arguments."
        if translation_count < 1:
            return "Invalid arguments."
        if not self.translation_chain_going_on:
            if not args[1] in self.translation_languages or not args[
                    2] in self.translation_languages:
                return "Language not in list. If the language is supported, ping ProgramFOX and he will add it."
            self.translation_chain_going_on = True
            thread.start_new_thread(
                self.translationchain,
                (args[3], args[1], args[2], translation_count))
            return "Translation chain started. Translation made by [Google Translate](https://translate.google.com). Some messages in the chain might not be posted due to a reason I don't know."
        else:
            return "There is already a translation chain going on."

    def command_translationswitch(self, args, msg, event):
        if event.user.id not in self.owner_ids:
            return "The `translationswitch` command is a command that posts many messages and it does not post all messages, and causes that some messages that have to be posted after the chain might not be posted, so it is an owner-only command now."
        if self.translation_switch_going_on:
            return "There is already a translation switch going on."
        if len(args) < 4:
            return "Not enough arguments."
        try:
            translation_count = int(args[0])
        except ValueError:
            return "Invalid arguments."
        if translation_count < 2:
            return "Invalid arguments."
        if (translation_count % 2) == 1:
            return "Translation count has to be an even number."
        if not args[1] in self.translation_languages or not args[
                2] in self.translation_languages:
            return "Language not in list. If the language is supported, ping ProgramFOX and he will add it."
        self.translation_switch_going_on = True
        thread.start_new_thread(self.translationswitch,
                                (args[3], args[1], args[2], translation_count))
        return "Translation switch started. Translation made by [Google Translate](https://translate.google.com). Some messages in the switch might not be posted due to a reason I don't know."

    def command_translate(self, args, msg, event):
        if len(args) < 3:
            return "Not enough arguments."
        if args[0] == args[1]:
            return "There's no point in having the same input language as output language."
        if not args[0] in self.translation_languages or not args[
                1] in self.translation_languages:
            return "Language not in list. If the language is supported, ping ProgramFOX and he will add it."
        return self.translate(args[2], args[0], args[1])

    def translationchain(self, text, start_lang, end_lang, translation_count):
        i = 0
        curr_lang = start_lang
        next_lang = None
        curr_text = text
        choices = list(self.translation_languages)
        if start_lang == end_lang:
            choices.remove(start_lang)
        else:
            choices.remove(start_lang)
            choices.remove(end_lang)
        while i < translation_count - 1:
            if next_lang is not None:
                curr_lang = next_lang
            while True:
                next_lang = random.choice(choices)
                if next_lang != curr_lang:
                    break
            result = self.translate(curr_text, curr_lang, next_lang)
            curr_text = result
            self.room.send_message("Translate %s-%s: %s" %
                                   (curr_lang, next_lang, result))
            i += 1
        final_result = self.translate(curr_text, next_lang, end_lang)
        self.room.send_message("Final translation result (%s-%s): %s" %
                               (next_lang, end_lang, final_result))
        self.translation_chain_going_on = False

    def translationswitch(self, text, lang1, lang2, translation_count):
        i = 1
        curr_text = text
        while i <= translation_count:
            if (i % 2) == 0:
                lang_order = (lang2, lang1)
            else:
                lang_order = (lang1, lang2)
            curr_text = self.translate(curr_text, lang_order[0], lang_order[1])
            msg_text = "Translate %s-%s: %s" if i != translation_count else "Final result (%s-%s): %s"
            self.room.send_message(msg_text % (lang_order + (curr_text, )))
            i += 1
        self.translation_switch_going_on = False

    def translate(self, text, start_lang, end_lang):
        translate_url = "https://translate.google.com/translate_a/single?client=t&sl=%s&tl=%s&hl=en&dt=bd&dt=ex&dt=ld&dt=md&dt=qc&dt=rw&dt=rm&dt=ss&dt=t&dt=at&dt=sw&ie=UTF-8&oe=UTF-8&prev=btn&srcrom=1&ssel=0&tsel=0&q=%s" % (
            start_lang, end_lang, urllib.quote_plus(text.encode("utf-8")))
        r = requests.get(translate_url)
        unparsed_json = r.text.split("],[\"\",,", 1)[0].split("]]", 1)[0][3:]
        return self.parse(unparsed_json)

    def parse(self, json):
        is_open = False
        is_backslash = False
        is_translation = True
        all_str = []
        curr_str = []
        for c in json:
            if c != '"' and not is_open:
                continue
            elif c == '"' and not is_open:
                is_open = True
            elif c == '\\':
                is_backslash = not is_backslash
                if is_translation:
                    curr_str.append(c)
            elif c == '"' and is_open and not is_backslash:
                is_open = False
                if is_translation:
                    s = "".join(curr_str).replace("\\\\",
                                                  "\\").replace("\\\"", "\"")
                    all_str.append(s)
                curr_str = []
                is_backslash = False
                is_translation = not is_translation
            else:
                is_backslash = False
                if is_translation:
                    curr_str.append(c)
        return " ".join(all_str)
Exemple #10
0
    def main(self, config_data, additional_general_config):
        if "owners" in Config.General:
            self.owners = Config.General["owners"]
        else:
            sys.exit("Error: no owners found. Please update Config.py.")
        if "privileged_users" in config_data:
            self.privileged_users = config_data["privileged_users"]
        if "owner_name" in Config.General:
            self.owner_name = Config.General["owner_name"]
        else:
            sys.exit("Error: no owner name found. Please update Config.py.")
        if "chatbot_name" in Config.General:
            self.chatbot_name = Config.General["chatbot_name"]
        else:
            sys.exit("Error: no chatbot name found. Please update Config.py.")
        # self.setup_logging() # if you want to have logging, un-comment this line
        self.spell_manager.init()
        if "in_shadows_den" in config_data:
            self.in_shadows_den = config_data["in_shadows_den"]
            print("In Shadow's Den: %s" % self.in_shadows_den)
        else:
            in_den = raw_input(
                "Does the bot run in Shadow's Den? (y/n) ").lower()
            if in_den == "y":
                self.in_shadows_den = True
            elif in_den == "n":
                self.in_shadows_den = False
            else:
                self.in_shadows_den = False
                print("Invalid input; assumed 'no'")
        if "site" in config_data:
            self.site = config_data["site"]
            print("Site: %s" % self.site)
        else:
            self.site = raw_input("Site: ")
        for o in self.owners:
            if self.site in o:
                self.owner_ids.append(o[self.site])
        if len(self.owner_ids) < 1:
            sys.exit("Error: no owners found for this site: %s." % self.site)
        for p in self.privileged_users:
            if self.site in p:
                self.privileged_user_ids.append(p[self.site])
        if "room" in config_data:
            room_number = config_data["room"]
            print("Room number: %i" % room_number)
        else:
            room_number = int(raw_input("Room number: "))
        if "email" in Config.General:
            email = Config.General["email"]
        elif "email" in additional_general_config:
            email = additional_general_config["email"]
        else:
            email = raw_input("Email address: ")
        if "password" in Config.General:  # I would not recommend to store the password in Config.py
            password = Config.General["password"]
        elif "password" in additional_general_config:
            password = additional_general_config["password"]
        else:
            password = getpass.getpass("Password: "******"config.txt"
        ):  # config.txt is for values that can change at runtime, Config.py is for static data
            f = open("config.txt", "r")
            self.waiting_time = int(f.read())
            f.close()
        else:
            f = open("config.txt", "w")
            f.write("20")
            f.close()

        if os.path.isfile("linkedWords.txt"):
            with open("linkedWords.txt", "r") as f:
                self.links = pickle.load(f)
        if os.path.isfile("bannedUsers.txt"):
            with open("bannedUsers.txt", "r") as f:
                self.banned = pickle.load(f)
        if os.path.isfile("linkExplanations.txt"):
            with open("linkExplanations.txt", "r") as f:
                self.link_explanations = pickle.load(f)

        self.client = Client(self.site)
        self.client.login(email, password)

        self.spell_manager.c = self.client
        self.spell_manager.bot_user_id = self.client.get_me().id

        self.room = self.client.get_room(room_number)
        self.room.join()
        bot_message = "Bot started with waiting time set to %i seconds." % self.waiting_time if self.in_shadows_den else "Bot started."
        self.room.send_message(bot_message)
        self.room.watch_socket(self.on_event)

        thread.start_new_thread(self.scheduled_empty_queue, ())

        while self.running:
            inputted = raw_input("<< ")
            if inputted.strip() == "":
                continue
            if inputted.startswith("$") and len(inputted) > 2:
                command_in = inputted[2:]
                command_out = self.command(command_in, None, None)
                if command_out != False and command_out is not None:
                    print command_out
                    if inputted[1] == "+":
                        self.room.send_message("%s" % command_out)
            else:
                self.room.send_message(inputted)
    def main(self, config_data, additional_general_config):
        if "owners" in Config.General:
            self.owners = Config.General["owners"]
        else:
            sys.exit("Error: no owners found. Please update Config.py.")
        if "privileged_users" in config_data:
            self.privileged_users = config_data["privileged_users"]
        if "owner_name" in Config.General:
            self.owner_name = Config.General["owner_name"]
        else:
            sys.exit("Error: no owner name found. Please update Config.py.")
        if "chatbot_name" in Config.General:
            self.chatbot_name = Config.General["chatbot_name"]
        else:
            sys.exit("Error: no chatbot name found. Please update Config.py.")
        # self.setup_logging() # if you want to have logging, un-comment this line
        self.spell_manager.init()
        if "in_shadows_den" in config_data:
            self.in_shadows_den = config_data["in_shadows_den"]
            print("In Shadow's Den: %s" % self.in_shadows_den)
        else:
            in_den = raw_input("Does the bot run in Shadow's Den? (y/n) ").lower()
            if in_den == "y":
                self.in_shadows_den = True
            elif in_den == "n":
                self.in_shadows_den = False
            else:
                self.in_shadows_den = False
                print("Invalid input; assumed 'no'")
        if "site" in config_data:
            self.site = config_data["site"]
            print("Site: %s" % self.site)
        else:
            self.site = raw_input("Site: ")
        for o in self.owners:
            if self.site in o:
                self.owner_ids.append(o[self.site])
        if len(self.owner_ids) < 1:
            sys.exit("Error: no owners found for this site: %s." % self.site)
        for p in self.privileged_users:
            if self.site in p:
                self.privileged_user_ids.append(p[self.site])
        if "room" in config_data:
            room_number = config_data["room"]
            print("Room number: %i" % room_number)
        else:
            room_number = int(raw_input("Room number: "))
        if "email" in Config.General:
            email = Config.General["email"]
        elif "email" in additional_general_config:
            email = additional_general_config["email"]
        else:
            email = raw_input("Email address: ")
        if "password" in Config.General: # I would not recommend to store the password in Config.py
            password = Config.General["password"]
        elif "password" in additional_general_config:
            password = additional_general_config["password"]
        else:
            password = getpass.getpass("Password: "******"config.txt"): # config.txt is for values that can change at runtime, Config.py is for static data
            f = open("config.txt", "r")
            self.waiting_time = int(f.read())
            f.close()
        else:
            f = open("config.txt", "w")
            f.write("20")
            f.close()
            
        if os.path.isfile("linkedWords.txt"):
            with open("linkedWords.txt", "r") as f:
                self.links = pickle.load(f)
        if os.path.isfile("bannedUsers.txt"):
            with open("bannedUsers.txt", "r") as f:
                self.banned = pickle.load(f)
        if os.path.isfile("linkExplanations.txt"):
            with open("linkExplanations.txt", "r") as f:
                self.link_explanations = pickle.load(f)

        self.client = Client(self.site)
        self.client.login(email, password)
        
        self.spell_manager.c = self.client
        self.spell_manager.bot_user_id = self.client.get_me().id
    
        self.room = self.client.get_room(room_number)
        self.room.join()
        bot_message = "Bot started with waiting time set to %i seconds." % self.waiting_time if self.in_shadows_den else "Bot started."
        self.room.send_message(bot_message)
        self.room.watch_socket(self.on_event)
            
        thread.start_new_thread(self.scheduled_empty_queue, ())
        
        while self.running:
            inputted = raw_input("<< ")
            if inputted.strip() == "":
                continue
            if inputted.startswith("$") and len(inputted) > 2:
                command_in = inputted[2:]
                command_out = self.command(command_in, None, None)
                if command_out != False and command_out is not None:
                    print command_out
                    if inputted[1] == "+":
                        self.room.send_message("%s" % command_out)
            else:
                self.room.send_message(inputted)
class Chatbot:
    def __init__(self):
        self.room = None
        self.client = None
        self.privileged_users = []
        self.owners = []
        self.owner_name = ""
        self.chatbot_name = ""
        self.enabled = True
        self.running = True
        self.waiting_time = -1
        self.latest_word_id = -1
        self.current_word_to_reply_to = ""
        self.latest_words = []
        self.in_shadows_den = False
        self.translation_languages = ["auto", "en", "fr", "nl", "de", "he", "ru", "el", "pt", "es", "fi", "af", "sq", "ar", "hy", "az", "eu", "be", "bn", "bs", "bg", "ca", "ceb", "zh-CN", "hr", "cs", "da",
                                      "eo", "et", "tl", "gl", "ka", "gu", "ht", "ha", "hi", "hmn", "hu", "is", "ig", "id", "ga", "it", "ja", "jw", "kn", "km", "ko", "lo", "la", "lv", "lt", "mk", "ms"
                                      "mt", "mi", "mr", "mn", "ne", "no", "fa", "pl", "pa", "ro", "sr", "sk", "sl", "so", "sw", "sv", "ta", "te", "th", "tr", "uk", "ur", "vi", "cy", "yi", "yo", "zu"]
        self.end_lang = None
        self.translation_chain_going_on = False
        self.translation_switch_going_on = False
        self.spell_manager = SpellManager()
        self.links = []
        self.link_explanations = []
        self.banned = {}
        self.site = ""
        self.msg_id_no_reply_found = -1
        self.owner_ids = []
        self.privileged_user_ids = []
        self.commands = {
            'translate': self.command_translate,
            'random': Commands.command_random,
            'randomint': Commands.command_randomint,
            'randomchoice': Commands.command_randomchoice,
            'shuffle': Commands.command_shuffle,
            'listcommands': self.command_listcommands,
            'help': self.command_help,
            'xkcdrandomnumber': Commands.command_xkcdrandomnumber,
            'xkcd': Commands.command_xkcd,
            'alive': Commands.command_alive,
            'utc': Commands.command_utc
        }
        self.shadows_den_specific_commands = {
            'time': self.command_time,
            'viewspells': self.command_viewspells,
            'link': self.command_link,
            'removelink': self.command_removelink,
            'addlinkexplanation': self.command_addlinkexplanation,
            'explainlink': self.command_explainlink,
            'removelinkexplanation': self.command_removelinkexplanation,
            'reply': self.command_reply,
            'showtime': self.command_showtime,
            'islink': self.command_islink,
            'latestword': self.command_latestword,
            'setlatestword': self.command_setlatestword,
            'continue': self.command_continue,
            'retry': self.command_retry,
            'rmword': self.command_rmword,
            'showlatest10': self.command_showlatest10
        }
        self.owner_commands = {
            'stop': self.command_stop,
            'disable': self.command_disable,
            'enable': self.command_enable,
            'award': self.command_award,
            'emptyqueue': self.command_emptyqueue,
            'ban': self.command_ban,
            'unban': self.command_unban,
            'translationchain': self.command_translationchain,
            'translationswitch': self.command_translationswitch,
            'removespell': self.command_removespell
        }
        self.privileged_commands = {
            'delete': self.command_delete
        }

    def main(self, config_data, additional_general_config):
        if "owners" in Config.General:
            self.owners = Config.General["owners"]
        else:
            sys.exit("Error: no owners found. Please update Config.py.")
        if "privileged_users" in config_data:
            self.privileged_users = config_data["privileged_users"]
        if "owner_name" in Config.General:
            self.owner_name = Config.General["owner_name"]
        else:
            sys.exit("Error: no owner name found. Please update Config.py.")
        if "chatbot_name" in Config.General:
            self.chatbot_name = Config.General["chatbot_name"]
        else:
            sys.exit("Error: no chatbot name found. Please update Config.py.")
        # self.setup_logging() # if you want to have logging, un-comment this line
        self.spell_manager.init()
        if "in_shadows_den" in config_data:
            self.in_shadows_den = config_data["in_shadows_den"]
            print("In Shadow's Den: %s" % self.in_shadows_den)
        else:
            in_den = raw_input("Does the bot run in Shadow's Den? (y/n) ").lower()
            if in_den == "y":
                self.in_shadows_den = True
            elif in_den == "n":
                self.in_shadows_den = False
            else:
                self.in_shadows_den = False
                print("Invalid input; assumed 'no'")
        if "site" in config_data:
            self.site = config_data["site"]
            print("Site: %s" % self.site)
        else:
            self.site = raw_input("Site: ")
        for o in self.owners:
            if self.site in o:
                self.owner_ids.append(o[self.site])
        if len(self.owner_ids) < 1:
            sys.exit("Error: no owners found for this site: %s." % self.site)
        for p in self.privileged_users:
            if self.site in p:
                self.privileged_user_ids.append(p[self.site])
        if "room" in config_data:
            room_number = config_data["room"]
            print("Room number: %i" % room_number)
        else:
            room_number = int(raw_input("Room number: "))
        if "email" in Config.General:
            email = Config.General["email"]
        elif "email" in additional_general_config:
            email = additional_general_config["email"]
        else:
            email = raw_input("Email address: ")
        if "password" in Config.General: # I would not recommend to store the password in Config.py
            password = Config.General["password"]
        elif "password" in additional_general_config:
            password = additional_general_config["password"]
        else:
            password = getpass.getpass("Password: "******"config.txt"): # config.txt is for values that can change at runtime, Config.py is for static data
            f = open("config.txt", "r")
            self.waiting_time = int(f.read())
            f.close()
        else:
            f = open("config.txt", "w")
            f.write("20")
            f.close()
            
        if os.path.isfile("linkedWords.txt"):
            with open("linkedWords.txt", "r") as f:
                self.links = pickle.load(f)
        if os.path.isfile("bannedUsers.txt"):
            with open("bannedUsers.txt", "r") as f:
                self.banned = pickle.load(f)
        if os.path.isfile("linkExplanations.txt"):
            with open("linkExplanations.txt", "r") as f:
                self.link_explanations = pickle.load(f)

        self.client = Client(self.site)
        self.client.login(email, password)
        
        self.spell_manager.c = self.client
        self.spell_manager.bot_user_id = self.client.get_me().id
    
        self.room = self.client.get_room(room_number)
        self.room.join()
        bot_message = "Bot started with waiting time set to %i seconds." % self.waiting_time if self.in_shadows_den else "Bot started."
        self.room.send_message(bot_message)
        self.room.watch_socket(self.on_event)
            
        thread.start_new_thread(self.scheduled_empty_queue, ())
        
        while self.running:
            inputted = raw_input("<< ")
            if inputted.strip() == "":
                continue
            if inputted.startswith("$") and len(inputted) > 2:
                command_in = inputted[2:]
                command_out = self.command(command_in, None, None)
                if command_out != False and command_out is not None:
                    print command_out
                    if inputted[1] == "+":
                        self.room.send_message("%s" % command_out)
            else:
                self.room.send_message(inputted)

    def setup_logging(self): # logging method taken from ChatExchange/examples/chat.py
        logger = logging.getLogger(__name__)
        logging.basicConfig(level=logging.INFO)
        logger.setLevel(logging.DEBUG)

        # In addition to the basic stderr logging configured globally
        # above, we'll use a log file for chatexchange.client.
        wrapper_logger = logging.getLogger('chatexchange.client')
        wrapper_handler = logging.handlers.TimedRotatingFileHandler(
           filename='client.log',
            when='midnight', delay=True, utc=True, backupCount=7,
        )
        wrapper_handler.setFormatter(logging.Formatter(
            "%(asctime)s: %(levelname)s: %(threadName)s: %(message)s"
        ))
        wrapper_logger.addHandler(wrapper_handler)
                    
    def scheduled_empty_queue(self):
        while self.running:
            time.sleep(15 * 60)
            awarded = self.spell_manager.empty_queue()
            for s in awarded:
                if self.room is not None and s != "This spell was already awarded."\
                        and s is not False:
                    self.room.send_message(s)
                else:
                    print s
            
    def reply_word(self, message, wait, orig_word):
        if orig_word in self.latest_words:
            message.reply("That word is already said in the latest 10 words. "
                          "Please use another. (In case I'm mistaken, "
                          "run `>>rmword %s` and then `>>reply %s`)"
                          % (orig_word, message.id))
            return
        self.current_word_to_reply_to = orig_word
        if wait and self.waiting_time > 0:
            time.sleep(self.waiting_time)
        if self.current_word_to_reply_to != orig_word:
            return
        word_tuple = self.find_associated_word(orig_word, message)
        word = word_tuple[0]
        word_found = word_tuple[1]
        if word is None and not word_found:
            self.room.send_message("No associated word found for %s." % orig_word)
            self.msg_id_no_reply_found = message.id
        elif word is None and word_found:
            self.room.send_message("Associated words found for %s, but all of them have been posted in the latest 10 messages." % orig_word)
            self.msg_id_no_reply_found = -1
        else:
            self.msg_id_no_reply_found = -1
            message.reply(word)

    def on_event(self, event, client):
        if self.in_shadows_den and self.enabled:
            self.spell_manager.check_spells(event)
        should_return = False
        if not self.enabled:
            should_return = True
        if isinstance(event, MessagePosted) and (not self.enabled) and event.user.id in self.owner_ids and event.message.content.startswith("&gt;&gt;"):
            should_return = False
        if not self.running:
            should_return = True
        if not isinstance(event, MessagePosted):
            should_return = True
        if isinstance(event, MessagePosted) and self.site in self.banned \
                and event.user.id in self.banned[self.site]:
            should_return = True
        if should_return:
            return
        
        message = event.message
        h = HTMLParser()
        content = h.unescape(message.content_source)

        if event.user.id == self.client.get_me().id:
            if self.in_shadows_den and re.compile(r"^:\d+ [a-zA-Z0-9-]+$").search(content):
                self.current_word_to_reply_to = content.split(" ")[1]
                self.latest_word_id = message.id
            return

        content = re.sub(r"^>>\s+", ">>", content)
        if not content.startswith(">>translat") and not content.startswith(">>addlinkexplanation"):
            content = re.sub(r"([:;][-']?[)/(DPdpoO\[\]\\|])", "", content) # strip smilies
            content = re.sub(r"\[(.+?)\]\(.+?\)", r"\1", content)
            content = re.sub(r"\(.+?\)", "", content)
        content = re.sub(r"\s+", " ", content)
        content = content.strip()
        parts = content.split(" ")
        if (not parts[0].startswith(">>")) and (len(parts) != 2 or not parts[0].startswith(":")) and (event.user.id != -2):
            return
        
        if len(parts) == 2 and parts[1] == "!delete!" and parts[0].startswith(":"):
            try:
                if event.user.id in self.privileged_user_ids or event.user.id in self.owner_ids:
                    msg_id_to_delete = int(parts[0][1:])
                    self.client.get_message(msg_id_to_delete).delete()
            except:
                pass

        if self.in_shadows_den and parts[0].startswith(":") and re.compile("^:([0-9]+)$").search(parts[0]):
            c = parts[1]
            if re.compile("[^a-zA-Z0-9-]").search(c):
                return
            self.latest_word_id = message.id
            thread.start_new_thread(self.reply_word, (message, True, c))
            return
        
        if parts[0].startswith(">>"):
            cmd_args = content[2:]
            if (not cmd_args.startswith("translat")) and (not cmd_args.startswith("addlinkexplanation")) and event.user.id not in self.owner_ids and re.compile("[^a-zA-Z0-9 _-]").search(cmd_args):
                message.reply("Command contains invalid characters.")
                return
            elif cmd_args.startswith("addlinkexplanation"): #and event.user.id not in self.owner_ids:
                parts = cmd_args.split(" ")
                if len(parts) != 4:
                    message.reply("3 arguments expected")
                    return
                arg_one = cmd_args.split(" ")[1]
                arg_two = cmd_args.split(" ")[2]
                if re.compile("[^a-zA-Z0-9 _-]").search(arg_one) or re.compile("[^a-zA-Z0-9 _-]").search(arg_two):
                    message.reply("Argument one and two contain invalid characters.")
                    return
            output = self.command(cmd_args, message, event)
            if output != False and output is not None:
                if len(output) > 500:
                    message.reply("Output would be longer than 500 characters (the limit), so only the first 500 characters are posted now.")
                    self.room.send_message(output[:500])
                else:
                    message.reply(output)
            
    def find_associated_word(self, word, message):
            latest_words_no_save = self.latest_words[:]
            latest_words_no_save.append(word.lower())
            # Create a temp list. Adding the word to the list of the class
            # should only happen if an associated word is found.
            get_word = GetAssociatedWord(word, latest_words_no_save)
            word_to_reply = get_word[0]
            word_found = get_word[1]
            if word_to_reply is None:
                found_links = self.find_links(word)
                valid_found_links = []
                if len(found_links) > 0:
                    word_found = True
                for link in found_links:
                    if not link in self.latest_words:
                        valid_found_links.append(link)
                if len(valid_found_links) > 0:
                    word_to_reply = random.choice(valid_found_links)
            if word_to_reply is not None:
                self.add_word_to_latest_words(word)
                self.add_word_to_latest_words(word_to_reply)
            return (word_to_reply, word_found)
            
    def add_word_to_latest_words(self, word):
        self.latest_words.insert(0, word.lower())
        if len(self.latest_words) > 10:
            self.latest_words.pop()

    def command(self, cmd, msg, event):
        cmd_args = cmd.split(' ')
        cmd_name = cmd_args[0].lower()
        args = cmd_args[1:]
        if cmd_name == "translationchain" or cmd_name == "translationswitch":
            to_translate = " ".join(args[3:])
            args = args[:3]
            args.append(to_translate)
        elif cmd_name == "translate":
            to_translate = " ".join(args[2:])
            args = args[:2]
            args.append(to_translate)
        commands_to_use = self.commands.copy()
        if self.in_shadows_den:
            commands_to_use.update(self.shadows_den_specific_commands)
        if cmd_name in commands_to_use:
            return commands_to_use[cmd_name](args, msg, event)

        elif cmd_name in self.owner_commands:
            if msg is None or event.user.id in self.owner_ids:
                return self.owner_commands[cmd_name](args, msg, event)
            else:
                return "You don't have the privilege to execute this command."
        elif cmd_name in self.privileged_commands:
            if msg is None or event.user.id in self.privileged_user_ids or event.user.id in self.owner_ids:
                return self.privileged_commands[cmd_name](args, msg, event)
            else:
                return "You don't have the privilege to execute this command."
        else:
            return "Command not found."
    
    def command_time(self, args, msg, event):
        if len(args) > 0:
            try:
                new_time = int(args[0])
                if new_time > 600:
                    return "Waiting time cannot be greater than 10 minutes (= 600 seconds)."
                if new_time > -1:
                    self.waiting_time = new_time
                    f = open("config.txt", "w")
                    f.write(str(self.waiting_time))
                    f.close()
                    return "Waiting time set to %s %s." % (args[0], ("seconds" if new_time != 1 else "second"))
                else:
                    return "Given argument has to be a positive integer."
            except ValueError:
                return "Given argument is not a valid integer."
        else:
            return "Command does not have enough arguments."

    def command_latestword(self, args, msg, event):
        lwi = self.latest_word_id
        if lwi != -1:
            return "http://chat.meta.stackexchange.com/transcript/message/%s#%s" % (lwi, lwi)
        else:
            return "I don't know."

    def command_setlatestword(self, args, msg, event):
        if len(args) != 1:
            return "1 argument expected, %i given" % (len(args),)
        try:
            new_lwi = int(args[0])
            self.latest_word_id = new_lwi
            return "Latest word set."
        except ValueError:
            return "Given argument is not an integer."

    def command_showlatest10(self, args, msg, event):
        l = len(self.latest_words)
        return "Latest %s %s: %s" % (l, "words" if l != 1 else "word",
                                        ", ".join(self.latest_words))

    def command_rmword(self, args, msg, event):
        if len(args) != 1:
            return "1 argument expected, %i given" % (len(args),)
        word = args[0]
        if word in self.latest_words:
            self.latest_words = filter(lambda l: l != word, self.latest_words)
            return "Word removed from latest words."
        else:
            return "Word not in the list of latest words."

    def command_showtime(self, args, msg, event):
        return "Waiting time: %i seconds." % self.waiting_time
                
    def command_stop(self, args, msg, event):
        self.enabled = False
        self.running = False
        if msg is not None:
            msg.reply("Bot terminated.")
            time.sleep(2)
        self.room.leave()
        self.client.logout()
        time.sleep(5)
        os._exit(0)
        
    def command_disable(self, args, msg, event):
        self.enabled = False
        return "Bot disabled, run >>enable to enable it again."
        
    def command_enable(self, args, msg, event):
        self.enabled = True
        return "Bot enabled."
    
    def command_award(self, args, msg, event):
        if len(args) < 3:
            return "Not enough arguments."
        try:
            spell_id = int(args[0])
            user_id = int(args[1])
        except ValueError:
            return "Not a valid id."
        if args[2] == "-n":
            add_to_queue = False
        elif args[2] == "-q":
            add_to_queue = True
        else:
            return "Invalid arguments."
        return self.spell_manager.award(spell_id, user_id, add_to_queue)
    
    def command_removespell(self, args, msg, event):
        self.spell_manager.remove(int(args[1]), int(args[0]))
        return "Spell removed (un-awarded)."
        
    def command_viewspells(self, args, msg, event):
        if len(args) < 1:
            return "Not enough arguments."
        try:
            user_id = int(args[0])
        except ValueError:
            return "Invalid arguments."
        try:
            spells = self.spell_manager.view_spells(user_id)
            return spells
        except:
            return "An error occurred."
    
    def command_emptyqueue(self, args, msg, event):
        awarded = self.spell_manager.empty_queue()
        for s in awarded:
            if self.room is not None:
                self.room.send_message(s)
            else:
                print s
    
    def command_ban(self, args, msg, event):
        try:
            banned_user = int(args[0])
        except ValueError:
            return "Invalid arguments."
        try:
            user_name = self.client.get_user(banned_user).name.replace(" ", "")
        except:
            return "Could not fetch user; please check whether the user exists."
        if not self.site in self.banned:
            self.banned[self.site] = []
        if not banned_user in self.banned[self.site]:
            self.banned[self.site].append(banned_user)
        else:
            return "Already banned."
        with open("bannedUsers.txt", "w") as f:
            pickle.dump(self.banned, f)
        return "User @%s has been banne.d." % user_name
            
    def command_unban(self, args, msg, event):
        try:
            banned_user = int(args[0])
        except ValueError:
            return "Invalid arguments."
        try:
            user_name = self.client.get_user(banned_user).name.replace(" ", "")
        except:
            return "Could not fetch user; please check whether the user exists."
        if not self.site in self.banned:
            return "Not banned."
        if not banned_user in self.banned[self.site]:
            return "Not banned."
        self.banned[self.site].remove(banned_user)
        with open("bannedUsers.txt", "w") as f:
            pickle.dump(self.banned, f)
        return "User @%s has been unbanned." % user_name
    
    def command_listcommands(self, args, msg, event):
        command_keys = self.commands.keys()
        if self.in_shadows_den:
            command_keys += self.shadows_den_specific_commands.keys()
        command_keys.sort()
        return "Commands: %s" % (", ".join(command_keys),)

    def command_help(self, args, msg, event):
        if len(args) == 0:
            return "I'm %s, %s's chatbot. You can find the source code [on GitHub](https://github.com/ProgramFOX/SE-Chatbot). You can get a list of all commands by running `>>listcommands`, or you can run `>>help command` to learn more about a specific command." % (self.chatbot_name, self.owner_name)
        command_to_look_up = args[0]
        if command_to_look_up in CommandHelp:
            return CommandHelp[command_to_look_up]
        elif command_to_look_up in self.commands or command_to_look_up in self.shadows_den_specific_commands or \
             command_to_look_up in self.owner_commands or command_to_look_up in self.privileged_commands:
            return "Command exists, but no help entry found."
        else:
            return "The command you want to look up, does not exist."

    def command_delete(self, args, msg, event):
        if len(args) == 0:
            return "Not enough arguments."
        try:
            message_id = int(args[0])
        except:
            return "Invalid arguments."
        message_to_delete = Message(message_id, self.client)
        try:
            message_to_delete.delete()
        except:
            pass

    def command_link(self, args, msg, event):
        if len(args) != 2:
            return "2 arguments expected, %i given." % len(args)
        if self.links_contain((args[0].replace("_", " "), args[1].replace("_", " "))):
            return "Link is already added."
        self.links.append((args[0].replace("_", " "), args[1].replace("_", " ")))
        with open("linkedWords.txt", "w") as f:
            pickle.dump(self.links, f)
        return "Link added."

    def command_islink(self, args, msg, event):
        if len(args) != 2:
            return "2 arguments expected, %i given" % len(args)
        if self.links_contain((args[0].replace("_", " "), args[1].replace("_", " "))):
            return "Yes, that's a manually added link."
        else:
            return "No, that's not a link."

    def removelinkexplanation(self, link):
        to_remove = []
        ret = False
        for exp in self.link_explanations:
            l = exp[0]
            if (l[0] == link[0] and l[1] == link[1]) or (l[0] == link[1] and l[1] == link[0]):
                to_remove.append(exp)
                ret = True
        for r in to_remove:
            self.link_explanations.remove(r)
        with open("linkExplanations.txt", "w") as f:
            pickle.dump(self.link_explanations, f)
        return ret

    def command_addlinkexplanation(self, args, msg, event):
        if len(args) != 3:
            return "3 arguments expected, %i given" % len(args)
        w1 = args[0].replace("_", " ").lower()
        w2 = args[1].replace("_", " ").lower()
        self.removelinkexplanation((w1, w2))  # remove any older explanations
        if not self.links_contain((w1, w2)):
            return "That link does not exist."
        if re.compile(r"[^a-zA-Z0-9_%*/:.#()\[\]?&=-]").search(args[2]):
            return "Sorry, your explanation can only contain the chars `a-zA-Z_*%/:.#()[]-`."
        self.link_explanations.append(((w1, w2), args[2]))
        with open("linkExplanations.txt", "w") as f:
            pickle.dump(self.link_explanations, f)
        return "Explanation added."

    def command_explainlink(self, args, msg, event):
        if len(args) != 2:
            return "2 arguments expected, %i given" % len(args)
        w1 = args[0].replace("_", " ").lower()
        w2 = args[1].replace("_", " ").lower()
        if not self.links_contain((w1, w2)):
            return "Words not linked."
        for exp in self.link_explanations:
            link = exp[0]
            explanation = exp[1]
            if (link[0] == w1 and link[1] == w2) or (link[1] == w1 and link[0] == w2):
                return explanation
        return "No explanation found."

    def command_removelinkexplanation(self, args, msg, event):
        if len(args) != 2:
            return "2 argumens expected, %i given" % len(args)
        w1 = args[0].replace("_", " ").lower()
        w2 = args[1].replace("_", " ").lower()
        if self.removelinkexplanation((w1, w2)):
            return "Explanation removed."
        else:
            return "No explanation found to remove."

    def command_reply(self, args, msg, event):
        if len(args) < 1:
            return "Not enough arguments."
        try:
            msg_id_to_reply_to = int(args[0])
        except ValueError:
            if args[0] == "recent":
                msg_id_to_reply_to = self.msg_id_no_reply_found
            else:
                return "Invalid arguments."
            if msg_id_to_reply_to == -1:
                return "'recent' has a value of -1, which is not a valid message ID. Please provide an explicit ID."
        msg_to_reply_to = Message(msg_id_to_reply_to, self.client)
        content = msg_to_reply_to.content_source
        content = re.sub(r"([:;][-']?[)/(DPdpoO\[\]\\|])", "", content) # strip smilies
        content = re.sub(r"\[(.+?)\]\(.+?\)", r"\1", content)
        content = re.sub(r"\(.+?\)", "", content)
        content = re.sub(r"\s+", " ", content)
        content = content.strip()
        parts = content.split(" ")
        msg_does_not_qualify = "Message does not qualify as a message that belongs to the word association game."
        if len(parts) != 2:
            return msg_does_not_qualify
        if not re.compile("^:([0-9]+)$").search(parts[0]):
            return msg_does_not_qualify
        if re.compile("[^a-zA-Z0-9-]").search(parts[1]):
            return "Word contains invalid characters."
        self.reply_word(msg_to_reply_to, False, parts[1])
        return None

    def command_continue(self, args, msg, event):
        if len(args) != 2:
            return "2 arguments expected, %i given." % (len(args),)
        self.command_link(args, None, None)
        return self.command_reply([ "recent" ], None, None)

    def command_retry(self, args, msg, event):
        return self.command_reply([ "recent" ], None, None)

    def command_removelink(self, args, msg, event):
        if len(args) < 2:
            return "Not enough arguments."
        w1 = args[0].replace("_", " ").lower()
        w2 = args[1].replace("_", " ").lower()
        self.removelinkexplanation((w1, w2))
        return self.remove_link(args[0].replace("_", " "), args[1].replace("_", " "))

    def links_contain(self, item):
        for link in self.links:
            lowercase_link = (link[0].lower(), link[1].lower())
            if item[0].lower() in lowercase_link and item[1].lower() in lowercase_link:
                return True
        return False

    def find_links(self, item):
        results = []
        for link in self.links:
            lowercase_link = (link[0].lower(), link[1].lower())
            lowercase_item = item.lower()
            if lowercase_item in lowercase_link:
                i = lowercase_link.index(lowercase_item)
                associated_index = 0 if i == 1 else 1
                results.append(link[associated_index])
        return results

    def remove_link(self, item0, item1):
        for i, link in enumerate(self.links):
            lowercase_link = (link[0].lower(), link[1].lower())
            if item0.lower() in lowercase_link and item1.lower() in lowercase_link:
                self.links.pop(i)
                with open("linkedWords.txt", "w") as f:
                    pickle.dump(self.links, f)
                return "Link removed."
        return "No link found."

    def command_translationchain(self, args, msg, event):
        if event.user.id not in self.owner_ids:
            return "The `translationchain` command is a command that posts many messages and it does not post all messages, and causes that some messages that have to be posted after the chain might not be posted, so it is an owner-only command now."
        if len(args) < 4:
            return "Not enough arguments."
        try:
            translation_count = int(args[0])
        except ValueError:
            return "Invalid arguments."
        if translation_count < 1:
            return "Invalid arguments."
        if not self.translation_chain_going_on:
            if not args[1] in self.translation_languages or not args[2] in self.translation_languages:
                return "Language not in list. If the language is supported, ping ProgramFOX and he will add it."
            self.translation_chain_going_on = True
            thread.start_new_thread(self.translationchain, (args[3], args[1], args[2], translation_count))
            return "Translation chain started. Translation made by [Google Translate](https://translate.google.com). Some messages in the chain might not be posted due to a reason I don't know."
        else:
            return "There is already a translation chain going on."

    def command_translationswitch(self, args, msg, event):
        if event.user.id not in self.owner_ids:
            return "The `translationswitch` command is a command that posts many messages and it does not post all messages, and causes that some messages that have to be posted after the chain might not be posted, so it is an owner-only command now."
        if self.translation_switch_going_on:
            return "There is already a translation switch going on."
        if len(args) < 4:
            return "Not enough arguments."
        try:
            translation_count = int(args[0])
        except ValueError:
            return "Invalid arguments."
        if translation_count < 2:
            return "Invalid arguments."
        if (translation_count % 2) == 1:
            return "Translation count has to be an even number."
        if not args[1] in self.translation_languages or not args[2] in self.translation_languages:
            return "Language not in list. If the language is supported, ping ProgramFOX and he will add it."
        self.translation_switch_going_on = True
        thread.start_new_thread(self.translationswitch, (args[3], args[1], args[2], translation_count))
        return "Translation switch started. Translation made by [Google Translate](https://translate.google.com). Some messages in the switch might not be posted due to a reason I don't know."

    def command_translate(self, args, msg, event):
        if len(args) < 3:
            return "Not enough arguments."
        if args[0] == args[1]:
            return "There's no point in having the same input language as output language."
        if not args[0] in self.translation_languages or not args[1] in self.translation_languages:
            return "Language not in list. If the language is supported, ping ProgramFOX and he will add it."
        return self.translate(args[2], args[0], args[1])

    def translationchain(self, text, start_lang, end_lang, translation_count):
        i = 0
        curr_lang = start_lang
        next_lang = None
        curr_text = text
        choices = list(self.translation_languages)
        if start_lang == end_lang:
            choices.remove(start_lang)
        else:
            choices.remove(start_lang)
            choices.remove(end_lang)
        while i < translation_count - 1:
            if next_lang is not None:
                curr_lang = next_lang
            while True:
                next_lang = random.choice(choices)
                if next_lang != curr_lang:
                    break
            result = self.translate(curr_text, curr_lang, next_lang)
            curr_text = result
            self.room.send_message("Translate %s-%s: %s" % (curr_lang, next_lang, result))
            i += 1
        final_result = self.translate(curr_text, next_lang, end_lang)
        self.room.send_message("Final translation result (%s-%s): %s" % (next_lang, end_lang, final_result))
        self.translation_chain_going_on = False

    def translationswitch(self, text, lang1, lang2, translation_count):
        i = 1
        curr_text = text
        while i <= translation_count:
            if (i % 2) == 0:
                lang_order = (lang2, lang1)
            else:
                lang_order = (lang1, lang2)
            curr_text = self.translate(curr_text, lang_order[0], lang_order[1])
            msg_text = "Translate %s-%s: %s" if i != translation_count else "Final result (%s-%s): %s"
            self.room.send_message(msg_text % (lang_order + (curr_text,)))
            i += 1
        self.translation_switch_going_on = False

    def translate(self, text, start_lang, end_lang):
        translate_url = "https://translate.google.com/translate_a/single?client=t&sl=%s&tl=%s&hl=en&dt=bd&dt=ex&dt=ld&dt=md&dt=qc&dt=rw&dt=rm&dt=ss&dt=t&dt=at&dt=sw&ie=UTF-8&oe=UTF-8&prev=btn&srcrom=1&ssel=0&tsel=0&q=%s" % (start_lang, end_lang, urllib.quote_plus(text.encode("utf-8")))
        r = requests.get(translate_url)
        unparsed_json = r.text.split("],[\"\",,", 1)[0].split("]]", 1)[0][3:]
        return self.parse(unparsed_json)

    def parse(self, json):
        is_open = False
        is_backslash = False
        is_translation = True
        all_str = []
        curr_str = []
        for c in json:
            if c != '"' and not is_open:
                continue
            elif c == '"' and not is_open:
                is_open = True
            elif c == '\\':
                is_backslash = not is_backslash
                if is_translation:
                    curr_str.append(c)
            elif c == '"' and is_open and not is_backslash:
                is_open = False
                if is_translation:
                    s = "".join(curr_str).replace("\\\\", "\\").replace("\\\"", "\"")
                    all_str.append(s)
                curr_str = []
                is_backslash = False
                is_translation = not is_translation
            else:
                is_backslash = False
                if is_translation:
                    curr_str.append(c)
        return " ".join(all_str)
Exemple #13
0
else:
    room_number = int(raw_input("Room number: "))
if len(sys.argv) > 2:
    email = sys.argv[2]
else:
    email = raw_input("Email: ")
if len(sys.argv) > 3:
    password = sys.argv[3]
else:
    password = getpass.getpass()
if len(sys.argv) > 4:
    secondary_room_number = sys.argv[4]
else:
    secondary_room_number = int(raw_input("Secondary room (0 to disable): "))
host = "stackoverflow.com"
c = Client(host)
c.login(email, password)
del email
del password
room = c.get_room(room_number)
room.join()

if secondary_room_number != 0:
    secondary_room = c.get_room(secondary_room_number)
    secondary_room.join()
else:
    secondary_room = None

fetcher = EditFetcher()

owners = []