class AliasController: def inject(self, registry): self.command_alias_service = registry.get_instance("command_alias_service") self.text = registry.get_instance("text") @command(command="alias", params=[Const("list")], access_level="all", description="List command aliases") def alias_list_cmd(self, request, _): data = self.command_alias_service.get_enabled_aliases() count = len(data) padded_rows = self.text.pad_table(list(map(lambda row: [row.alias, row.command], data))) blob = "" for cols in padded_rows: blob += " ".join(cols) + "\n" return ChatBlob(f"Aliases ({count})", blob) @command(command="alias", params=[Const("add"), Any("alias"), Any("command")], access_level="admin", description="Add a command alias", sub_command="modify") def alias_add_cmd(self, request, _, alias, command_str): if self.command_alias_service.add_alias(alias, command_str, force_enable=True): return f"Alias <highlight>{alias}</highlight> for command <highlight>{command_str}</highlight> added successfully." else: return f"Cannot add alias <highlight>{alias}</highlight> since there is already an active alias with that name." @command(command="alias", params=[Options(["rem", "remove"]), Any("alias")], access_level="admin", description="Remove a command alias", sub_command="modify") def alias_remove_cmd(self, request, _, alias): if self.command_alias_service.remove_alias(alias): return f"Alias <highlight>{alias}</highlight> has been removed successfully." else: return f"Could not find alias <highlight>{alias}</highlight>."
class SqlController: def inject(self, registry): self.bot = registry.get_instance("bot") self.db = registry.get_instance("db") self.command_alias_service = registry.get_instance("command_alias_service") self.getresp = registry.get_instance("translation_service").get_response def start(self): self.command_alias_service.add_alias("querysql", "sql query") self.command_alias_service.add_alias("executesql", "sql exec") @command(command="sql", params=[Const("query"), Any("sql_statement")], access_level="superadmin", description="Execute a SQL query and return the results") def sql_query_cmd(self, request, _, sql): try: results = self.db.query(sql) return ChatBlob(self.getresp("module/system", "sql_blob_title", {"count": len(results)}), json.dumps(results, indent=4, sort_keys=True)) except Exception as e: return self.getresp("module/system", "sql_fail", {"error": str(e)}) @command(command="sql", params=[Const("exec"), Any("sql_statement")], access_level="superadmin", description="Execute a SQL query and return number of affected rows") def sql_exec_cmd(self, request, _, sql): try: row_count = self.db.exec(sql) return self.getresp("module/system", "sql_exec_success", {"count": row_count}) except Exception as e: return self.getresp("module/system", "sql_fail", {"error": str(e)})
class SqlController: def inject(self, registry): self.bot = registry.get_instance("bot") self.db = registry.get_instance("db") self.command_alias_service = registry.get_instance( "command_alias_service") def start(self): self.command_alias_service.add_alias("querysql", "sql query") self.command_alias_service.add_alias("executesql", "sql exec") @command(command="sql", params=[Const("query"), Any("sql_statement")], access_level="superadmin", description="Execute a SQL query and return the results") def sql_query_cmd(self, request, _, sql): try: results = self.db.query(sql) return ChatBlob("Results (%d)" % len(results), json.dumps(results, indent=4, sort_keys=True)) except Exception as e: return "There was an error executing your query: %s" % str(e) @command( command="sql", params=[Const("exec"), Any("sql_statement")], access_level="superadmin", description="Execute a SQL query and return number of affected rows") def sql_exec_cmd(self, request, _, sql): try: row_count = self.db.exec(sql) return "%d row(s) affected." % row_count except Exception as e: return "There was an error executing your query: %s" % str(e)
class AliasController: def __init__(self): pass def inject(self, registry): self.command_alias_manager = registry.get_instance( "command_alias_manager") def start(self): pass @command(command="alias", params=[Const("list")], access_level="all", description="List command aliases") def alias_list_cmd(self, channel, sender, reply, args): blob = "" data = self.command_alias_manager.get_enabled_aliases() count = len(data) for row in data: blob += row.alias + " - " + row.command + "\n" reply(ChatBlob("Aliases (%d)" % count, blob)) @command(command="alias", params=[Const("add"), Any("alias"), Any("command")], access_level="superadmin", description="Add a command alias", sub_command="modify") def alias_add_cmd(self, channel, sender, reply, args): alias = args[1] command = args[2] if self.command_alias_manager.add_alias(alias, command): reply( "Alias <highlight>%s<end> for command <highlight>%s<end> added successfully." % (alias, command)) else: reply( "Cannot add alias <highlight>%s<end> since there is already an active alias with that name." % alias) @command(command="alias", params=[Options(["rem", "remove"]), Any("alias")], access_level="superadmin", description="Remove a command alias", sub_command="modify") def alias_remove_cmd(self, channel, sender, reply, args): alias = args[1] if self.command_alias_manager.remove_alias(alias): reply("Alias <highlight>%s<end> has been removed successfully." % alias) else: reply("Could not find alias <highlight>%s<end>." % alias)
class AliasController: def inject(self, registry): self.command_alias_service = registry.get_instance( "command_alias_service") self.ts: TranslationService = registry.get_instance( "translation_service") self.getresp = self.ts.get_response @command(command="alias", params=[Const("list")], access_level="all", description="List command aliases") def alias_list_cmd(self, request, _): blob = "" data = self.command_alias_service.get_enabled_aliases() count = len(data) for row in data: blob += row.alias + " - " + row.command + "\n" return ChatBlob( self.getresp("module/config", "alias_blob_title", {"amount": count}), blob) @command(command="alias", params=[Const("add"), Any("alias"), Any("command")], access_level="admin", description="Add a command alias", sub_command="modify") def alias_add_cmd(self, request, _, alias, command_str): if self.command_alias_service.add_alias(alias, command_str, force_enable=True): return self.getresp("module/config", "alias_add_success", { "alias": alias, "cmd": command_str }) else: return self.getresp("module/config", "alias_add_fail", {"alias": alias}) @command(command="alias", params=[Options(["rem", "remove"]), Any("alias")], access_level="admin", description="Remove a command alias", sub_command="modify") def alias_remove_cmd(self, request, _, alias): if self.command_alias_service.remove_alias(alias): return self.getresp("module/config", "alias_rem_success", {"alias": alias}) else: return self.getresp("module/config", "alias_rem_fail", {"alias": alias})
def start(self): self.db.exec( "CREATE TABLE IF NOT EXISTS discord_char_link (discord_id BIGINT NOT NULL, char_id INT NOT NULL)" ) self.message_hub_service.register_message_destination( self.MESSAGE_SOURCE, self.handle_incoming_relay_message, [ "private_channel", "org_channel", "websocket_relay", "tell_relay", "shutdown_notice" ], [self.MESSAGE_SOURCE]) self.register_discord_command_handler( self.discord_link_cmd, "discord", [Const("link"), Character("ao_character")]) self.register_discord_command_handler(self.discord_unlink_cmd, "discord", [Const("unlink")]) self.ts.register_translation("module/discord", self.load_discord_msg) self.setting_service.register(self.module_name, "discord_enabled", False, BooleanSettingType(), "Enable the Discord relay") self.setting_service.register(self.module_name, "discord_bot_token", "", HiddenSettingType(allow_empty=True), "Discord bot token") self.setting_service.register( self.module_name, "discord_channel_id", "", TextSettingType(allow_empty=True), "Discord channel id for relaying messages to and from", "You can get the Discord channel ID by right-clicking on a channel name in Discord and then clicking \"Copy ID\"" ) self.setting_service.register(self.module_name, "discord_embed_color", "#00FF00", ColorSettingType(), "Discord embedded message color") self.setting_service.register( self.module_name, "relay_color_prefix", "#FCA712", ColorSettingType(), "Set the prefix color for messages coming from Discord") self.setting_service.register( self.module_name, "relay_color_name", "#808080", ColorSettingType(), "Set the color of the name for messages coming from Discord") self.setting_service.register( self.module_name, "relay_color_message", "#00DE42", ColorSettingType(), "Set the color of the content for messages coming from Discord") self.setting_service.register_change_listener( "discord_channel_id", self.update_discord_channel) self.setting_service.register_change_listener( "discord_enabled", self.update_discord_state)
class BanController: def inject(self, registry): self.text = registry.get_instance("text") self.util = registry.get_instance("util") self.ban_service = registry.get_instance("ban_service") self.command_alias_service = registry.get_instance("command_alias_service") def start(self): self.command_alias_service.add_alias("unban", "ban rem") @command(command="ban", params=[Const("list", is_optional=True)], access_level="moderator", description="Show the ban list") def ban_list_cmd(self, request, _): t = int(time.time()) data = self.ban_service.get_ban_list() blob = "" for row in data: ends = "never" if row.finished_at == -1 else self.util.format_timestamp(row.finished_at) time_left = "" if row.finished_at == -1 else " (%s left)" % self.util.time_to_readable(row.finished_at - t) blob += "<pagebreak>Name: <highlight>%s<end>\n" % row.name blob += "Added: <highlight>%s<end>\n" % self.util.format_timestamp(row.created_at) blob += "By: <highlight>%s<end>\n" % row.sender_name blob += "Ends: <highlight>%s<end>%s\n" % (ends, time_left) blob += "Reason: <highlight>%s<end>\n\n" % row.reason return ChatBlob("Ban List (%d)" % len(data), blob) @command(command="ban", params=[Options(["rem", "remove"]), Character("character")], access_level="moderator", description="Remove a character from the ban list") def ban_remove_cmd(self, request, _, char): if not char.char_id: return "Could not find <highlight>%s<end>." % char.name elif not self.ban_service.get_ban(char.char_id): return "<highlight>%s<end> is not banned." % char.name else: self.ban_service.remove_ban(char.char_id) return "<highlight>%s<end> has been removed from the ban list." % char.name @command(command="ban", params=[Const("add", is_optional=True), Character("character"), Time("duration", is_optional=True), Any("reason", is_optional=True)], access_level="moderator", description="Add a character to the ban list") def ban_add_cmd(self, request, _, char, duration, reason): reason = reason or "" if not char.char_id: return "Could not find <highlight>%s<end>." % char.name elif self.ban_service.get_ban(char.char_id): return "<highlight>%s<end> is already banned." % char.name else: self.ban_service.add_ban(char.char_id, request.sender.char_id, duration, reason) return "<highlight>%s<end> has been added to the ban list." % char.name
class TopicController: def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.util = registry.get_instance("util") @setting(name="topic", value="", description="The bot topic") def topic(self): return DictionarySettingType() @command(command="topic", params=[], access_level="all", description="Show the current topic") def topic_show_command(self, request): topic = self.topic().get_value() if topic: time_string = self.util.time_to_readable( int(time.time()) - topic["created_at"]) return "Topic: <highlight>%s<end> [set by <highlight>%s<end>][%s ago]" % ( topic["topic_message"], topic["created_by"]["name"], time_string) else: return "There is no current topic." @command(command="topic", params=[Const("clear")], access_level="all", description="Clears the current topic") def topic_clear_command(self, request, _): self.topic().set_value("") return "The topic has been cleared." @command(command="topic", params=[Const("set", is_optional=True), Any("topic_message")], access_level="all", description="Set the current topic") def topic_set_command(self, request, _, topic_message): topic = { "topic_message": topic_message, "created_by": sender, "created_at": int(time.time()) } self.topic().set_value(topic) return "The topic has been set."
class AliasController: def inject(self, registry): self.command_alias_service = registry.get_instance( "command_alias_service") @command(command="alias", params=[Const("list")], access_level="all", description="List command aliases") def alias_list_cmd(self, request, _): blob = "" data = self.command_alias_service.get_enabled_aliases() count = len(data) for row in data: blob += row.alias + " - " + row.command + "\n" return ChatBlob("Aliases (%d)" % count, blob) @command(command="alias", params=[Const("add"), Any("alias"), Any("command")], access_level="admin", description="Add a command alias", sub_command="modify") def alias_add_cmd(self, request, _, alias, command_str): if self.command_alias_service.add_alias(alias, command_str, force_enable=True): return "Alias <highlight>%s<end> for command <highlight>%s<end> added successfully." % ( alias, command_str) else: return "Cannot add alias <highlight>%s<end> since there is already an active alias with that name." % alias @command(command="alias", params=[Options(["rem", "remove"]), Any("alias")], access_level="admin", description="Remove a command alias", sub_command="modify") def alias_remove_cmd(self, request, _, alias): if self.command_alias_service.remove_alias(alias): return "Alias <highlight>%s<end> has been removed successfully." % alias else: return "Could not find alias <highlight>%s<end>." % alias
class LeaderController: def __init__(self): self.leader = None def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.access_service = registry.get_instance("access_service") self.character_service = registry.get_instance("character_service") @command(command="leader", params=[], access_level="all", description="Show the current raidleader") def leader_show_command(self, request): if self.leader: return "The current raidleader is <highlight>%s<end>." % self.character_service.resolve_char_to_name(self.leader) else: return "There is no current raidleader." @command(command="leader", params=[Const("set")], access_level="all", description="Set yourself as raidleader") def leader_set_self_command(self, request, _): if self.leader == request.sender.char_id: self.leader = None return "You have been removed as raidleader." elif not self.leader: self.leader = request.sender.char_id return "You have been set as raidleader." elif self.access_service.has_sufficient_access_level(request.sender.char_id, self.leader): self.leader = request.sender.char_id return "You have taken leader from <highlight>%s<end>." % self.character_service.resolve_char_to_name(self.leader) else: return "You do not have a high enough access level to take raidleader from <highlight>%s<end>." % self.character_service.resolve_char_to_name(self.leader) @command(command="leader", params=[Const("set", is_optional=True), Character("character")], access_level="all", description="Set another character as raidleader") def leader_set_other_command(self, request, _, char): if not char.char_id: return "Could not find <highlight>%s<end>." % char.name if not self.leader or self.access_service.has_sufficient_access_level(request.sender.char_id, self.leader): self.leader = char.char_id return "<highlight>%s<end> has been set as raidleader." % char.name else: return "You do not have a high enough access level to take raidleader from <highlight>%s<end>." % self.character_service.resolve_char_to_name(self.leader)
class SendMessageController: def inject(self, registry): self.bot = registry.get_instance("bot") self.command_service = registry.get_instance("command_service") self.getresp = registry.get_instance( "translation_service").get_response @command(command="send", params=[Const("tell"), Character("character"), Any("message")], access_level="superadmin", description="Send a message to a character from the bot") def send_tell_cmd(self, request, _, char, message): if char.char_id: self.bot.send_private_message(char.char_id, message, add_color=False, conn=request.conn) return self.getresp("module/system", "msg_sent") else: return self.getresp("global", "char_not_found", {"char": char.name}) @command(command="send", params=[Const("org"), Any("message")], access_level="superadmin", description="Send a message to the org channel from the bot") def send_org_cmd(self, request, _, message): for _id, conn in self.bot.get_conns(lambda x: x.is_main): self.bot.send_org_message(message, add_color=False, conn=conn) return self.getresp("module/system", "msg_sent") @command(command="send", params=[Const("priv"), Any("message")], access_level="superadmin", description="Send a message to the private channel from the bot") def send_priv_cmd(self, request, _, message): self.bot.send_private_channel_message(message, add_color=False, conn=self.bot.get_primary_conn()) return self.getresp("module/system", "msg_sent")
class NotesController: def inject(self, registry): self.db = registry.get_instance("db") self.text = registry.get_instance("text") self.alts_service = registry.get_instance("alts_service") def start(self): self.db.exec("CREATE TABLE IF NOT EXISTS notes (" "id INT PRIMARY KEY AUTO_INCREMENT, " "char_id INT NOT NULL, " "note TEXT NOT NULL," "created_at INT NOT NULL)") @command(command="notes", params=[], access_level="all", description="Show your notes") def notes_list_cmd(self, request): alts = self.alts_service.get_alts(request.sender.char_id) cnt = 0 blob = "" for alt in alts: data = self.db.query("SELECT * FROM notes WHERE char_id = ? ORDER BY created_at DESC", [alt.char_id]) alt_cnt = len(data) cnt += alt_cnt if alt_cnt: blob += "\n<header2>%s<end>\n" % alt.name for row in data: blob += "%s %s\n\n" % (row.note, self.text.make_chatcmd("Remove", "/tell <myname> notes remove %d" % row.id)) return ChatBlob("Notes for %s (%d)" % (alts[0].name, cnt), blob) @command(command="notes", params=[Const("add"), Any("note")], access_level="all", description="Add a note") def notes_add_cmd(self, request, _, note): self.db.exec("INSERT INTO notes (char_id, note, created_at) VALUES (?, ?, ?)", [request.sender.char_id, note, int(time.time())]) return "Note added successfully." @command(command="notes", params=[Options(["rem", "remove"]), Int("note_id")], access_level="all", description="Remove a note") def notes_remove_cmd(self, request, _, note_id): note = self.db.query_single("SELECT n.*, p.name FROM notes n LEFT JOIN player p ON n.char_id = p.char_id WHERE n.id = ?", [note_id]) if not note: return "Could not find note with ID <highlight>%d<end>." % note_id if self.alts_service.get_main(request.sender.char_id).char_id != self.alts_service.get_main(note.char_id).char_id: return "You must be a confirmed alt of <highlight>%s<end> to remove this note." % note.name self.db.exec("DELETE FROM notes WHERE id = ?", [note_id]) return "Note with ID <highlight>%d<end> deleted successfully." % note_id
class AssistController: def __init__(self): self.assist = [] def inject(self, registry): self.leader_controller = registry.get_instance("leader_controller") @command(command="assist", params=[], access_level="all", description="Show current assist targets") def assist_command(self, request): return self.get_assist_output() @command(command="assist", params=[Const("clear")], access_level="all", description="Clear all assist targets", sub_command="modify") def assist_clear_command(self, request, _): if not self.assist: return "No assist targets set." if not self.leader_controller.can_use_command(request.sender.char_id): return LeaderController.NOT_LEADER_MSG else: self.assist = [] return "Assist targets have been cleared." @command( command="assist", params=[Any("assist_targets")], access_level="all", description="Set one or more assist targets", sub_command="modify", extended_description="Multiple assist targets should be space-delimited" ) def assist_set_command(self, request, assist_targets): targets = assist_targets.split(" ") if not self.leader_controller.can_use_command(request.sender.char_id): return LeaderController.NOT_LEADER_MSG else: self.assist = targets return self.get_assist_output() def get_assist_output(self): if not self.assist: return "No assist targets set." return "/macro assist " + " \\n ".join( map(lambda x: "/assist " + x.capitalize(), reversed(self.assist)))
class QueueController: def inject(self, registry): self.bot = registry.get_instance("bot") self.command_alias_service = registry.get_instance("command_alias_service") self.getresp = registry.get_instance("translation_service").get_response def start(self): self.command_alias_service.add_alias("clearqueue", "queue clear") @command(command="queue", params=[Const("clear")], access_level="moderator", description="Clear the outgoing message queue") def queue_clear_cmd(self, request, _): num_messages = len(self.bot.conns["main"].packet_queue) self.bot.conns["main"].packet_queue.clear() return self.getresp("module/system", "clear_queue", {"count": num_messages})
def test_multiple(self): param1 = Multiple(Int("num")) self.assertEqual([1], self.param_test_helper(param1, "1")) self.assertEqual([1, 2, 3], self.param_test_helper(param1, "1 2 3")) param2 = Multiple(Const("something")) self.assertEqual(["something"], self.param_test_helper(param2, "something")) self.assertEqual(["something", "something", "something"], self.param_test_helper(param2, "something something something")) param3 = Multiple(Time("time")) self.assertEqual([60], self.param_test_helper(param3, "1m")) self.assertEqual([304], self.param_test_helper(param3, "5M4S")) self.assertEqual([60, 304, 14521], self.param_test_helper(param3, "1m 5M4S 4h2m1s")) param4 = Multiple(Item("item")) self.assertEqual([{'low_id': 246817, 'high_id': 246817, 'ql': 200, 'name': 'Novictum Seed'}], self.param_test_helper(param4, "<a href=\"itemref://246817/246817/200\">Novictum Seed</a>")) self.assertEqual([{'low_id': 246817, 'high_id': 246817, 'ql': 200, 'name': 'Novictum Seed'}, {'low_id': 100, 'high_id': 101, 'ql': 300, 'name': 'It\'s cool'}], self.param_test_helper(param4, "<a href=\"itemref://246817/246817/200\">Novictum Seed</a> " "<a href=\"itemref://100/101/300\">It's cool</a>")) self.assertEqual([{'low_id': 246817, 'high_id': 246817, 'ql': 200, 'name': 'Novictum Seed'}, {'low_id': 100, 'high_id': 101, 'ql': 300, 'name': 'It\'s cool'}, {'low_id': 12345, 'high_id': 54321, 'ql': 123, 'name': 'It Works!'}], self.param_test_helper(param4, "<a href=\"itemref://246817/246817/200\">Novictum Seed</a> " "<a href=\"itemref://100/101/300\">It's cool</a> " "<a href=\"itemref://12345/54321/123\">It Works!</a>")) # no spaces self.assertEqual([{'low_id': 246817, 'high_id': 246817, 'ql': 200, 'name': 'Novictum Seed'}, {'low_id': 100, 'high_id': 101, 'ql': 300, 'name': 'It\'s cool'}, {'low_id': 12345, 'high_id': 54321, 'ql': 123, 'name': 'It Works!'}], self.param_test_helper(param4, "<a href=\"itemref://246817/246817/200\">Novictum Seed</a>" "<a href=\"itemref://100/101/300\">It's cool</a>" "<a href=\"itemref://12345/54321/123\">It Works!</a>")) param5 = Multiple(Any("time")) self.assertEqual(["test"], self.param_test_helper(param5, "test")) self.assertEqual(["test1", "test2"], self.param_test_helper(param5, "test1 test2")) self.assertEqual(["test1", "test2", "test3"], self.param_test_helper(param5, "test1 test2 test3"))
class LinksController: def inject(self, registry): self.db = registry.get_instance("db") self.text = registry.get_instance("text") def start(self): self.db.exec("CREATE TABLE IF NOT EXISTS links (" "id INT PRIMARY KEY AUTO_INCREMENT," "char_id INT NOT NULL," "website VARCHAR(255) NOT NULL," "comments VARCHAR(255) NOT NULL," "created_at INT NOT NULL);") @command(command="links", params=[], access_level="all", description="Show links") def links_list_cmd(self, request): data = self.db.query("SELECT l.*, p.name FROM links l LEFT JOIN player p ON l.char_id = p.char_id ORDER BY name ASC") blob = "" for row in data: blob += "%s <highlight>%s<end> [%s] %s" % (self.text.make_chatcmd("[Link]", "/start %s" % row.website), row.comments, row.name, self.text.make_chatcmd("Remove", "/tell <myname> links remove %d" % row.id)) return ChatBlob("Links (%d)" % len(data), blob) @command(command="links", params=[Const("add"), Any("website"), Any("comment")], access_level="moderator", description="Add a link") def links_add_cmd(self, request, _, website, comment): if not website.startswith("https://") and not website.startswith("http://"): return "Website must start with 'http://' or 'https://'." self.db.exec("INSERT INTO links (char_id, website, comments, created_at) VALUES (?, ?, ?, ?)", [request.sender.char_id, website, comment, int(time.time())]) return "Link added successfully." @command(command="links", params=[Options(["rem", "remove"]), Int("link_id")], access_level="moderator", description="Remove a link") def links_remove_cmd(self, request, _, link_id): link = self.db.query_single("SELECT * FROM links WHERE id = ?", [link_id]) if not link: return "Could not find link with ID <highlight>%d<end>." % link_id self.db.exec("DELETE FROM links WHERE id = ?", [link_id]) return "Link has been deleted"
class QueueController: def inject(self, registry): self.bot = registry.get_instance("bot") self.command_alias_service = registry.get_instance( "command_alias_service") def start(self): self.command_alias_service.add_alias("clearqueue", "queue clear") @command(command="queue", params=[Const("clear")], access_level="moderator", description="Clear the outgoing message queue") def queue_clear_cmd(self, request, _): num_messages = len(self.bot.packet_queue) self.bot.packet_queue.clear() return "Cleared <highlight>%d<end> messages from the outgoing message queue." % num_messages
class CacheController: invalid_chars = ["/", "\\", ".."] def inject(self, registry): self.bot = registry.get_instance("bot") self.db = registry.get_instance("db") self.text = registry.get_instance("text") @command(command="cache", params=[Const("delete"), Any("category"), Any("filename")], access_level="superadmin", description="Manually remove a cache entry") def cache_remove_cmd(self, request, _, category, filename): full_file_path = os.sep.join([".", "data", "cache", category, filename]) full_file_path = os.path.normpath(full_file_path) if not full_file_path.startswith(os.path.normpath(os.sep.join([".", "data", "cache"]))): return f"<highlight>{full_file_path}</highlight> is not a valid cache entry." if os.path.isfile(full_file_path): os.remove(full_file_path) return f"Cache entry <highlight>{full_file_path}</highlight> has been removed." else: return f"Cache entry <highlight>{full_file_path}</highlight> does not exist or is not a file."
class QueueController: def inject(self, registry): self.bot = registry.get_instance("bot") self.command_alias_service = registry.get_instance("command_alias_service") self.command_service = registry.get_instance("command_service") def start(self): self.command_alias_service.add_alias("clearqueue", "queue clear") @command(command="queue", params=[Const("clear")], access_level="moderator", description="Clear the outgoing message queue") def queue_clear_cmd(self, request, _): num_messages = len(request.conn.packet_queue) request.conn.packet_queue.clear() return f"Cleared <highlight>{num_messages}</highlight> messages from the outgoing message queue." @command(command="massmsg", params=[Any("command")], access_level="moderator", description="Force the reply of the specified command to be sent via non-main bots") def massmsg_cmd(self, request, command_str): def reply(msg): self.bot.send_mass_message(request.sender.char_id, msg, conn=request.conn) self.command_service.process_command(command_str, request.channel, request.sender.char_id, reply, request.conn)
class TowerAttackController: def __init__(self): self.logger = Logger(__name__) def inject(self, registry): self.bot = registry.get_instance("bot") self.db = registry.get_instance("db") self.text = registry.get_instance("text") self.util = registry.get_instance("util") self.event_service = registry.get_instance("event_service") self.playfield_controller = registry.get_instance( "playfield_controller") self.command_alias_service = registry.get_instance( "command_alias_service") self.public_channel_service = registry.get_instance( "public_channel_service") def start(self): self.db.exec( "CREATE TABLE IF NOT EXISTS tower_attacker (id INT PRIMARY KEY AUTO_INCREMENT, att_org_name VARCHAR(50) NOT NULL, att_faction VARCHAR(10) NOT NULL, " "att_char_id INT, att_char_name VARCHAR(20) NOT NULL, att_level INT NOT NULL, att_ai_level INT NOT NULL, att_profession VARCHAR(15) NOT NULL, " "x_coord INT NOT NULL, y_coord INT NOT NULL, is_victory SMALLINT NOT NULL, " "tower_battle_id INT NOT NULL, created_at INT NOT NULL)") self.db.exec( "CREATE TABLE IF NOT EXISTS tower_battle (id INT PRIMARY KEY AUTO_INCREMENT, playfield_id INT NOT NULL, site_number INT NOT NULL, " "def_org_name VARCHAR(50) NOT NULL, def_faction VARCHAR(10) NOT NULL, is_finished INT NOT NULL, battle_type VARCHAR(20) NOT NULL, last_updated INT NOT NULL)" ) self.command_alias_service.add_alias("victory", "attacks") @command(command="attacks", params=[NamedParameters(["page"])], access_level="all", description="Show recent tower attacks and victories") def attacks_cmd(self, request, named_params): page = int(named_params.page or "1") page_size = 30 offset = (page - 1) * page_size sql = """ SELECT b.*, a.*, COALESCE(a.att_level, 0) AS att_level, COALESCE(a.att_ai_level, 0) AS att_ai_level, p.short_name, b.id AS battle_id FROM tower_battle b LEFT JOIN tower_attacker a ON a.tower_battle_id = b.id LEFT JOIN playfields p ON p.id = b.playfield_id ORDER BY b.last_updated DESC, a.created_at DESC LIMIT %d, %d """ % (offset, page_size) data = self.db.query(sql) t = int(time.time()) blob = self.check_for_all_towers_channel() if page > 1: blob += " " + self.text.make_chatcmd( "<< Page %d" % (page - 1), self.get_chat_command(page - 1)) if len(data) > 0: blob += " Page " + str(page) blob += " " + self.text.make_chatcmd( "Page %d >>" % (page + 1), self.get_chat_command(page + 1)) blob += "\n" current_battle_id = -1 for row in data: if current_battle_id != row.battle_id: blob += "\n<pagebreak>" current_battle_id = row.battle_id blob += self.format_battle_info(row, t) blob += self.text.make_chatcmd( "More Info", "/tell <myname> attacks battle %d" % row.battle_id) + "\n" blob += "<header2>Attackers:<end>\n" blob += "<tab>" + self.format_attacker(row) + "\n" return ChatBlob("Tower Attacks", blob) @command(command="attacks", params=[Const("battle"), Int("battle_id")], access_level="all", description="Show battle info for a specific battle") def attacks_battle_cmd(self, request, _, battle_id): battle = self.db.query_single( "SELECT b.*, p.short_name FROM tower_battle b LEFT JOIN playfields p ON p.id = b.playfield_id WHERE b.id = ?", [battle_id]) if not battle: return "Could not find battle with ID <highlight>%d<end>." % battle_id t = int(time.time()) attackers = self.db.query( "SELECT * FROM tower_attacker WHERE tower_battle_id = ? ORDER BY created_at DESC", [battle_id]) first_activity = attackers[-1].created_at if len( attackers) > 0 else battle.last_updated blob = self.check_for_all_towers_channel() blob += self.format_battle_info(battle, t) blob += "Duration: <highlight>%s<end>\n\n" % self.util.time_to_readable( battle.last_updated - first_activity) blob += "<header2>Attackers:<end>\n" for row in attackers: blob += "<tab>" + self.format_attacker(row) blob += " " + self.format_timestamp(row.created_at, t) blob += "\n" return ChatBlob("Battle Info %d" % battle_id, blob) @event(event_type=TowerController.TOWER_ATTACK_EVENT, description="Record tower attacks", is_hidden=True) def tower_attack_event(self, event_type, event_data): t = int(time.time()) site_number = self.find_closest_site_number( event_data.location.playfield.id, event_data.location.x_coord, event_data.location.y_coord) attacker = event_data.attacker or {} defender = event_data.defender battle = self.find_or_create_battle(event_data.location.playfield.id, site_number, defender.org_name, defender.faction, "attack", t) self.db.exec( "INSERT INTO tower_attacker (att_org_name, att_faction, att_char_id, att_char_name, att_level, att_ai_level, att_profession, " "x_coord, y_coord, is_victory, tower_battle_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ attacker.get("org_name", ""), attacker.get("faction", ""), attacker.get("char_id", 0), attacker.get("name", ""), attacker.get("level", 0), attacker.get("ai_level", 0), attacker.get("profession", ""), event_data.location.x_coord, event_data.location.y_coord, 0, battle.id, t ]) @event(event_type=TowerController.TOWER_VICTORY_EVENT, description="Record tower victories", is_hidden=True) def tower_victory_event(self, event_type, event_data): t = int(time.time()) if event_data.type == "attack": row = self.get_last_attack(event_data.winner.faction, event_data.winner.org_name, event_data.loser.faction, event_data.loser.org_name, event_data.location.playfield.id, t) if not row: site_number = 0 is_finished = 1 self.db.exec( "INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)", [ event_data.location.playfield.id, site_number, event_data.loser.org_name, event_data.loser.faction, is_finished, event_data.type, t ]) battle_id = self.db.last_insert_id() attacker = event_data.winner or {} self.db.exec( "INSERT INTO tower_attacker (att_org_name, att_faction, att_char_id, att_char_name, att_level, att_ai_level, att_profession, " "x_coord, y_coord, is_victory, tower_battle_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ attacker.get("org_name", ""), attacker.get("faction", ""), attacker.get("char_id", 0), attacker.get("name", ""), attacker.get("level", 0), attacker.get("ai_level", 0), attacker.get("profession", ""), 0, 0, 0, battle_id, t ]) else: is_victory = 1 self.db.exec( "UPDATE tower_attacker SET is_victory = ? WHERE id = ?", [is_victory, row.attack_id]) is_finished = 1 self.db.exec( "UPDATE tower_battle SET is_finished = ?, last_updated = ? WHERE id = ?", [is_finished, t, row.battle_id]) elif event_data.type == "terminated": site_number = 0 is_finished = 1 self.db.exec( "INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)", [ event_data.location.playfield.id, site_number, event_data.loser.org_name, event_data.loser.faction, is_finished, event_data.type, t ]) else: raise Exception("Unknown victory event type: '%s'" % event_data.type) def format_attacker(self, row): if row.att_char_name: level = ("%d/<green>%d<end>" % (row.att_level, row.att_ai_level) ) if row.att_ai_level > 0 else "%d" % row.att_level org = row.att_org_name + " " if row.att_org_name else "" victor = " - <notice>Winner!<end>" if row.is_victory else "" return "%s (%s %s) %s(%s)%s" % (row.att_char_name, level, row.att_profession, org, row.att_faction, victor) else: return "Unknown attacker" def find_closest_site_number(self, playfield_id, x_coord, y_coord): sql = """ SELECT site_number, ((x_distance * x_distance) + (y_distance * y_distance)) radius FROM (SELECT playfield_id, site_number, min_ql, max_ql, x_coord, y_coord, site_name, (x_coord - ?) as x_distance, (y_coord - ?) as y_distance FROM tower_site WHERE playfield_id = ?) t ORDER BY radius ASC LIMIT 1""" row = self.db.query_single(sql, [x_coord, y_coord, playfield_id]) if row: return row.site_number else: return 0 def find_or_create_battle(self, playfield_id, site_number, org_name, faction, battle_type, t): last_updated = t - (8 * 3600) is_finished = 0 sql = """ SELECT * FROM tower_battle WHERE playfield_id = ? AND site_number = ? AND is_finished = ? AND def_org_name = ? AND def_faction = ? AND last_updated >= ? """ battle = self.db.query_single(sql, [ playfield_id, site_number, is_finished, org_name, faction, last_updated ]) if battle: self.db.exec( "UPDATE tower_battle SET last_updated = ? WHERE id = ?", [t, battle.id]) return battle else: self.db.exec( "INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)", [ playfield_id, site_number, org_name, faction, is_finished, battle_type, t ]) return self.db.query_single( "SELECT * FROM tower_battle WHERE id = ?", [self.db.last_insert_id()]) def get_last_attack(self, att_faction, att_org_name, def_faction, def_org_name, playfield_id, t): last_updated = t - (8 * 3600) is_finished = 0 sql = """ SELECT b.id AS battle_id, a.id AS attack_id FROM tower_battle b JOIN tower_attacker a ON a.tower_battle_id = b.id WHERE a.att_faction = ? AND a.att_org_name = ? AND b.def_faction = ? AND b.def_org_name = ? AND b.playfield_id = ? AND b.is_finished = ? AND b.last_updated >= ? ORDER BY last_updated DESC LIMIT 1""" return self.db.query_single(sql, [ att_faction, att_org_name, def_faction, def_org_name, playfield_id, is_finished, last_updated ]) def format_battle_info(self, row, t): blob = "" defeated = " - <notice>Defeated!<end>" if row.is_finished else "" blob += "Site: <highlight>%s %s<end>\n" % (row.short_name, row.site_number or "?") blob += "Defender: <highlight>%s<end> (%s)%s\n" % ( row.def_org_name, row.def_faction, defeated) blob += "Last Activity: %s\n" % self.format_timestamp( row.last_updated, t) return blob def format_timestamp(self, t, current_t): return "<highlight>%s<end> (%s ago)" % (self.util.format_datetime( t), self.util.time_to_readable(current_t - t)) def get_chat_command(self, page): return "/tell <myname> attacks --page=%d" % page def check_for_all_towers_channel(self): if not self.public_channel_service.get_channel_name( TowerController.ALL_TOWERS_ID): return "Notice: The bot must belong to an org and be promoted to a rank that is high enough to have the All Towers channel (e.g., Squad Commander) in order for the <symbol>attacks command to work correctly.\n\n" else: return ""
class MigrateController: DATABASE_TYPE_MYSQL = "mysql" DATABASE_TYPE_SQLITE = "sqlite" def __init__(self): self.logger = Logger(__name__) def inject(self, registry): self.bot = registry.get_instance("bot") self.db = registry.get_instance("db") self.character_service = registry.get_instance("character_service") self.alts_service = registry.get_instance("alts_service") self.pork_service = registry.get_instance("pork_service") @event(event_type="connect", description="Configure migration controller", is_system=True) def connect_event(self, event_type, event_data): self.db2 = DB() # Optional: the name of the bot character that the budabot/bebot ran as # if the bot name is the same, then you can leave this blank, otherwise you must fill in this value bot_name = "" # Optional: the org_id of the org # the bot will use the org_id of the primary conn if this is not set, which is usually correct org_id = 0 # if your budabot/bebot used mysql, then uncomment the second line below and fill out the appropriate values # otherwise, if your budabot used sqlite, then uncomment the first line below and enter the path to the sqlite db file # do NOT uncomment both of them # REQUIRED: uncomment ONE of these two lines below # self.db2.connect_sqlite("./data/budabot.db") # self.db2.connect_mysql(host="localhost", port=3306, username="", password="", database_name="") self.bot_name = bot_name.lower( ) if bot_name else self.bot.get_primary_conn().get_char_name().lower() self.org_id = org_id if org_id else self.bot.get_primary_conn().org_id self.dimension = self.bot.dimension # TODO in each command, check if db has been initialized properly first @command(command="bebot", params=[Const("migrate"), Const("alts")], access_level="superadmin", description="Migrate alts from a Bebot database") def migrate_bebot_alts_cmd(self, request, _1, _2): data = self.db2.query( "SELECT a.alt, u1.char_id AS alt_char_id, a.main, u2.char_id AS main_char_id " "FROM alts a " f"LEFT JOIN {self.bot_name}_users u1 ON a.alt = u1.nickname " f"LEFT JOIN {self.bot_name}_users u2 ON a.main = u2.nickname " "WHERE a.confirmed = 1 " "ORDER BY main, alt") current_main = None current_main_id = None count_inactive = 0 count_success = 0 count_failure = 0 request.reply("Processing %s alt records..." % len(data)) for row in data: if row.main != current_main: current_main = row.main current_main_id = self.resolve_to_char_id( row.main, row.main_char_id) if not current_main_id: self.logger.warning( f"Could not resolve main char '{current_main}' to char id") count_inactive += 1 continue alt_id = self.resolve_to_char_id(row.alt, row.alt_char_id) if not alt_id: self.logger.warning( f"Could not resolve alt char '{row.alt}' to char id") count_inactive += 1 continue msg, result = self.alts_service.add_alt(current_main_id, alt_id) if result: count_success += 1 else: count_failure += 1 return f"<highlight>{count_success}</highlight> alts were migrated successfully, " \ f"<highlight>{count_failure}</highlight> alts failed to be added, " \ f"and <highlight>{count_inactive}</highlight> chars were inactive and could not be resolved to char ids." @command(command="bebot", params=[Const("migrate"), Const("logon")], access_level="superadmin", description="Migrate logon messages from a Bebot database") def migrate_bebot_logon_cmd(self, request, _1, _2): data = self.db2.query(f"SELECT message, id FROM {self.bot_name}_logon") request.reply("Processing %s logon records..." % len(data)) for row in data: self.db.exec( "INSERT INTO log_messages (char_id, logon) VALUES(?, ?)", [row.id, row.message]) return f"Successfully migrated <highlight>%d</highlight> logon messages." % len( data) @command(command="budabot", params=[Const("migrate"), Const("admins")], access_level="superadmin", description="Migrate admins from a Budabot database") def migrate_budabot_admins_cmd(self, request, _1, _2): data = self.db2.query( "SELECT a.name, p.charid AS char_id, CASE WHEN adminlevel = 4 THEN 'admin' WHEN adminlevel = 3 THEN 'moderator' END AS access_level " f"FROM admin_{self.bot_name} a LEFT JOIN players p ON (a.name = p.name AND p.dimension = ?) " "WHERE p.charid > 0", [self.dimension]) with self.db.transaction(): for row in data: char_id = self.resolve_to_char_id(row.name, row.char_id) if char_id and row.access_level: self.db.exec("DELETE FROM admin WHERE char_id = ?", [char_id]) self.db.exec( "INSERT INTO admin (char_id, access_level) VALUES (?, ?)", [char_id, row.access_level]) return f"Successfully migrated <highlight>%d</highlight> admin characters." % len( data) @command(command="budabot", params=[Const("migrate"), Const("banlist")], access_level="superadmin", description="Migrate ban list from a Budabot database") def migrate_budabot_banlist_cmd(self, request, _1, _2): data = self.db2.query( "SELECT b.charid AS char_id, p.charid AS sender_char_id, time AS created_at, banend AS finished_at, reason " f"FROM banlist_{self.bot_name} b JOIN players p ON (b.admin = p.name AND p.dimension = ?)" "WHERE p.charid > 0", [self.dimension]) with self.db.transaction(): for row in data: self.db.exec("DELETE FROM ban_list WHERE char_id = ?", [row.char_id]) self.db.exec( "INSERT INTO ban_list (char_id, sender_char_id, created_at, finished_at, reason, ended_early) VALUES (?, ?, ?, ?, ?, ?)", [ row.char_id, row.sender_char_id, row.created_at, row.finished_at, row.reason, 0 ]) return f"Successfully migrated <highlight>%d</highlight> banned characters." % len( data) @command(command="budabot", params=[Const("migrate"), Const("alts")], access_level="superadmin", description="Migrate alts from a Budabot database") def migrate_budabot_alts_cmd(self, request, _1, _2): data = self.db2.query( "SELECT p1.charid AS main_char_id, p2.charid AS alt_char_id " "FROM alts a JOIN players p1 ON (p1.name = a.main AND p1.dimension = ?) " "JOIN players p2 ON (p2.name = a.alt AND p2.dimension = ?)" "WHERE validated = 1 AND p1.charid > 0 AND p2.charid > 0 ORDER BY a.main ASC", [self.dimension, self.dimension]) with self.db.transaction(): current_main = 0 group_id = 0 for row in data: if row.main_char_id != current_main: current_main = row.main_char_id group_id = self.db.query_single( "SELECT (COALESCE(MAX(group_id), 0) + 1) AS next_group_id FROM alts" ).next_group_id self.db.exec("DELETE FROM alts WHERE char_id = ?", [row.main_char_id]) self.db.exec( "INSERT INTO alts (char_id, group_id, status) VALUES (?, ?, ?)", [row.main_char_id, group_id, AltsService.MAIN]) self.db.exec("DELETE FROM alts WHERE char_id = ?", [row.alt_char_id]) self.db.exec( "INSERT INTO alts (char_id, group_id, status) VALUES (?, ?, ?)", [row.alt_char_id, group_id, AltsService.CONFIRMED]) return f"Successfully migrated <highlight>%d</highlight> alt characters." % len( data) @command(command="budabot", params=[Const("migrate"), Const("members")], access_level="superadmin", description="Migrate members from a Budabot database") def migrate_budabot_members_cmd(self, request, _1, _2): data = self.db2.query( "SELECT m.name AS sender, p.charid AS char_id, m.autoinv AS auto_invite " f"FROM members_{self.bot_name} m JOIN players p ON (m.name = p.name AND p.dimension = ?) " "WHERE p.charid > 0", [self.dimension]) num = 0 for row in data: char_id = self.resolve_to_char_id(row.sender, row.char_id) if char_id: num += 1 self.db.exec("DELETE FROM member WHERE char_id = ?", [row.char_id]) self.db.exec( "INSERT INTO member (char_id, auto_invite) VALUES (?, ?)", [row.char_id, row.auto_invite]) return f"Successfully migrated <highlight>{num}</highlight> members." @command(command="budabot", params=[Const("migrate"), Const("quotes")], access_level="superadmin", description="Migrate quotes from a Budabot database") def migrate_budabot_quotes_cmd(self, request, _1, _2): data = self.db2.query( "SELECT q.poster, p.charid AS char_id, q.id, q.msg, q.dt " "FROM quote q LEFT JOIN players p ON (q.poster = p.name AND p.dimension = ?)", [self.dimension]) count_inactive = 0 request.reply("Processing %s quote records..." % len(data)) for row in data: char_id = self.resolve_to_char_id(row.poster, row.char_id) if not char_id: char_id = -1 count_inactive += 1 self.db.exec("DELETE FROM quote WHERE id = ?", [row.id]) self.db.exec( "INSERT INTO quote (id, char_id, created_at, content) VALUES (?, ?, ?, ?)", [row.id, char_id, row.dt, row.msg]) return f"Quotes successfully migrated. <highlight>{count_inactive}</highlight> posters were inactive and could not be resolved to char ids." @command(command="budabot", params=[Const("migrate"), Const("log_messages")], access_level="superadmin", description="Migrate log messages from a Budabot database") def migrate_budabot_log_messages_cmd(self, request, _1, _2): data = self.db2.query( f"SELECT p2.charid AS char_id, p1.sender, p1.name, p1.value " f"FROM preferences_{self.bot_name} p1 LEFT JOIN players p2 ON (p1.sender = p2.name AND p2.dimension = ?) " "WHERE p1.name = 'logon_msg' OR p1.name = 'logoff_msg'", [self.dimension]) count_inactive = 0 count_logon = 0 count_logoff = 0 request.reply("Processing %s log messages records..." % len(data)) for row in data: char_id = self.resolve_to_char_id(row.sender, row.char_id) if not char_id: count_inactive += 1 else: existing = self.db.query_single( "SELECT 1 FROM log_messages WHERE char_id = ?", [char_id]) if not existing: self.db.exec( "INSERT INTO log_messages (char_id, logon, logoff) VALUES (?, NULL, NULL)", [char_id]) if row.name == 'logon_msg' and row.value: self.db.exec( "UPDATE log_messages SET logon = ? WHERE char_id = ?", [row.value, char_id]) count_logon += 1 elif row.name == 'logoff_msg' and row.value: self.db.exec( "UPDATE log_messages SET logoff = ? WHERE char_id = ?", [row.value, char_id]) count_logoff += 1 return f"<highlight>{count_logon}</highlight> logon and <highlight>{count_logoff}</highlight> logoff messages successfully migrated. " \ f"<highlight>{count_inactive}</highlight> messages were from inactive characters that could not be resolved to char ids." @command(command="budabot", params=[Const("migrate"), Const("name_history")], access_level="superadmin", description="Migrate name history from a Budabot database") def migrate_budabot_name_history_cmd(self, request, _1, _2): data = self.db2.query( "SELECT charid AS char_id, name, dt AS created_at FROM name_history" ) request.reply( "Processing %s name history records. This may take some time..." % len(data)) with self.db.transaction(): for row in data: self.db.exec( "DELETE FROM name_history WHERE char_id = ? AND name = ?", [row.char_id, row.name]) self.db.exec( "INSERT INTO name_history (char_id, name, created_at) VALUES (?, ?, ?)", [row.char_id, row.name, row.created_at]) return f"Successfully migrated <highlight>%d</highlight> name history records." % len( data) @command(command="budabot", params=[Const("migrate"), Const("news")], access_level="superadmin", description="Migrate news from a Budabot database") def migrate_budabot_news_cmd(self, request, _1, _2): data = self.db2.query( "SELECT n.name AS poster, p.charid AS char_id, news, sticky, time AS created_at, deleted AS deleted_at " "FROM news n JOIN players p ON (n.name = p.name AND p.dimension = ?) WHERE p.charid > 0", [self.dimension]) for row in data: char_id = self.resolve_to_char_id(row.poster, row.char_id) if not char_id: char_id = -1 self.db.exec("DELETE FROM news WHERE char_id = ? AND news = ?", [char_id, row.news]) self.db.exec( "INSERT INTO news (char_id, news, sticky, created_at, deleted_at) VALUES (?, ?, ?, ?, ?)", [ char_id, row.news, row.sticky, row.created_at, row.deleted_at ]) return f"Successfully migrated <highlight>%d</highlight> news records." % len( data) @command(command="budabot", params=[Const("migrate"), Const("notes")], access_level="superadmin", description="Migrate notes from a Budabot database") def migrate_budabot_notes_cmd(self, request, _1, _2): data = self.db2.query( "SELECT n.added_by AS sender, p.charid AS char_id, n.note, n.dt AS created_at " "FROM notes n JOIN players p ON (p.name = n.added_by AND p.dimension = ?) WHERE p.charid > 0", [self.dimension]) num = 0 for row in data: char_id = self.resolve_to_char_id(row.sender, row.char_id) if char_id: num += 1 self.db.exec( "DELETE FROM notes WHERE char_id = ? AND note = ?", [char_id, row.note]) self.db.exec( "INSERT INTO notes (char_id, note, created_at) VALUES (?, ?, ?)", [char_id, row.note, row.created_at]) return f"Successfully migrated <highlight>{num}</highlight> note records." @command(command="budabot", params=[Const("migrate"), Const("last_seen")], access_level="superadmin", description="Migrate last_seen data from a Budabot database") def migrate_budabot_last_seen_cmd(self, request, _1, _2): data = self.db2.query( "SELECT o.name AS sender, p.charid AS char_id, logged_off AS last_seen " f"FROM org_members_{self.bot_name} o JOIN players p ON (o.name = p.name AND p.dimension = ?) " "WHERE p.charid > 0", [self.dimension]) num = 0 with self.db.transaction(): for row in data: char_id = self.resolve_to_char_id(row.sender, row.char_id) if char_id: num += 1 self.db.exec("DELETE FROM last_seen WHERE char_id = ?", [char_id]) self.db.exec( "INSERT INTO last_seen (char_id, dt) VALUES (?, ?)", [char_id, row.last_seen]) return f"Successfully migrated <highlight>{num}</highlight> last seen records." @command(command="budabot", params=[Const("migrate"), Const("cloak_status")], access_level="superadmin", description="Migrate cloak status records from a Budabot database" ) def migrate_budabot_cloak_status_cmd(self, request, _1, _2): if not self.org_id: return "Could not migrate cloak status record since org id is not set." data = self.db2.query( "SELECT o.player AS name, p.charid AS char_id, action, time AS created_at " f"FROM org_city_{self.bot_name} o JOIN players p ON (o.player = p.name AND p.dimension = ?) " "WHERE p.charid > 0", [self.dimension]) num = 0 with self.db.transaction(): self.db.exec("DELETE FROM cloak_status WHERE org_id = ?", [self.org_id]) for row in data: char_id = self.resolve_to_char_id(row.name, row.char_id) if char_id: num += 1 self.db.exec( "INSERT INTO cloak_status (char_id, action, created_at, org_id) VALUES (?, ?, ?, ?)", [char_id, row.action, row.created_at, self.org_id]) return f"Successfully migrated <highlight>{num}</highlight> cloak status records." @command(command="budabot", params=[Const("migrate"), Const("org_activity")], access_level="superadmin", description="Migrate org activity records from a Budabot database" ) def migrate_budabot_org_activity_cmd(self, request, _1, _2): if not self.org_id: return "Could not migrate cloak status record since org id is not set." request.reply("Processing records. This may take some time...") data = self.db2.query( "SELECT o.actor AS actor_name, p1.charid AS actor_char_id, o.actee AS actee_name, p2.charid AS actee_char_id, action, time AS created_at " "FROM org_history o JOIN players p1 ON (o.actor = p1.name AND p1.dimension = ?) JOIN players p2 ON (o.actee = p2.name AND p2.dimension = ?) " "WHERE p1.charid > 0 AND p2.charid > 0", [self.dimension, self.dimension]) num = 0 with self.db.transaction(): self.db.exec("DELETE FROM org_activity WHERE org_id = ?", [self.org_id]) for row in data: actor_char_id = self.resolve_to_char_id( row.actor_name, row.actor_char_id) actee_char_id = self.resolve_to_char_id( row.actee_name, row.actee_char_id) if actor_char_id and actee_char_id: num += 1 self.db.exec( "INSERT INTO org_activity (actor_char_id, actee_char_id, action, created_at, org_id) VALUES (?, ?, ?, ?, ?)", [ actor_char_id, actee_char_id, row.action, row.created_at, self.org_id ]) return f"Successfully migrated <highlight>{num}</highlight> org activity records." @command( command="budabot", params=[Const("migrate"), Const("players")], access_level="superadmin", description="Migrate character info records from a Budabot database") def migrate_budabot_player_cmd(self, request, _1, _2): data = self.db2.query( "SELECT * FROM players WHERE charid > 0 AND dimension = ?", [self.dimension]) request.reply("Processing %s records. This may take some time..." % len(data)) num = 0 with self.db.transaction(): for row in data: if row.charid: num += 1 self.db.exec("DELETE FROM player WHERE char_id = ?", [row.charid]) self.db.exec( "INSERT INTO player (ai_level, ai_rank, breed, char_id, dimension, faction, first_name, gender, head_id, last_name, " "last_updated, level, name, org_id, org_name, org_rank_id, org_rank_name, profession, profession_title, pvp_rating, pvp_title, source) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ row.ai_level, row.ai_rank, row.breed, row.charid, row.dimension, row.faction, row.firstname, row.gender, row.head_id if row.head_id else 0, row.lastname, row.last_update, row.level, row.name, row.guild_id, row.guild, row.guild_rank_id or 0, row.guild_rank, row.profession, row.prof_title, row.pvp_rating if row.pvp_rating else 0, row.pvp_title if row.pvp_title else "", row.source ]) # maybe this is needed also? self.db.exec("DELETE FROM player WHERE char_id = 4294967295") return f"Successfully migrated <highlight>{num}</highlight> character info records." def resolve_to_char_id(self, name, char_id): if char_id and char_id > 0: return char_id char_id = self.character_service.resolve_char_to_id(name) if char_id: return char_id char_info = self.pork_service.get_character_info(name) if char_info: return char_info.char_id return None
class AdminController: def __init__(self): pass def inject(self, registry): self.bot = registry.get_instance("bot") self.admin_manager = registry.get_instance("admin_manager") self.character_manager = registry.get_instance("character_manager") self.pork_manager = registry.get_instance("pork_manager") @command(command="admin", params=[], access_level="all", description="Show the admin list") def admin_list_cmd(self, channel, sender, reply, args): admins = self.admin_manager.get_all() superadmin = self.pork_manager.get_character_info(self.bot.superadmin) superadmin.access_level = "superadmin" admins.insert(0, superadmin) blob = "" current_access_level = "" for row in admins: if row.access_level != current_access_level: blob += "\n<header2>%s<end>\n" % row.access_level.capitalize() current_access_level = row.access_level blob += row.name + "\n" reply(ChatBlob("Admin List (%d)" % len(admins), blob)) @command(command="admin", params=[Const("add"), Any("character")], access_level="superadmin", description="Add an admin", sub_command="modify") def admin_add_cmd(self, channel, sender, reply, args): name = args[1].capitalize() char_id = self.character_manager.resolve_char_to_id(name) if not char_id: reply("Could not find character <highlight>%s<end>." % name) return if self.admin_manager.add(char_id, AdminManager.ADMIN): reply( "Character <highlight>%s<end> added as <highlight>%s<end> successfully." % (name, AdminManager.ADMIN)) else: reply( "Could not add character <highlight>%s<end> as <highlight>%s<end>." % (name, AdminManager.ADMIN)) @command(command="admin", params=[Options(["remove", "rem"]), Any("character")], access_level="superadmin", description="Remove an admin", sub_command="modify") def admin_remove_cmd(self, channel, sender, reply, args): name = args[1].capitalize() char_id = self.character_manager.resolve_char_to_id(name) if not char_id: reply("Could not find character <highlight>%s<end>." % name) return if self.admin_manager.remove(char_id): reply( "Character <highlight>%s<end> removed as <highlight>%s<end> successfully." % (name, AdminManager.ADMIN)) else: reply( "Could not remove character <highlight>%s<end> as <highlight>%s<end>." % (name, AdminManager.ADMIN)) @command(command="moderator", params=[Const("add"), Any("character")], access_level="superadmin", description="Add a moderator", sub_command="modify") def moderator_add_cmd(self, channel, sender, reply, args): name = args[1].capitalize() char_id = self.character_manager.resolve_char_to_id(name) if not char_id: reply("Could not find character <highlight>%s<end>." % name) return if self.admin_manager.add(char_id, AdminManager.MODERATOR): reply( "Character <highlight>%s<end> added as <highlight>%s<end> successfully." % (name, AdminManager.MODERATOR)) else: reply( "Could not add character <highlight>%s<end> as <highlight>%s<end>." % (name, AdminManager.MODERATOR)) @command(command="moderator", params=[Options(["remove", "rem"]), Any("character")], access_level="superadmin", description="Remove a moderator", sub_command="modify") def moderator_remove_cmd(self, channel, sender, reply, args): name = args[1].capitalize() char_id = self.character_manager.resolve_char_to_id(name) if not char_id: reply("Could not find character <highlight>%s<end>." % name) return if self.admin_manager.remove(char_id): reply( "Character <highlight>%s<end> removed as <highlight>%s<end> successfully." % (name, AdminManager.MODERATOR)) else: reply( "Could not remove character <highlight>%s<end> as <highlight>%s<end>." % (name, AdminManager.MODERATOR))
class AuctionController: def __init__(self): self.auction: AuctionStrategy = None @setting(name="auction_length", value="90s", description="Regular auction length in seconds") def auction_length(self): return TimeSettingType() @setting(name="auction_announce_interval", value="15s", description="Auction announce interval") def auction_announce_interval(self): return TimeSettingType() @setting(name="auction_late_bid_extension", value="10s", description="How much to extend auction in case of late bid") def auction_late_bid_extension(self): return TimeSettingType() @setting(name="auction_late_bid_threshold", value="10s", description="Threshold for a bid to be considered late") def auction_late_bid_threshold(self): return TimeSettingType() @command(command="auction", params=[], description="Show auction status", access_level="member") def auction_cmd(self, request): if not self.is_auction_running(): return "No auction running." return self.auction.get_auction_list() @command(command="auction", params=[Const("start"), Any("items")], description="Start an auction, with one or more items", access_level="moderator", sub_command="modify") def auction_start_cmd(self, request, _, items): if self.is_auction_running(): return "Auction already running." items = re.findall( r"(([^<]+)?<a href=[\"\']itemref://(\d+)/(\d+)/(\d+)[\"\']>([^<]+)</a>([^<]+)?)", items) # TODO choose auction strategy impl self.auction = AuctionStrategy() for item in items: self.auction.add_item(item[0]) auction_length = self.auction_length().get_value() announce_interval = self.auction_announce_interval().get_value() late_bid_threshold = self.auction_late_bid_threshold().get_value() late_bid_extension = self.auction_late_bid_extension().get_value() return self.auction.start(request.sender, auction_length, announce_interval, late_bid_threshold, late_bid_extension) @command(command="auction", params=[Options(["cancel", "end"])], description="Cancel ongoing auction", access_level="moderator", sub_command="modify") def auction_cancel_cmd(self, request, _): if not self.is_auction_running(): return "No auction running." result = self.auction.cancel(request.sender) self.auction = None return result @command(command="auction", params=[ Const("bid"), Int("amount", is_optional=True), Const("all", is_optional=True), Int("item_index", is_optional=True) ], description="Bid on an item", access_level="member") def auction_bid_cmd(self, request, _, amount, all_amount, item_index): if not self.is_auction_running(): return "No auction running." return self.auction.add_bid(request.sender, all_amount or amount, item_index) def is_auction_running(self): return self.auction and self.auction.is_running
class TopicController: def inject(self, registry): self.bot = registry.get_instance("bot") self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.util = registry.get_instance("util") self.command_alias_service = registry.get_instance( "command_alias_service") self.private_channel_service: PrivateChannelService = registry.get_instance( "private_channel_service") def start(self): self.command_alias_service.add_alias("motd", "topic") @setting(name="topic", value="", description="The bot topic") def topic(self): return DictionarySettingType() @command(command="topic", params=[], access_level="all", description="Show the current topic") def topic_show_command(self, request): topic = self.topic().get_value() if topic: return self.format_topic_message(topic) else: return "There is no current topic." @command(command="topic", params=[Options(["clear", "unset"])], access_level="all", description="Clears the current topic") def topic_clear_command(self, request, _): self.topic().set_value("") return "The topic has been cleared." @command(command="topic", params=[Const("set", is_optional=True), Any("topic_message")], access_level="all", description="Set the current topic") def topic_set_command(self, request, _, topic_message): sender = DictObject({ "name": request.sender.name, "char_id": request.sender.char_id }) topic = { "topic_message": topic_message, "created_by": sender, "created_at": int(time.time()) } self.topic().set_value(topic) return "The topic has been set." def format_topic_message(self, topic): time_string = self.util.time_to_readable( int(time.time()) - topic["created_at"]) return "Topic: <highlight>%s<end> [set by <highlight>%s<end>][%s ago]" % ( topic["topic_message"], topic["created_by"]["name"], time_string) @event(PrivateChannelService.JOINED_PRIVATE_CHANNEL_EVENT, "Show topic to characters joining the private channel") def show_topic(self, _, event_data): topic = self.topic().get_value() if topic: self.bot.send_private_message(event_data.char_id, self.format_topic_message(topic)) @event(PrivateChannelService.LEFT_PRIVATE_CHANNEL_EVENT, "Clear topic when there are no characters in the private channel") def clear_topic(self, _, event_data): if self.topic().get_value() and len(self.private_channel_service. get_all_in_private_channel()) == 0: self.topic().set_value("")
class CloakController: MESSAGE_SOURCE = "cloak_reminder" CLOAK_EVENT = "cloak" CLOAK_STATUS_OFF = "off" CLOAK_STATUS_ON = "on" CLOAK_STATUS_MANUAL = "on*" def inject(self, registry): self.bot = registry.get_instance("bot") self.db = registry.get_instance("db") self.util = registry.get_instance("util") self.text = registry.get_instance("text") self.character_service = registry.get_instance("character_service") self.command_alias_service = registry.get_instance("command_alias_service") self.timer_controller = registry.get_instance("timer_controller", is_optional=True) self.event_service = registry.get_instance("event_service") self.access_service = registry.get_instance("access_service") self.message_hub_service = registry.get_instance("message_hub_service") def pre_start(self): self.bot.register_packet_handler(PublicChannelMessage.id, self.handle_public_message) self.event_service.register_event_type(self.CLOAK_EVENT) self.message_hub_service.register_message_source(self.MESSAGE_SOURCE) def start(self): self.db.exec("CREATE TABLE IF NOT EXISTS cloak_status (char_id INT NOT NULL, action VARCHAR(10) NOT NULL, created_at INT NOT NULL, org_id INT NOT NULL)") self.command_alias_service.add_alias("city", "cloak") @command(command="cloak", params=[Const("history"), Int("org_id")], access_level="org_member", description="Shows the cloak history") def cloak_history_command(self, request, _, org_id): data = self.db.query("SELECT c.*, p.name FROM cloak_status c LEFT JOIN player p ON c.char_id = p.char_id " "WHERE c.org_id = ? ORDER BY created_at DESC LIMIT 20", [org_id]) blob = "" for row in data: action = self.get_cloak_status_display(row.action) blob += "%s turned the device %s at %s.\n" % (row.name, action, self.util.format_datetime(row.created_at)) conn = self.bot.get_conn_by_org_id(org_id) org_name = conn.get_org_name() return ChatBlob(f"Cloak History for {org_name}", blob) @command(command="cloak", params=[NamedFlagParameters(["all"])], access_level="org_member", description="Shows the cloak status") def cloak_command(self, request, flag_params): t = int(time.time()) if flag_params.all: blob = "" for _id, conn in self.bot.get_conns(lambda x: x.is_main and x.org_id): row = self.db.query_single("SELECT c.char_id, c.action, c.created_at, p.name FROM cloak_status c LEFT JOIN player p ON c.char_id = p.char_id " "WHERE c.org_id = ? ORDER BY created_at DESC LIMIT 1", [conn.org_id]) org_name = conn.get_org_name() if row: action = self.get_cloak_status_display(row.action) time_str = self.util.time_to_readable(t - row.created_at) history_link = self.text.make_tellcmd("History", f"cloak history {conn.org_id}") blob += f"{org_name} - {action} [{time_str} ago] {history_link}\n" else: blob += f"{org_name} - Unknown\n" title = "Cloak Status" return ChatBlob(title, blob) else: conn = request.conn if not conn.org_id: return "This bot is not a member of an org." row = self.db.query_single("SELECT c.char_id, c.action, c.created_at, p.name FROM cloak_status c LEFT JOIN player p ON c.char_id = p.char_id " "WHERE c.org_id = ? ORDER BY created_at DESC LIMIT 1", [conn.org_id]) org_name = conn.get_org_name() if row: action = self.get_cloak_status_display(row.action) time_str = self.util.time_to_readable(t - row.created_at) return f"{org_name} - {action} [{time_str} ago]" else: return f"{org_name} - Unknown cloak status" @command(command="cloak", params=[Options(["raise", "on"]), Int("org_id", is_optional=True)], access_level="org_member", description="Manually raises the cloak status on the bot") def cloak_raise_command(self, request, _, org_id): org_id = org_id or request.conn.org_id if not org_id: return "This bot is not a member of an org." row = self.db.query_single("SELECT action FROM cloak_status WHERE org_id = ?", [org_id]) if row and (row.action == self.CLOAK_STATUS_ON or row.action == self.CLOAK_STATUS_MANUAL): return "The cloaking device is already <green>enabled</green>." self.db.exec("INSERT INTO cloak_status (char_id, action, created_at, org_id) VALUES (?, ?, ?, ?)", [request.sender.char_id, self.CLOAK_STATUS_MANUAL, int(time.time()), org_id]) return "The cloaking device has been set to enabled in the bot (you must still enable the cloak if it is disabled)." @event(event_type=CLOAK_EVENT, description="Record when the city cloak is turned off and on", is_system=True) def city_cloak_event(self, event_type, event_data): self.db.exec("INSERT INTO cloak_status (char_id, action, created_at, org_id) VALUES (?, ?, ?, ?)", [event_data.char_id, event_data.action, int(time.time()), event_data.conn.org_id]) @timerevent(budatime="15m", description="Reminds the players when cloak can be raised") def cloak_reminder_event(self, event_type, event_data): messages = [] for _id, conn in self.bot.get_conns(lambda x: x.is_main and x.org_id): row = self.db.query_single("SELECT c.*, p.name FROM cloak_status c LEFT JOIN player p ON c.char_id = p.char_id " "WHERE c.org_id = ? ORDER BY created_at DESC LIMIT 1", [conn.org_id]) if row: one_hour = 3600 t = int(time.time()) time_until_change = row.created_at + one_hour - t if row.action == self.CLOAK_STATUS_OFF and 0 >= time_until_change > (one_hour * 6 * -1): time_str = self.util.time_to_readable(t - row.created_at) org_name = conn.get_org_name() messages.append(f"The cloaking device for org <highlight>{org_name}</highlight> is <orange>disabled</orange> but can be enabled. " f"<highlight>{row.name}</highlight> disabled it {time_str} ago.") if messages: self.message_hub_service.send_message(self.MESSAGE_SOURCE, None, None, "\n".join(messages)) @event(event_type=CLOAK_EVENT, description="Set a timer for when cloak can be raised and lowered") def city_cloak_timer_event(self, event_type, event_data): if event_data.action == self.CLOAK_STATUS_OFF: timer_name = f"Raise City Cloak ({event_data.conn.get_org_name()})" elif event_data.action == self.CLOAK_STATUS_ON: timer_name = f"Lower City Cloak ({event_data.conn.get_org_name()})" else: raise Exception(f"Unknown cloak action '{event_data.action}'") timer_name = self.timer_controller.get_timer_name(timer_name) self.timer_controller.add_timer(timer_name, event_data.char_id, PublicChannelService.ORG_CHANNEL_COMMAND, int(time.time()), 3600) def handle_public_message(self, conn: Conn, packet: PublicChannelMessage): if not conn.is_main: return ext_msg = packet.extended_message if ext_msg and ext_msg.category_id == 1001 and ext_msg.instance_id == 1: char_name = ext_msg.params[0] char_id = self.character_service.resolve_char_to_id(char_name) action = ext_msg.params[1] self.event_service.fire_event(self.CLOAK_EVENT, DictObject({"char_id": char_id, "name": char_name, "action": action, "conn": conn})) def get_cloak_status_display(self, status): if status == self.CLOAK_STATUS_ON: return "<green>on</green>" elif status == self.CLOAK_STATUS_MANUAL: return "<green>on*</green>" elif status == self.CLOAK_STATUS_OFF: return "<orange>off</orange>" else: return "<highlight>Unknown</highlight>"
class LogController: def inject(self, registry): self.db: DB = registry.get_instance("db") def start(self): self.db.exec( "CREATE TABLE IF NOT EXISTS log_messages (char_id INT NOT NULL PRIMARY KEY, logon TEXT, logoff TEXT)" ) @command(command="logon", params=[], access_level="member", description="Check your current logon message") def check_current_logon(self, request): current_logon = self.get_logon(request.sender.char_id) if current_logon: return "%s's logon message is: %s" % (request.sender.name, current_logon) else: return "Your logon message has not been set." @command(command="logon", params=[Const("clear")], access_level="member", description="Clear your logon message") def clear_logon(self, request, params): if self.db.query_single( "SELECT logon FROM log_messages WHERE char_id=?;", [request.sender.char_id]): self.db.exec("UPDATE log_messages SET logon=NULL WHERE char_id=?;", [request.sender.char_id]) return "Your logon message has been cleared." @command(command="logon", params=[Any("logon_message")], access_level="member", description="Set your logon message") def set_logon(self, request: CommandRequest, logon_message): if self.db.query_single( "SELECT logon FROM log_messages WHERE char_id=?;", [request.sender.char_id]): self.db.exec("UPDATE log_messages SET logon=? WHERE char_id=?;", [logon_message, request.sender.char_id]) else: self.db.exec( "INSERT INTO log_messages (char_id, logon) VALUES(?, ?);", [request.sender.char_id, logon_message]) return "Your new logon message is: %s" % logon_message @command(command="logoff", params=[], access_level="member", description="Check your current logoff message") def check_current_logoff(self, request): current_logoff = self.get_logoff(request.sender.char_id) if current_logoff: return "%s's logoff message is: %s" % (request.sender.name, current_logoff) else: return "Your logoff message has not been set." @command(command="logoff", params=[Const("clear")], access_level="member", description="Clear your logoff message") def clear_logoff(self, request, params): if self.db.query_single( "SELECT logoff FROM log_messages WHERE char_id=?;", [request.sender.char_id]): self.db.exec( "UPDATE log_messages SET logoff=NULL WHERE char_id=?;", [request.sender.char_id]) return "Your logoff message has been cleared." @command(command="logoff", params=[Any("logoff_message")], access_level="member", description="Set your logoff message") def set_logoff(self, request: CommandRequest, logoff_message): if self.db.query_single( "SELECT logoff FROM log_messages WHERE char_id=?;", [request.sender.char_id]): self.db.exec("UPDATE log_messages SET logoff=? WHERE char_id=?;", [logoff_message, request.sender.char_id]) else: self.db.exec( "INSERT INTO log_messages (char_id, logoff) VALUES(?, ?);", [request.sender.char_id, logoff_message]) return "Your new logoff message is: %s" % logoff_message def get_logon(self, char_id): row = self.db.query_single( "SELECT * FROM log_messages WHERE char_id=?", [char_id]) if row and row.logon: return "<grey>" + row.logon + "</grey>" return "" def get_logoff(self, char_id): row = self.db.query_single( "SELECT * FROM log_messages WHERE char_id=?", [char_id]) if row and row.logoff: return "<grey>" + row.logoff + "</grey>" return ""
class TowerController: def __init__(self): self.logger = Logger(__name__) def inject(self, registry): self.bot: Tyrbot = registry.get_instance("bot") self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.util = registry.get_instance("util") self.command_alias_service = registry.get_instance( "command_alias_service") self.setting_service = registry.get_instance("setting_service") self.playfield_controller: PlayfieldController = registry.get_instance( "playfield_controller") self.level_controller = registry.get_instance("level_controller") def pre_start(self): self.db.load_sql_file(self.module_dir + "/" + "tower_site.sql") self.db.load_sql_file(self.module_dir + "/" + "tower_site_bounds.sql") def start(self): self.command_alias_service.add_alias("hot", "lc open") self.setting_service.register( self.module_name, "tower_api_address", "https://tower-api.jkbff.com/v1/api/towers", TextSettingType(["https://tower-api.jkbff.com/v1/api/towers"]), "The address of the Tower API") self.setting_service.register(self.module_name, "tower_api_custom_headers", "", DictionarySettingType(), "Custom headers for the Tower API") @command(command="lc", params=[], access_level="guest", description= "See a list of playfields containing land control tower sites") def lc_list_cmd(self, request): data = self.db.query( "SELECT id, long_name, short_name FROM playfields WHERE id IN (SELECT DISTINCT playfield_id FROM tower_site) ORDER BY short_name" ) blob = "" for row in data: blob += "[%d] %s <highlight>%s</highlight>\n" % ( row.id, self.text.make_tellcmd( row.long_name, "lc %s" % row.short_name), row.short_name) blob += "\n" + self.get_lc_blob_footer() return ChatBlob("Land Control Playfields (%d)" % len(data), blob) if FeatureFlags.USE_TOWER_API: @command(command="lc", params=[Const("org"), Any("org", is_optional=True)], access_level="guest", description="See a list of land control tower sites by org") def lc_org_cmd(self, request, _, org): params = list() params.append(("enabled", "true")) if not org: org = str(request.conn.org_id) if not org: return "Bot does not belong to an org so an org name or org id must be specified." if org.isdigit(): params.append(("org_id", org)) else: for org_name_piece in org.split(" "): params.append(("org_name", "%" + org_name_piece + "%")) data = self.lookup_tower_info(params).results t = int(time.time()) grouped_data = self.util.group_by(data, lambda x: (x.org_id, x.org_name)) blob = "" for k, v in grouped_data.items(): v = sorted(v, key=lambda x: x.ql) org_blob = "" ct_types = [] ql_total = 0 for ct in v: ct_types.append(self.get_ct_type(ct.ql)) ql_total += ct.ql org_blob += self.format_site_info(ct, t) + "\n" blob += f"<pagebreak><header2>{k[1]} ({k[0]})</header2>" blob += " Types: <highlight>" + ", ".join( ct_types ) + f"</highlight> Total CT QL: <highlight>{ql_total}</highlight>\n\n" blob += org_blob + "\n" return ChatBlob(f"Org Info for '{org}' ({len(data)})", blob) @command( command="lc", params=[ Options([ "all", "open", "closed", "penalty", "unplanted", "disabled" ]), Options(["omni", "clan", "neutral", "all"], is_optional=True), Int("pvp_level", is_optional=True), Time("time", is_optional=True) ], access_level="guest", description= "See a list of land control tower sites by QL, faction, and open status", extended_description= "The time param only applies when the first param is either 'open' or 'closed'" ) def lc_search_cmd(self, request, site_status, faction, pvp_level, time_offset): t = int(time.time()) relative_time = t + (time_offset or 0) min_ql = 1 max_ql = 300 if pvp_level: level_info = self.level_controller.get_level_info(pvp_level) if not level_info: return "PVP level must be between 1 and 220." min_ql = level_info.pvp_min max_ql = level_info.pvp_max params = list() if site_status.lower() == "disabled": params.append(("enabled", "false")) else: params.append(("enabled", "true")) if min_ql > 1: params.append(("min_ql", min_ql)) if max_ql < 300: params.append(("max_ql", max_ql)) if faction and faction != "all": params.append(("faction", faction)) if site_status.lower() == "open": params.append(("min_close_time", relative_time)) params.append(("max_close_time", relative_time + (3600 * 6))) elif site_status.lower() == "closed": params.append(("min_close_time", relative_time + (3600 * 6))) params.append(("max_close_time", relative_time + (3600 * 24))) elif site_status.lower() == "penalty": params.append(("penalty", "true")) elif site_status.lower() == "unplanted": params.append(("planted", "false")) data = self.lookup_tower_info(params).results blob = "" for row in data: blob += "<pagebreak>" + self.format_site_info(row, t) + "\n" if blob: blob += self.get_lc_blob_footer() title = "Tower Info: %s" % site_status.capitalize() if min_ql > 1 or max_ql < 300: title += " QL %d - %d" % (min_ql, max_ql) if faction: title += " [%s]" % faction.capitalize() if time_offset: title += " in " + self.util.time_to_readable(time_offset) title += " (%d)" % len(data) return ChatBlob(title, blob) @command(command="lc", params=[Any("playfield"), Int("site_number", is_optional=True)], access_level="guest", description= "See a list of land control tower sites in a particular playfield" ) def lc_playfield_cmd(self, request, playfield_name, site_number): playfield = self.playfield_controller.get_playfield_by_name_or_id( playfield_name) if not playfield: return f"Could not find playfield <highlight>{playfield_name}</highlight>." data = self.get_tower_site_info(playfield.id, site_number) blob = "" t = int(time.time()) for row in data: blob += "<pagebreak>" + self.format_site_info(row, t) + "\n" blob += self.get_lc_blob_footer() if site_number: title = "Tower Info: %s %d" % (playfield.long_name, site_number) else: title = "Tower Info: %s (%d)" % (playfield.long_name, len(data)) return ChatBlob(title, blob) def format_site_info(self, row, t): blob = "<highlight>%s %d</highlight> (QL %d-%d) %s\n" % ( row.playfield_short_name, row.site_number, row.min_ql, row.max_ql, row.site_name) if row.get("org_name"): current_day_time = t % 86400 value = datetime.fromtimestamp(row.close_time, tz=pytz.UTC) current_status_time = row.close_time - current_day_time if current_status_time < 0: current_status_time += 86400 status = "" if current_status_time <= 3600: status += "<red>5%%</red> (closes in %s)" % self.util.time_to_readable( current_status_time) elif current_status_time <= (3600 * 6): status += "<orange>25%%</orange> (closes in %s)" % self.util.time_to_readable( current_status_time) else: status += "<green>75%%</green> (opens in %s)" % self.util.time_to_readable( current_status_time - (3600 * 6)) if row.penalty_until > t: status += " <red>Penalty</red> (for %s)" % self.util.time_to_readable( row.penalty_until - t) blob += "%s (%d) [%s] <highlight>QL %d</highlight> %s %s\n" % ( row.org_name, row.org_id, self.text.get_formatted_faction(row.faction), row.ql, self.text.make_chatcmd( "%dx%d" % (row.x_coord, row.y_coord), "/waypoint %d %d %d" % (row.x_coord, row.y_coord, row.playfield_id)), self.util.time_to_readable(t - row.created_at)) blob += "Close Time: <highlight>%s</highlight> %s\n" % ( value.strftime("%H:%M:%S %Z"), status) else: blob += "%s\n" % self.text.make_chatcmd( "%dx%d" % (row.x_coord, row.y_coord), "/waypoint %d %d %d" % (row.x_coord, row.y_coord, row.playfield_id)) if not row.enabled: blob += "<red>Disabled</red>\n" return blob def get_tower_site_info(self, playfield_id, site_number): if FeatureFlags.USE_TOWER_API: params = list() params.append(("playfield_id", playfield_id)) if site_number: params.append(("site_number", site_number)) data = self.lookup_tower_info(params).results else: if site_number: data = self.db.query( "SELECT t.*, p.short_name AS playfield_short_name, p.long_name AS playfield_long_name " "FROM tower_site t JOIN playfields p ON t.playfield_id = p.id WHERE t.playfield_id = ? AND site_number = ?", [playfield_id, site_number]) else: data = self.db.query( "SELECT t.*, p.short_name AS playfield_short_name, p.long_name AS playfield_long_name " "FROM tower_site t JOIN playfields p ON t.playfield_id = p.id WHERE t.playfield_id = ?", [playfield_id]) return data def lookup_tower_info(self, params): url = self.setting_service.get("tower_api_address").get_value() headers = self.setting_service.get( "tower_api_custom_headers").get_value() or {} headers.update({"User-Agent": f"Tyrbot {self.bot.version}"}) r = requests.get(url, params, headers=headers, timeout=5) result = DictObject(r.json()) return result def get_lc_blob_footer(self): return "Thanks to Draex and Unk for providing the tower information. And a special thanks to Trey." def get_ct_type(self, ql): if ql < 34: return "I" elif ql < 82: return "II" elif ql < 129: return "III" elif ql < 177: return "IV" elif ql < 201: return "V" elif ql < 226: return "VI" else: return "VII"
class ConfigEventsController: def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.command_service = registry.get_instance("command_service") self.event_service = registry.get_instance("event_service") self.setting_service = registry.get_instance("setting_service") self.ts: TranslationService = registry.get_instance("translation_service") self.getresp = self.ts.get_response @command(command="config", params=[Const("event"), Any("event_type"), Any("event_handler"), Options(["enable", "disable"])], access_level="admin", description="Enable or disable an event") def config_event_status_cmd(self, request, _, event_type, event_handler, action): event_type = event_type.lower() event_handler = event_handler.lower() action = action.lower() event_base_type, event_sub_type = self.event_service.get_event_type_parts(event_type) enabled = 1 if action == "enable" else 0 if not self.event_service.is_event_type(event_base_type): return self.getresp("module/config", "unknown event", {"type", event_type}) count = self.event_service.update_event_status(event_base_type, event_sub_type, event_handler, enabled) if count == 0: return self.getresp("module/config", "event_enable_fail", {"type": event_type, "handler": event_handler}) else: action = self.getresp("module/config", "enabled_high" if action == "enable" else "disabled_high") return self.getresp("module/config", "event_enable_success", {"type": event_type, "handler": event_handler, "changedto": action}) @command(command="config", params=[Const("event"), Any("event_type"), Any("event_handler"), Const("run")], access_level="admin", description="Execute a timed event immediately") def config_event_run_cmd(self, request, _1, event_type, event_handler, _2): action = "run" event_type = event_type.lower() event_handler = event_handler.lower() event_base_type, event_sub_type = self.event_service.get_event_type_parts(event_type) if not self.event_service.is_event_type(event_base_type): return self.getresp("module/config", "unknown event", {"type", event_type}) row = self.db.query_single("SELECT e.event_type, e.event_sub_type, e.handler, t.next_run FROM timer_event t " "JOIN event_config e ON t.event_type = e.event_type AND t.handler = e.handler " "WHERE e.event_type = ? AND e.event_sub_type = ? AND e.handler LIKE ?", [event_base_type, event_sub_type, event_handler]) if not row: return self.getresp("module/config", "event_enable_fail", {"type": event_type, "handler": event_handler}) elif row.event_type != "timer": return self.getresp("module/config", "event_manual") else: self.event_service.execute_timed_event(row, int(time.time())) action = self.getresp("module/config", "run") return self.getresp("module/config", "event_enable_success", {"type": event_type, "handler": event_handler, "changedto": action}) @command(command="config", params=[Const("eventlist")], access_level="admin", description="List all events") def config_eventlist_cmd(self, request, _): sql = "SELECT module, event_type, event_sub_type, handler, description, enabled FROM event_config WHERE is_hidden = 0" sql += " ORDER BY module, event_type, event_sub_type, handler" data = self.db.query(sql) blob = "" current_module = "" for row in data: if current_module != row.module: blob += "\n<pagebreak><header2>%s<end>\n" % row.module current_module = row.module event_type_key = self.format_event_type(row) on_link = self.text.make_chatcmd("On", "/tell <myname> config event %s %s enable" % (event_type_key, row.handler)) off_link = self.text.make_chatcmd("Off", "/tell <myname> config event %s %s disable" % (event_type_key, row.handler)) blob += "%s [%s] %s %s - %s\n" % (event_type_key, self.format_enabled(row.enabled), on_link, off_link, row.description) return ChatBlob(self.getresp("module/config", "blob_events", {"amount": len(data)}), blob) def format_enabled(self, enabled): return "<green>E<end>" if enabled else "<red>D<end>" def format_event_type(self, row): if row.event_sub_type: return row.event_type + ":" + row.event_sub_type else: return row.event_type
class RandomController: def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.util = registry.get_instance("util") self.character_service = registry.get_instance("character_service") self.command_alias_service = registry.get_instance( "command_alias_service") def start(self): self.command_alias_service.add_alias("verify", "roll verify") @command( command="random", params=[Any("items")], access_level="all", description="Randomly order a list of elements", extended_description="Enter a space-delimited list of items to randomize" ) def random_command(self, request, items): items = items.split(" ") random.shuffle(items) return " ".join(items) @command(command="roll", params=[Int("start_value", is_optional=True), Int("end_value")], access_level="all", description="Roll a number between 1 and a number") def roll_command(self, request, start_value, end_value): start_value = start_value or 1 if start_value > end_value: start_value, end = end_value, start_value result = random.randint(start_value, end_value) self.db.exec( "INSERT INTO roll (char_id, min_value, max_value, result, created_at) VALUES (?, ?, ?, ?, ?)", [ request.sender.char_id, start_value, end_value, result, int(time.time()) ]) return "Rolling between %d and %d: <highlight>%d<end>. /tell <myname> roll verify %d" % ( start_value, end_value, result, self.db.last_insert_id()) @command(command="roll", params=[Const("verify"), Int("roll_id")], access_level="all", description="Verify a roll that happened") def roll_verify_command(self, request, _, roll_id): row = self.db.query_single("SELECT * FROM roll WHERE id = ?", [roll_id]) if not row: return "Could not find roll with id <highlight>%d<end>." % roll_id else: time_string = self.util.time_to_readable( int(time.time()) - row.created_at) name = self.character_service.resolve_char_to_name(row.char_id) return "Rolling between %d and %d: <highlight>%d<end>. %s ago for %s." % ( row.min_value, row.max_value, row.result, time_string, name)
class AltsController: def inject(self, registry): self.bot = registry.get_instance("bot") self.alts_service = registry.get_instance("alts_service") self.buddy_service = registry.get_instance("buddy_service") self.ts: TranslationService = registry.get_instance("translation_service") self.getresp = self.ts.get_response def start(self): self.ts.register_translation("module/alts", self.load_alts_msg) def load_alts_msg(self): with open("modules/core/alts/alts.msg", mode="r", encoding="UTF-8") as f: return hjson.load(f) @command(command="alts", params=[], access_level="all", description="Show your alts") def alts_list_cmd(self, request): alts = self.alts_service.get_alts(request.sender.char_id) blob = self.format_alt_list(alts) return ChatBlob(self.getresp("module/alts", "list", {"char": alts[0].name, "amount": len(alts)}), blob) def get_alt_status(self, status): if status == AltsService.MAIN: return " - [main]" else: return "" @command(command="alts", params=[Const("setmain")], access_level="all", description="Set a new main", extended_description="You must run this from the character you want to be your new main") def alts_setmain_cmd(self, request, _): msg, result = self.alts_service.set_as_main(request.sender.char_id) if result: return self.getresp("module/alts", "new_main", {"char":request.sender.name}) elif msg == "not_an_alt": return self.getresp("module/alts", "not_an_alt") elif msg == "already_main": return self.getresp("module/alts", "already_main") else: raise Exception("Unknown msg: " + msg) @command(command="alts", params=[Const("add"), Character("character")], access_level="all", description="Add an alt") def alts_add_cmd(self, request, _, alt_char): if not alt_char.char_id: return self.getresp("global", "char_not_found", {"char":alt_char.name}) elif alt_char.char_id == request.sender.char_id: return self.getresp("module/alts", "add_fail_self") msg, result = self.alts_service.add_alt(request.sender.char_id, alt_char.char_id) if result: self.bot.send_private_message(alt_char.char_id, self.getresp("module/alts", "add_success_target", {"char": request.sender.name})) return self.getresp("module/alts", "add_success_self", {"char": alt_char.name}) elif msg == "another_main": return self.getresp("module/alts", "add_fail_already", {"char": alt_char.name}) else: raise Exception("Unknown msg: " + msg) @command(command="alts", params=[Options(["rem", "remove"]), Character("character")], access_level="all", description="Remove an alt") def alts_remove_cmd(self, request, _, alt_char): if not alt_char.char_id: return self.getresp("global", "char_not_found", {"char":alt_char.name}) msg, result = self.alts_service.remove_alt(request.sender.char_id, alt_char.char_id) if result: return self.getresp("module/alts", "rem_success", {"char": alt_char.name}) elif msg == "not_alt": return self.getresp("module/alts", "rem_fail_not", {"char": alt_char.name}) elif msg == "remove_main": return self.getresp("module/alts", "rem_fail_main") else: raise Exception("Unknown msg: " + msg) @command(command="alts", params=[Character("character")], access_level="member", description="Show alts of another character", sub_command="show") def alts_list_other_cmd(self, request, char): if not char.char_id: return self.getresp("global", "char_not_found", {"char":char.name}) alts = self.alts_service.get_alts(char.char_id) blob = self.format_alt_list(alts) return ChatBlob(self.getresp("module/alts", "alts_list", {"char": alts[0].name, "amount": len(alts)}), blob) def format_alt_list(self, alts): blob = "" for alt in alts: blob += "<highlight>%s<end> (%d/<green>%d<end>) %s %s" % (alt.name, alt.level, alt.ai_level, alt.faction, alt.profession) if self.buddy_service.is_online(alt.char_id): blob += " [<green>Online<end>]" blob += "\n" return blob