Ejemplo n.º 1
0
 def __init__(self, _id, failure_callback):
     super().__init__()
     self.id = _id
     self.packet_queue = DelayQueue(2, 3.5)
     self.packet_last_received_timestamp = time.time()
     self.failure_callback = failure_callback
     self.send_lock = threading.Lock()
Ejemplo n.º 2
0
 def __init__(self):
     super().__init__()
     self.ready = False
     self.packet_handlers = {}
     self.superadmin = None
     self.status: BotStatus = BotStatus.SHUTDOWN
     self.dimension = None
     self.packet_queue = DelayQueue(2, 2.5)
     self.last_timer_event = 0
     self.start_time = int(time.time())
     self.version = "0.4-beta"
Ejemplo n.º 3
0
 def __init__(self):
     super().__init__()
     self.ready = False
     self.packet_handlers = {}
     self.org_id = None
     self.org_name = None
     self.superadmin = None
     self.status: BotStatus = BotStatus.SHUTDOWN
     self.dimension = None
     self.packet_queue = DelayQueue(2, 2.5)
     self.last_timer_event = 0
Ejemplo n.º 4
0
 def __init__(self, _id, failure_callback):
     super().__init__()
     self.id = _id
     self.packet_queue = DelayQueue(2, 2.5)
     self.packet_last_received_timestamp = time.time()
     self.failure_callback = failure_callback
     self.send_lock = threading.Lock()
     self.org_channel_id = None
     self.org_id = None
     self.org_name = None
     self.channels = {}
     self.buddy_list = {}
     self.private_channel = {}
     # store module data that is conn-specific here
     self.data = DictObject({"wave_counter_job_id": None})
Ejemplo n.º 5
0
class Conn(Bot):
    def __init__(self, _id, failure_callback):
        super().__init__()
        self.id = _id
        self.packet_queue = DelayQueue(2, 3.5)
        self.packet_last_received_timestamp = time.time()
        self.failure_callback = failure_callback
        self.send_lock = threading.Lock()

    def read_packet(self, max_delay_time=1):
        self.check_outgoing_message_queue()
        packet = super().read_packet(max_delay_time)
        if not packet:
            time_since = time.time() - self.packet_last_received_timestamp
            if time_since > 90:
                self.logger.error(f"no packet received in 90 seconds for conn {self.id}")
                self.failure_callback()
            elif time_since > 60:
                self.send_packet(Ping("tyrbot_aochat"))
        else:
            self.packet_last_received_timestamp = time.time()
        return packet

    def send_packet(self, packet):
        # synchronize sending packets
        with self.send_lock:
            super().send_packet(packet)

    def add_packet_to_queue(self, packet):
        self.packet_queue.enqueue(packet)
        self.check_outgoing_message_queue()

    def check_outgoing_message_queue(self):
        # check packet queue for outgoing packets
        outgoing_packet = self.packet_queue.dequeue()
        while outgoing_packet:
            self.send_packet(outgoing_packet)
            outgoing_packet = self.packet_queue.dequeue()

        num_messages = len(self.packet_queue)
        if num_messages > 30:
            self.logger.warning("automatically clearing outgoing message queue (%d messages)" % num_messages)
            self.packet_queue.clear()
        elif num_messages > 10:
            self.logger.warning("%d messages in outgoing message queue" % num_messages)

    def __str__(self):
        return self.id

    def __repr__(self):
        return self.__str__()
Ejemplo n.º 6
0
class Tyrbot(Bot):
    CONNECT_EVENT = "connect"
    PACKET_EVENT = "packet"
    PRIVATE_MSG_EVENT = "private_msg"

    OUTGOING_ORG_MESSAGE_EVENT = "outgoing_org_message"
    OUTGOING_PRIVATE_MESSAGE_EVENT = "outgoing_private_message"
    OUTGOING_PRIVATE_CHANNEL_MESSAGE_EVENT = "outgoing_private_channel_message"

    def __init__(self):
        super().__init__()
        self.ready = False
        self.packet_handlers = {}
        self.superadmin = None
        self.status: BotStatus = BotStatus.SHUTDOWN
        self.dimension = None
        self.packet_queue = DelayQueue(2, 2.5)
        self.last_timer_event = 0
        self.start_time = int(time.time())
        self.version = "0.4-beta"

    def inject(self, registry):
        self.db = registry.get_instance("db")
        self.character_service: CharacterService = registry.get_instance(
            "character_service")
        self.public_channel_service: PublicChannelService = registry.get_instance(
            "public_channel_service")
        self.text: Text = registry.get_instance("text")
        self.setting_service: SettingService = registry.get_instance(
            "setting_service")
        self.access_service: AccessService = registry.get_instance(
            "access_service")
        self.event_service = registry.get_instance("event_service")
        self.job_scheduler = registry.get_instance("job_scheduler")

    def init(self, config, registry, paths, mmdb_parser):
        self.mmdb_parser = mmdb_parser
        self.superadmin = config.superadmin.capitalize()
        self.dimension = config.server.dimension

        self.db.exec("UPDATE db_version SET verified = 0")
        self.db.exec(
            "UPDATE db_version SET verified = 1 WHERE file = 'db_version'")

        self.load_sql_files(paths)

        # prepare commands, events, and settings
        self.db.exec("UPDATE command_config SET verified = 0")
        self.db.exec("UPDATE event_config SET verified = 0")
        self.db.exec("UPDATE setting SET verified = 0")

        with self.db.transaction():
            registry.pre_start_all()
            registry.start_all()

        # remove commands, events, and settings that are no longer registered
        self.db.exec("DELETE FROM db_version WHERE verified = 0")
        self.db.exec("DELETE FROM command_config WHERE verified = 0")
        self.db.exec("DELETE FROM event_config WHERE verified = 0")
        self.db.exec(
            "DELETE FROM timer_event WHERE handler NOT IN (SELECT handler FROM event_config WHERE event_type = ?)",
            ["timer"])
        self.db.exec("DELETE FROM setting WHERE verified = 0")

        self.status = BotStatus.RUN

    def pre_start(self):
        self.access_service.register_access_level("superadmin", 10,
                                                  self.check_superadmin)
        self.event_service.register_event_type(self.CONNECT_EVENT)
        self.event_service.register_event_type(self.PACKET_EVENT)
        self.event_service.register_event_type(self.PRIVATE_MSG_EVENT)
        self.event_service.register_event_type(self.OUTGOING_ORG_MESSAGE_EVENT)
        self.event_service.register_event_type(
            self.OUTGOING_PRIVATE_MESSAGE_EVENT)
        self.event_service.register_event_type(
            self.OUTGOING_PRIVATE_CHANNEL_MESSAGE_EVENT)

    def start(self):
        self.setting_service.register(
            "symbol", "!", "Symbol for executing bot commands",
            TextSettingType(["!", "#", "*", "@", "$", "+", "-"]),
            "core.system")

        self.setting_service.register(
            "org_channel_max_page_length", 7500,
            "Maximum size of blobs in org channel",
            NumberSettingType([4500, 6000, 7500, 9000, 10500,
                               12000]), "core.system")
        self.setting_service.register(
            "private_message_max_page_length",
            7500,
            "Maximum size of blobs in private messages",
            NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]),
            "core.system",
        )
        self.setting_service.register(
            "private_channel_max_page_length", 7500,
            "Maximum size of blobs in private channel",
            NumberSettingType([4500, 6000, 7500, 9000, 10500,
                               12000]), "core.system")

        self.setting_service.register("header_color",
                                      "#FFFF00", "color for headers",
                                      ColorSettingType(), "core.colors")
        self.setting_service.register("header2_color", "#FCA712",
                                      "color for sub-headers",
                                      ColorSettingType(), "core.colors")
        self.setting_service.register("highlight_color",
                                      "#00BFFF", "color for highlight",
                                      ColorSettingType(), "core.colors")
        self.setting_service.register("notice_color", "#FF8C00",
                                      "color for important notices",
                                      ColorSettingType(), "core.colors")

        self.setting_service.register("neutral_color", "#E6E1A6",
                                      "color for neutral faction",
                                      ColorSettingType(), "core.colors")
        self.setting_service.register("omni_color", "#FA8484",
                                      "color for omni faction",
                                      ColorSettingType(), "core.colors")
        self.setting_service.register("clan_color", "#F79410",
                                      "color for clan faction",
                                      ColorSettingType(), "core.colors")
        self.setting_service.register("unknown_color", "#FF0000",
                                      "color for unknown faction",
                                      ColorSettingType(), "core.colors")

        self.setting_service.register("org_channel_color", "#89D2E8",
                                      "default org channel color",
                                      ColorSettingType(), "core.colors")
        self.setting_service.register("private_channel_color", "#89D2E8",
                                      "default private channel color",
                                      ColorSettingType(), "core.colors")
        self.setting_service.register("private_message_color", "#89D2E8",
                                      "default private message color",
                                      ColorSettingType(), "core.colors")
        self.setting_service.register("blob_color", "#FFFFFF",
                                      "default blob content color",
                                      ColorSettingType(), "core.colors")

        self.add_packet_handler(server_packets.PrivateMessage.id,
                                self.handle_private_message,
                                priority=40)

    def check_superadmin(self, char_id):
        char_name = self.character_service.resolve_char_to_name(char_id)
        return char_name == self.superadmin

    def run(self):
        start = time.time()

        # wait for flood of packets from login to stop sending
        time_waited = 0
        while time_waited < 5:
            if not self.iterate():
                time_waited += 1

        self.logger.info("Login complete (%fs)" % (time.time() - start))

        start = time.time()
        self.event_service.fire_event("connect", None)
        self.logger.info("Connect events finished (%fs)" %
                         (time.time() - start))

        self.ready = True

        # TODO this prevents restarting as a way to clear the packet queue
        while self.status == BotStatus.RUN or len(self.packet_queue) > 0:
            try:
                timestamp = int(time.time())

                # timer events will execute no more often than once per second
                if self.last_timer_event < timestamp:
                    self.last_timer_event = timestamp
                    self.job_scheduler.check_for_scheduled_jobs(timestamp)
                    self.event_service.check_for_timer_events(timestamp)

                self.iterate()
            except (EOFError, OSError) as e:
                raise e
            except Exception as e:
                self.logger.error("", e)

        return self.status

    def add_packet_handler(self, packet_id: int, handler, priority=50):
        handlers = self.packet_handlers.get(packet_id, [])
        handlers.append(DictObject({"priority": priority, "handler": handler}))
        self.packet_handlers[packet_id] = sorted(handlers,
                                                 key=lambda x: x.priority)

    def remove_packet_handler(self, packet_id, handler):
        handlers = self.packet_handlers.get(packet_id, [])
        for h in handlers:
            if h.handler == handler:
                handlers.remove(h)

    def iterate(self):
        packet = self.read_packet(1)
        if packet:
            if isinstance(packet, server_packets.SystemMessage):
                packet = self.system_message_ext_msg_handling(packet)
            elif isinstance(packet, server_packets.PublicChannelMessage):
                packet = self.public_channel_message_ext_msg_handling(packet)
            if isinstance(packet, server_packets.BuddyAdded):
                if packet.char_id == 0:
                    return

            for handler in self.packet_handlers.get(packet.id, []):
                handler.handler(packet)

            self.event_service.fire_event("packet:" + str(packet.id), packet)

        self.check_outgoing_message_queue()

        return packet

    def check_outgoing_message_queue(self):
        # check packet queue for outgoing packets
        outgoing_packet = self.packet_queue.dequeue()
        while outgoing_packet:
            self.send_packet(outgoing_packet)
            outgoing_packet = self.packet_queue.dequeue()

        num_messages = len(self.packet_queue)
        if num_messages > 30:
            self.logger.warning(
                "automatically clearing outgoing message queue (%d messages)" %
                num_messages)
            self.packet_queue.clear()
        elif num_messages > 10:
            self.logger.warning("%d messages in outgoing message queue" %
                                num_messages)

    def public_channel_message_ext_msg_handling(
            self, packet: server_packets.PublicChannelMessage):
        msg = packet.message
        if msg.startswith("~&") and msg.endswith("~"):
            try:
                msg = msg[2:-1].encode("utf-8")
                category_id = self.mmdb_parser.read_base_85(msg[0:5])
                instance_id = self.mmdb_parser.read_base_85(msg[5:10])
                template = self.mmdb_parser.get_message_string(
                    category_id, instance_id)
                params = self.mmdb_parser.parse_params(msg[10:])
                packet.extended_message = ExtendedMessage(
                    category_id, instance_id, template, params)
            except Exception as e:
                self.logger.error(
                    "Error handling extended message for packet: " +
                    str(packet), e)

        return packet

    def system_message_ext_msg_handling(self,
                                        packet: server_packets.SystemMessage):
        try:
            category_id = 20000
            instance_id = packet.message_id
            template = self.mmdb_parser.get_message_string(
                category_id, instance_id)
            params = self.mmdb_parser.parse_params(packet.message_args)
            packet.extended_message = ExtendedMessage(category_id, instance_id,
                                                      template, params)
            self.logger.log_chat("SystemMessage", None,
                                 packet.extended_message.get_message())
        except Exception as e:
            self.logger.error(
                "Error handling extended message: " + str(packet), e)

        return packet

    def send_org_message(self, msg, add_color=True, fire_outgoing_event=True):
        org_channel_id = self.public_channel_service.org_channel_id
        if org_channel_id is None:
            self.logger.debug(
                "ignoring message to org channel since the org_channel_id is unknown"
            )
        else:
            color = self.setting_service.get(
                "org_channel_color").get_font_color() if add_color else ""
            pages = self.get_text_pages(
                msg,
                self.setting_service.get(
                    "org_channel_max_page_length").get_value())
            for page in pages:
                packet = client_packets.PublicChannelMessage(
                    org_channel_id, color + page, "")
                self.packet_queue.enqueue(packet)
                self.check_outgoing_message_queue()

            if fire_outgoing_event:
                self.event_service.fire_event(
                    self.OUTGOING_ORG_MESSAGE_EVENT,
                    DictObject({
                        "org_channel_id": org_channel_id,
                        "message": msg
                    }))

    def send_private_message(self,
                             char,
                             msg,
                             add_color=True,
                             fire_outgoing_event=True):
        char_id = self.character_service.resolve_char_to_id(char)
        if char_id is None:
            self.logger.warning(
                "Could not send message to %s, could not find char id" % char)
        else:
            color = self.setting_service.get(
                "private_message_color").get_font_color() if add_color else ""
            pages = self.get_text_pages(
                msg,
                self.setting_service.get(
                    "private_message_max_page_length").get_value())
            for page in pages:
                self.logger.log_tell(
                    "To", self.character_service.get_char_name(char_id), page)
                packet = client_packets.PrivateMessage(char_id, color + page,
                                                       "\0")
                self.packet_queue.enqueue(packet)
                self.check_outgoing_message_queue()

            if fire_outgoing_event:
                self.event_service.fire_event(
                    self.OUTGOING_PRIVATE_MESSAGE_EVENT,
                    DictObject({
                        "char_id": char_id,
                        "message": msg
                    }))

    def send_private_channel_message(self,
                                     msg,
                                     private_channel=None,
                                     add_color=True,
                                     fire_outgoing_event=True):
        if private_channel is None:
            private_channel = self.char_id

        private_channel_id = self.character_service.resolve_char_to_id(
            private_channel)
        if private_channel_id is None:
            self.logger.warning(
                "Could not send message to private channel %s, could not find private channel"
                % private_channel)
        else:
            color = self.setting_service.get(
                "private_channel_color").get_font_color() if add_color else ""
            pages = self.get_text_pages(
                msg,
                self.setting_service.get(
                    "private_channel_max_page_length").get_value())
            for page in pages:
                packet = client_packets.PrivateChannelMessage(
                    private_channel_id, color + page, "\0")
                self.send_packet(packet)

            if fire_outgoing_event and private_channel_id == self.char_id:
                self.event_service.fire_event(
                    self.OUTGOING_PRIVATE_CHANNEL_MESSAGE_EVENT,
                    DictObject({
                        "private_channel_id": private_channel_id,
                        "message": msg
                    }))

    def handle_private_message(self, packet: server_packets.PrivateMessage):
        self.logger.log_tell(
            "From", self.character_service.get_char_name(packet.char_id),
            packet.message)
        self.event_service.fire_event(self.PRIVATE_MSG_EVENT, packet)

    def get_text_pages(self, msg, max_page_length):
        if isinstance(msg, ChatBlob):
            return self.text.paginate(msg, max_page_length=max_page_length)
        else:
            return [self.text.format_message(msg)]

    def is_ready(self):
        return self.ready

    def shutdown(self):
        self.status = BotStatus.SHUTDOWN

    def restart(self):
        self.status = BotStatus.RESTART

    def load_sql_files(self, paths):
        dirs = flatmap(lambda x: os.walk(x), paths)
        dirs = filter(lambda y: not y[0].endswith("__pycache__"), dirs)

        def get_files(tup):
            return map(lambda x: os.path.join(tup[0], x), tup[2])

        # get files from subdirectories
        files = flatmap(get_files, dirs)
        files = filter(lambda z: z.endswith(".sql"), files)

        base_path = os.getcwd()
        for file in files:
            self.db.load_sql_file(file, base_path)
Ejemplo n.º 7
0
    def test_equivalent_priority(self):
        # when using default or equivalent priority, order should be insertion order
        delay_queue = DelayQueue(1, 100)

        delay_queue.enqueue("A")
        delay_queue.enqueue("B")
        delay_queue.enqueue("C")
        delay_queue.enqueue("D")
        delay_queue.enqueue("E")

        self.assertEqual("A", delay_queue.dequeue())
        self.assertEqual("B", delay_queue.dequeue())
        self.assertEqual("C", delay_queue.dequeue())
        self.assertEqual("D", delay_queue.dequeue())
        self.assertEqual("E", delay_queue.dequeue())
Ejemplo n.º 8
0
    def test_specified_priority(self):
        # when using differing priorities, order should be priority order and then insertion order
        delay_queue = DelayQueue(1, 100)

        delay_queue.enqueue("A", 5)
        delay_queue.enqueue("B", 5)
        delay_queue.enqueue("C", 3)
        delay_queue.enqueue("D", 1)
        delay_queue.enqueue("E", 1)

        # D should come before E since D was inserted first
        # then C
        # then A before B since A was inserted first

        self.assertEqual("D", delay_queue.dequeue())
        self.assertEqual("E", delay_queue.dequeue())
        self.assertEqual("C", delay_queue.dequeue())
        self.assertEqual("A", delay_queue.dequeue())
        self.assertEqual("B", delay_queue.dequeue())
Ejemplo n.º 9
0
class Mangopie(Bot):
    def __init__(self):
        super().__init__()
        self.ready = False
        self.packet_handlers = {}
        self.org_id = None
        self.org_name = None
        self.superadmin = None
        self.status: BotStatus = BotStatus.SHUTDOWN
        self.dimension = None
        self.packet_queue = DelayQueue(2, 2.5)
        self.last_timer_event = 0

    def inject(self, registry):
        self.db = registry.get_instance("db")
        self.buddy_manager: BuddyManager = registry.get_instance("buddy_manager")
        self.character_manager: CharacterManager = registry.get_instance("character_manager")
        self.setting_manager: SettingManager = registry.get_instance("setting_manager")
        self.access_manager: AccessManager = registry.get_instance("access_manager")
        self.command_manager = registry.get_instance("command_manager")
        self.public_channel_manager: PublicChannelManager = registry.get_instance("public_channel_manager")
        self.text: Text = registry.get_instance("text")
        self.pork_manager = registry.get_instance("pork_manager")
        self.event_manager = registry.get_instance("event_manager")
        self.job_scheduler = registry.get_instance("job_scheduler")
        self.mmdb = registry.get_instance("mmdb_parser")

    def init(self, config, registry):
        self.superadmin = config["superadmin"].capitalize()
        self.dimension = 5

        # prepare indexes, commands, events, and settings
        self.db.client['admin'].create_index("char_id", unique=True,background=True)
        self.db.client['player'].create_index("char_id", unique=True,background=True)
        self.db.client['online'].create_index("char_id", unique=True,background=True)
        self.db.client['event_config'].create_index("event_type", background=True)
        self.db.client['command_config'].update_many({}, {'$set': {'verified': 0}})
        self.db.client['event_config'].update_many({}, {'$set': {'verified': 0}})
        self.db.client['settings'].update_many({}, {'$set': {'verified': 0}})

        registry.pre_start_all()
        registry.start_all()

        # remove commands, events, and settings that are no longer registered
        self.db.delete_all('settings', {'verified': 0})
        self.db.delete_all('command_config', {'verified': 0})
        self.db.delete_all('event_config', {'verified': 0})
        self.status = BotStatus.RUN

    def post_start(self):
        self.pork_manager.get_character_info(self.superadmin)
        self.ready = True

    def pre_start(self):
        pass
        self.access_manager.register_access_level("superadmin", 10, self.check_superadmin)
        self.event_manager.register_event_type("connect")
        self.event_manager.register_event_type("packet")

    def start(self):
        self.setting_manager.register("org_channel_max_page_length", 7500, "Maximum size of blobs in org channel",
                                      NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]), "core.system")
        self.setting_manager.register("private_message_max_page_length", 7500,
                                      "Maximum size of blobs in private messages",
                                      NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]), "core.system", )
        self.setting_manager.register("private_channel_max_page_length", 7500,
                                      "Maximum size of blobs in private channel",
                                      NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]), "core.system")
        self.setting_manager.register("header_color", "#FFFF00", "color for headers", ColorSettingType(), "core.colors")
        self.setting_manager.register("header2_color", "#FCA712", "color for sub-headers", ColorSettingType(),
                                      "core.colors")
        self.setting_manager.register("highlight_color", "#FFFFFF", "color for highlight", ColorSettingType(),
                                      "core.colors")
        self.setting_manager.register("neutral_color", "#E6E1A6", "color for neutral faction", ColorSettingType(),
                                      "core.colors")
        self.setting_manager.register("omni_color", "#FA8484", "color for omni faction", ColorSettingType(),
                                      "core.colors")
        self.setting_manager.register("clan_color", "#F79410", "color for clan faction", ColorSettingType(),
                                      "core.colors")
        self.setting_manager.register("unknown_color", "#FF0000", "color for unknown faction", ColorSettingType(),
                                      "core.colors")
        self.setting_manager.register("notice_color", "#FF8C00", "color for important notices", ColorSettingType(),
                                      "core.colors")
        self.setting_manager.register("symbol", "!", "Symbol for executing bot commands",
                                      TextSettingType(["!", "#", "*", "@", "$", "+", "-"]), "core.system")

    def check_superadmin(self, char_id):
        char_name = self.character_manager.resolve_char_to_name(char_id)
        return char_name == self.superadmin

    def run(self):
        while None is not self.iterate():
            pass

        self.event_manager.fire_event("connect", None)
        self.post_start()

        while self.status == BotStatus.RUN:
            timestamp = int(time.time())

            # timer events will execute not more often than once per second
            if self.last_timer_event < timestamp:
                self.last_timer_event = timestamp
                self.job_scheduler.check_for_scheduled_jobs(timestamp)
                self.event_manager.check_for_timer_events(timestamp)

            self.iterate()

        return self.status

    def add_packet_handler(self, packet_id, handler):
        handlers = self.packet_handlers.get(packet_id, [])
        handlers.append(handler)
        self.packet_handlers[packet_id] = handlers

    def iterate(self):
        packet = self.read_packet()
        if packet:
            if isinstance(packet, server_packets.PrivateMessage):
                self.handle_private_message(packet)
            elif isinstance(packet, server_packets.PublicChannelJoined):
                # set org id and org name
                if packet.channel_id >> 32 == 3:
                    self.org_id = 0x00ffffffff & packet.channel_id
                    if packet.name != "Clan (name unknown)":
                        self.org_name = packet.name
            elif isinstance(packet, server_packets.SystemMessage):
                category_id = 20000
                instance_id = packet.message_id
                template = self.mmdb.get_message_string(category_id, instance_id)
                params = self.mmdb.parse_params(packet.message_args)
                self.logger.info(template % tuple(params))

            for handler in self.packet_handlers.get(packet.id, []):
                handler(packet)

            self.event_manager.fire_event("packet:" + str(packet.id), packet)

        # check packet queue for outgoing packets
        outgoing_packet = self.packet_queue.dequeue()
        while outgoing_packet:
            self.send_packet(outgoing_packet)
            outgoing_packet = self.packet_queue.dequeue()

        return packet

    def send_org_message(self, msg):
        org_channel_id = self.public_channel_manager.org_channel_id
        if org_channel_id is None:
            self.logger.warning("Could not send message to org channel, unknown org id")
        else:
            for page in self.get_text_pages(msg, self.setting_manager.get("org_channel_max_page_length").get_value()):
                packet = client_packets.PublicChannelMessage(org_channel_id, page, "")
                # self.send_packet(packet)
                self.packet_queue.enqueue(packet)

    def send_private_message(self, char, msg):
        char_id = self.character_manager.resolve_char_to_id(char)
        if char_id is None:
            self.logger.warning("Could not send message to %s, could not find char id" % char)
        else:
            for page in self.get_text_pages(msg,
                                            self.setting_manager.get("private_message_max_page_length").get_value()):
                self.logger.log_tell("To", self.character_manager.get_char_name(char_id), page)
                packet = client_packets.PrivateMessage(char_id, page, "\0")
                # self.send_packet(packet)
                self.packet_queue.enqueue(packet)

    def send_private_channel_message(self, msg, private_channel=None):
        if private_channel is None:
            private_channel = self.char_id

        private_channel_id = self.character_manager.resolve_char_to_id(private_channel)
        if private_channel_id is None:
            self.logger.warning(
                "Could not send message to private channel %s, could not find private channel" % private_channel)
        else:
            for page in self.get_text_pages(msg,
                                            self.setting_manager.get("private_channel_max_page_length").get_value()):
                packet = client_packets.PrivateChannelMessage(private_channel_id, page, "\0")
                self.send_packet(packet)

    def handle_private_message(self, packet: server_packets.PrivateMessage):
        self.logger.log_tell("From", self.character_manager.get_char_name(packet.char_id), packet.message)

    def handle_public_channel_message(self, packet: server_packets.PublicChannelMessage):
        self.logger.log_chat(
            self.public_channel_manager.get_channel_name(packet.channel_id),
            self.character_manager.get_char_name(packet.char_id),
            packet.message)

    def get_text_pages(self, msg, max_page_length):
        if isinstance(msg, ChatBlob):
            return self.text.paginate(msg.title, msg.msg, max_page_length, msg.max_num_pages, msg.footer)
        else:
            return [self.text.format_message(msg)]

    def is_ready(self):
        return self.ready

    def shutdown(self):
        self.status = BotStatus.SHUTDOWN

    def restart(self):
        self.status = BotStatus.RESTART
Ejemplo n.º 10
0
class Conn(Bot):
    def __init__(self, _id, failure_callback):
        super().__init__()
        self.id = _id
        self.packet_queue = DelayQueue(2, 2.5)
        self.packet_last_received_timestamp = time.time()
        self.failure_callback = failure_callback
        self.send_lock = threading.Lock()
        self.org_channel_id = None
        self.org_id = None
        self.org_name = None
        self.channels = {}
        self.buddy_list = {}
        self.private_channel = {}
        # store module data that is conn-specific here
        self.data = DictObject({"wave_counter_job_id": None})

    def read_packet(self, max_delay_time=1):
        self.check_outgoing_message_queue()
        packet = super().read_packet(max_delay_time)
        if not packet:
            time_since = time.time() - self.packet_last_received_timestamp
            if time_since > 90:
                self.logger.error(
                    f"no packet received in 90 seconds for conn {self.id}")
                self.failure_callback()
            elif time_since > 60:
                self.send_packet(Ping("tyrbot_aochat"))
        else:
            self.packet_last_received_timestamp = time.time()
        return packet

    def send_packet(self, packet):
        # synchronize sending packets
        try:
            with self.send_lock:
                super().send_packet(packet)
        except Exception as e:
            self.failure_callback()

    def add_packet_to_queue(self, packet):
        self.packet_queue.enqueue(packet)
        self.check_outgoing_message_queue()

    def check_outgoing_message_queue(self):
        # check packet queue for outgoing packets
        outgoing_packet = self.packet_queue.dequeue()
        while outgoing_packet:
            self.send_packet(outgoing_packet)
            outgoing_packet = self.packet_queue.dequeue()

        num_messages = len(self.packet_queue)
        if num_messages > 30:
            self.logger.warning(
                "automatically clearing outgoing message queue (%d messages)" %
                num_messages)
            self.packet_queue.clear()
        elif num_messages > 10:
            self.logger.warning("%d messages in outgoing message queue" %
                                num_messages)

    def get_char_name(self):
        return self.char_name

    def get_char_id(self):
        return self.char_id

    def get_org_name(self):
        return self.org_name or f"UnknownOrg({self.org_id})"

    def __str__(self):
        return self.id

    def __repr__(self):
        return self.__str__()
Ejemplo n.º 11
0
class Tyrbot(Bot):
    def __init__(self):
        super().__init__()
        self.ready = False
        self.packet_handlers = {}
        self.org_id = None
        self.org_name = None
        self.superadmin = None
        self.status: BotStatus = BotStatus.SHUTDOWN
        self.dimension = None
        self.packet_queue = DelayQueue(2, 2.5)
        self.last_timer_event = 0

    def inject(self, registry):
        self.db = registry.get_instance("db")
        self.buddy_manager: BuddyManager = registry.get_instance(
            "buddy_manager")
        self.character_manager: CharacterManager = registry.get_instance(
            "character_manager")
        self.public_channel_manager: PublicChannelManager = registry.get_instance(
            "public_channel_manager")
        self.text: Text = registry.get_instance("text")
        self.setting_manager: SettingManager = registry.get_instance(
            "setting_manager")
        self.access_manager: AccessManager = registry.get_instance(
            "access_manager")
        self.command_manager = registry.get_instance("command_manager")
        self.event_manager = registry.get_instance("event_manager")
        self.job_scheduler = registry.get_instance("job_scheduler")

    def init(self, config, registry, paths, mmdb_parser):
        self.mmdb_parser = mmdb_parser
        self.superadmin = config.superadmin.capitalize()
        self.dimension = 5

        if config.database.type == "sqlite":
            self.db.connect_sqlite(config.database.name)
        elif config.database.type == "mysql":
            self.db.connect_mysql(config.database.host,
                                  config.database.username,
                                  config.database.password,
                                  config.database.name)
        else:
            raise Exception("Unknown database type '%s'" %
                            config.database.type)

        self.load_sql_files(paths)

        # prepare commands, events, and settings
        self.db.exec("UPDATE command_config SET verified = 0")
        self.db.exec("UPDATE event_config SET verified = 0")
        self.db.exec("UPDATE setting SET verified = 0")

        registry.pre_start_all()
        registry.start_all()

        # remove commands, events, and settings that are no longer registered
        self.db.exec("DELETE FROM command_config WHERE verified = 0")
        self.db.exec("DELETE FROM event_config WHERE verified = 0")
        self.db.exec(
            "DELETE FROM timer_event WHERE handler NOT IN (SELECT handler FROM event_config WHERE event_type = ?)",
            ["timer"])
        self.db.exec("DELETE FROM setting WHERE verified = 0")

        self.status = BotStatus.RUN

    def pre_start(self):
        self.access_manager.register_access_level("superadmin", 10,
                                                  self.check_superadmin)
        self.event_manager.register_event_type("connect")
        self.event_manager.register_event_type("packet")

    def start(self):
        self.setting_manager.register(
            "org_channel_max_page_length", 7500,
            "Maximum size of blobs in org channel",
            NumberSettingType([4500, 6000, 7500, 9000, 10500,
                               12000]), "core.system")
        self.setting_manager.register(
            "private_message_max_page_length",
            7500,
            "Maximum size of blobs in private messages",
            NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]),
            "core.system",
        )
        self.setting_manager.register(
            "private_channel_max_page_length", 7500,
            "Maximum size of blobs in private channel",
            NumberSettingType([4500, 6000, 7500, 9000, 10500,
                               12000]), "core.system")
        self.setting_manager.register("header_color",
                                      "#FFFF00", "color for headers",
                                      ColorSettingType(), "core.colors")
        self.setting_manager.register("header2_color", "#FCA712",
                                      "color for sub-headers",
                                      ColorSettingType(), "core.colors")
        self.setting_manager.register("highlight_color",
                                      "#FFFFFF", "color for highlight",
                                      ColorSettingType(), "core.colors")
        self.setting_manager.register("neutral_color", "#E6E1A6",
                                      "color for neutral faction",
                                      ColorSettingType(), "core.colors")
        self.setting_manager.register("omni_color", "#FA8484",
                                      "color for omni faction",
                                      ColorSettingType(), "core.colors")
        self.setting_manager.register("clan_color", "#F79410",
                                      "color for clan faction",
                                      ColorSettingType(), "core.colors")
        self.setting_manager.register("unknown_color", "#FF0000",
                                      "color for unknown faction",
                                      ColorSettingType(), "core.colors")
        self.setting_manager.register("notice_color", "#FF8C00",
                                      "color for important notices",
                                      ColorSettingType(), "core.colors")
        self.setting_manager.register(
            "symbol", "!", "Symbol for executing bot commands",
            TextSettingType(["!", "#", "*", "@", "$", "+", "-"]),
            "core.system")

    def check_superadmin(self, char_id):
        char_name = self.character_manager.resolve_char_to_name(char_id)
        return char_name == self.superadmin

    def run(self):
        while None is not self.iterate():
            pass

        self.event_manager.fire_event("connect", None)
        self.ready = True

        while self.status == BotStatus.RUN:
            try:
                timestamp = int(time.time())

                # timer events will execute not more often than once per second
                if self.last_timer_event < timestamp:
                    self.last_timer_event = timestamp
                    self.job_scheduler.check_for_scheduled_jobs(timestamp)
                    self.event_manager.check_for_timer_events(timestamp)

                self.iterate()
            except Exception as e:
                self.logger.error("", e)

        return self.status

    def add_packet_handler(self, packet_id, handler):
        handlers = self.packet_handlers.get(packet_id, [])
        handlers.append(handler)
        self.packet_handlers[packet_id] = handlers

    def iterate(self):
        packet = self.read_packet()
        if packet:
            if isinstance(packet, server_packets.PrivateMessage):
                self.handle_private_message(packet)
            elif isinstance(packet, server_packets.PublicChannelJoined):
                # set org id and org name
                if packet.channel_id >> 32 == 3:
                    self.org_id = 0x00ffffffff & packet.channel_id
                    if packet.name != "Clan (name unknown)":
                        self.org_name = packet.name
            elif isinstance(packet, server_packets.SystemMessage):
                try:
                    category_id = 20000
                    instance_id = packet.message_id
                    template = self.mmdb_parser.get_message_string(
                        category_id, instance_id)
                    params = self.mmdb_parser.parse_params(packet.message_args)
                    packet.extended_message = ExtendedMessage(
                        category_id, instance_id, template, params)
                    self.logger.log_chat("SystemMessage", None,
                                         packet.extended_message.get_message())
                except Exception as e:
                    self.logger.error("", e)
            elif isinstance(packet, server_packets.PublicChannelMessage):
                msg = packet.message
                if msg.startswith("~&") and msg.endswith("~"):
                    msg = msg[2:-1]
                    try:
                        category_id = self.mmdb_parser.read_base_85(msg[0:5])
                        instance_id = self.mmdb_parser.read_base_85(msg[5:10])
                        template = self.mmdb_parser.get_message_string(
                            category_id, instance_id)
                        params = self.mmdb_parser.parse_params(msg[10:])
                        packet.extended_message = ExtendedMessage(
                            category_id, instance_id, template, params)
                    except Exception as e:
                        self.logger.error("", e)

            for handler in self.packet_handlers.get(packet.id, []):
                handler(packet)

            self.event_manager.fire_event("packet:" + str(packet.id), packet)

        # check packet queue for outgoing packets
        outgoing_packet = self.packet_queue.dequeue()
        while outgoing_packet:
            self.send_packet(outgoing_packet)
            outgoing_packet = self.packet_queue.dequeue()

        return packet

    def send_org_message(self, msg):
        org_channel_id = self.public_channel_manager.org_channel_id
        if org_channel_id is None:
            self.logger.warning(
                "Could not send message to org channel, unknown org id")
        else:
            for page in self.get_text_pages(
                    msg,
                    self.setting_manager.get(
                        "org_channel_max_page_length").get_value()):
                packet = client_packets.PublicChannelMessage(
                    org_channel_id, page, "")
                # self.send_packet(packet)
                self.packet_queue.enqueue(packet)

    def send_private_message(self, char, msg):
        char_id = self.character_manager.resolve_char_to_id(char)
        if char_id is None:
            self.logger.warning(
                "Could not send message to %s, could not find char id" % char)
        else:
            for page in self.get_text_pages(
                    msg,
                    self.setting_manager.get(
                        "private_message_max_page_length").get_value()):
                self.logger.log_tell(
                    "To", self.character_manager.get_char_name(char_id), page)
                packet = client_packets.PrivateMessage(char_id, page, "\0")
                # self.send_packet(packet)
                self.packet_queue.enqueue(packet)

    def send_private_channel_message(self, msg, private_channel=None):
        if private_channel is None:
            private_channel = self.char_id

        private_channel_id = self.character_manager.resolve_char_to_id(
            private_channel)
        if private_channel_id is None:
            self.logger.warning(
                "Could not send message to private channel %s, could not find private channel"
                % private_channel)
        else:
            for page in self.get_text_pages(
                    msg,
                    self.setting_manager.get(
                        "private_channel_max_page_length").get_value()):
                packet = client_packets.PrivateChannelMessage(
                    private_channel_id, page, "\0")
                self.send_packet(packet)

    def handle_private_message(self, packet: server_packets.PrivateMessage):
        self.logger.log_tell(
            "From", self.character_manager.get_char_name(packet.char_id),
            packet.message)

    def handle_public_channel_message(
            self, packet: server_packets.PublicChannelMessage):
        self.logger.log_chat(
            self.public_channel_manager.get_channel_name(packet.channel_id),
            self.character_manager.get_char_name(packet.char_id),
            packet.message)

    def get_text_pages(self, msg, max_page_length):
        if isinstance(msg, ChatBlob):
            return self.text.paginate(msg.title, msg.msg, max_page_length,
                                      msg.max_num_pages, msg.footer)
        else:
            return [self.text.format_message(msg)]

    def is_ready(self):
        return self.ready

    def shutdown(self):
        self.status = BotStatus.SHUTDOWN

    def restart(self):
        self.status = BotStatus.RESTART

    def load_sql_files(self, paths):
        dirs = flatmap(lambda x: os.walk(x), paths)
        dirs = filter(lambda y: not y[0].endswith("__pycache__"), dirs)

        def get_files(tup):
            return map(lambda x: os.path.join(tup[0], x), tup[2])

        # get files from subdirectories
        files = flatmap(get_files, dirs)
        files = filter(lambda z: z.endswith(".sql"), files)

        base_path = os.getcwd()
        for file in files:
            self.db.load_sql_file(file, base_path)