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 __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 __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 __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})
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__()
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)
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())
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())
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
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__()
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)