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 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") self.command_alias_service = registry.get_instance( "command_alias_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: 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=[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."
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})
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 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 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 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 LangController: def inject(self, registry): self.ts = registry.get_instance("translation_service") self.bot: Tyrbot = registry.get_instance("bot") @command(command="lang", params=[Options(["de_DE", "en_US"])], description="Changes the language of the bot", access_level="moderator", sub_command="set") def reload_translation(self, request, lang): self.ts.reload_translation(lang) return "Language changed to <highlight>{lang}<end>".format(lang=lang) @command(command="lang", params=[], description="Gets the language of the bot", access_level="all") def print_lang(self, _): return "My current language is <highlight>{lang}<end>".format( lang=self.ts.language)
class LangController: def inject(self, registry): self.ts = registry.get_instance("translation_service") self.bot: Tyrbot = registry.get_instance("bot") self.getresp = registry.get_instance( "translation_service").get_response @command(command="lang", params=[Options(["de_DE", "en_US"])], description="Changes the language of the bot", access_level="moderator", sub_command="set") def reload_translation(self, request, lang): self.ts.reload_translation(lang) return self.getresp("module/system", "reload_lang", {"lang_code": lang}) @command(command="lang", params=[], description="Gets the language of the bot", access_level="all") def print_lang(self, _): return self.getresp("module/system", "current_lang", {"lang_code": self.ts.language})
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
class LeaderController: NOT_LEADER_MSG = "Error! You must be raid leader, or have higher access " \ "level than the raid leader to use this command." def __init__(self): self.leader = None self.last_activity = None self.echo = False def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.access_service: AccessService = registry.get_instance( "access_service") self.character_service: CharacterService = registry.get_instance( "character_service") self.bot: Tyrbot = registry.get_instance("bot") self.setting_service: SettingService = registry.get_instance( "setting_service") @setting( name="leader_echo_color", value="#00FF00", description="Color with which the leader's messages will be echoed with" ) def leader_echo_color(self): return ColorSettingType() @setting(name="leader_auto_echo", value=True, description= "If turned on, when someone assume the leader role, leader echo " "will automatically be activated for said person") def leader_auto_echo(self): return BooleanSettingType() @command(command="leader", params=[], access_level="all", description="Show the current raid leader") def leader_show_command(self, _): if self.leader: return "The current raid leader is <highlight>%s<end>." % self.leader.name else: return "There is no current raid leader. Use <highlight><symbol>leader set<end> to become the raid leader." @command( command="leader", params=[Const("echo"), Options(["on", "off"])], access_level="all", description= "Echo whatever the current leader types in channel, in a distinctive color" ) def leader_echo_command(self, request, _2, switch_to): if self.leader: if self.can_use_command(request.sender.char_id): self.echo = switch_to == "on" return "Leader echo for <highlight>%s<end> has been turned <highlight>%s<end>." % \ (self.leader.name, switch_to) else: return "Insufficient access level." elif self.leader is None and switch_to == "on": return "No current leader set, can't turn on leader echo." @command(command="leader", params=[Const("echo")], access_level="all", description="See the current status for leader echoing") def leader_echo_status_command(self, _1, _2): if self.leader: on_off = "on" if self.echo else "off" return "<highlight>%s<end> is set as leader, leader echo is <highlight>%s<end>" % \ (self.leader.name, on_off) return "No current leader set." @command(command="leader", params=[Const("clear")], access_level="all", description="Clear the current raid leader") def leader_clear_command(self, request, _): return self.set_raid_leader(request.sender, None) @command(command="leader", params=[Const("set")], access_level="all", description="Set (or unset) yourself as raid leader") def leader_set_self_command(self, request, _): return self.set_raid_leader(request.sender, request.sender) @command(command="leader", params=[Const("set", is_optional=True), Character("character")], access_level="all", description="Set another character as raid leader") def leader_set_other_command(self, request, _, char): if not char.char_id: return "Could not find <highlight>%s<end>." % char.name return self.set_raid_leader(request.sender, char) @timerevent( budatime="1h", description= "Remove raid leader if raid leader hasn't been active for more than 1 hour" ) def leader_auto_remove(self, _1, _2): if self.last_activity: if self.last_activity - int(time.time()) > 3600: self.leader = None self.last_activity = None self.echo = False self.bot.send_private_channel_message( "Raid leader has been automatically " "cleared because of inactivity.") self.bot.send_org_message( "Raid leader has been automatically cleared because of inactivity." ) @event(PrivateChannelService.LEFT_PRIVATE_CHANNEL_EVENT, "Remove raid leader if raid leader leaves private channel") def leader_remove_on_leave_private(self, _, event_data): if self.leader: if self.leader.char_id == event_data.char_id: self.leader = None self.last_activity = None self.echo = False self.bot.send_private_channel_message( "%s left private channel, and has been cleared as raid leader." % self.character_service.resolve_char_to_name( event_data.char_id)) @event(OrgMemberController.ORG_MEMBER_LOGOFF_EVENT, "Remove raid leader if raid leader logs off") def leader_remove_on_logoff(self, _, event_data): if self.leader: if self.leader.char_id == event_data.char_id: self.leader = None self.last_activity = None self.echo = False self.bot.send_org_message( "%s has logged off, and has been cleared as raid leader." % self.character_service.resolve_char_to_name( event_data.char_id)) @event(PrivateChannelService.PRIVATE_CHANNEL_MESSAGE_EVENT, "Echo leader messages from private channel", is_hidden=True) def leader_echo_private_event(self, _, event_data): if self.leader and self.echo: if self.leader.char_id == event_data.char_id: if self.setting_service.get( "symbol").get_value() != event_data.message[0]: self.leader_echo(event_data.char_id, event_data.message, "priv") @event(PublicChannelService.ORG_CHANNEL_MESSAGE_EVENT, "Echo leader messages from org channel", is_hidden=True) def leader_echo_org_event(self, _, event_data): if self.leader and self.echo: if self.leader.char_id == event_data.char_id: if self.setting_service.get( "symbol").get_value() != event_data.message[0]: self.leader_echo(event_data.char_id, event_data.message, "org") def leader_echo(self, char_id, message, channel): sender = self.character_service.resolve_char_to_name(char_id) color = self.setting_service.get("leader_echo_color").get_value() if channel == "org": self.bot.send_org_message("%s: <font color=%s>%s" % (sender, color, message)) if channel == "priv": self.bot.send_private_channel_message("%s: <font color=%s>%s" % (sender, color, message)) self.activity_done() def activity_done(self): self.last_activity = int(time.time()) def can_use_command(self, char_id): if not self.leader or self.access_service.has_sufficient_access_level( char_id, self.leader.char_id): self.activity_done() return True return False def set_raid_leader(self, sender, set_to): if set_to is None: if not self.leader: return "There is no current raid leader." elif self.leader.char_id == sender.char_id: self.leader = None self.echo = False return "You have been removed as raid leader." elif self.can_use_command(sender.char_id): old_leader = self.leader self.leader = None self.echo = False self.bot.send_private_message( old_leader.char_id, "You have been removed as raid leader by <highlight>%s<end>." % sender.name) return "You have removed <highlight>%s<end> as raid leader." % old_leader.name else: return "You do not have a high enough access level to remove raid leader from <highlight>%s<end>." % \ self.leader.name elif sender.char_id == set_to.char_id: if not self.leader: self.leader = sender self.echo = self.setting_service.get( "leader_auto_echo").get_value() reply = "You have been set as raid leader." if self.echo: reply += " Leader echo is <green>enabled<end>." return reply elif self.leader.char_id == sender.char_id: self.leader = None self.echo = False return "You have been removed as raid leader." elif self.can_use_command(sender.char_id): old_leader = self.leader self.leader = sender self.echo = self.setting_service.get( "leader_auto_echo").get_value() reply = "<highlight>%s<end> has taken raid leader from you." % sender.name if self.echo: reply += " Leader echo is <green>enabled<end>." self.bot.send_private_message(old_leader.char_id, reply) reply = "You have taken raid leader from <highlight>%s<end>." % old_leader.name if self.echo: reply += " Leader echo is <green>enabled<end>." return reply else: return "You do not have a high enough access level to take raid leader from <highlight>%s<end>." % \ self.leader.name else: if self.can_use_command(sender.char_id): self.leader = set_to self.echo = self.setting_service.get( "leader_auto_echo").get_value() reply = "<highlight>%s<end> has set you as raid leader." % sender.name if self.echo: reply += " Leader echo is <green>enabled<end>." self.bot.send_private_message(set_to.char_id, reply) reply = "<highlight>%s<end> has been set as raid leader by %s." % ( set_to.name, sender.name) if self.echo: reply += " Leader echo is <green>enabled<end>." return reply else: return "You do not have a high enough access level to take raid leader from <highlight>%s<end>." % \ self.leader.name
class WhatBuffsController: def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.command_alias_service = registry.get_instance( "command_alias_service") def pre_start(self): self.db.load_sql_file(self.module_dir + "/sql/" + "item_buffs.sql") self.db.load_sql_file(self.module_dir + "/sql/" + "item_types.sql") self.db.load_sql_file(self.module_dir + "/sql/" + "skills.sql") def start(self): self.command_alias_service.add_alias("buffs", "whatbuffs") @command(command="whatbuffs", params=[], access_level="all", description="Find items or nanos that buff a skill (or ability)") def whatbuffs_list_cmd(self, request): data = self.db.query("SELECT name FROM skills ORDER BY name ASC") blob = "" for row in data: blob += self.text.make_tellcmd(row.name, "whatbuffs %s" % row.name) + "\n" blob += self.get_footer() return ChatBlob("Whatbuffs Skill List", blob) @command( command="whatbuffs", params=[ Any("skill"), Options([ "arms", "back", "chest", "deck", "feet", "fingers", "hands", "head", "hud", "legs", "nanoprogram", "neck", "shoulders", "unknown", "util", "weapon", "wrists", "all" ]) ], access_level="all", description= "Find items or nanos that buff a skill (or ability) for a particular item type" ) def whatbuffs_detail_cmd(self, request, skill_name, item_type): item_type = item_type.capitalize() return self.show_search_results(item_type, skill_name) @command(command="whatbuffs", params=[Any("skill")], access_level="all", description="Find items or nanos that buff a skill (or ability)") def whatbuffs_skill_cmd(self, request, skill_name): skills = self.search_for_skill(skill_name) if len(skills) == 0: return "Could not find skill <highlight>%s</highlight>." % skill_name elif len(skills) == 1: skill = skills.pop() data = self.db.query( "SELECT i.item_type, COUNT(1) AS cnt " "FROM aodb " "JOIN item_types i ON aodb.highid = i.item_id " "JOIN item_buffs b ON aodb.highid = b.item_id " "JOIN skills s ON b.attribute_id = s.id " "WHERE s.id = ? " "GROUP BY item_type " "HAVING cnt > 0 " "ORDER BY item_type ASC", [skill.id]) blob = "" total_count = 0 for row in data: blob += "%s (%d)\n" % (self.text.make_tellcmd( row.item_type, "whatbuffs %s %s" % (skill.name, row.item_type)), row.cnt) total_count += row.cnt blob += "\n%s (%d)\n" % (self.text.make_tellcmd( "All", "whatbuffs %s %s" % (skill.name, "All")), total_count) blob += self.get_footer() return ChatBlob("Whatbuffs %s - Choose Type" % skill.name, blob) else: blob = "Choose a skill:\n\n" for skill in skills: blob += self.text.make_tellcmd( skill.name, "whatbuffs %s" % skill.name) + "\n" blob += self.get_footer() return ChatBlob("Whatbuffs - Choose Skill", blob) def search_for_skill(self, skill_name): skill_name = skill_name.lower() data = self.db.query( "SELECT id, name FROM skills WHERE name <EXTENDED_LIKE=0> ? OR common_name <EXTENDED_LIKE=1> ?", [skill_name, skill_name], extended_like=True) # check for exact match first, in order to disambiguate between Bow and Bot Special Attack for row in data: if row.name.lower() == skill_name: return [row] return data def show_search_results(self, item_type, skill_name): skills = self.search_for_skill(skill_name) if len(skills) == 0: return "Could not find skill <highlight>%s</highlight>." % skill_name elif len(skills) == 1: skill = skills.pop() return self.get_search_results(item_type, skill) else: blob = "" for skill in skills: blob += self.text.make_tellcmd( skill.name, "whatbuffs %s %s" % (skill.name, item_type)) + "\n" return ChatBlob("Whatbuffs - Choose Skill", blob) def get_search_results(self, item_type, skill): data = self.db.query( "SELECT aodb.*, b.amount, i.item_type " "FROM aodb " "JOIN item_types i ON aodb.highid = i.item_id " "JOIN item_buffs b ON aodb.highid = b.item_id " "JOIN skills s ON b.attribute_id = s.id " "WHERE i.item_type LIKE ? AND s.id = ? " "ORDER BY item_type, amount DESC", ["%" if item_type == "All" else item_type, skill.id]) if len(data) == 0: return "No items found of type <highlight>%s</highlight> that buff <highlight>%s</highlight>." % ( item_type, skill.name) else: current_item_type = "" blob = "" for row in data: if current_item_type != row.item_type: blob += "\n\n<header2>%s</header2>\n" % row.item_type current_item_type = row.item_type blob += "%s (%d)\n" % (self.text.make_item( row.lowid, row.highid, row.highql, row.name), row.amount) blob += self.get_footer() return ChatBlob( "Whatbuffs - %s %s (%d)" % (skill.name, item_type, len(data)), blob) def get_footer(self): return "\nItem DB Extraction Info provided by Unk"
class MemberController: MEMBER_BUDDY_TYPE = "member" MEMBER_LOGON_EVENT = "member_logon_event" MEMBER_LOGOFF_EVENT = "member_logoff_event" def inject(self, registry): self.db = registry.get_instance("db") self.private_channel_service = registry.get_instance("private_channel_service") self.buddy_service = registry.get_instance("buddy_service") self.bot = registry.get_instance("bot") self.access_service = registry.get_instance("access_service") self.command_alias_service = registry.get_instance("command_alias_service") self.event_service = registry.get_instance("event_service") def pre_start(self): self.access_service.register_access_level("member", 90, self.check_member) self.event_service.register_event_type(self.MEMBER_LOGON_EVENT) self.event_service.register_event_type(self.MEMBER_LOGOFF_EVENT) self.bot.add_packet_handler(BuddyAdded.id, self.handle_member_logon) def start(self): self.db.exec("CREATE TABLE IF NOT EXISTS members (char_id INT NOT NULL PRIMARY KEY, auto_invite INT DEFAULT 0);") self.command_alias_service.add_alias("adduser", "member add") self.command_alias_service.add_alias("remuser", "member rem") self.command_alias_service.add_alias("members", "member") @event(event_type="connect", description="Add members as buddies of the bot on startup") def handle_connect_event(self, event_type, event_data): for row in self.get_all_members(): if row.auto_invite == 1: self.buddy_service.add_buddy(row.char_id, self.MEMBER_BUDDY_TYPE) @command(command="member", params=[Const("add"), Character("character")], access_level=OrgMemberController.ORG_ACCESS_LEVEL, description="Add a member") def member_add_cmd(self, request, _, char): if char.char_id: if self.get_member(char.char_id): return "<highlight>%s<end> is already a member." % char.name else: self.add_member(char.char_id) return "<highlight>%s<end> has been added as a member." % char.name else: return "Could not find character <highlight>%s<end>." % char.name @command(command="member", params=[Options(["rem", "remove"]), Character("character")], access_level=OrgMemberController.ORG_ACCESS_LEVEL, description="Remove a member") def member_remove_cmd(self, request, _, char): if char.char_id: if self.get_member(char.char_id): self.remove_member(char.char_id) return "<highlight>%s<end> has been removed as a member." % char.name else: return "<highlight>%s<end> is not a member." % char.name else: return "Could not find character <highlight>%s<end>." % char.name @command(command="member", params=[Const("list", is_optional=True)], access_level=OrgMemberController.ORG_ACCESS_LEVEL, description="List members") def member_list_cmd(self, request, _): data = self.get_all_members() count = len(data) blob = "" for row in data: blob += str(row.name) + "\n" return ChatBlob("Members (%d)" % count, blob) @command(command="autoinvite", params=[Options(["on", "off"])], access_level="all", description="Set your auto invite preference") def autoinvite_cmd(self, request, pref): pref = pref.lower() member = self.get_member(request.sender.char_id) if member: self.update_auto_invite(request.sender.char_id, 1 if pref == "on" else 0) return "Your auto invite preference has been set to <highlight>%s<end>." % pref else: return "You must be a member of this bot to set your auto invite preference." @event(event_type=MEMBER_LOGON_EVENT, description="Auto invite members to the private channel when they logon") def handle_buddy_logon(self, event_type, event_data): if event_data.auto_invite == 1: self.bot.send_private_message(event_data.char_id, "You have been auto-invited to the private channel.") self.private_channel_service.invite(event_data.char_id) @event(event_type=BanService.BAN_ADDED_EVENT, description="Remove characters as members when they are banned") def ban_added_event(self, event_type, event_data): self.remove_member(event_data.char_id) def handle_member_logon(self, packet: BuddyAdded): member = self.get_member(packet.char_id) if member: if packet.online: self.event_service.fire_event(self.MEMBER_LOGON_EVENT, member) else: self.event_service.fire_event(self.MEMBER_LOGOFF_EVENT, member) def add_member(self, char_id, auto_invite=1): self.buddy_service.add_buddy(char_id, self.MEMBER_BUDDY_TYPE) if not self.get_member(char_id): self.db.exec("INSERT INTO members (char_id, auto_invite) VALUES (?, ?)", [char_id, auto_invite]) def remove_member(self, char_id): self.buddy_service.remove_buddy(char_id, self.MEMBER_BUDDY_TYPE) self.db.exec("DELETE FROM members WHERE char_id = ?", [char_id]) def update_auto_invite(self, char_id, auto_invite): self.db.exec("UPDATE members SET auto_invite = ? WHERE char_id = ?", [auto_invite, char_id]) def get_member(self, char_id): return self.db.query_single("SELECT char_id, auto_invite FROM members WHERE char_id = ?", [char_id]) def get_all_members(self): return self.db.query("SELECT COALESCE(p.name, m.char_id) AS name, m.char_id, m.auto_invite FROM members m LEFT JOIN player p ON m.char_id = p.char_id ORDER BY p.name ASC") def check_member(self, char_id): return self.get_member(char_id) is not None
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") self.setting_service = registry.get_instance("setting_service") def start(self): self.command_alias_service.add_alias("motd", "topic") self.command_alias_service.add_alias("orders", "topic") self.setting_service.register(self.module_name, "topic", "", DictionarySettingType(), "The bot topic") @command(command="topic", params=[], access_level="guest", description="Show the current topic") def topic_show_command(self, request): topic = self.get_topic() if topic: return self.format_topic_message(topic) else: return "There is no current topic." @command(command="topic", params=[Options(["clear", "unset"])], access_level="guest", description="Clears the current topic") def topic_clear_command(self, request, _): # TODO compare against access level of existing topic author self.clear_topic() return "The topic has been cleared." @command(command="topic", params=[Const("set", is_optional=True), Any("topic_message")], access_level="guest", description="Set the current topic") def topic_set_command(self, request, _, topic_message): # TODO compare against access level of existing topic author self.set_topic(topic_message, request.sender) 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</highlight> [set by <highlight>%s</highlight>][%s ago]" % ( topic["topic_message"], topic["created_by"]["name"], time_string) def get_topic(self): return self.setting_service.get("topic").get_value() def clear_topic(self): self.setting_service.get("topic").set_value("") def set_topic(self, message, sender): topic = { "topic_message": message, "created_by": { "char_id": sender.char_id, "name": sender.name }, "created_at": int(time.time()) } self.setting_service.get("topic").set_value(topic) @event(PrivateChannelService.JOINED_PRIVATE_CHANNEL_EVENT, "Show topic to characters joining the private channel") def char_join_event(self, _, event_data): topic = self.get_topic() if topic: self.bot.send_private_message(event_data.char_id, self.format_topic_message(topic), conn=event_data.conn) @event(PrivateChannelService.LEFT_PRIVATE_CHANNEL_EVENT, "Clear topic when there are no characters in the private channel") def char_leave_event(self, _, event_data): if self.get_topic() and len(event_data.conn.private_channel) == 0: self.clear_topic()
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 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 PointsController: def __init__(self): pass 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.character_service: CharacterService = registry.get_instance("character_service") self.util: Util = registry.get_instance("util") self.setting_service: SettingService = registry.get_instance("setting_service") self.alts_service: AltsService = registry.get_instance("alts_service") def start(self): self.db.exec("CREATE TABLE IF NOT EXISTS points (char_id BIGINT PRIMARY KEY, points INT DEFAULT 0, created_at INT NOT NULL, " "disabled SMALLINT DEFAULT 0)") self.db.exec("CREATE TABLE IF NOT EXISTS points_log (log_id INT PRIMARY KEY, char_id BIGINT NOT NULL, audit INT NOT NULL, " "leader_id BIGINT NOT NULL, reason VARCHAR(255), created_at INT NOT NULL)") self.db.exec("CREATE TABLE IF NOT EXISTS points_presets (preset_id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, " "points INT DEFAULT 1, UNIQUE(name))") if self.db.query_single("SELECT COUNT(*) AS count FROM points_presets").count < 1: # Populate with pre-made presets if empty presets = ["s13", "s28", "s35", "s42", "zodiac", "zod", "tnh", "beast", "12m", "tara", "pvp", "towers", "wipe", "clanwipe", "clan", "omniwipe", "omni", "bonus", "early"] sql = "INSERT INTO points_presets (name) VALUES (?)" for preset in presets: self.db.exec(sql, [preset]) @command(command="account", params=[Const("create"), Character("char")], access_level="moderator", description="Create a new account for given character name", sub_command="modify") def bank_create_cmd(self, request, _, char: SenderObj): alts_info = self.alts_service.get_alts(char.char_id) for alt in alts_info: sql = "SELECT char_id, disabled FROM points WHERE char_id = ? LIMIT 1" row = self.db.query_single(sql, [alt.char_id]) if row: was_disabled = False if row.disabled == 1: if self.db.exec("UPDATE points SET disabled = 0 WHERE char_id = ?", [alt.char_id]): was_disabled = True if alt.char_id == char.char_id: if was_disabled: self.add_log_entry(alt.char_id, request.sender.char_id, "Account was re-enabled by %s" % request.sender.name) return "<highlight>%s</highlight>'s account has been re-enabled." % char.name else: return "<highlight>%s</highlight> already has an account." % char.name else: if was_disabled: self.add_log_entry(alt.char_id, request.sender.char_id, "Account was re-enabled by %s" % request.sender.name) return "<highlight>%s</highlight>'s (%s) account has been re-enabled." % (char.name, alt.name) else: return "<highlight>%s</highlight> (%s) already has an account." % (char.name, alt.name) main_info = alts_info[0] changed_to_main = main_info.char_id == char.char_id self.create_account(main_info.char_id, request.sender) name_reference = "%s (%s)" % (char.name, main_info.name) if changed_to_main else char.name return "A new account has been created for <highlight>%s</highlight>." % name_reference @command(command="account", params=[Const("close"), Character("char")], access_level="moderator", description="Close the account for given character name", sub_command="modify") def close_account_cmd(self, request, _, char: SenderObj): main = self.alts_service.get_main(char.char_id) sql = "UPDATE points SET disabled = 1 WHERE char_id = ?" if self.db.exec(sql, [main.char_id]) > 0: reason = f"Account was closed by {request.sender.name}" self.add_log_entry(main.char_id, request.sender.char_id, reason) name_reference = "%s (%s)" % (char.name, main.name) if main.char_id != char.char_id else char.name return f"<highlight>{name_reference}</highlight> has had their account disabled. Logs have been preserved." else: return "<highlight>%s</highlight> does not have an open account." % char.name @command(command="account", params=[Const("history"), Int("log_id")], access_level="moderator", description="Look up specific account history record", sub_command="modify") def account_history_cmd(self, request, _, log_id: int): log_entry = self.db.query_single("SELECT log_id, char_id, audit, leader_id, reason, created_at FROM points_log WHERE log_id = ?", [log_id]) if not log_entry: return "No account history record with given ID <highlight>%d</highlight>." % log_id char_name = self.character_service.resolve_char_to_name(log_entry.char_id) leader_name = self.character_service.resolve_char_to_name(log_entry.leader_id) blob = f"ID: <highlight>{log_entry.log_id}</highlight>\n" blob += f"Account: <highlight>{char_name}</highlight>\n" blob += f"Action by: <highlight>{leader_name}</highlight>\n" blob += "Type: <highlight>%s</highlight>\n" % ("Management" if log_entry.audit == 0 else "Altering of points") blob += f"Reason: <highlight>{log_entry.reason}</highlight>\n" blob += f"Audit: <highlight>{log_entry.audit}</highlight>\n" action_links = None if log_entry.audit == 0: if "closed" in log_entry.reason: action_links = self.text.make_tellcmd("Open the account", "account create %s" % char_name) elif "re-enabled" in log_entry.reason: action_links = self.text.make_tellcmd("Close the account", "account close %s" % char_name) else: reason = f"Points from event ({log_id}) have been retracted" if log_entry.audit < 0: action_links = self.text.make_tellcmd("Retract", f"account add {char_name} {-log_entry.audit} {reason}") else: action_links = self.text.make_tellcmd("Retract", f"account rem {char_name} {log_entry.audit} {reason}") blob += "Actions available: [%s]\n" % (action_links if action_links is not None else "No actions available") return ChatBlob(f"Account History Record ({log_id})", blob) @command(command="account", params=[Const("add"), Character("char"), Int("amount"), Any("reason")], access_level="moderator", description="Add points to an account", sub_command="modify") def account_add_cmd(self, request, _, char: SenderObj, amount: int, reason: str): main = self.alts_service.get_main(char.char_id) row = self.get_account(main.char_id, request.conn) if not row: return f"<highlight>{char.name}</highlight> does not have an account." if row.disabled == 1: return f"Account for <highlight>{char.name}</highlight> is disabled and cannot be altered." self.alter_points(main.char_id, request.sender.char_id, reason, amount) return f"<highlight>{char.name}</highlight> has had <highlight>{amount}</highlight> points added to their account." @command(command="account", params=[Options(["rem", "remove"]), Character("char"), Int("amount"), Any("reason")], access_level="moderator", description="Remove points from an account", sub_command="modify") def account_remove_cmd(self, request, _, char: SenderObj, amount: int, reason: str): main = self.alts_service.get_main(char.char_id) row = self.get_account(main.char_id, request.conn) if not row: return f"<highlight>{char.name}</highlight> does not have an account." if row.disabled == 1: return f"Account for <highlight>{char.name}</highlight> is disabled and cannot be altered." if amount > row.points: return f"<highlight>{char.name}</highlight> only has <highlight>{row.points}</highlight> points." self.alter_points(main.char_id, request.sender.char_id, reason, -amount) return f"<highlight>{char.name}</highlight> has had <highlight>{amount}</highlight> points removed from their account." @command(command="account", params=[NamedParameters(["page"])], access_level="all", description="Look up your account") def account_self_cmd(self, request, named_params): return self.get_account_display(request.sender, named_params.page) @command(command="account", params=[Character("char"), NamedParameters(["page"])], access_level="moderator", description="Look up account of another char", sub_command="modify") def account_other_cmd(self, request, char: SenderObj, named_params): return self.get_account_display(char, named_params.page) @command(command="raid", params=[Const("presets"), Const("add"), Any("name"), Int("points")], access_level="admin", description="Add new points preset", sub_command="manage_points") def presets_add_cmd(self, request, _1, _2, name: str, points: int): count = self.db.query_single("SELECT COUNT(*) AS count FROM points_presets WHERE name = ?", [name]).count if count > 0: return "A preset already exists with the name <highlight>%s</highlight>." % name sql = "INSERT INTO points_presets (name, points) VALUES (?,?)" self.db.exec(sql, [name, points]) return "A preset with the name <highlight>%s</highlight> was added, worth <green>%d</green> points." % (name, points) @command(command="raid", params=[Const("presets"), Const("rem"), Int("preset_id")], access_level="admin", description="Delete preset", sub_command="manage_points") def presets_rem_cmd(self, request, _1, _2, preset_id: int): if self.db.exec("DELETE FROM points_presets WHERE preset_id = ?", [preset_id]) > 0: return "Successfully removed preset with ID <highlight>%d</highlight>." % preset_id else: return "No preset with given ID <highlight>%d</highlight>." % preset_id @command(command="raid", params=[Const("presets"), Const("alter"), Int("preset_id"), Int("new_points")], access_level="admin", description="Alter the points dished out by given preset", sub_command="manage_points") def presets_alter_cmd(self, request, _1, _2, preset_id: int, new_points: int): preset = self.db.query_single("SELECT * FROM points_presets WHERE preset_id = ?", [preset_id]) if not preset: return f"Preset with ID <highlight>{preset_id}</highlight> does not exist." self.db.exec("UPDATE points_presets SET points = ? WHERE preset_id = ?", [new_points, preset_id]) return "Successfully updated the preset, <highlight>%s</highlight>, to dish out " \ "<green>%d</green> points instead of <red>%d</red>." % (preset.name, new_points, preset.points) @command(command="raid", params=[Options(["presets", "addpts"])], access_level="member", description="See list of points presets") def presets_cmd(self, request, _): return ChatBlob("Raid Points Presets", self.build_preset_list()) def build_preset_list(self): presets = self.db.query("SELECT * FROM points_presets ORDER BY name ASC, points DESC") if presets: blob = "" for preset in presets: add_points_link = self.text.make_tellcmd("Add pts", "raid addpts %s" % preset.name) blob += "<highlight>%s</highlight> worth <green>%d</green> points %s [id: %d]\n\n" \ % (preset.name, preset.points, add_points_link, preset.preset_id) return blob return "No presets available. To add new presets use <highlight><symbol>presets add preset_name preset_points</highlight>." def add_log_entry(self, char_id: int, leader_id: int, reason: str, amount=0): sql = "INSERT INTO points_log (char_id, audit, leader_id, reason, created_at) VALUES (?,?,?,?,?)" return self.db.exec(sql, [char_id, amount, leader_id, reason, int(time.time())]) def alter_points(self, char_id: int, leader_id: int, reason: str, amount: int): sql = "UPDATE points SET points = points + ? WHERE char_id = ?" self.db.exec(sql, [amount, char_id]) self.add_log_entry(char_id, leader_id, reason, amount) def get_account(self, main_id, conn): sql = "SELECT p.char_id, p.points, p.disabled FROM points p WHERE p.char_id = ?" row = self.db.query_single(sql, [main_id]) if not row: self.create_account(main_id, SenderObj(conn.get_char_id(), conn.get_char_name(), None)) row = self.db.query_single(sql, [main_id]) return row def create_account(self, main_id, sender): sql = "INSERT INTO points (char_id, points, created_at) VALUES (?,?,?)" self.db.exec(sql, [main_id, 0, int(time.time())]) self.add_log_entry(main_id, sender.char_id, "Account opened by %s" % sender.name) def get_account_display(self, char: SenderObj, page): main = self.alts_service.get_main(char.char_id) if not main: return "Could not find character <highlight>%s</highlight>." % char.name page = int(page) if page else 1 page_size = 20 offset = (page - 1) * page_size points = self.db.query_single("SELECT points, disabled FROM points WHERE char_id = ?", [main.char_id]) if not points: return "Could not find raid account for <highlight>%s</highlight>." % char.name alts_link = self.text.make_tellcmd("Alts", "alts %s" % main.name) blob = "" blob += "Account: %s [%s]\n" % (main.name, alts_link) blob += "Points: %d\n" % points.points blob += "Status: %s\n\n" % ("<green>Open</green>" if points.disabled == 0 else "<red>Disabled</red>") points_log = self.db.query("SELECT * FROM points_log WHERE char_id = ? ORDER BY created_at DESC LIMIT ?, ?", [main.char_id, offset, page_size]) blob += "<header2>Account log</header2>\n" if points_log is None: blob += "No entries in log." else: for entry in points_log: if entry.audit > 0: pts = "<green>+%d</green>" % entry.audit blob += "<grey>[%s]</grey> %s points by <highlight>%s</highlight>; <orange>%s</orange>" \ % (self.util.format_datetime(entry.created_at), pts, self.character_service.resolve_char_to_name(entry.leader_id), entry.reason) elif entry.audit < 0: pts = "<red>-%d</red>" % (-1 * entry.audit) blob += "<grey>[%s]</grey> %s points by <highlight>%s</highlight>; <orange>%s</orange>" \ % (self.util.format_datetime(entry.created_at), pts, self.character_service.resolve_char_to_name(entry.leader_id), entry.reason) else: # If points is 0, then it's a general case log blob += "<grey>[%s]</grey> <orange>%s</orange>" % (self.util.format_datetime(entry.created_at), entry.reason) log_entry_link = self.text.make_tellcmd(entry.log_id, f"account history {entry.log_id}") blob += " [%s]\n" % log_entry_link return ChatBlob("%s Account" % char.name, blob)
class AuctionController: def __init__(self): self.auction_running = False self.auction_time = None self.ignore = False self.bidder_accounts = {} self.announce_ids = [] self.announe_anti_ids = [] self.auction_anti_spam = [] def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.bot: Tyrbot = registry.get_instance("bot") self.raid_controller: RaidController = registry.get_instance( "raid_controller") self.setting_service: SettingService = registry.get_instance( "setting_service") self.job_scheduler: JobScheduler = registry.get_instance( "job_scheduler") self.character_service: CharacterService = registry.get_instance( "character_service") self.alts_service: AltsService = registry.get_instance("alts_service") self.points_controller: PointsController = registry.get_instance( "points_controller") @setting( name="vickrey_auction", value=False, description= "If true, the auction procedure will be done in the Vickrey approach") def vickrey_auction(self): return BooleanSettingType() @setting(name="minimum_bid", value="2", description="Minimum bid required for any bid to be valid") def minimum_bid(self): return NumberSettingType() @setting(name="auction_length", value="90", description="Regular auction length in seconds") def auction_length(self): return NumberSettingType() @setting(name="auction_announce_interval", value="15", description="Auction announce interval") def auction_announce_interval(self): return NumberSettingType() @setting(name="mass_auction_length", value="180", description="Mass auction length in seconds") def mass_auction_length(self): return NumberSettingType() @setting(name="mass_auction_announce_interval", value="30", description="Mass auction announce interval") def mass_auction_announce_interval(self): return NumberSettingType() @command(command="auction", params=[Const("start"), Any("items")], description="Start an auction, with one or more items", access_level="moderator") def auction_start_cmd(self, request, _, items): if self.auction_running: return "Auction already running." items = re.findall( "(([^<]+)?<a href=\"itemref://(\d+)/(\d+)/(\d+)\">([^<]+)</a>([^<]+)?)", items) if items: auction_item = None for item in items: _, prefix, low_id, high_id, ql, name, suffix = item auction_item = self.add_auction_item_to_loot( int(low_id), int(high_id), int(ql), name, request.sender.char_id, prefix, suffix, 1) if len(self.raid_controller.loot_list) > 1: self.bot.send_org_message( "%s just started a mass auction " "for %d items." % (request.sender.name, len(self.raid_controller.loot_list))) self.bot.send_private_channel_message( "%s just started a mass auction for %d items." % request.sender.name, len(self.raid_controller.loot_list)) self.raid_controller.loot_cmd(request) else: sql = "SELECT winning_bid FROM auction_log WHERE " \ "item_name LIKE '%' || ? || '%' ORDER BY time DESC LIMIT 5" bids = self.db.query(sql, [auction_item.item.name]) if bids: avg_win_bid = int( sum(map(lambda x: x.winning_bid, bids)) / len(bids)) else: avg_win_bid = 0 bid_link = self.raid_controller.get_auction_list() bid_link = self.text.paginate( ChatBlob("Click to bid", bid_link.msg), 5000, 1)[0] msg = "\n<yellow>----------------------------------------<end>\n" msg += "<yellow>%s<end> has just started an auction " \ "for <yellow>%s<end>.\n" % (request.sender.name, auction_item.item.name) msg += "Average winning bid: <highlight>%s<end>\n" % avg_win_bid msg += "%s\n" % bid_link msg += "<yellow>----------------------------------------<end>" self.bot.send_private_channel_message(msg) self.bot.send_org_message(msg) self.auction_running = True self.auction_time = int(time.time()) self.create_auction_timers() return return "Can't start empty auction." @command(command="auction", params=[Options(["cancel", "end"])], description="Cancel ongoing auction", access_level="moderator") def auction_cancel_cmd(self, _1, _2): if self.auction_running: self.auction_running = False self.auction_time = None self.raid_controller.loot_list.clear() self.bidder_accounts.clear() for announce_id in self.announce_ids: self.job_scheduler.cancel_job(announce_id) for announce_id in self.announe_anti_ids: self.job_scheduler.cancel_job(announce_id) self.announce_ids.clear() self.announe_anti_ids.clear() self.auction_anti_spam.clear() return "Auction canceled." return "No auction running." @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", sub_command="bid") def auction_bid_cmd(self, request, _, amount, all_amount, item_index): if not self.auction_running: return "No auction in progress." if amount or all_amount: vickrey = self.setting_service.get("vickrey_auction").get_value() minimum_bid = self.setting_service.get("minimum_bid").get_value() main_id = self.alts_service.get_main( request.sender.char_id).char_id item_index = item_index or 1 new_bidder = False try: auction_item = self.raid_controller.loot_list[item_index] except KeyError: return "No item at given index." try: bidder_account = self.bidder_accounts[main_id] t_available = bidder_account.points_available except KeyError: sql = "SELECT p.points, p.disabled FROM points p WHERE p.char_id = ?" t_available = self.db.query_single(sql, [main_id]) if t_available: if t_available.disabled > 0: return "Your account has been frozen. Contact an admin." t_available = t_available.points else: return "You do not have an active account with this bot." self.bidder_accounts[main_id] = BidderAccount( main_id, request.sender.char_id, t_available) bidder_account = self.bidder_accounts[main_id] bidder = next((bidder for bidder in auction_item.bidders if bidder.char_id == request.sender.char_id), None) if bidder: used = bidder_account.points_used - bidder.bid available = t_available - used else: new_bidder = True available = t_available used = 0 bidder = AuctionBid(request.sender.char_id, amount) if all_amount: amount = available if amount > available: return "You do not have enough points to make this bid. You have %d points " \ "available (%d points on account, %d points reserved for other bids)" \ % (available, t_available, used) if amount < minimum_bid: return "Invalid bid. The minimum allowed bid value is %d." % minimum_bid if vickrey: if bidder.bid_count < 2: if bidder.bid == amount and not new_bidder: return "The submitted bid matches your current bid. You can only submit a " \ "new bid if it's of higher value than the previous bid. This bid did not " \ "count against your bid count for this item." if bidder.bid > amount: return "The submitted bid is lower than your current bid. You can only submit a " \ "new bid if it's of higher value than the previous bid. This bid did not " \ "count against your bid count for this item." bidder.bid_count += 1 bidder.bid = amount bidder_account.points_used = used + amount auction_item.second_highest = auction_item.winning_bid \ if auction_item.winning_bid > 0 else minimum_bid auction_item.winning_bid = amount auction_item.winner_id = bidder.char_id if new_bidder: auction_item.bidders.append(bidder) bid_count_text = "first" if bidder.bid_count == 1 else "second" return "Your bid of %d points was accepted. This was your %s bid on " \ "this item. You have %d points left to use in the ongoing " \ "auction." % (amount, bid_count_text, (available - amount)) else: return "You've already exhausted your allowed bids for this item; as this " \ "is a Vickrey auction, you only get to bid twice on the same item." else: winner_name = self.character_service.resolve_char_to_name( auction_item.winner_id) bidder_name = self.character_service.resolve_char_to_name( request.sender.char_id) item_name = auction_item.item.name if bidder.bid > auction_item.second_highest: auction_item.second_highest = bidder.bid if bidder.bid <= auction_item.winning_bid: self.auction_anti_spam.append( "%s's bid of <red>%d<end> points did not exceed the " "current winning bid for %s. %s holds the winning bid." % (bidder_name, bidder.bid, item_name, winner_name)) return "Your bid of %d points was not enough. %s is currently " \ "winning with a minimum bid of %d." % (bidder.bid, winner_name, auction_item.second_highest) if bidder.bid > auction_item.winning_bid: if auction_item.winner_id: prev_main_id = self.alts_service.get_main( auction_item.winner_id).char_id prev_bidder_account = self.bidder_accounts[ prev_main_id] print("tot: %d -- used: %d" % (prev_bidder_account.points_available, prev_bidder_account.points_used)) prev_bidder_account.points_used -= auction_item.winning_bid print("tot: %d -- used: %d" % (prev_bidder_account.points_available, prev_bidder_account.points_used)) new_available = prev_bidder_account.points_available - prev_bidder_account.points_used print("tot: %d -- used: %d -- new avail: %d" % (prev_bidder_account.points_available, prev_bidder_account.points_used, new_available)) self.bot.send_private_message( auction_item.winner_id, "Your bid on %s has been overtaken by %s. The points have been returned to your pool of " "available points (%d points)." % (item_name, bidder_name, new_available)) auction_item.second_highest = auction_item.winning_bid + 1 if auction_item.second_highest == 0: auction_item.second_highest = minimum_bid auction_item.winning_bid = bidder.bid auction_item.winner_id = bidder.char_id if new_bidder: auction_item.bidders.append(bidder) bidder_account.points_used = used + amount new_available = t_available - bidder_account.points_used if len(self.announce_ids) < 2 and len( self.raid_controller.loot_list) == 1: self.job_scheduler.cancel_job(self.announce_ids.pop()) self.announce_ids.append( self.job_scheduler.delayed_job( self.auction_results, 10)) self.bot.send_org_message( "%s now holds the leading bid for %s. Bid was " "made with less than 10 seconds left of auction, end timer has " "been pushed back 10 seconds." % (bidder_name, item_name)) self.bot.send_private_channel_message( "%s now holds the leading bid for %s. Bid was made with " "less than 10 seconds left of auction, end timer has been" " pushed back 10 seconds." % (bidder_name, item_name)) else: self.auction_anti_spam.append( "%s now holds the leading bid for %s." % (bidder_name, item_name)) return "Your bid of %d points for %s has put you in the lead. " \ "You have %d points available for bidding." % (bidder.bid, item_name, new_available) return "Invalid bid." @command(command="auction", params=[Const("bid"), Const("item"), Int("item_index")], description="Get bid info for a specific item", access_level="member", sub_command="bid_info") def auction_bid_info_cmd(self, _1, _2, _3, item_index): if not self.raid_controller.loot_list: return "No auction running." blob = "" loot_item = self.raid_controller.loot_list[item_index] ao_item = loot_item.item min_bid = self.setting_service.get("minimum_bid").get_value() blob += "To bid for <yellow>%s<end>, you must send a tell to <myname> with\n\n" % ao_item.name blob += "<highlight><tab>/tell <myname> auction bid <amount> %d<end>\n\n" % item_index blob += "Where you replace <amount> with the amount of points you're willing to bid for the item.\n" blob += "Minimum bid is %d. You can also use \"all\" as bid, to bid all your available points.\n\n" % min_bid blob += "<highlight>Please note<end> that if you leave out the trailing number, %d. " % item_index blob += "It determines the auction item number you're bidding on, " blob += "which can be noted on the loot list, in front of the item name.\n\n" if self.setting_service.get("vickrey_auction").get_value(): blob += "<header2>This is a Vickrey auction<end>\n" blob += " - In a Vickrey auction, you only get to bid twice on the same item.\n" blob += " - You won't be notified of the outcome of your bid, as all bids will be anonymous.\n" blob += " - The highest anonymous bid will win, and pay the second-highest bid.\n" blob += " - Bids are anonymous; sharing your bid with others defeat the purpose of the Vickrey format.\n" blob += " - Bidding is done the same way as regular bids, as described above." return ChatBlob("Bid on item %s" % ao_item.name, blob) def add_auction_item_to_loot(self, low_id: int, high_id: int, ql: int, name: str, auctioneer_id: int, prefix=None, suffix=None, item_count=1): end_index = list(self.raid_controller.loot_list.keys())[-1] + 1 if len( self.raid_controller.loot_list) > 0 else 1 item_ref = { "low_id": low_id, "high_id": high_id, "ql": ql, "name": name } self.raid_controller.loot_list[end_index] = AuctionItem( item_ref, auctioneer_id, prefix, suffix, item_count) return self.raid_controller.loot_list[end_index] def create_auction_timers(self): if len(self.raid_controller.loot_list) > 1: auction_length = self.setting_service.get( "mass_auction_length").get_value() interval = self.setting_service.get( "mass_auction_announce_interval") else: auction_length = self.setting_service.get( "auction_length").get_value() interval = self.setting_service.get( "auction_announce_interval").get_value() iterations = int( (auction_length - auction_length % interval) / interval) for i in range(1, iterations): iteration = i * interval time_left = int(auction_length - iteration) self.announce_ids.append( self.job_scheduler.delayed_job(self.auction_announce, iteration, time_left)) for i in range(1, auction_length): self.announe_anti_ids.append( self.job_scheduler.delayed_job(self.auction_anti_spam_announce, i)) if len(self.raid_controller.loot_list) == 1: self.announce_ids.append( self.job_scheduler.delayed_job(self.auction_results, auction_length)) def auction_announce(self, _, time_left): if self.auction_running: self.announce_ids.pop(0) item_count = len(self.raid_controller.loot_list) if self.announce_ids and item_count > 1: msg = "Auction for %d items running. %d seconds left of auction." % ( item_count, time_left) elif len(self.raid_controller.loot_list) > 1: msg = "Auction for %d items will end within 30 seconds" % item_count self.announce_ids.append( self.job_scheduler.delayed_job( self.auction_results, secrets.choice(range(5, 30)))) else: auction_item = list( self.raid_controller.loot_list.values())[-1] item = auction_item.item if self.setting_service.get("vickrey_auction").get_value(): winner = "%d bid(s) have been made." % len(auction_item.bidders) \ if auction_item.bidders else "No bids made." else: winner_name = self.character_service.resolve_char_to_name( auction_item.winner_id) winner = "%s holds the winning bid." % winner_name \ if auction_item.winning_bid > 0 else "No bids made." item_ref = self.text.make_item(item.low_id, item.high_id, item.ql, item.name) msg = "Auction for %s running. %s " \ "<yellow>%d<end> seconds left of auction." % (item_ref, winner, time_left) self.bot.send_private_channel_message(msg) self.bot.send_org_message(msg) self.raid_controller.loot_cmd(None) def auction_anti_spam_announce(self, _): announce = None if len(self.auction_anti_spam) >= 1: announce = self.auction_anti_spam.pop(-1) self.auction_anti_spam.clear() if announce: self.bot.send_org_message(announce) self.bot.send_private_channel_message(announce) def auction_results(self, _): sql = "INSERT INTO auction_log (item_ref, item_name, winner_id, " \ "auctioneer_id, time, winning_bid, second_highest_bid) VALUES (?,?,?,?,?,?,?)" blob = "" for i, auction_item in self.raid_controller.loot_list.items(): was_rolled = False if self.setting_service.get("vickrey_auction").get_value(): was_rolled = self.find_vickrey_winner(auction_item) item = auction_item.item item_ref = self.text.make_item(item.low_id, item.high_id, item.ql, item.name) prefix = "%s " % auction_item.prefix if auction_item.prefix is not None else "" suffix = " %s" % auction_item.suffix if auction_item.prefix is not None else "" item_name = "%s%s%s" % (prefix, item.name, suffix) if auction_item.winning_bid > 0: self.db.exec(sql, [ item_ref, item_name, auction_item.winner_id, auction_item.auctioneer_id, int(time.time()), auction_item.winning_bid, auction_item.second_highest ]) winner_name = self.character_service.resolve_char_to_name( auction_item.winner_id) was_rolled = " (winner was determined by roll)" if was_rolled else "" blob += "%d. %s (ql%d), won by <yellow>%s<end> " \ "with <green>%d<end> points%s" \ % (i, item_ref, item.ql, winner_name, auction_item.second_highest, was_rolled) main_id = self.alts_service.get_main( auction_item.winner_id).char_id current_points = self.bidder_accounts[main_id].points_available self.points_controller.alter_points( current_points, main_id, -auction_item.second_highest, auction_item.auctioneer_id, "Won auction for %s" % item_name) else: blob += "%d. %s (ql%d), no bids made" % (i, item_ref, item.ql) self.auction_running = False self.auction_time = None self.bidder_accounts.clear() self.announce_ids.clear() self.announe_anti_ids.clear() self.raid_controller.loot_list.clear() result_blob = ChatBlob("Auction results", blob) self.bot.send_org_message(result_blob) self.bot.send_private_channel_message(result_blob) def find_vickrey_winner(self, item: AuctionItem): was_rolled = False if item.bidders: minimum_bid = self.setting_service.get("minimum_bid").get_value() vickrey_list = [] winning_bid = 0 winner = None for bidder in item.bidders: if bidder.bid > winning_bid: item.second_highest = winning_bid winning_bid = bidder.bid winner = bidder.char_id vickrey_list.clear() elif bidder.bid == winning_bid and winner: # Potential pitfall if winner for some strange reason is None. # It should not be possible to do a bid of 0 or less, so we just # assume that if bid is equal to winning bid, winning bid must be # greater than 0, and thus a winner must exist from a previous # iterated bid vickrey_list.append(bidder.char_id) if vickrey_list: vickrey_list.append(winner) item.second_highest = winning_bid winner = secrets.choice(vickrey_list) was_rolled = True if winning_bid > 0 and winner: item.winning_bid = winning_bid item.winner_id = winner if item.second_highest == 0: item.second_highest = minimum_bid return was_rolled
class ConfigCommandController: 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.command_service = registry.get_instance("command_service") @command(command="config", params=[ Const("cmd"), Any("cmd_name"), Options(["enable", "disable"]), Any("channel") ], access_level="admin", description="Enable or disable a command") def config_cmd_status_cmd(self, request, _, cmd_name, action, cmd_channel): cmd_name = cmd_name.lower() action = action.lower() cmd_channel = cmd_channel.lower() command_str, sub_command_str = self.command_service.get_command_key_parts( cmd_name) enabled = 1 if action == "enable" else 0 if cmd_channel != "all" and not self.command_service.is_command_channel( cmd_channel): return "Unknown command channel <highlight>%s<end>." % cmd_channel sql = "UPDATE command_config SET enabled = ? WHERE command = ? AND sub_command = ?" params = [enabled, command_str, sub_command_str] if cmd_channel != "all": sql += " AND channel = ?" params.append(cmd_channel) count = self.db.exec(sql, params) if count == 0: return "Could not find command <highlight>%s<end> for channel <highlight>%s<end>." % ( cmd_name, cmd_channel) else: if cmd_channel == "all": return "Command <highlight>%s<end> has been <highlight>%sd<end> successfully." % ( cmd_name, action) else: return "Command <highlight>%s<end> for channel <highlight>%s<end> has been <highlight>%sd<end> successfully." % ( cmd_name, cmd_channel, action) @command(command="config", params=[ Const("cmd"), Any("cmd_name"), Const("access_level"), Any("channel"), Any("access_level") ], access_level="admin", description="Change access_level for a command") def config_cmd_access_level_cmd(self, request, _1, cmd_name, _2, cmd_channel, access_level): cmd_name = cmd_name.lower() cmd_channel = cmd_channel.lower() access_level = access_level.lower() command_str, sub_command_str = self.command_service.get_command_key_parts( cmd_name) if cmd_channel != "all" and not self.command_service.is_command_channel( cmd_channel): return "Unknown command channel <highlight>%s<end>." % cmd_channel if self.access_service.get_access_level_by_label(access_level) is None: return "Unknown access level <highlight>%s<end>." % access_level sql = "UPDATE command_config SET access_level = ? WHERE command = ? AND sub_command = ?" params = [access_level, command_str, sub_command_str] if cmd_channel != "all": sql += " AND channel = ?" params.append(cmd_channel) count = self.db.exec(sql, params) if count == 0: return "Could not find command <highlight>%s<end> for channel <highlight>%s<end>." % ( cmd_name, cmd_channel) else: if cmd_channel == "all": return "Access level <highlight>%s<end> for command <highlight>%s<end> has been set successfully." % ( access_level, cmd_name) else: return "Access level <highlight>%s<end> for command <highlight>%s<end> on channel <highlight>%s<end> has been set successfully." % ( access_level, cmd_name, cmd_channel) @command(command="config", params=[Const("cmd"), Any("cmd_name")], access_level="admin", description="Show command configuration") def config_cmd_show_cmd(self, request, _, cmd_name): cmd_name = cmd_name.lower() command_str, sub_command_str = self.command_service.get_command_key_parts( cmd_name) blob = "" for command_channel, channel_label in self.command_service.channels.items( ): cmd_configs = self.command_service.get_command_configs( command=command_str, sub_command=sub_command_str, channel=command_channel, enabled=None) if len(cmd_configs) > 0: cmd_config = cmd_configs[0] if cmd_config.enabled == 1: status = "<green>Enabled<end>" else: status = "<red>Disabled<end>" blob += "<header2>%s<end> %s (Access Level: %s)\n" % ( channel_label, status, cmd_config.access_level.capitalize()) # show status config blob += "Status:" enable_link = self.text.make_chatcmd( "Enable", "/tell <myname> config cmd %s enable %s" % (cmd_name, command_channel)) disable_link = self.text.make_chatcmd( "Disable", "/tell <myname> config cmd %s disable %s" % (cmd_name, command_channel)) blob += " " + enable_link + " " + disable_link # show access level config blob += "\nAccess Level:" for access_level in self.access_service.access_levels: # skip "None" access level if access_level["level"] == 0: continue label = access_level["label"] link = self.text.make_chatcmd( label.capitalize(), "/tell <myname> config cmd %s access_level %s %s" % (cmd_name, command_channel, label)) blob += " " + link blob += "\n\n\n" if blob: # include help text blob += "\n\n".join( map(lambda handler: handler["help"], self.command_service.get_handlers(cmd_name))) return ChatBlob("Command (%s)" % cmd_name, blob) else: return "Could not find command <highlight>%s<end>." % cmd_name
class AlienArmorController: def inject(self, registry): self.text: Text = registry.get_instance("text") self.items_controller = registry.get_instance("items_controller") @command(command="aiarmor", params=[], access_level="all", description="List the alien armor types") def aiarmor_list_command(self, request): blob = "" blob += "<highlight>Normal Armor:<end>\n" blob += self.text.make_chatcmd("Strong Armor", "/tell <myname> aiarmor Strong") + "\n" blob += self.text.make_chatcmd("Supple Armor", "/tell <myname> aiarmor Supple") + "\n" blob += self.text.make_chatcmd( "Enduring Armor", "/tell <myname> aiarmor Enduring") + "\n" blob += self.text.make_chatcmd( "Observant Armor", "/tell <myname> aiarmor Observant") + "\n" blob += self.text.make_chatcmd( "Arithmetic Armor", "/tell <myname> aiarmor Arithmetic") + "\n" blob += self.text.make_chatcmd( "Spiritual Armor", "/tell <myname> aiarmor Spiritual") + "\n" blob += "\n<highlight>Combined Armor:<end>\n" blob += self.text.make_chatcmd("Combined Commando's Armor", "/tell <myname> aiarmor cc") + "\n" blob += self.text.make_chatcmd("Combined Mercenary's Armor", "/tell <myname> aiarmor cm") + "\n" blob += self.text.make_chatcmd("Combined Officer's", "/tell <myname> aiarmor co") + "\n" blob += self.text.make_chatcmd("Combined Paramedic's Armor", "/tell <myname> aiarmor cp") + "\n" blob += self.text.make_chatcmd("Combined Scout's Armor", "/tell <myname> aiarmor cs") + "\n" blob += self.text.make_chatcmd("Combined Sharpshooter's Armor", "/tell <myname> aiarmor css") return ChatBlob("Alien Armor", blob) @command(command="aiarmor", params=[ Options([ "strong", "supple", "enduring", "observant", "arithmetic", "spiritual" ]), Int("ql", is_optional=True) ], access_level="all", description="Show the process for making normal alien armor") def aiarmor_normal_command(self, request, armor_type, ql): armor_type = armor_type.capitalize() ql = ql or 300 misc_ql = math.floor(ql * 0.8) blob = "Note: All tradeskill processes are based on the lowest QL items usable.\n\n" blob += "<header2>You need the following items to build %s Armor:<end>\n" % armor_type blob += "- Kyr'Ozch Viralbots\n" blob += "- Kyr'Ozch Atomic Re-Structulazing Tool\n" blob += "- Solid Clump of Kyr'Ozch Biomaterial\n" blob += "- Arithmetic/Strong/Enduring/Spiritual/Observant/Supple Viralbots\n\n" blob += "<header2>Step 1<end>\n" blob += "<tab>%s (<highlight>Drops from Alien City Generals<end>)\n" % self.display_item_by_name( "Kyr'Ozch Viralbots", misc_ql) blob += "<tab><tab>+\n" blob += "<tab>%s (<highlight>Drops from every Alien<end>)\n" % self.display_item_by_name( "Kyr'Ozch Atomic Re-Structuralizing Tool", 100) blob += "<tab><tab>=\n" blob += "<tab>%s\n" % self.display_item_by_name( "Memory-Wiped Kyr'Ozch Viralbots", misc_ql) blob += "<highlight>Required Skills:<end>\n" blob += "- %d Computer Literacy\n" % math.ceil(misc_ql * 4.5) blob += "- %d Nano Programming\n\n" % math.ceil(misc_ql * 4.5) blob += "<header2>Step 2<end>\n" blob += "<tab>%s (<highlight>Can be bought in General Shops<end>)\n" % self.display_item_by_name( "Nano Programming Interface", 1) blob += "<tab><tab>+\n" blob += "<tab>%s\n" % self.display_item_by_name( "Memory-Wiped Kyr'Ozch Viralbots", misc_ql) blob += "<tab><tab>=\n" blob += "<tab>%s\n" % self.display_item_by_name( "Formatted Kyr'Ozch Viralbots", misc_ql) blob += "<highlight>Required Skills:<end>\n" blob += "- %d Computer Literacy\n" % math.ceil(misc_ql * 4.5) blob += "- %d Nano Programming\n\n" % math.ceil(misc_ql * 6) blob += "<header2>Step 3<end>\n" blob += "<tab>%s\n" % self.display_item_by_name( "Kyr'Ozch Structural Analyzer", 100) blob += "<tab><tab>+\n" blob += "<tab>%s QL%d (<highlight>Drops from every Alien<end>)\n" % ( self.display_item_by_name("Solid Clump of Kyr'Ozch Bio-Material", ql), ql) blob += "<tab><tab>=\n" blob += "<tab>%s QL%d" % (self.display_item_by_name( "Mutated Kyr'Ozch Bio-Material", ql), ql) blob += "\n\nor\n\n<tab>%s QL%d\n" % (self.display_item_by_name( "Pristine Kyr'Ozch Bio-Material", ql), ql) blob += "<highlight>Required Skills:<end>\n" blob += "- %d Chemistry (Both require the same amount)\n\n" % math.ceil( ql * 4.5) blob += "<header2>Step 4<end>\n" blob += "<tab>%s QL%d" % (self.display_item_by_name( "Mutated Kyr'Ozch Bio-Material", ql), ql) blob += "\n\nor\n\n<tab>%s QL%d\n" % (self.display_item_by_name( "Pristine Kyr'Ozch Bio-Material", ql), ql) blob += "<tab><tab>+\n" blob += "<tab>%s (<highlight>Can be bought in Bazzit Shop in MMD<end>)\n" % self.display_item_by_name( "Uncle Bazzit's Generic Nano-Solvent", 100) blob += "<tab><tab>=\n" blob += "<tab>%s\n" % self.display_item_by_name( "Generic Kyr'Ozch DNA-Soup", ql) blob += "<highlight>Required Skills:<end>\n" blob += "- %d Chemistry(for Pristine)\n" % math.ceil(ql * 4.5) blob += "- %d Chemistry(for Mutated)\n\n" % math.ceil(ql * 7) blob += "<header2>Step 5<end>\n" blob += "<tab>" + self.display_item_by_name( "Generic Kyr'Ozch DNA-Soup", ql) + "\n" blob += "<tab><tab>+\n" blob += "<tab>" + self.display_item_by_name( "Essential Human DNA", 100) + " (<highlight>Can be bought in Bazzit Shop in MMD<end>)\n" blob += "<tab><tab>=\n" blob += "<tab>" + self.display_item_by_name("DNA Cocktail", ql) + "\n" blob += "<highlight>Required Skills:<end>\n" blob += "- %d Pharma Tech\n\n" % math.ceil(ql * 6) blob += "<header2>Step 6<end>\n" blob += "<tab>" + self.display_item_by_name( "Formatted Kyr'Ozch Viralbots", misc_ql) + "\n" blob += "<tab><tab>+\n" blob += "<tab>" + self.display_item_by_name("DNA Cocktail", ql) + "\n" blob += "<tab><tab>=\n" blob += "<tab>" + self.display_item_by_name( "Kyr'Ozch Formatted Viralbot Solution", ql) + "\n" blob += "<highlight>Required Skills:<end>\n" blob += "- %d Pharma Tech\n\n" % math.ceil(ql * 6) blob += "<header2>Step 7<end>\n" blob += "<tab>" + self.display_item_by_name( "Kyr'Ozch Formatted Viralbot Solution", ql) + "\n" blob += "<tab><tab>+\n" blob += "<tab>" + self.display_item_by_name( "Basic Fashion Vest", 1 ) + " (<highlight>Can be obtained by the Basic Armor Quest<end>)\n" blob += "<tab><tab>=\n" blob += "<tab>" + self.display_item_by_name("Formatted Viralbot Vest", ql) + "\n\n" blob += "<header2>Step 8<end>\n" vb_ql = math.floor(ql * 0.8) if armor_type == "Arithmetic": blob += "<tab>%s QL%d (<highlight>Rare Drop off Alien City Generals<end>)\n" % ( self.display_item_by_name("Arithmetic Lead Viralbots", vb_ql), vb_ql) elif armor_type == "Supple": blob += "<tab>%s QL%d (<highlight>Rare Drop off Alien City Generals<end>)\n" % ( self.display_item_by_name("Supple Lead Viralbots", vb_ql), vb_ql) elif armor_type == "Enduring": blob += "<tab>%s QL%d (<highlight>Rare Drop off Alien City Generals<end>)\n" % ( self.display_item_by_name("Enduring Lead Viralbots", vb_ql), vb_ql) elif armor_type == "Observant": blob += "<tab>%s QL%d (<highlight>Rare Drop off Alien City Generals<end>)\n" % ( self.display_item_by_name("Observant Lead Viralbots", vb_ql), vb_ql) elif armor_type == "Strong": blob += "<tab>%s QL%d (<highlight>Rare Drop off Alien City Generals<end>)\n" % ( self.display_item_by_name("Strong Lead Viralbots", vb_ql), vb_ql) elif armor_type == "Spiritual": blob += "<tab>%s QL%d (<highlight>Rare Drop off Alien City Generals<end>)\n" % ( self.display_item_by_name("Spiritual Lead Viralbots", vb_ql), vb_ql) blob += "<tab><tab>+\n" blob += "<tab>" + self.display_item_by_name("Formatted Viralbot Vest", ql) + "\n" blob += "<tab><tab>=\n" if armor_type == "Arithmetic": blob += "<tab>%s QL%d\n" % (self.display_item_by_name( "Arithmetic Body Armor", ql), ql) elif armor_type == "Supple": blob += "<tab>%s QL%d\n" % (self.display_item_by_name( "Supple Body Armor", ql), ql) elif armor_type == "Enduring": blob += "<tab>%s QL%d\n" % (self.display_item_by_name( "Enduring Body Armor", ql), ql) elif armor_type == "Observant": blob += "<tab>%s QL%d\n" % (self.display_item_by_name( "Observant Body Armor", ql), ql) elif armor_type == "Strong": blob += "<tab>%s QL%d\n" % (self.display_item_by_name( "Strong Body Armor", ql), ql) elif armor_type == "Spiritual": blob += "<tab>%s QL%d\n" % (self.display_item_by_name( "Spiritual Body Armor", ql), ql) blob += "<highlight>Required Skills:<end>\n" blob += "- %d Psychology\n\n" % math.floor(ql * 6) return ChatBlob("Building process for %d %s" % (ql, armor_type), blob) @command( command="aiarmor", params=[ Options(["cc", "cm", "co", "cp", "cs", "css", "ss"]), Int("ql", is_optional=True) ], access_level="all", description="Show the process for making combined alien armor", extended_description="CSS and SS both refer to Combined Sharpshooters") def aiarmor_combined_command(self, request, armor_type, target_ql): armor_type = armor_type.lower() target_ql = target_ql or 300 source_ql = math.floor(target_ql * 0.8) if armor_type == "cc": result_armor_id = 246660 # Combined Commando's Jacket source_armor_id = 246616 # Strong Body Armor name_source = "strong" target_armor_id = 246622 # Supple Body Armor name_target = "supple" elif armor_type == "cm": result_armor_id = 246638 # Combined Mercenary's Jacket source_armor_id = 246616 # Strong Body Armor name_source = "strong" target_armor_id = 246580 # Enduring Body Armor name_target = "enduring" elif armor_type == "co": result_armor_id = 246672 # Combined Officer's Jacket source_armor_id = 246600 # Spiritual Body Armor name_source = "spiritual" target_armor_id = 246560 # Arithmetic Body Armor name_target = "arithmetic" elif armor_type == "cp": result_armor_id = 246648 # Combined Paramedic's Jacket source_armor_id = 246600 # Spiritual Body Armor name_source = "spiritual" target_armor_id = 246580 # Enduring Body Armor name_target = "enduring" elif armor_type == "cs": result_armor_id = 246684 # Combined Scout's Jacket source_armor_id = 246592 # Observant Body Armor name_source = "observant" target_armor_id = 246560 # Arithmetic Body Armor name_target = "arithmetic" elif armor_type == "css" or armor_type == "ss": result_armor_id = 246696 # Combined Sharpshooter's Jacket source_armor_id = 246592 # Observant Body Armor name_source = "observant" target_armor_id = 246622 # Supple Body Armor name_target = "supple" else: return "Unknown armor type <highlight>%s<end>" % armor_type result_item = self.items_controller.get_by_item_id(result_armor_id) blob = "<header2>Result<end>\n" blob += "%s QL%d\n\n" % (self.text.format_item(result_item, target_ql), target_ql) blob += "<header2>Source Armor<end>\n" blob += "%s QL%d" % (self.text.format_item( self.items_controller.get_by_item_id(source_armor_id), target_ql), source_ql) blob += " (%s)\n\n" % self.text.make_chatcmd( "Tradeskill process for this item", "/tell <myname> aiarmor %s %d" % (name_source, source_ql)) blob += "<header2>Target Armor<end>\n" blob += "%s QL%d" % (self.text.format_item( self.items_controller.get_by_item_id(target_armor_id), target_ql), target_ql) blob += " (%s)" % self.text.make_chatcmd( "Tradeskill process for this item", "/tell <myname> aiarmor %s %d" % (name_target, target_ql)) return ChatBlob( "Building process for %d %s" % (target_ql, result_item.name), blob) def display_item_by_name(self, name, ql): return self.text.format_item( self.items_controller.find_by_name(name, ql), ql)
class AlienGeneralController: def inject(self, registry): self.text: Text = registry.get_instance("text") self.items_controller = registry.get_instance("items_controller") self.ts: TranslationService = registry.get_instance( "translation_service") self.getresp = self.ts.get_response @command(command="aigen", params=[], access_level="all", description="List alien city ground generals") def aigen_list_command(self, request): blob = "" blob += " - <a href='chatcmd:///tell <myname> aigen Ankari'>Ankari</a>\n" blob += " - <a href='chatcmd:///tell <myname> aigen Ilari'>Ilari</a>\n" blob += " - <a href='chatcmd:///tell <myname> aigen Rimah'>Rimah</a>\n" blob += " - <a href='chatcmd:///tell <myname> aigen Jaax'>Jaax</a>\n" blob += " - <a href='chatcmd:///tell <myname> aigen Xoch'>Xoch</a>\n" blob += " - <a href='chatcmd:///tell <myname> aigen Cha'>Cha</a>\n" return ChatBlob(self.getresp("module/alien", "ai_gen_list_title"), blob) @command( command="aigen", params=[Options(["ankari", "ilari", "rimah", "jaax", "xoch", "cha"])], access_level="all", description="Show info about an alien city ground general") def aigen_show_command(self, request, general): general = general.capitalize() blob = "" if general == "Ankari": blob += "Low Evade/Dodge, Low AR, Casts Viral/Virral nukes\n\n" blob += self.text.format_item( self.items_controller.get_by_item_id( 247145)) + "\n" # Arithmetic Lead Viralbots blob += "(Nanoskill / Tradeskill)\n\n" blob += self.text.format_item( self.items_controller.get_by_item_id( 247684)) + "\n\n" # type 1 blob += self.text.format_item( self.items_controller.get_by_item_id( 247686)) + "\n\n" # type 2 blob += self.text.format_item( self.items_controller.get_by_item_id(288673)) # type 48 elif general == "Ilari": blob += "Low Evade/Dodge\n\n" blob += self.text.format_item( self.items_controller.get_by_item_id( 247147)) + "\n" # Spiritual Lead Viralbots blob += "(Nanocost / Nanopool / Max Nano)\n\n" blob += self.text.format_item( self.items_controller.get_by_item_id( 247682)) + "\n\n" # type 992 blob += self.text.format_item( self.items_controller.get_by_item_id(247680)) # type 880 elif general == "Rimah": blob += "Low Evade/Dodge\n\n" blob += self.text.format_item( self.items_controller.get_by_item_id( 247143)) + "\n" # Observant Lead Viralbots blob += "(Init / Evades)\n\n" blob += self.text.format_item( self.items_controller.get_by_item_id( 247676)) + "\n\n" # type 112 blob += self.text.format_item( self.items_controller.get_by_item_id(247678)) # type 240 elif general == "Jaax": blob += "High Evade, Low Dodge\n\n" blob += self.text.format_item( self.items_controller.get_by_item_id( 247139)) + "\n" # Strong Lead Viralbots blob += "(Melee / Spec Melee / Add All Def / Add Damage)\n\n" blob += self.text.format_item( self.items_controller.get_by_item_id( 247694)) + "\n\n" # type 3 blob += self.text.format_item( self.items_controller.get_by_item_id(247688)) # type 4 elif general == "Xoch": blob += "High Evade/Dodge, Casts Ilari Biorejuvenation heals\n\n" blob += self.text.format_item( self.items_controller.get_by_item_id( 247137)) + "\n" # Enduring Lead Viralbots blob += "(Max Health / Body Dev)\n\n" blob += self.text.format_item( self.items_controller.get_by_item_id( 247690)) + "\n\n" # type 5 blob += self.text.format_item( self.items_controller.get_by_item_id(247692)) # type 12 elif general == "Cha": blob += "High Evade/NR, Low Dodge\n\n" blob += self.text.format_item( self.items_controller.get_by_item_id( 247141)) + "\n" # Supple Lead Viralbots blob += "(Ranged / Spec Ranged / Add All Off)\n\n" blob += self.text.format_item( self.items_controller.get_by_item_id( 247696)) + "\n\n" # type 13 blob += self.text.format_item( self.items_controller.get_by_item_id(247674)) # type 76 return ChatBlob("General %s" % general, blob)
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 PointsController: def __init__(self): pass 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.character_service: CharacterService = registry.get_instance( "character_service") self.util: Util = registry.get_instance("util") self.setting_service: SettingService = registry.get_instance( "setting_service") self.alts_service: AltsService = registry.get_instance("alts_service") def start(self): if self.db.query_single( "SELECT COUNT(*) AS count FROM points_presets").count < 1: # Populate with pre-made presets if empty presets = [ "s13", "s28", "s35", "s42", "zodiac", "zod", "tnh", "beast", "12m", "tara", "pvp", "towers", "wipe", "clanwipe", "clan", "omniwipe", "omni", "bonus", "early" ] sql = "INSERT INTO points_presets (name) VALUES (?)" for preset in presets: self.db.exec(sql, [preset]) @setting(name="initial_points_value", value="0", description="How many points new accounts start with") def initial_points_value(self): return NumberSettingType() @command(command="account", params=[Const("create"), Character("char")], access_level="moderator", description="Create a new account for given character name", sub_command="modify") def bank_create_cmd(self, request, _, char: SenderObj): alts_info = self.alts_service.get_alts(char.char_id) for alt in alts_info: sql = "SELECT char_id, disabled FROM points WHERE char_id = ? LIMIT 1" count = self.db.query_single(sql, [alt.char_id]) if count: was_disabled = False if count.disabled == 1: if self.db.exec( "UPDATE points SET disabled = 0 WHERE char_id = ?", [alt.char_id]): was_disabled = True if alt.char_id == char.char_id: if was_disabled: if self.add_log_entry( alt.char_id, request.sender.char_id, "Account was re-enabled by %s" % self.character_service.resolve_char_to_name( request.sender.char_id)): return "<highlight>%s<end>'s account has been re-enabled." % char.name else: return "<highlight>%s<end> has an account, but failed to re-enable it." % char.name else: return "<highlight>%s<end> already has an account." % char.name else: if was_disabled: if self.add_log_entry( alt.char_id, request.sender.char_id, "Account was re-enabled by %s" % self.character_service.resolve_char_to_name( request.sender.char_id)): return "<highlight>%s<end>'s (%s) account has been re-enabled." % ( char.name, self.character_service.resolve_char_to_name( alt.char_id)) else: return "<highlight>%s<end> (%s) has an account, but failed to re-enable it." % ( char.name, self.character_service.resolve_char_to_name( alt.char_id)) else: return "<highlight>%s<end> (%s) already has an account." % ( char.name, self.character_service.resolve_char_to_name( alt.char_id)) main_info = alts_info.pop(0) changed_to_main = main_info.char_id == char.char_id initial_points = self.setting_service.get( "initial_points_value").get_value() sql = "INSERT INTO points (char_id, points, created) VALUES (?,?,?)" if self.db.exec(sql, [main_info.char_id, initial_points, int(time.time())]) < 1: return "Failed to create an account for <highlight>%s<end>." % char.name if not self.add_log_entry( main_info.char_id, request.sender.char_id, "Account opened by %s" % request.sender.name): sql = "DELETE FROM points WHERE char_id = ?" self.db.exec(sql, [main_info.char_id]) return "Failed to create an account for <highlight>%s<end>." % char.name name_reference = "%s (%s)" % ( char.name, self.character_service.resolve_char_to_name( main_info.char_id)) if changed_to_main else char.name return "A new account has been created for <highlight>%s<end>." % name_reference @command(command="account", params=[Const("close"), Character("char")], access_level="moderator", description="Close the account for given character name", sub_command="modify") def close_account_cmd(self, request, _, char: SenderObj): main_id = self.alts_service.get_main(char.char_id) sql = "UPDATE points SET disabled = 1 WHERE char_id = ?" if self.db.exec(sql, [main_id.char_id]) > 0: reason = "Account was closed by %s" % self.character_service.resolve_char_to_name( request.sender.char_id) if self.add_log_entry(main_id.char_id, request.sender.char_id, reason): name_reference = "%s (%s)" % ( char.name, self.character_service.resolve_char_to_name( main_id.char_id) ) if main_id.char_id != char.char_id else char.name return "<highlight>%s<end> has had their account disabled. Logs have been preserved." % name_reference return "<highlight>%s<end> does not have an open account." % char.name @command(command="account", params=[], access_level="all", description="Look up your account") def account_self_cmd(self, request): return self.get_account_display(request.sender) @command(command="account", params=[Const("logentry"), Int("log_id")], access_level="moderator", description="Look up specific log entry", sub_command="modify") def account_log_entry_cmd(self, _1, _2, log_id: int): log_entry = self.db.query_single( "SELECT * FROM points_log WHERE log_id = ?", [log_id]) if log_entry: char_name = self.character_service.resolve_char_to_name( log_entry.char_id) leader_name = self.character_service.resolve_char_to_name( log_entry.leader_id) blob = "Log entry ID: <highlight>%d<end>\n" % log_id blob += "Affecting account: <highlight>%s<end>\n" % char_name blob += "Action by: <highlight>%s<end>\n" % leader_name blob += "Type: <highlight>%s<end>\n" % ( "Management" if log_entry.audit == 0 else "Altering of points") blob += "Reason: <highlight>%s<end>\n" % log_entry.reason action_links = None if log_entry.audit == 0: if "closed" in log_entry.reason: action_links = self.text.make_chatcmd( "Open the account", "/tell <myname> account create %s" % char_name) elif "re-enabled" in log_entry.reason: action_links = self.text.make_chatcmd( "Close the account", "/tell <myname> account close %s" % char_name) else: if log_entry.audit < 0: reason = "Points from event (%d) has been retracted, %d points have been added." \ % (log_id, (-1*log_entry.audit)) action_links = self.text.make_chatcmd( "Retract", "/tell <myname> bank give %d %s %s" % ((-1 * log_entry.audit), char_name, reason)) else: reason = "Points from event (%d) has been retracted, %d points have been deducted." \ % (log_id, log_entry.audit) action_links = self.text.make_chatcmd( "Retract", "/tell <myname> bank take %d %s %s" % (log_entry.audit, char_name, reason)) blob += "Actions available: [%s]\n" % (action_links if action_links is not None else "No actions available") return ChatBlob("Log entry (%d)" % log_id, blob) return "No log entry with given ID <highlight>%d<end>." % log_id @command(command="account", params=[ Options(["give", "take"]), Int("amount"), Character("char"), Any("reason") ], access_level="moderator", description="Give or take points from character account", sub_command="modify") def account_give_take_cmd(self, request, action: str, amount: int, char: SenderObj, reason: str): main_id = self.alts_service.get_main(char.char_id) sql = "SELECT * FROM points WHERE char_id = ?" points = self.db.query_single(sql, [main_id.char_id]) if points: if points.disabled == 1: return "<highlight>%s<end>'s account is disabled, altering the account is not possible." % char.name if points.points == 0 and action == "take": return "<highlight>%s<end> has 0 points - can't have less than 0 points." % char.name if amount > points.points and action == "take": amount = points.points new_points = amount if action == "give" else 0 - amount if not self.alter_points(points.points, main_id.char_id, new_points, request.sender.char_id, reason): return "Failed to alter <highlight>%s<end>'s account." % char.name action = "taken from" if action == "take" else "added to" return "<highlight>%s<end> has had <highlight>%d<end> points %s their account." % ( char.name, amount, action) return "<highlight>%s<end> does not have an account." % char.name @command(command="account", params=[Character("char")], access_level="moderator", description="Look up account of another char", sub_command="modify") def account_other_cmd(self, request, char: SenderObj): return self.get_account_display(char) @command(command="presets", params=[Const("add"), Any("name"), Int("points")], access_level="admin", description="Add new points preset") def presets_add_cmd(self, _1, _2, name: str, points: int): count = self.db.query_single( "SELECT COUNT(*) AS count FROM points_presets WHERE name = ?", [name]).count if count > 0: return "A preset already exists with the name <highlight>%s<end>." % name sql = "INSERT INTO points_presets (name, points) VALUES (?,?)" if self.db.exec(sql, [name, points]) > 0: return "A preset with the name <highlight>%s<end> was added, worth <green>%d<end> points." % ( name, points) return "Failed to insert new preset in DB." @command(command="presets", params=[Const("rem"), Int("preset_id")], access_level="admin", description="Delete preset") def presets_rem_cmd(self, _1, _2, preset_id: int): if self.db.exec("DELETE FROM points_presets WHERE preset_id = ?", [preset_id]) > 0: return "Successfully removed preset with ID <highlight>%d<end>." % preset_id return "No preset with given ID <highlight>%d<end>." % preset_id @command(command="presets", params=[Const("alter"), Int("preset_id"), Int("new_points")], access_level="admin", description="Alter the points dished out by given preset") def presets_alter_cmd(self, _1, _2, preset_id: int, new_points: int): preset = self.db.query_single( "SELECT * FROM points_presets WHERE preset_id = ?", [preset_id]) if preset: if self.db.exec( "UPDATE points_presets SET points = ? WHERE preset_id = ?", [new_points, preset_id]) > 0: return "Successfully updated the preset, <highlight>%s<end>, to dish out " \ "<green>%d<end> points instead of <red>%d<end>." % (preset.name, new_points, preset.points) return "Failed to update preset with ID <highlight>%d<end>." % preset_id @command(command="presets", params=[], access_level="admin", description="See list of points presets") def presets_cmd(self, _): return ChatBlob("Points presets", self.build_preset_list()) def build_preset_list(self): presets = self.db.query( "SELECT * FROM points_presets ORDER BY name ASC, points DESC") if presets: blob = "" for preset in presets: add_points_link = self.text.make_chatcmd( "Add pts", "/tell <myname> raid addpts %s" % preset.name) delete_link = self.text.make_chatcmd( "Delete", "/tell <myname> presets rem %d" % preset.preset_id) blob += "<highlight>%s<end> worth <green>%d<end> points [id: %d]\n | [%s] [%s]\n\n" \ % (preset.name, preset.points, preset.preset_id, add_points_link, delete_link) return blob return "No presets available. To add new presets use <highlight><symbol>presets add preset_name preset_points<end>." def add_log_entry(self, char_id: int, leader_id: int, reason: str, amount=0): sql = "INSERT INTO points_log (char_id, audit, leader_id, reason, time) VALUES (?,?,?,?,?)" return self.db.exec( sql, [char_id, amount, leader_id, reason, int(time.time())]) > 0 def alter_points(self, current_points: int, char_id: int, amount: int, leader_id: int, reason: str): sql = "UPDATE points SET points = points + ? WHERE char_id = ?" if self.db.exec(sql, [amount, char_id]) < 1: return False if not self.add_log_entry(char_id, leader_id, reason, amount): sql = "UPDATE points p SET p.points = ? WHERE p.char_id = ?" self.db.exec(sql, [current_points, char_id]) return False return True def get_account(self, main_id): sql = "SELECT p.char_id, p.points, p.disabled FROM points p WHERE p.char_id = ?" return self.db.query_single(sql, [main_id]) def get_account_display(self, char: SenderObj): main = self.alts_service.get_main(char.char_id) if not main: return "Could not find character <highlight>%s<end>." % char.name points_log = self.db.query( "SELECT * FROM points_log WHERE char_id = ? ORDER BY time DESC LIMIT 50", [main.char_id]) points = self.db.query_single( "SELECT points, disabled FROM points WHERE char_id = ?", [main.char_id]) if not points: return "Could not find raid account for <highlight>%s<end>." % char.name alts_link = self.text.make_chatcmd( "Alts", "/tell <myname> alts %s" % main.name) blob = "" blob += "Holder of account: %s [%s]\n" % (main.name, alts_link) blob += "Points: %d\n" % points.points blob += "Status: %s\n\n" % ("<green>Open<end>" if points.disabled == 0 else "<red>Disabled<end>") blob += "<header2>Account log<end>\n" if points_log is None: blob += "No entries in log." else: for entry in points_log: name_reference = "<highlight>%s<end>" % char.name if entry.audit == 0: # If points is 0, then it's a general case log blob += "<grey>[%s]<end> <orange>\"%s\"<end>" % ( self.util.format_datetime(entry.time), entry.reason) elif entry.audit > 0: pts = "<green>%d<end>" % entry.audit blob += "<grey>[%s]<end> %s points were added to %s account " \ "by <highlight>%s<end> with reason <orange>%s<end>" \ % (self.util.format_datetime(entry.time), pts, name_reference, self.character_service.resolve_char_to_name(entry.leader_id), entry.reason) elif entry.audit < 0: pts = "<red>%d<end>" % (-1 * entry.audit) blob += "<grey>[%s]<end> %s points were taken from %s account " \ "by <highlight>%s<end> with reason <orange>%s<end>" \ % (self.util.format_datetime(entry.time), pts, name_reference, self.character_service.resolve_char_to_name(entry.leader_id), entry.reason) log_entry_link = self.text.make_chatcmd( "%d" % entry.log_id, "/tell <myname> account logentry %d" % entry.log_id) blob += " [%s]\n" % log_entry_link return ChatBlob("%s Account" % char.name, blob)
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 AdminController: def __init__(self): pass def inject(self, registry): self.bot = registry.get_instance("bot") self.admin_service = registry.get_instance("admin_service") self.pork_service = registry.get_instance("pork_service") self.command_alias_service = registry.get_instance( "command_alias_service") self.buddy_service = registry.get_instance("buddy_service") def start(self): self.command_alias_service.add_alias("adminlist", "admin") self.command_alias_service.add_alias("admins", "admin") @command(command="admin", params=[], access_level="all", description="Show the admin list") def admin_list_cmd(self, request): admins = self.admin_service.get_all() blob = "" current_access_level = "" for row in admins: if row.access_level != current_access_level: blob += "\n<header2>%s</header2>\n" % row.access_level.capitalize( ) current_access_level = row.access_level if row.name: blob += row.name else: blob += "Unknown(%d)" % row.char_id if self.buddy_service.is_online(row.char_id): blob += " [<green>Online</green>]" blob += "\n" return ChatBlob("Admin List (%d)" % len(admins), blob) @command(command="admin", params=[Const("add"), Character("character")], access_level="superadmin", description="Add an admin", sub_command="modify") def admin_add_cmd(self, request, _, char): if not char.char_id: return StandardMessage.char_not_found(char.name) if self.admin_service.add(char.char_id, AdminService.ADMIN): return f"Character <highlight>{char.name}</highlight> added as <highlight>{AdminService.ADMIN}</highlight> successfully." else: return f"Could not add character <highlight>{char.name}</highlight> as <highlight>{AdminService.ADMIN}</highlight>." @command(command="admin", params=[Options(["remove", "rem"]), Character("character")], access_level="superadmin", description="Remove an admin", sub_command="modify") def admin_remove_cmd(self, request, _, char): if not char.char_id: return StandardMessage.char_not_found(char.name) if self.admin_service.remove(char.char_id): return f"Character <highlight>{char.name}</highlight> removed as <highlight>{AdminService.ADMIN}</highlight> successfully." else: return f"Could not remove character <highlight>{char.name}</highlight> as <highlight>{AdminService.ADMIN}</highlight>." @command(command="moderator", params=[Const("add"), Character("character")], access_level="admin", description="Add a moderator", sub_command="modify") def moderator_add_cmd(self, request, _, char): if not char.char_id: return StandardMessage.char_not_found(char.name) if self.admin_service.add(char.char_id, AdminService.MODERATOR): return f"Character <highlight>{char.name}</highlight> added as <highlight>{AdminService.MODERATOR}</highlight> successfully." else: return f"Could not add character <highlight>{char.name}</highlight> as <highlight>{AdminService.MODERATOR}</highlight>." @command(command="moderator", params=[Options(["remove", "rem"]), Character("character")], access_level="admin", description="Remove a moderator", sub_command="modify") def moderator_remove_cmd(self, request, _, char): if not char.char_id: return StandardMessage.char_not_found(char.name) if self.admin_service.remove(char.char_id): return f"Character <highlight>{char.name}</highlight> removed as <highlight>{AdminService.MODERATOR}</highlight> successfully." else: return f"Could not remove character <highlight>{char.name}</highlight> as <highlight>{AdminService.MODERATOR}</highlight>." @event(event_type="connect", description="Add admins as buddies", is_system=True) def connect_event(self, event_type, event_data): for row in self.admin_service.get_all(): self.buddy_service.add_buddy(row.char_id, "admin")
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 AdminController: def __init__(self): pass def inject(self, registry): self.bot = registry.get_instance("bot") self.admin_service = registry.get_instance("admin_service") self.pork_service = registry.get_instance("pork_service") self.command_alias_service = registry.get_instance( "command_alias_service") self.buddy_service = registry.get_instance("buddy_service") def start(self): self.command_alias_service.add_alias("adminlist", "admin") self.command_alias_service.add_alias("admins", "admin") @command(command="admin", params=[], access_level="all", description="Show the admin list") def admin_list_cmd(self, request): admins = self.admin_service.get_all() 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 if self.buddy_service.is_online(row.char_id): blob += " [<green>Online<end>]" blob += "\n" return ChatBlob("Admin List (%d)" % len(admins), blob) @command(command="admin", params=[Const("add"), Character("character")], access_level="superadmin", description="Add an admin", sub_command="modify") def admin_add_cmd(self, request, _, char): if not char.char_id: return "Could not find character <highlight>%s<end>." % char.name if self.admin_service.add(char.char_id, AdminService.ADMIN): return "Character <highlight>%s<end> added as <highlight>%s<end> successfully." % ( char.name, AdminService.ADMIN) else: return "Could not add character <highlight>%s<end> as <highlight>%s<end>." % ( char.name, AdminService.ADMIN) @command(command="admin", params=[Options(["remove", "rem"]), Character("character")], access_level="superadmin", description="Remove an admin", sub_command="modify") def admin_remove_cmd(self, request, _, char): if not char.char_id: return "Could not find character <highlight>%s<end>." % char.name if self.admin_service.remove(char.char_id): return "Character <highlight>%s<end> removed as <highlight>%s<end> successfully." % ( char.name, AdminService.ADMIN) else: return "Could not remove character <highlight>%s<end> as <highlight>%s<end>." % ( char.name, AdminService.ADMIN) @command(command="moderator", params=[Const("add"), Character("character")], access_level="admin", description="Add a moderator", sub_command="modify") def moderator_add_cmd(self, request, _, char): if not char.char_id: return "Could not find character <highlight>%s<end>." % char.name if self.admin_service.add(char.char_id, AdminService.MODERATOR): return "Character <highlight>%s<end> added as <highlight>%s<end> successfully." % ( char.name, AdminService.MODERATOR) else: return "Could not add character <highlight>%s<end> as <highlight>%s<end>." % ( char.name, AdminService.MODERATOR) @command(command="moderator", params=[Options(["remove", "rem"]), Character("character")], access_level="admin", description="Remove a moderator", sub_command="modify") def moderator_remove_cmd(self, request, _, char): if not char.char_id: return "Could not find character <highlight>%s<end>." % char.name if self.admin_service.remove(char.char_id): return "Character <highlight>%s<end> removed as <highlight>%s<end> successfully." % ( char.name, AdminService.MODERATOR) else: return "Could not remove character <highlight>%s<end> as <highlight>%s<end>." % ( char.name, AdminService.MODERATOR)
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 QuoteController: def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.util = registry.get_instance("util") def start(self): self.db.exec( "CREATE TABLE IF NOT EXISTS quote (id INT PRIMARY KEY AUTO_INCREMENT, char_id INT NOT NULL, created_at INT NOT NULL, content VARCHAR(4096) NOT NULL)" ) @command(command="quote", params=[], access_level="guest", description="Show a random quote") def quote_command(self, request): quote = self.get_random_quote() if quote: return self.format_quote(quote, request.conn) else: return "There are no quotes to display." @command(command="quote", params=[Int("quote_id")], access_level="guest", description="Show a specific quote") def quote_view_command(self, request, quote_id): quote = self.get_quote_info(quote_id) if quote: return self.format_quote(quote, request.conn) else: return "Quote with ID <highlight>%d</highlight> does not exist." % quote_id @command(command="quote", params=[Const("add"), Any("quote")], access_level="guest", description="Show a specific quote") def quote_add_command(self, request, _, quote): if len(quote) > 4096: return "Your quote must be less than 4096 characters." next_id = self.db.query_single( "SELECT (COALESCE(MAX(id), 0) + 1) AS next_id FROM quote").next_id self.db.exec( "INSERT INTO quote (id, char_id, created_at, content) VALUES (?, ?, ?, ?)", [next_id, request.sender.char_id, int(time.time()), quote]) return f"Quote with ID <highlight>{next_id}</highlight> has been added successfully." @command(command="quote", params=[Options(["rem", "remove"]), Int("quote_id")], access_level="moderator", description="Remove a quote", sub_command="remove") def quote_remove_command(self, request, _, quote_id): num_rows = self.db.exec("DELETE FROM quote WHERE id = ?", [quote_id]) if num_rows: return f"Quote with ID <highlight>{quote_id}</highlight> has been removed successfully." else: return "Quote with ID <highlight>%d</highlight> does not exist." % quote_id @command(command="quote", params=[Const("search"), Any("search_params")], access_level="guest", description="Search for a quote") def quote_search_command(self, request, _, search_params): sql = "SELECT q.*, p.name FROM quote q LEFT JOIN player p ON q.char_id = p.char_id " \ "WHERE q.content <EXTENDED_LIKE=0> ? OR p.name LIKE ? " \ "ORDER BY q.id ASC" data = self.db.query(sql, [search_params, search_params], extended_like=True) blob = "" for row in data: blob += self.text.make_tellcmd(row.id, f"quote {row.id}") blob += " " blob += row.content blob += "\n\n" return ChatBlob("Quote Search Results (%d)" % len(data), blob) def get_random_quote(self): quotes = self.db.query( "SELECT q.*, p.name FROM quote q LEFT JOIN player p ON q.char_id = p.char_id ORDER BY q.id ASC" ) if quotes: return random.choice(quotes) else: return None def get_quote_info(self, quote_id): return self.db.query_single( "SELECT q.*, p.name FROM quote q LEFT JOIN player p ON q.char_id = p.char_id WHERE q.id = ?", [quote_id]) def format_quote(self, quote, conn): blob = f"ID: <highlight>{quote.id}</highlight>\n" blob += f"Created By: <highlight>{quote.name or quote.char_id}</highlight>\n" blob += f"Created At: <highlight>{self.util.format_datetime(quote.created_at)}</highlight>\n\n" blob += quote.content chat_blob = ChatBlob("More Info", blob) more_info_link = self.text.paginate(chat_blob, conn)[0] return "%d. %s %s" % (quote.id, quote.content, more_info_link)