class PrivateChannelManager: JOINED_PRIVATE_CHANNEL_EVENT = "private_channel_joined" LEFT_PRIVATE_CHANNEL_EVENT = "private_channel_left" def __init__(self): self.logger = Logger("private_channel_manager") self.private_channel_chars = {} def inject(self, registry): self.bot = registry.get_instance("bot") self.event_manager = registry.get_instance("event_manager") self.character_manager = registry.get_instance("character_manager") def pre_start(self): self.event_manager.register_event_type(self.JOINED_PRIVATE_CHANNEL_EVENT) self.event_manager.register_event_type(self.LEFT_PRIVATE_CHANNEL_EVENT) self.bot.add_packet_handler(server_packets.PrivateChannelClientJoined.id, self.handle_private_channel_client_joined) self.bot.add_packet_handler(server_packets.PrivateChannelClientLeft.id, self.handle_private_channel_client_left) self.bot.add_packet_handler(server_packets.PrivateChannelMessage.id, self.handle_private_channel_message) def handle_private_channel_message(self, packet: server_packets.PrivateChannelMessage): char_name = self.character_manager.get_char_name(packet.char_id) self.logger.log_chat("Private Channel", char_name, packet.message) def handle_private_channel_client_joined(self, packet: server_packets.PrivateChannelClientJoined): if packet.private_channel_id == self.bot.char_id: self.private_channel_chars[packet.char_id] = packet self.logger.log_chat("Private Channel", None, "%s joined the channel." % self.character_manager.get_char_name(packet.char_id)) self.event_manager.fire_event(self.JOINED_PRIVATE_CHANNEL_EVENT, packet) def handle_private_channel_client_left(self, packet: server_packets.PrivateChannelClientLeft): if packet.private_channel_id == self.bot.char_id: del self.private_channel_chars[packet.char_id] self.logger.log_chat("Private Channel", None, "%s left the channel." % self.character_manager.get_char_name(packet.char_id)) self.event_manager.fire_event(self.LEFT_PRIVATE_CHANNEL_EVENT, packet) def invite(self, char_id): self.bot.send_packet(client_packets.PrivateChannelInvite(char_id)) def kick(self, char_id): self.bot.send_packet(client_packets.PrivateChannelKick(char_id)) def kickall(self): self.bot.send_packet(client_packets.PrivateChannelKickAll()) def in_private_channel(self, char_id): return char_id in self.private_channel_chars
class PrivateChannelService: PRIVATE_CHANNEL_MESSAGE_EVENT = "private_channel_message" JOINED_PRIVATE_CHANNEL_EVENT = "private_channel_joined" LEFT_PRIVATE_CHANNEL_EVENT = "private_channel_left" def __init__(self): self.logger = Logger(__name__) self.private_channel_chars = {} def inject(self, registry): self.bot = registry.get_instance("bot") self.event_service = registry.get_instance("event_service") self.character_service = registry.get_instance("character_service") self.access_service = registry.get_instance("access_service") def pre_start(self): self.event_service.register_event_type(self.JOINED_PRIVATE_CHANNEL_EVENT) self.event_service.register_event_type(self.LEFT_PRIVATE_CHANNEL_EVENT) self.event_service.register_event_type(self.PRIVATE_CHANNEL_MESSAGE_EVENT) self.bot.register_packet_handler(server_packets.PrivateChannelClientJoined.id, self.handle_private_channel_client_joined) self.bot.register_packet_handler(server_packets.PrivateChannelClientLeft.id, self.handle_private_channel_client_left) # priority must be above that of CommandService in order for relaying of commands to work correctly self.bot.register_packet_handler(server_packets.PrivateChannelMessage.id, self.handle_private_channel_message, priority=30) self.access_service.register_access_level("guest", 90, self.in_private_channel) def handle_private_channel_message(self, conn: Conn, packet: server_packets.PrivateChannelMessage): if conn.id != "main": return if packet.private_channel_id == self.bot.get_char_id(): char_name = self.character_service.get_char_name(packet.char_id) self.logger.log_chat("Private Channel", char_name, packet.message) self.event_service.fire_event(self.PRIVATE_CHANNEL_MESSAGE_EVENT, packet) def handle_private_channel_client_joined(self, conn: Conn, packet: server_packets.PrivateChannelClientJoined): if conn.id != "main": return if packet.private_channel_id == self.bot.get_char_id(): self.private_channel_chars[packet.char_id] = packet self.logger.log_chat("Private Channel", None, "%s joined the channel." % self.character_service.get_char_name(packet.char_id)) self.event_service.fire_event(self.JOINED_PRIVATE_CHANNEL_EVENT, packet) def handle_private_channel_client_left(self, conn: Conn, packet: server_packets.PrivateChannelClientLeft): if conn.id != "main": return if packet.private_channel_id == self.bot.get_char_id(): del self.private_channel_chars[packet.char_id] self.logger.log_chat("Private Channel", None, "%s left the channel." % self.character_service.get_char_name(packet.char_id)) self.event_service.fire_event(self.LEFT_PRIVATE_CHANNEL_EVENT, packet) def invite(self, char_id): if char_id != self.bot.get_char_id(): self.bot.send_packet(client_packets.PrivateChannelInvite(char_id)) def kick(self, char_id): if char_id != self.bot.get_char_id(): self.bot.send_packet(client_packets.PrivateChannelKick(char_id)) def kickall(self): self.bot.send_packet(client_packets.PrivateChannelKickAll()) def in_private_channel(self, char_id): return char_id in self.private_channel_chars def get_all_in_private_channel(self): return self.private_channel_chars
class PublicChannelService: ORG_CHANNEL_COMMAND_EVENT = "org_channel_command" ORG_CHANNEL_MESSAGE_EVENT = "org_channel_message" ORG_MSG_EVENT = "org_msg" ORG_CHANNEL_COMMAND = "org" ORG_MSG_CHANNEL_ID = 42949672961 def __init__(self): self.logger = Logger(__name__) def inject(self, registry): self.bot = registry.get_instance("bot") self.db = registry.get_instance("db") self.event_service = registry.get_instance("event_service") self.character_service = registry.get_instance("character_service") self.setting_service = registry.get_instance("setting_service") self.command_service = registry.get_instance("command_service") def pre_start(self): self.bot.register_packet_handler(server_packets.LoginOK.id, self.handle_login_ok) self.bot.register_packet_handler(server_packets.PublicChannelJoined.id, self.add) self.bot.register_packet_handler(server_packets.PublicChannelLeft.id, self.remove) self.bot.register_packet_handler( server_packets.PublicChannelMessage.id, self.public_channel_message) self.event_service.register_event_type(self.ORG_CHANNEL_COMMAND_EVENT) self.event_service.register_event_type(self.ORG_CHANNEL_MESSAGE_EVENT) self.event_service.register_event_type(self.ORG_MSG_EVENT) self.command_service.register_command_channel("Org Channel", self.ORG_CHANNEL_COMMAND) def start(self): self.db.exec( "CREATE TABLE IF NOT EXISTS org_name_cache (org_id INT NOT NULL, name VARCHAR(255) NOT NULL)" ) def handle_login_ok(self, conn: Conn, packet: server_packets.LoginOK): if not conn.is_main: return def add(self, conn: Conn, packet: server_packets.PublicChannelJoined): if not conn.is_main: return conn.channels[packet.channel_id] = packet if not conn.org_id and self.is_org_channel_id(packet.channel_id): conn.org_channel_id = packet.channel_id conn.org_id = 0x00ffffffff & packet.channel_id row = self.db.query_single( "SELECT name FROM org_name_cache WHERE org_id = ?", [conn.org_id]) if packet.name != "Clan (name unknown)": source = "chat_server" if not row: self.db.exec( "INSERT INTO org_name_cache (org_id, name) VALUES (?, ?)", [conn.org_id, packet.name]) elif packet.name != row.name: self.db.exec( "UPDATE org_name_cache SET name = ? WHERE org_id = ?", [packet.name, conn.org_id]) conn.org_name = packet.name elif row: source = "cache" conn.org_name = row.name else: source = "none" self.logger.info( f"Org info for '{conn.id}': {conn.org_name} ({conn.org_id}); source: '{source}'" ) def remove(self, conn: Conn, packet: server_packets.PublicChannelLeft): if not conn.is_main: return del conn.channels[packet.channel_id] def public_channel_message(self, conn: Conn, packet: server_packets.PublicChannelMessage): if not conn.is_main: return if conn.org_channel_id == packet.channel_id: char_name = self.character_service.get_char_name(packet.char_id) if packet.extended_message: message = packet.extended_message.get_message() else: message = packet.message self.logger.log_chat(conn, "Org Channel", char_name, message) if conn.char_id == packet.char_id: return if not self.handle_public_channel_command(conn, packet): self.event_service.fire_event( self.ORG_CHANNEL_MESSAGE_EVENT, DictObject({ "char_id": packet.char_id, "name": char_name, "message": message, "extended_message": packet.extended_message, "conn": conn })) elif packet.channel_id == self.ORG_MSG_CHANNEL_ID: char_name = self.character_service.get_char_name(packet.char_id) if packet.extended_message: message = packet.extended_message.get_message() else: message = packet.message self.logger.log_chat(conn, "Org Msg", char_name, message) self.event_service.fire_event( self.ORG_MSG_EVENT, DictObject({ "char_id": packet.char_id, "name": char_name, "message": packet.message, "extended_message": packet.extended_message, "conn": conn })) def handle_public_channel_command( self, conn: Conn, packet: server_packets.PublicChannelMessage): if not self.setting_service.get("accept_commands_from_slave_bots" ).get_value() and not conn.is_main: return False # since the command symbol is required in the org channel, # the command_str must have length of at least 2 in order to be valid, # otherwise it is ignored if len(packet.message) < 2: return False # ignore leading space message = packet.message.lstrip() def reply(msg): self.bot.send_org_message(msg, conn=conn) self.event_service.fire_event( self.ORG_CHANNEL_COMMAND_EVENT, DictObject({ "char_id": None, "name": None, "message": msg, "conn": conn })) if message.startswith(self.setting_service.get("symbol").get_value() ) and conn.org_channel_id == packet.channel_id: char_name = self.character_service.get_char_name(packet.char_id) self.event_service.fire_event( self.ORG_CHANNEL_COMMAND_EVENT, DictObject({ "char_id": packet.char_id, "name": char_name, "message": packet.message, "conn": conn })) self.command_service.process_command( self.command_service.trim_command_symbol(message), self.ORG_CHANNEL_COMMAND, packet.char_id, reply, conn) return True else: return False def is_org_channel_id(self, channel_id): return channel_id >> 32 == 3
class Tyrbot: CONNECT_EVENT = "connect" PRIVATE_MSG_EVENT = "private_msg" def __init__(self): super().__init__() self.logger = Logger(__name__) self.ready = False self.packet_handlers = {} self.superadmin = None self.status: BotStatus = BotStatus.SHUTDOWN self.dimension = None self.last_timer_event = 0 self.start_time = int(time.time()) self.version = "0.7-beta" self.incoming_queue = FifoQueue() self.mass_message_queue = None self.conns = DictObject() self.primary_conn_id = None 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, mmdb_parser): self.mmdb_parser = mmdb_parser self.superadmin = config.superadmin.capitalize() self.dimension = config.server.dimension self.db.exec( "CREATE TABLE IF NOT EXISTS command_config (command VARCHAR(50) NOT NULL, sub_command VARCHAR(50) NOT NULL, access_level VARCHAR(50) NOT NULL, channel VARCHAR(50) NOT NULL, " "module VARCHAR(50) NOT NULL, enabled SMALLINT NOT NULL, verified SMALLINT NOT NULL)" ) self.db.exec( "CREATE TABLE IF NOT EXISTS event_config (event_type VARCHAR(50) NOT NULL, event_sub_type VARCHAR(50) NOT NULL, handler VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, " "module VARCHAR(50) NOT NULL, enabled SMALLINT NOT NULL, verified SMALLINT NOT NULL, is_hidden SMALLINT NOT NULL)" ) self.db.exec( "CREATE TABLE IF NOT EXISTS timer_event (event_type VARCHAR(50) NOT NULL, event_sub_type VARCHAR(50) NOT NULL, handler VARCHAR(255) NOT NULL, next_run INT NOT NULL)" ) self.db.exec( "CREATE TABLE IF NOT EXISTS setting (name VARCHAR(50) NOT NULL, value VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, module VARCHAR(50) NOT NULL, verified SMALLINT NOT NULL)" ) self.db.exec( "CREATE TABLE IF NOT EXISTS command_alias (alias VARCHAR(50) NOT NULL, command VARCHAR(1024) NOT NULL, enabled SMALLINT NOT NULL)" ) self.db.exec( "CREATE TABLE IF NOT EXISTS command_usage (command VARCHAR(255) NOT NULL, handler VARCHAR(255) NOT NULL, char_id INT NOT NULL, channel VARCHAR(20) NOT NULL, created_at INT NOT NULL)" ) self.db.exec( "CREATE TABLE IF NOT EXISTS ban_list (char_id INT NOT NULL, sender_char_id INT NOT NULL, created_at INT NOT NULL, finished_at INT NOT NULL, reason VARCHAR(255) NOT NULL, ended_early SMALLINT NOT NULL)" ) self.db.exec("UPDATE db_version SET verified = 0") self.db.exec( "UPDATE db_version SET verified = 1 WHERE file = 'db_version'") # 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.PRIVATE_MSG_EVENT) def start(self): self.setting_service.register( "core.system", "symbol", "!", TextSettingType(["!", "#", "*", "@", "$", "+", "-"]), "Symbol for executing bot commands") self.setting_service.register( "core.system", "org_channel_max_page_length", 7500, NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]), "Maximum size of blobs in org channel") self.setting_service.register( "core.system", "private_message_max_page_length", 7500, NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]), "Maximum size of blobs in private messages") self.setting_service.register( "core.system", "private_channel_max_page_length", 7500, NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]), "Maximum size of blobs in private channel") self.setting_service.register( "core.system", "accept_commands_from_slave_bots", False, BooleanSettingType(), "Accept and respond to commands sent to slave bots (only applies if you have added slave bots in the config)" ) self.setting_service.register("core.colors", "header_color", "#FFFF00", ColorSettingType(), "Color for headers") self.setting_service.register("core.colors", "header2_color", "#FCA712", ColorSettingType(), "Color for sub-headers") self.setting_service.register("core.colors", "highlight_color", "#00BFFF", ColorSettingType(), "Color for highlight") self.setting_service.register("core.colors", "notice_color", "#FF8C00", ColorSettingType(), "Color for important notices") self.setting_service.register("core.colors", "neutral_color", "#E6E1A6", ColorSettingType(), "Color for neutral faction") self.setting_service.register("core.colors", "omni_color", "#FA8484", ColorSettingType(), "Color for omni faction") self.setting_service.register("core.colors", "clan_color", "#F79410", ColorSettingType(), "Color for clan faction") self.setting_service.register("core.colors", "unknown_color", "#FF0000", ColorSettingType(), "Color for unknown faction") self.setting_service.register("core.colors", "org_channel_color", "#89D2E8", ColorSettingType(), "Default org channel color") self.setting_service.register("core.colors", "private_channel_color", "#89D2E8", ColorSettingType(), "Default private channel color") self.setting_service.register("core.colors", "private_message_color", "#89D2E8", ColorSettingType(), "Default private message color") self.setting_service.register("core.colors", "blob_color", "#FFFFFF", ColorSettingType(), "Default blob content color") self.register_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 connect(self, config): for i, bot in enumerate(config.bots): if "id" in bot: _id = bot.id else: _id = "bot" + str(i) if i == 0: self.primary_conn_id = _id conn = self.create_conn(_id) conn.connect(config.server.host, config.server.port) # only create the mass_message_queue if there is at least 1 non-main bot if not bot.is_main and not self.mass_message_queue: self.mass_message_queue = FifoQueue() packet = conn.login(bot.username, bot.password, bot.character, is_main=bot.is_main) if not packet: self.status = BotStatus.ERROR return False else: self.incoming_queue.put((conn, packet)) self.create_conn_thread( conn, None if bot.is_main else self.mass_message_queue) return True def create_conn_thread(self, conn: Conn, mass_message_queue=None): def read_packets(): try: while self.status == BotStatus.RUN: packet = conn.read_packet(1) if packet: self.incoming_queue.put((conn, packet)) while mass_message_queue and not mass_message_queue.empty( ) and conn.packet_queue.is_empty(): packet = mass_message_queue.get_or_default(block=False) if packet: conn.add_packet_to_queue(packet) except (EOFError, OSError) as e: self.status = BotStatus.ERROR self.logger.error("", e) raise e dthread = threading.Thread(target=read_packets, daemon=True) dthread.start() def create_conn(self, _id): if _id in self.conns: raise Exception(f"A connection with id {_id} already exists") def failure_callback(): self.status = BotStatus.ERROR conn = Conn(_id, failure_callback) self.conns[_id] = conn return conn def disconnect(self): # wait for all threads to stop reading packets, then disconnect them all time.sleep(2) for _id, conn in self.get_conns(): conn.disconnect() def run(self): start = time.time() # wait for flood of packets from login to stop sending time_waited = 0 while time_waited < 2: if not self.iterate(1): time_waited += 1 self.logger.info("Login complete (%fs)" % (time.time() - start)) start = time.time() self.event_service.fire_event("connect", None) self.event_service.run_timer_events_at_startup() self.event_service.check_for_timer_events(int(start)) self.logger.info("Connect events finished (%fs)" % (time.time() - start)) time_waited = 0 while time_waited < 2: if not self.iterate(1): time_waited += 1 self.ready = True timestamp = int(time.time()) while self.status == BotStatus.RUN: try: timestamp = int(time.time()) self.check_for_timer_events(timestamp) self.iterate() except Exception as e: self.logger.error("", e) # run any pending jobs/events self.check_for_timer_events(timestamp + 1) return self.status def check_for_timer_events(self, timestamp): # 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) def register_packet_handler(self, packet_id: int, handler, priority=50): """ Call during pre_start Args: packet_id: int handler: (conn, packet) -> void priority: int """ if len(inspect.signature(handler).parameters) != 2: raise Exception( "Incorrect number of arguments for handler '%s.%s()'" % (handler.__module__, handler.__name__)) 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, timeout=0.1): conn, packet = self.incoming_queue.get_or_default(block=True, timeout=timeout, default=(None, None)) if packet: if isinstance(packet, server_packets.SystemMessage): packet = self.system_message_ext_msg_handling(packet) self.logger.log_chat(conn, "SystemMessage", None, packet.extended_message.get_message()) elif isinstance(packet, server_packets.PublicChannelMessage): packet = self.public_channel_message_ext_msg_handling(packet) elif isinstance(packet, server_packets.BuddyAdded) and packet.char_id == 0: return for handler in self.packet_handlers.get(packet.id, []): handler.handler(conn, packet) return packet 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) 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, conn=None): if not conn: conn = self.get_primary_conn() if not conn.org_channel_id: self.logger.debug( f"Ignoring message to org channel for {conn.id} 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, conn, self.setting_service.get( "org_channel_max_page_length").get_value()) for page in pages: packet = client_packets.PublicChannelMessage( conn.org_channel_id, color + page, "") conn.add_packet_to_queue(packet) def send_private_message(self, char_id, msg, add_color=True, conn=None): if not conn: conn = self.get_primary_conn() if char_id is None: raise Exception("Cannot send message, char_id is empty") else: color = self.setting_service.get( "private_message_color").get_font_color() if add_color else "" pages = self.get_text_pages( msg, conn, self.setting_service.get( "private_message_max_page_length").get_value()) for page in pages: self.logger.log_tell( conn, "To", self.character_service.get_char_name(char_id), page) packet = client_packets.PrivateMessage(char_id, color + page, "\0") conn.add_packet_to_queue(packet) def send_private_channel_message(self, msg, private_channel_id=None, add_color=True, conn=None): if not conn: conn = self.get_primary_conn() if private_channel_id is None: private_channel_id = conn.get_char_id() color = self.setting_service.get( "private_channel_color").get_font_color() if add_color else "" pages = self.get_text_pages( msg, conn, 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") conn.send_packet(packet) def send_mass_message(self, char_id, msg, add_color=True, conn=None): if not conn: conn = self.get_primary_conn() if not char_id: self.logger.warning("Could not send message to empty char_id") else: color = self.setting_service.get( "private_message_color").get_font_color() if add_color else "" pages = self.get_text_pages( msg, conn, self.setting_service.get( "private_message_max_page_length").get_value()) for page in pages: if self.mass_message_queue: packet = client_packets.PrivateMessage( char_id, color + page, "\0") self.mass_message_queue.put(packet) else: packet = client_packets.PrivateMessage( char_id, color + page, "spam") self.get_primary_conn().send_packet(packet) def send_message_to_other_org_channels(self, msg, from_conn: Conn): for _id, conn in self.get_conns( lambda x: x.is_main and x.org_id and x != from_conn): self.send_org_message(msg, conn=conn) def handle_private_message(self, conn: Conn, packet: server_packets.PrivateMessage): char_name = self.character_service.get_char_name(packet.char_id) self.logger.log_tell(conn, "From", char_name, packet.message) self.event_service.fire_event( self.PRIVATE_MSG_EVENT, DictObject({ "char_id": packet.char_id, "name": char_name, "message": packet.message, "conn": conn })) def get_text_pages(self, msg, conn, max_page_length): if isinstance(msg, ChatBlob): return self.text.paginate(msg, conn, max_page_length=max_page_length) else: return [self.text.format_message(msg, conn)] def is_ready(self): return self.ready def shutdown(self): self.status = BotStatus.SHUTDOWN def restart(self): self.status = BotStatus.RESTART def get_primary_conn_id(self): return self.primary_conn_id def get_primary_conn(self): return self.conns[self.get_primary_conn_id()] def get_conn_by_char_id(self, char_id): for _id, conn in self.get_conns(): if char_id == conn.get_char_id(): return conn return None def get_conn_by_org_id(self, org_id): for _id, conn in self.get_conns(): if conn.org_id == org_id: return conn return None # placeholder to keep track of things that need to be fixed/updated def get_temp_conn(self): return self.get_primary_conn() def get_conns(self, conn_filter=None): if conn_filter: return [(_id, conn) for _id, conn in self.conns.items() if conn_filter(conn)] else: return self.conns.items()
class Tyrbot: 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.logger = Logger(__name__) self.ready = False self.packet_handlers = {} self.superadmin = None self.status: BotStatus = BotStatus.SHUTDOWN self.dimension = None self.last_timer_event = 0 self.start_time = int(time.time()) self.version = "0.5-beta" self.incoming_queue = FifoQueue() self.mass_message_queue = None self.conns = DictObject() 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_new("core.system", "symbol", "!", TextSettingType(["!", "#", "*", "@", "$", "+", "-"]), "Symbol for executing bot commands") self.setting_service.register_new("core.system", "org_channel_max_page_length", 7500, NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]), "Maximum size of blobs in org channel") self.setting_service.register_new("core.system", "private_message_max_page_length", 7500, NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]), "Maximum size of blobs in private messages") self.setting_service.register_new("core.system", "private_channel_max_page_length", 7500, NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]), "Maximum size of blobs in private channel") self.setting_service.register_new("core.system", "org_id", "", NumberSettingType(allow_empty=True), "Override the default org id", "This setting is is for development/debug purposes and should not be changed unless you understand the implications") self.setting_service.register_new("core.system", "org_name", "", TextSettingType(allow_empty=True), "The exact org name of the bot", "This setting is automatically set by the bot and should not be changed manually") self.setting_service.register_new("core.colors", "header_color", "#FFFF00", ColorSettingType(), "Color for headers") self.setting_service.register_new("core.colors", "header2_color", "#FCA712", ColorSettingType(), "Color for sub-headers") self.setting_service.register_new("core.colors", "highlight_color", "#00BFFF", ColorSettingType(), "Color for highlight") self.setting_service.register_new("core.colors", "notice_color", "#FF8C00", ColorSettingType(), "Color for important notices") self.setting_service.register_new("core.colors", "neutral_color", "#E6E1A6", ColorSettingType(), "Color for neutral faction") self.setting_service.register_new("core.colors", "omni_color", "#FA8484", ColorSettingType(), "Color for omni faction") self.setting_service.register_new("core.colors", "clan_color", "#F79410", ColorSettingType(), "Color for clan faction") self.setting_service.register_new("core.colors", "unknown_color", "#FF0000", ColorSettingType(), "Color for unknown faction") self.setting_service.register_new("core.colors", "org_channel_color", "#89D2E8", ColorSettingType(), "Default org channel color") self.setting_service.register_new("core.colors", "private_channel_color", "#89D2E8", ColorSettingType(), "Default private channel color") self.setting_service.register_new("core.colors", "private_message_color", "#89D2E8", ColorSettingType(), "Default private message color") self.setting_service.register_new("core.colors", "blob_color", "#FFFFFF", ColorSettingType(), "Default blob content color") self.register_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 connect(self, config): conn = self.create_conn("main") conn.connect(config.server.host, config.server.port) packet = conn.login(config.username, config.password, config.character) if not packet: self.status = BotStatus.ERROR return False else: self.incoming_queue.put((conn, packet)) self.mass_message_queue = FifoQueue() self.create_conn_thread(conn, self.mass_message_queue) if "slaves" in config: for i, slave in enumerate(config.slaves): conn = self.create_conn("slave" + str(i)) conn.connect(config.server.host, config.server.port) packet = conn.login(slave.username, slave.password, slave.character) if not packet: self.status = BotStatus.ERROR return False else: self.incoming_queue.put((conn, packet)) self.create_conn_thread(conn, self.mass_message_queue) return True def create_conn_thread(self, conn: Conn, mass_message_queue=None): def read_packets(): try: while self.status == BotStatus.RUN: packet = conn.read_packet(1) if packet: self.incoming_queue.put((conn, packet)) while mass_message_queue and not mass_message_queue.empty() and conn.packet_queue.is_empty(): packet = mass_message_queue.get_or_default(block=False) if packet: conn.add_packet_to_queue(packet) except (EOFError, OSError) as e: self.status = BotStatus.ERROR self.logger.error("", e) raise e dthread = threading.Thread(target=read_packets, daemon=True) dthread.start() def create_conn(self, _id): if _id in self.conns: raise Exception(f"A connection with id {_id} already exists") conn = Conn(_id, self.disconnect) self.conns[_id] = conn return conn # passthrough def send_packet(self, packet): self.conns["main"].send_packet(packet) def disconnect(self): # wait for all threads to stop reading packets, then disconnect them all time.sleep(2) for _id, conn in self.conns.items(): conn.disconnect() 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(1): time_waited += 1 self.logger.info("Login complete (%fs)" % (time.time() - start)) start = time.time() self.event_service.fire_event("connect", None) self.event_service.run_timer_events_at_startup() self.logger.info("Connect events finished (%fs)" % (time.time() - start)) self.ready = True timestamp = int(time.time()) while self.status == BotStatus.RUN: try: timestamp = int(time.time()) self.check_for_timer_events(timestamp) self.iterate() except (EOFError, OSError) as e: raise e except Exception as e: self.logger.error("", e) # run any pending jobs/events self.check_for_timer_events(timestamp + 1) return self.status def check_for_timer_events(self, timestamp): # 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) def register_packet_handler(self, packet_id: int, handler, priority=50): """ Call during pre_start Args: packet_id: int handler: (conn, packet) -> void priority: int """ if len(inspect.signature(handler).parameters) != 2: raise Exception("Incorrect number of arguments for handler '%s.%s()'" % (handler.__module__, handler.__name__)) 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, timeout=0.1): conn, packet = self.incoming_queue.get_or_default(block=True, timeout=timeout, default=(None, None)) 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(conn, packet) self.event_service.fire_event("packet:" + str(packet.id), packet) return packet 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, conn_id="main"): 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.conns[conn_id].add_packet_to_queue(packet) 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, conn_id="main"): 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.conns[conn_id].add_packet_to_queue(packet) 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, conn_id="main"): if private_channel is None: private_channel_id = self.get_char_id() else: 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.conns[conn_id].send_packet(packet) if fire_outgoing_event and private_channel_id == self.get_char_id(): self.event_service.fire_event(self.OUTGOING_PRIVATE_CHANNEL_MESSAGE_EVENT, DictObject({"private_channel_id": private_channel_id, "message": msg})) def send_mass_message(self, char_id, msg, add_color=True): if not char_id: self.logger.warning("Could not send message to empty char_id") 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) if self.mass_message_queue: packet = client_packets.PrivateMessage(char_id, color + page, "\0") self.mass_message_queue.put(packet) else: packet = client_packets.PrivateMessage(char_id, color + page, "spam") self.conns["main"].send_packet(packet) def handle_private_message(self, conn: Conn, packet: server_packets.PrivateMessage): if conn.id != "main": return 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 get_char_name(self): return self.conns["main"].char_name def get_char_id(self): return self.conns["main"].char_id
class DarkController: relay_channel_id = None relay_name = None MESSAGE_SOURCE = "darknet" message_regex = re.compile(r"^(<font color='#\S+'>){2}\[([a-zA-Z]{2,})\]<\/font> <font color='#\S+'>(.+)<\/font> <font color='#\S+'>\[(.+)\]<\/font> \[(.+)\]$", re.DOTALL) def __init__(self): self.logger = Logger(__name__) def inject(self, registry): self.bot: Tyrbot = registry.get_instance("bot") self.setting_service: SettingService = registry.get_instance("setting_service") self.character_service: CharacterService = registry.get_instance("character_service") self.message_hub_service = registry.get_instance("message_hub_service") def pre_start(self): self.bot.register_packet_handler(server_packets.PrivateChannelInvited.id, self.handle_private_channel_invite, 50) self.bot.register_packet_handler(server_packets.PrivateChannelMessage.id, self.handle_private_channel_message) self.message_hub_service.register_message_source(self.MESSAGE_SOURCE) def start(self): self.setting_service.register(self.module_name, "dark_relay", "false", BooleanSettingType(), "Is the Module Enabled?") self.setting_service.register(self.module_name, "dark_wts", "true", BooleanSettingType(), "Is the WTS channel visible?") self.setting_service.register(self.module_name, "dark_wtb", "true", BooleanSettingType(), "Is the WTB channel visible?") self.setting_service.register(self.module_name, "dark_lr", "true", BooleanSettingType(), "Is the Lootrights channel visible?") self.setting_service.register(self.module_name, "dark_gen", "true", BooleanSettingType(), "Is the General channel visible?") self.setting_service.register(self.module_name, "dark_pvp", "true", BooleanSettingType(), "Is the PvP channel visible?") self.setting_service.register(self.module_name, "dark_pvm", "true", BooleanSettingType(), "Is the PVM channel visible?") self.setting_service.register(self.module_name, "dark_event", "true", BooleanSettingType(), "Is the Event channel visible?") def handle_private_channel_invite(self, conn: Conn, packet: server_packets.PrivateChannelInvited): if not conn.is_main: pass if self.setting_service.get_value("dark_relay") == "0": return if "Darknet" == self.character_service.get_char_name(packet.private_channel_id): channel_name = self.character_service.get_char_name(packet.private_channel_id) conn.send_packet(client_packets.PrivateChannelJoin(packet.private_channel_id)) self.logger.info("Joined private channel {channel}".format(channel=channel_name)) self.relay_channel_id = packet.private_channel_id self.relay_name = channel_name def handle_private_channel_message(self, conn, packet: server_packets.PrivateChannelMessage): if not conn.is_main: pass if self.setting_service.get_value("dark_relay") == "0": return if packet.private_channel_id == self.relay_channel_id: if conn.get_char_id() == packet.char_id: return if packet.char_id != self.relay_channel_id: return channel_name = self.character_service.get_char_name(packet.private_channel_id) char_name = self.character_service.get_char_name(packet.char_id) self.logger.log_chat(conn, "Private Channel(%s)" % channel_name, char_name, packet.message) message = packet.message.lstrip() self.process_incoming_relay_message(message) def process_incoming_relay_message(self, message): if re.search(self.message_regex, message): cont = re.findall(self.message_regex, message) cont = cont[0] ch = cont[1].lower() msg = cont[2] tell = cont[3] report = cont[4] if ch == "wts": if self.setting_service.get_value("dark_wts") == "0": return channel = "<red>[WTS]</red>" elif ch == "wtb": if self.setting_service.get_value("dark_wtb") == "0": return channel = "<green>[WTB]</green>" elif ch == "lootrights": if self.setting_service.get_value("dark_lr") == "0": return channel = "<violet>[LR]</violet>" elif ch == "general": if self.setting_service.get_value("dark_gen") == "0": return channel = "<notice>[Gen]</notice>" elif ch == "pvm": if self.setting_service.get_value("dark_pvm") == "0": return channel = "<cyan>[PvM]</cyan>" elif ch == "event": if self.setting_service.get_value("dark_event") == "0": return channel = "<highlight>[Event]</highlight>" elif ch == "pvp": if self.setting_service.get_value("dark_pvp") == "0": return channel = "<grey>[PvP]</grey>" elif ch == "auction": channel = "<yellow>[AUCTION]</yellow>" else: return message = "<orange>%s<end> [%s] [%s]" % (msg, tell, report) self.message_hub_service.send_message(self.MESSAGE_SOURCE, None, channel, message)
class DarknetController: relay_channel_id = None relay_name = None MESSAGE_SOURCE = "darknet" message_regex = re.compile( r"^(<font color='#\S+'>){2}\[([a-zA-Z]{2,})\]<\/font> (.+)$", re.DOTALL) def __init__(self): self.logger = Logger(__name__) def inject(self, registry): self.bot: Tyrbot = registry.get_instance("bot") self.setting_service: SettingService = registry.get_instance( "setting_service") self.character_service: CharacterService = registry.get_instance( "character_service") self.message_hub_service = registry.get_instance("message_hub_service") def pre_start(self): self.bot.register_packet_handler( server_packets.PrivateChannelInvited.id, self.handle_private_channel_invite, 50) self.bot.register_packet_handler( server_packets.PrivateChannelMessage.id, self.handle_private_channel_message) self.message_hub_service.register_message_source(self.MESSAGE_SOURCE) def start(self): self.setting_service.register(self.module_name, "dark_relay", "false", BooleanSettingType(), "Is the Module Enabled?") self.setting_service.register(self.module_name, "dark_wts", "true", BooleanSettingType(), "Is the WTS channel visible?") self.setting_service.register(self.module_name, "dark_wtb", "true", BooleanSettingType(), "Is the WTB channel visible?") self.setting_service.register(self.module_name, "dark_lr", "true", BooleanSettingType(), "Is the Lootrights channel visible?") self.setting_service.register(self.module_name, "dark_gen", "true", BooleanSettingType(), "Is the General channel visible?") self.setting_service.register(self.module_name, "dark_pvp", "true", BooleanSettingType(), "Is the PvP channel visible?") self.setting_service.register(self.module_name, "dark_pvm", "true", BooleanSettingType(), "Is the PVM channel visible?") self.setting_service.register(self.module_name, "dark_event", "true", BooleanSettingType(), "Is the Event channel visible?") def handle_private_channel_invite( self, conn: Conn, packet: server_packets.PrivateChannelInvited): if not conn.is_main: pass if self.setting_service.get_value("dark_relay") == "0": return if "Darknet" == self.character_service.get_char_name( packet.private_channel_id): channel_name = self.character_service.get_char_name( packet.private_channel_id) conn.send_packet( client_packets.PrivateChannelJoin(packet.private_channel_id)) self.logger.info("Joined private channel {channel}".format( channel=channel_name)) self.relay_channel_id = packet.private_channel_id self.relay_name = channel_name def handle_private_channel_message( self, conn, packet: server_packets.PrivateChannelMessage): if not conn.is_main: pass if self.setting_service.get_value("dark_relay") == "0": return if packet.private_channel_id == self.relay_channel_id: if packet.char_id != self.relay_channel_id: return channel_name = self.character_service.get_char_name( packet.private_channel_id) char_name = self.character_service.get_char_name(packet.char_id) self.logger.log_chat(conn, "Private Channel(%s)" % channel_name, char_name, packet.message) message = packet.message.lstrip() self.process_incoming_relay_message(message) def process_incoming_relay_message(self, message): if re.search(self.message_regex, message): cont = re.findall(self.message_regex, message) cont = cont[0] channel = cont[1] ch = channel.lower() rest_of_message = cont[2] if ch == "wts" and self.setting_service.get_value( "dark_wts") == "0": return elif ch == "wtb" and self.setting_service.get_value( "dark_wtb") == "0": return elif ch == "lootrights" and self.setting_service.get_value( "dark_lr") == "0": return elif ch == "general" and self.setting_service.get_value( "dark_gen") == "0": return elif ch == "pvm" and self.setting_service.get_value( "dark_pvm") == "0": return elif ch == "event" and self.setting_service.get_value( "dark_event") == "0": return elif ch == "pvp" and self.setting_service.get_value( "dark_pvp") == "0": return if ch == "lootrights": channel = "LR" elif ch == "gen": channel = "Gen" channel_formatted = "[<highlight>%s</highlight>]" % channel self.message_hub_service.send_message(self.MESSAGE_SOURCE, None, channel_formatted, rest_of_message)
class DarknetController: relay_channel_id = None relay_name = None MESSAGE_SOURCE = "darknet" message_regex = re.compile( r"^(<font color='#\S+'>){2}\[([a-zA-Z]{2,})\]<\/font> (.+)$", re.DOTALL) DARKNET_NAME = "Darknet" def __init__(self): self.logger = Logger(__name__) def inject(self, registry): self.bot: Tyrbot = registry.get_instance("bot") self.setting_service: SettingService = registry.get_instance( "setting_service") self.character_service: CharacterService = registry.get_instance( "character_service") self.message_hub_service = registry.get_instance("message_hub_service") def pre_start(self): self.bot.register_packet_handler( server_packets.PrivateChannelInvited.id, self.handle_private_channel_invite, 50) self.bot.register_packet_handler( server_packets.PrivateChannelMessage.id, self.handle_private_channel_message) self.bot.register_packet_handler(server_packets.PrivateMessage.id, self.handle_private_message) self.message_hub_service.register_message_source(self.MESSAGE_SOURCE) def start(self): self.setting_service.register( self.module_name, "dark_relay", "false", BooleanSettingType(), "Is the Module Enabled?", extended_description= "Use !messagehub to control where Darknet messages are relayed to") self.setting_service.register(self.module_name, "dark_wts", "true", BooleanSettingType(), "Is the WTS channel visible?") self.setting_service.register(self.module_name, "dark_wtb", "true", BooleanSettingType(), "Is the WTB channel visible?") self.setting_service.register(self.module_name, "dark_lr", "true", BooleanSettingType(), "Is the Lootrights channel visible?") self.setting_service.register(self.module_name, "dark_gen", "true", BooleanSettingType(), "Is the General channel visible?") self.setting_service.register(self.module_name, "dark_pvp", "true", BooleanSettingType(), "Is the PvP channel visible?") self.setting_service.register(self.module_name, "dark_pvm", "true", BooleanSettingType(), "Is the PVM channel visible?") self.setting_service.register(self.module_name, "dark_event", "true", BooleanSettingType(), "Is the Event channel visible?") self.setting_service.register_change_listener( "dark_relay", self.update_darket_status) def handle_private_channel_invite( self, conn: Conn, packet: server_packets.PrivateChannelInvited): if not conn.is_main: pass if self.setting_service.get_value("dark_relay") == "0": return if self.DARKNET_NAME == self.character_service.get_char_name( packet.private_channel_id): channel_name = self.character_service.get_char_name( packet.private_channel_id) conn.send_packet( client_packets.PrivateChannelJoin(packet.private_channel_id)) self.logger.info("Joined private channel {channel}".format( channel=channel_name)) self.relay_channel_id = packet.private_channel_id self.relay_name = channel_name def handle_private_channel_message( self, conn, packet: server_packets.PrivateChannelMessage): if not conn.is_main: pass if self.setting_service.get_value("dark_relay") == "0": return if packet.private_channel_id == self.relay_channel_id: if packet.char_id != self.relay_channel_id: return channel_name = self.character_service.get_char_name( packet.private_channel_id) char_name = self.character_service.get_char_name(packet.char_id) self.logger.log_chat(conn, "Private Channel(%s)" % channel_name, char_name, packet.message) message = packet.message.lstrip() self.process_incoming_relay_message(message) def handle_private_message(self, conn, packet: server_packets.PrivateMessage): if not conn.is_main: pass #if self.setting_service.get_value("dark_relay") == "0": # return char_id = self.character_service.resolve_char_to_id(self.DARKNET_NAME) if packet.char_id == char_id: message = packet.message.lstrip() self.message_hub_service.send_message( self.MESSAGE_SOURCE, None, f"[<highlight>{self.DARKNET_NAME}</highlight>]", message) def process_incoming_relay_message(self, message): if re.search(self.message_regex, message): cont = re.findall(self.message_regex, message) cont = cont[0] channel = cont[1] ch = channel.lower() rest_of_message = cont[2] if ch == "wts" and self.setting_service.get_value( "dark_wts") == "0": return elif ch == "wtb" and self.setting_service.get_value( "dark_wtb") == "0": return elif ch == "lootrights" and self.setting_service.get_value( "dark_lr") == "0": return elif ch == "general" and self.setting_service.get_value( "dark_gen") == "0": return elif ch == "pvm" and self.setting_service.get_value( "dark_pvm") == "0": return elif ch == "event" and self.setting_service.get_value( "dark_event") == "0": return elif ch == "pvp" and self.setting_service.get_value( "dark_pvp") == "0": return if ch == "lootrights": channel = "LR" elif ch == "gen": channel = "Gen" channel_formatted = "[<highlight>%s</highlight>]" % channel self.message_hub_service.send_message(self.MESSAGE_SOURCE, None, channel_formatted, rest_of_message) def update_darket_status(self, setting_name, old_value, new_value): char_id = self.character_service.resolve_char_to_id(self.DARKNET_NAME) if not char_id: self.logger.warning( f"Could not resolve {self.DARKNET_NAME} to a char id.") else: if new_value: self.bot.send_private_message(char_id, "register", add_color=False) self.bot.send_private_message(char_id, "autoinvite on", add_color=False) self.bot.send_private_message(char_id, "join", add_color=False) else: self.bot.send_private_message(char_id, "leave", add_color=False) self.bot.send_private_message(char_id, "autoinvite off", add_color=False) self.bot.send_private_message(char_id, "unregister", add_color=False)
class PrivateChannelService: PRIVATE_CHANNEL_MESSAGE_EVENT = "private_channel_message" PRIVATE_CHANNEL_COMMAND_EVENT = "private_channel_command" JOINED_PRIVATE_CHANNEL_EVENT = "private_channel_joined" LEFT_PRIVATE_CHANNEL_EVENT = "private_channel_left" PRIVATE_CHANNEL_COMMAND = "priv" def __init__(self): self.logger = Logger(__name__) def inject(self, registry): self.bot = registry.get_instance("bot") self.setting_service = registry.get_instance("setting_service") self.event_service = registry.get_instance("event_service") self.character_service = registry.get_instance("character_service") self.access_service = registry.get_instance("access_service") self.command_service = registry.get_instance("command_service") def pre_start(self): self.bot.register_packet_handler(server_packets.PrivateChannelClientJoined.id, self.handle_private_channel_client_joined) self.bot.register_packet_handler(server_packets.PrivateChannelClientLeft.id, self.handle_private_channel_client_left) self.bot.register_packet_handler(server_packets.PrivateChannelMessage.id, self.handle_private_channel_message) self.event_service.register_event_type(self.JOINED_PRIVATE_CHANNEL_EVENT) self.event_service.register_event_type(self.LEFT_PRIVATE_CHANNEL_EVENT) self.event_service.register_event_type(self.PRIVATE_CHANNEL_MESSAGE_EVENT) self.event_service.register_event_type(self.PRIVATE_CHANNEL_COMMAND_EVENT) self.access_service.register_access_level("guest", 90, self.in_any_private_channel) self.command_service.register_command_channel("Private Channel", self.PRIVATE_CHANNEL_COMMAND) def handle_private_channel_message(self, conn: Conn, packet: server_packets.PrivateChannelMessage): char_name = self.character_service.get_char_name(packet.char_id) if packet.private_channel_id != conn.char_id: # channel_name = self.character_service.get_char_name(packet.private_channel_id) # self.logger.log_chat(conn, f"Private Channel({channel_name})", char_name, packet.message) pass else: self.logger.log_chat(conn, "Private Channel", char_name, packet.message) if not conn.is_main or conn.char_id == packet.char_id: return if not self.handle_private_channel_command(conn, packet): self.event_service.fire_event(self.PRIVATE_CHANNEL_MESSAGE_EVENT, DictObject({"char_id": packet.char_id, "name": char_name, "message": packet.message, "conn": conn})) def handle_private_channel_client_joined(self, conn: Conn, packet: server_packets.PrivateChannelClientJoined): char_name = self.character_service.get_char_name(packet.char_id) if packet.private_channel_id != conn.char_id: # channel_name = self.character_service.get_char_name(packet.private_channel_id) # self.logger.log_chat(conn, f"Private Channel({channel_name}", None, f"{char_name} joined the channel.") pass else: self.logger.log_chat(conn, "Private Channel", None, f"{char_name} joined the channel.") conn.private_channel[packet.char_id] = packet if conn.is_main: self.event_service.fire_event(self.JOINED_PRIVATE_CHANNEL_EVENT, DictObject({"char_id": packet.char_id, "name": char_name, "conn": conn})) def handle_private_channel_client_left(self, conn: Conn, packet: server_packets.PrivateChannelClientLeft): char_name = self.character_service.get_char_name(packet.char_id) if packet.private_channel_id != conn.char_id: # channel_name = self.character_service.get_char_name(packet.private_channel_id) # self.logger.log_chat(conn, f"Private Channel({channel_name})", None, f"{char_name} left the channel.") pass else: self.logger.log_chat(conn, "Private Channel", None, f"{char_name} left the channel.") del conn.private_channel[packet.char_id] if conn.is_main: self.event_service.fire_event(self.LEFT_PRIVATE_CHANNEL_EVENT, DictObject({"char_id": packet.char_id, "name": char_name, "conn": conn})) def handle_private_channel_command(self, conn: Conn, packet: server_packets.PrivateChannelMessage): if not self.setting_service.get("accept_commands_from_slave_bots").get_value() and not conn.is_main: return False # since the command symbol is required in the private channel, # the command_str must have length of at least 2 in order to be valid, # otherwise it is ignored if len(packet.message) < 2: return False # ignore leading space message = packet.message.lstrip() def reply(msg): self.bot.send_private_channel_message(msg, private_channel_id=conn.char_id, conn=conn) self.event_service.fire_event(self.PRIVATE_CHANNEL_COMMAND_EVENT, DictObject({"char_id": None, "name": None, "message": msg, "conn": conn})) if message.startswith(self.setting_service.get("symbol").get_value()) and packet.private_channel_id == conn.get_char_id(): char_name = self.character_service.get_char_name(packet.char_id) self.event_service.fire_event(self.PRIVATE_CHANNEL_COMMAND_EVENT, DictObject({"char_id": packet.char_id, "name": char_name, "message": packet.message, "conn": conn})) self.command_service.process_command( self.command_service.trim_command_symbol(message), self.PRIVATE_CHANNEL_COMMAND, packet.char_id, reply, conn) return True else: return False def invite(self, char_id, conn: Conn): if char_id != conn.char_id and conn.is_main: conn.send_packet(client_packets.PrivateChannelInvite(char_id)) def kick(self, char_id, conn: Conn): if char_id != conn.char_id: conn.send_packet(client_packets.PrivateChannelKick(char_id)) def kick_from_all(self, char_id): for _id, conn in self.bot.get_conns(): if char_id in conn.private_channel: conn.send_packet(client_packets.PrivateChannelKick(char_id)) def kickall(self, conn: Conn): conn.send_packet(client_packets.PrivateChannelKickAll()) def in_any_private_channel(self, char_id): for _id, conn in self.bot.get_conns(): if char_id in conn.private_channel: return True return False
class PublicChannelService: ORG_CHANNEL_MESSAGE_EVENT = "org_channel_message" ORG_MSG_EVENT = "org_msg" ORG_MSG_CHANNEL_ID = 42949672961 def __init__(self): self.logger = Logger(__name__) self.name_to_id = {} self.id_to_name = {} self.org_channel_id = None self.org_id = None self.org_name = None def inject(self, registry): self.bot = registry.get_instance("bot") self.event_service = registry.get_instance("event_service") self.character_service = registry.get_instance("character_service") def pre_start(self): self.bot.add_packet_handler(server_packets.PublicChannelJoined.id, self.add) self.bot.add_packet_handler(server_packets.PublicChannelLeft.id, self.remove) # priority must be above that of CommandService in order for relaying of commands to work correctly self.bot.add_packet_handler(server_packets.PublicChannelMessage.id, self.public_channel_message, priority=30) self.event_service.register_event_type(self.ORG_CHANNEL_MESSAGE_EVENT) self.event_service.register_event_type(self.ORG_MSG_EVENT) def get_channel_id(self, channel_name): return self.name_to_id.get(channel_name) def get_channel_name(self, channel_id): return self.id_to_name.get(channel_id, None) def add(self, packet: server_packets.PublicChannelJoined): self.id_to_name[packet.channel_id] = packet.name self.name_to_id[packet.name] = packet.channel_id if self.is_org_channel_id(packet.channel_id): self.org_channel_id = packet.channel_id self.org_id = 0x00ffffffff & packet.channel_id self.logger.debug("Org Id: %d" % self.org_id) self.logger.debug("Org Name: %s" % packet.name) if packet.name != "Clan (name unknown)": self.org_name = packet.name def remove(self, packet: server_packets.PublicChannelLeft): channel_name = self.get_channel_name(packet.channel_id) del self.id_to_name[packet.channel_id] del self.name_to_id[channel_name] def public_channel_message(self, packet: server_packets.PublicChannelMessage): if self.is_org_channel_id(packet.channel_id): char_name = self.character_service.get_char_name(packet.char_id) if packet.extended_message: message = packet.extended_message.get_message() else: message = packet.message self.logger.log_chat("Org Channel", char_name, message) self.event_service.fire_event(self.ORG_CHANNEL_MESSAGE_EVENT, packet) elif packet.channel_id == self.ORG_MSG_CHANNEL_ID: char_name = self.character_service.get_char_name(packet.char_id) if packet.extended_message: message = packet.extended_message.get_message() else: message = packet.message self.logger.log_chat("Org Msg", char_name, message) self.event_service.fire_event(self.ORG_MSG_EVENT, packet) def is_org_channel_id(self, channel_id): return channel_id >> 32 == 3 def get_org_id(self): return self.org_id def get_org_name(self): return self.org_name def get_all_public_channels(self): return self.id_to_name
class PublicChannelService: ORG_MESSAGE_EVENT = "org_message" def __init__(self): self.logger = Logger(__name__) self.name_to_id = {} self.id_to_name = {} self.org_channel_id = None self.org_id = None self.org_name = None def inject(self, registry): self.bot = registry.get_instance("bot") self.event_service = registry.get_instance("event_service") self.character_service = registry.get_instance("character_service") def pre_start(self): self.bot.add_packet_handler(server_packets.PublicChannelJoined.id, self.add) self.bot.add_packet_handler(server_packets.PublicChannelLeft.id, self.remove) self.bot.add_packet_handler(server_packets.PublicChannelMessage.id, self.public_channel_message) self.event_service.register_event_type(self.ORG_MESSAGE_EVENT) def get_channel_id(self, channel_name): return self.name_to_id.get(channel_name, None) def get_channel_name(self, channel_id): return self.id_to_name[channel_id] def add(self, packet: server_packets.PublicChannelJoined): self.id_to_name[packet.channel_id] = packet.name self.name_to_id[packet.name] = packet.channel_id if self.is_org_channel_id(packet.channel_id): self.org_channel_id = packet.channel_id self.org_id = 0x00ffffffff & packet.channel_id self.logger.debug("Org Id: %d" % self.org_id) self.logger.debug("Org Name: %s" % packet.name) if packet.name != "Clan (name unknown)": self.org_name = packet.name def remove(self, packet: server_packets.PublicChannelLeft): channel_name = self.get_channel_name(packet.channel_id) del self.id_to_name[packet.channel_id] del self.name_to_id[channel_name] def public_channel_message(self, packet: server_packets.PublicChannelMessage): if self.is_org_channel_id(packet.channel_id): char_name = self.character_service.get_char_name(packet.char_id) self.logger.log_chat("Org Channel", char_name, packet.message) self.event_service.fire_event(self.ORG_MESSAGE_EVENT, packet) def is_org_channel_id(self, channel_id): return channel_id >> 32 == 3 def get_org_id(self): return self.org_id def get_org_name(self): return self.org_name