Exemple #1
0
class SystemController:
    SHUTDOWN_EVENT = "shutdown"
    MESSAGE_SOURCE = "shutdown_notice"

    def __init__(self):
        self.logger = Logger(__name__)

    def inject(self, registry):
        self.bot = registry.get_instance("bot")
        self.setting_service: SettingService = registry.get_instance(
            "setting_service")
        self.event_service = registry.get_instance("event_service")
        self.character_service = registry.get_instance("character_service")
        self.message_hub_service = registry.get_instance("message_hub_service")

    def pre_start(self):
        self.event_service.register_event_type(self.SHUTDOWN_EVENT)
        self.message_hub_service.register_message_source(self.MESSAGE_SOURCE)

    def start(self):
        self.setting_service.register(
            self.module_name, "expected_shutdown", True, BooleanSettingType(),
            "Helps bot to determine if last shutdown was expected or due to a problem"
        )

    def expected_shutdown(self):
        return self.setting_service.get("expected_shutdown")

    @command(command="shutdown",
             params=[Any("reason", is_optional=True)],
             access_level="superadmin",
             description="Shutdown the bot")
    def shutdown_cmd(self, request, reason):
        if request.channel not in [
                PublicChannelService.ORG_CHANNEL_COMMAND,
                PrivateChannelService.PRIVATE_CHANNEL_COMMAND
        ]:
            request.reply(self._format_message(False, reason))
        self.shutdown(False, reason)

    @command(command="restart",
             params=[Any("reason", is_optional=True)],
             access_level="admin",
             description="Restart the bot")
    def restart_cmd(self, request, reason):
        if request.channel not in [
                PublicChannelService.ORG_CHANNEL_COMMAND,
                PrivateChannelService.PRIVATE_CHANNEL_COMMAND
        ]:
            request.reply(self._format_message(True, reason))
        self.shutdown(True, reason)

    @event(event_type="connect",
           description="Notify superadmin that bot has come online")
    def connect_event(self, event_type, event_data):
        if self.expected_shutdown().get_value():
            msg = "<myname> is now <green>online</green>."
        else:
            self.logger.warning(
                "The bot has recovered from an unexpected shutdown or restart")
            msg = "<myname> is now <green>online</green> but may have shut down or restarted unexpectedly."

        char_id = self.character_service.resolve_char_to_id(
            self.bot.superadmin)
        self.bot.send_private_message(char_id,
                                      msg,
                                      conn=self.bot.get_primary_conn())
        self.bot.send_private_channel_message(msg,
                                              conn=self.bot.get_primary_conn())
        for _id, conn in self.bot.get_conns(lambda x: x.is_main):
            self.bot.send_org_message(msg, conn=conn)

        self.expected_shutdown().set_value(False)

    def shutdown(self, should_restart, reason=None):
        self.event_service.fire_event(
            self.SHUTDOWN_EVENT,
            DictObject({
                "restart": should_restart,
                "reason": reason
            }))
        # set expected flag
        self.expected_shutdown().set_value(True)
        self.message_hub_service.send_message(
            self.MESSAGE_SOURCE, None, None,
            self._format_message(should_restart, reason))
        if should_restart:
            self.bot.restart()
        else:
            self.bot.shutdown()

    def _format_message(self, restart, reason):
        msg = ""
        if restart:
            msg += "The bot is restarting."
        else:
            msg += "The bot is shutting down."

        if reason:
            msg += f" <highlight>Reason: {reason}</highlight>"

        return msg
Exemple #2
0
class RaidController:
    NO_RAID_RUNNING_RESPONSE = "No raid is running."

    def __init__(self):
        self.raid = None

    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.setting_service: SettingService = registry.get_instance(
            "setting_service")
        self.alts_service: AltsService = registry.get_instance("alts_service")
        self.character_service: CharacterService = registry.get_instance(
            "character_service")
        self.points_controller: PointsController = registry.get_instance(
            "points_controller")
        self.util: Util = registry.get_instance("util")

    @command(command="raid",
             params=[],
             access_level="member",
             description="Show the current raid status")
    def raid_cmd(self, request):
        if not self.raid:
            return self.NO_RAID_RUNNING_RESPONSE

        t = int(time.time())

        blob = ""
        blob += "Name: <highlight>%s<end>\n" % self.raid.raid_name
        blob += "Started By: <highlight>%s<end>\n" % self.raid.started_by.name
        blob += "Started At: <highlight>%s<end> (%s ago)\n" % (
            self.util.format_datetime(self.raid.started_at),
            self.util.time_to_readable(t - self.raid.started_at))
        blob += "Status: %s" % ("<green>Open<end>"
                                if self.raid.is_open else "<red>Closed<end>")
        if self.raid.is_open:
            blob += " (%s)" % self.text.make_chatcmd(
                "Join", "/tell <myname> raid join")
        blob += "\n\n"
        if self.raid.raid_orders:
            blob += "<header2>Orders<end>\n"
            blob += self.raid.raid_orders + "\n\n"
        blob += "<header2>Raiders<end>\n"
        for raider in self.raid.raiders:
            if raider.is_active:
                blob += self.text.format_char_info(
                    raider.get_active_char()) + "\n"

        return ChatBlob("Raid Status", blob)

    @command(command="raid",
             params=[Const("start"), Any("raid_name")],
             description="Start new raid",
             access_level="moderator",
             sub_command="manage")
    def raid_start_cmd(self, request, _, raid_name: str):
        if self.raid:
            return "The <highlight>%s<end> raid is already running." % self.raid.raid_name

        self.raid = Raid(raid_name, request.sender)

        leader_alts = self.alts_service.get_alts(request.sender.char_id)
        self.raid.raiders.append(Raider(leader_alts, request.sender.char_id))

        join_link = self.get_raid_join_blob("Click here")

        msg = "\n<highlight>----------------------------------------<end>\n"
        msg += "<highlight>%s<end> has just started the <highlight>%s<end> raid.\n" % (
            request.sender.name, raid_name)
        msg += "%s to join\n" % join_link
        msg += "<highlight>----------------------------------------<end>"

        self.bot.send_org_message(msg)
        self.bot.send_private_channel_message(msg)

    @command(command="raid",
             params=[Options(["end", "cancel"])],
             description="End raid without saving/logging.",
             access_level="moderator",
             sub_command="manage")
    def raid_cancel_cmd(self, request, _):
        if self.raid is None:
            return self.NO_RAID_RUNNING_RESPONSE

        raid_name = self.raid.raid_name
        self.raid = None
        self.bot.send_org_message(
            "<highlight>%s<end> canceled the <highlight>%s<end> raid prematurely."
            % (request.sender.name, raid_name))
        self.bot.send_private_channel_message(
            "<highlight>%s<end> canceled the <highlight>%s<end> raid prematurely."
            % (request.sender.name, raid_name))

    @command(command="raid",
             params=[Const("join")],
             description="Join the ongoing raid",
             access_level="member")
    def raid_join_cmd(self, request, _):
        if not self.raid:
            return self.NO_RAID_RUNNING_RESPONSE

        main_id = self.alts_service.get_main(request.sender.char_id).char_id
        in_raid = self.is_in_raid(main_id)

        if in_raid is not None:
            if in_raid.active_id == request.sender.char_id:
                if in_raid.is_active:
                    return "You are already participating in the raid."
                else:
                    if not self.raid.is_open:
                        return "Raid is closed."
                    in_raid.is_active = True
                    in_raid.was_kicked = None
                    in_raid.was_kicked_reason = None
                    in_raid.left_raid = None
                    self.bot.send_private_channel_message(
                        "%s returned to actively participating in the raid." %
                        request.sender.name)
                    self.bot.send_org_message(
                        "%s returned to actively participating in the raid." %
                        request.sender.name)

            elif in_raid.is_active:
                former_active_name = self.character_service.resolve_char_to_name(
                    in_raid.active_id)
                in_raid.active_id = request.sender.char_id
                self.bot.send_private_channel_message(
                    "<highlight>%s<end> joined the raid with a different alt, <highlight>%s<end>."
                    % (former_active_name, request.sender.name))
                self.bot.send_org_message(
                    "<highlight>%s<end> joined the raid with a different alt, <highlight>%s<end>."
                    % (former_active_name, request.sender.name))

            elif not in_raid.is_active:
                if not self.raid.is_open:
                    return "Raid is closed."
                former_active_name = self.character_service.resolve_char_to_name(
                    in_raid.active_id)
                in_raid.active_id = request.sender.char_id
                in_raid.was_kicked = None
                in_raid.was_kicked_reason = None
                in_raid.left_raid = None
                self.bot.send_private_channel_message(
                    "%s returned to actively participate with a different alt, <highlight>%s<end>."
                    % (former_active_name, request.sender.name))
                self.bot.send_org_message(
                    "%s returned to actively participate with a different alt, <highlight>%s<end>."
                    % (former_active_name, request.sender.name))

        elif self.raid.is_open:
            alts = self.alts_service.get_alts(request.sender.char_id)
            self.raid.raiders.append(Raider(alts, request.sender.char_id))
            self.bot.send_private_channel_message(
                "<highlight>%s<end> joined the raid." % request.sender.name)
            self.bot.send_org_message("<highlight>%s<end> joined the raid." %
                                      request.sender.name)
        else:
            return "Raid is closed."

    @command(command="raid",
             params=[Const("leave")],
             description="Leave the ongoing raid",
             access_level="member")
    def raid_leave_cmd(self, request, _):
        in_raid = self.is_in_raid(
            self.alts_service.get_main(request.sender.char_id).char_id)
        if in_raid:
            if not in_raid.is_active:
                return "You are not active in the raid."

            in_raid.is_active = False
            in_raid.left_raid = int(time.time())
            self.bot.send_private_channel_message(
                "<highlight>%s<end> left the raid." % request.sender.name)
            self.bot.send_org_message("<highlight>%s<end> left the raid." %
                                      request.sender.name)
        else:
            return "You are not in the raid."

    @command(command="raid",
             params=[Const("addpts"), Any("name")],
             description="Add points to all active participants",
             access_level="moderator",
             sub_command="manage")
    def points_add_cmd(self, request, _, name: str):
        if not self.raid:
            return self.NO_RAID_RUNNING_RESPONSE

        preset = self.db.query_single(
            "SELECT * FROM points_presets WHERE name = ?", [name])
        if not preset:
            return ChatBlob("No such preset - see list of presets",
                            self.points_controller.build_preset_list())

        for raider in self.raid.raiders:
            current_points = self.db.query_single(
                "SELECT points, disabled FROM points WHERE char_id = ?",
                [raider.main_id])

            if raider.is_active:
                if current_points and current_points.disabled == 0:
                    self.points_controller.alter_points(
                        current_points.points, raider.main_id, preset.points,
                        request.sender.char_id, preset.name)
                    raider.accumulated_points += preset.points
                else:
                    self.points_controller.add_log_entry(
                        raider.main_id, request.sender.char_id,
                        "Participated in raid without an open account, "
                        "missed points from %s." % preset.name)
            else:
                if current_points:
                    self.points_controller.add_log_entry(
                        raider.main_id, request.sender.char_id,
                        "Was inactive during raid, %s, when points "
                        "for %s was dished out." %
                        (self.raid.raid_name, preset.name))
                else:
                    self.points_controller.add_log_entry(
                        raider.main_id, request.sender.char_id,
                        "Was inactive during raid, %s, when points for %s "
                        "was dished out - did not have an active account at "
                        "the given time." % (self.raid.raid_name, preset.name))
        return "<highlight>%d<end> points added to all active raiders." % preset.points

    @command(command="raid",
             params=[Const("active")],
             description="Get a list of raiders to do active check",
             access_level="moderator",
             sub_command="manage")
    def raid_active_cmd(self, request, _):
        if not self.raid:
            return self.NO_RAID_RUNNING_RESPONSE

        blob = ""

        count = 0
        raider_names = []
        for raider in self.raid.raiders:
            if count == 10:
                active_check_names = "/assist "
                active_check_names += "\\n /assist ".join(raider_names)
                blob += "[<a href='chatcmd://%s'>Active check</a>]\n\n" % active_check_names
                count = 0
                raider_names.clear()

            raider_name = self.character_service.resolve_char_to_name(
                raider.active_id)
            akick_link = self.text.make_chatcmd(
                "Active kick",
                "/tell <myname> raid kick %s inactive" % raider.main_id)
            warn_link = self.text.make_chatcmd(
                "Warn",
                "/tell <myname> cmd %s missed active check, please give notice."
                % raider_name)
            blob += "<highlight>%s<end> [%s] [%s]\n" % (raider_name,
                                                        akick_link, warn_link)
            raider_names.append(raider_name)
            count += 1

        if len(raider_names) > 0:
            active_check_names = "/assist "
            active_check_names += "\\n /assist ".join(raider_names)

            blob += "[<a href='chatcmd://%s'>Active check</a>]\n\n" % active_check_names
            raider_names.clear()

        return ChatBlob("Active check", blob)

    @command(command="raid",
             params=[Const("kick"),
                     Character("char"),
                     Any("reason")],
             description="Set raider as kicked with a reason",
             access_level="moderator",
             sub_command="manage")
    def raid_kick_cmd(self, _1, _2, char: SenderObj, reason: str):
        if self.raid is None:
            return self.NO_RAID_RUNNING_RESPONSE

        main_id = self.alts_service.get_main(char.char_id).char_id
        in_raid = self.is_in_raid(main_id)

        try:
            int(char.name)
            char.name = self.character_service.resolve_char_to_name(char.name)
        except ValueError:
            pass

        if in_raid is not None:
            if not in_raid.is_active:
                return "<highlight>%s<end> is already set as inactive." % char.name

            in_raid.is_active = False
            in_raid.was_kicked = int(time.time())
            in_raid.was_kicked_reason = reason
            return "<highlight>%s<end> has been kicked from the raid with reason \"%s\"." % (
                char.name, reason)
        else:
            return "<highlight>%s<end> is not participating." % char.name

    @command(command="raid",
             params=[Options(["open", "close"])],
             description="Open/close raid for new participants",
             access_level="moderator",
             sub_command="manage")
    def raid_open_close_cmd(self, request, action):
        if not self.raid:
            return self.NO_RAID_RUNNING_RESPONSE

        if action == "open":
            if self.raid.is_open:
                return "Raid is already open."
            self.raid.is_open = True
            self.bot.send_private_channel_message(
                "Raid has been opened by %s." % request.sender.name)
            self.bot.send_org_message("Raid has been opened by %s." %
                                      request.sender.name)
            return
        elif action == "close":
            if self.raid.is_open:
                self.raid.is_open = False
                self.bot.send_private_channel_message(
                    "Raid has been closed by %s." % request.sender.name)
                self.bot.send_org_message("Raid has been closed by %s." %
                                          request.sender.name)
                return
            return "Raid is already closed."

    @command(command="raid",
             params=[Const("save")],
             description="Save and log running raid",
             access_level="moderator",
             sub_command="manage")
    def raid_save_cmd(self, _1, _2):
        if not self.raid:
            return self.NO_RAID_RUNNING_RESPONSE

        sql = "INSERT INTO raid_log (raid_name, started_by, raid_start, raid_end) VALUES (?,?,?,?)"
        num_rows = self.db.exec(sql, [
            self.raid.raid_name, self.raid.started_by.char_id,
            self.raid.started_at,
            int(time.time())
        ])
        if num_rows > 0:
            raid_id = self.db.query_single(
                "SELECT raid_id FROM raid_log ORDER BY raid_id DESC LIMIT 1"
            ).raid_id

            for raider in self.raid.raiders:
                sql = "INSERT INTO raid_log_participants (raid_id, raider_id, accumulated_points, left_raid, was_kicked, was_kicked_reason) VALUES (?,?,?,?,?,?)"
                self.db.exec(sql, [
                    raid_id, raider.active_id, raider.accumulated_points,
                    raider.left_raid, raider.was_kicked,
                    raider.was_kicked_reason
                ])

            self.raid = None

            return "Raid saved."
        else:
            return "Failed to log raid. Try again or cancel raid to end raid."

    @command(
        command="raid",
        params=[
            Const("logentry"),
            Int("raid_id"),
            Character("char", is_optional=True)
        ],
        description=
        "Show log entry for raid, with possibility of narrowing down the log for character in raid",
        access_level="moderator",
        sub_command="manage")
    def raid_log_entry_cmd(self, _1, _2, raid_id: int, char: SenderObj):
        log_entry_spec = None
        if char:
            sql = "SELECT * FROM raid_log r LEFT JOIN raid_log_participants p ON r.raid_id = p.raid_id WHERE r.raid_id = ? AND p.raider_id = ?"
            log_entry_spec = self.db.query_single(sql, [raid_id, char.char_id])

        sql = "SELECT * FROM raid_log r LEFT JOIN raid_log_participants p ON r.raid_id = p.raid_id WHERE r.raid_id = ? ORDER BY p.accumulated_points DESC"
        log_entry = self.db.query(sql, [raid_id])
        pts_sum = self.db.query_single(
            "SELECT SUM(p.accumulated_points) AS sum FROM raid_log_participants p WHERE p.raid_id = ?",
            [raid_id]).sum

        if not log_entry:
            return "No such log entry."

        blob = "Raid name: <highlight>%s<end>\n" % log_entry[0].raid_name
        blob += "Started by: <highlight>%s<end>\n" % self.character_service.resolve_char_to_name(
            log_entry[0].started_by)
        blob += "Start time: <highlight>%s<end>\n" % self.util.format_datetime(
            log_entry[0].raid_start)
        blob += "End time: <highlight>%s<end>\n" % self.util.format_datetime(
            log_entry[0].raid_end)
        blob += "Run time: <highlight>%s<end>\n" % self.util.time_to_readable(
            log_entry[0].raid_end - log_entry[0].raid_start)
        blob += "Total points: <highlight>%d<end>\n\n" % pts_sum

        if char and log_entry_spec:
            raider_name = self.character_service.resolve_char_to_name(
                log_entry_spec.raider_id)
            main_info = self.alts_service.get_main(log_entry_spec.raider_id)
            alt_link = "Alt of %s" % main_info.name if main_info.char_id != log_entry_spec.raider_id else "Alts"
            alt_link = self.text.make_chatcmd(
                alt_link, "/tell <myname> alts %s" % main_info.name)
            blob += "<header2>Log entry for %s<end>\n" % raider_name
            blob += "Raider: <highlight>%s<end> [%s]\n" % (raider_name,
                                                           alt_link)
            blob += "Left raid: %s\n" % (
                "n/a" if log_entry_spec.left_raid is None else
                self.util.format_datetime(log_entry_spec.left_raid))
            blob += "Was kicked: %s\n" % (
                "No" if log_entry_spec.was_kicked is None else "Yes [%s]" %
                (self.util.format_datetime(log_entry_spec.was_kicked)))
            blob += "Kick reason: %s\n\n" % (
                "n/a" if log_entry_spec.was_kicked_reason is None else
                log_entry_spec.was_kicked_reason)

        blob += "<header2>Participants<end>\n"
        for raider in log_entry:
            raider_name = self.character_service.resolve_char_to_name(
                raider.raider_id)
            main_info = self.alts_service.get_main(raider.raider_id)
            alt_link = "Alt of %s" % main_info.name if main_info.char_id != raider.raider_id else "Alts"
            alt_link = self.text.make_chatcmd(
                alt_link, "/tell <myname> alts %s" % main_info.name)
            log_link = self.text.make_chatcmd(
                "Log",
                "/tell <myname> raid logentry %d %s" % (raid_id, raider_name))
            account_link = self.text.make_chatcmd(
                "Account", "/tell <myname> account %s" % raider_name)
            blob += "%s - %d points earned [%s] [%s] [%s]\n" % (
                raider_name, raider.accumulated_points, log_link, account_link,
                alt_link)

        log_entry_reference = "the raid %s" % log_entry[0].raid_name \
            if char is None \
            else "%s in raid %s" \
                 % (self.character_service.resolve_char_to_name(char.char_id), log_entry[0].raid_name)
        return ChatBlob("Log entry for %s" % log_entry_reference, blob)

    @command(command="raid",
             params=[Const("history")],
             description="Show a list of recent raids",
             access_level="member")
    def raid_history_cmd(self, _1, _2):
        sql = "SELECT * FROM raid_log ORDER BY raid_end DESC LIMIT 30"
        raids = self.db.query(sql)

        blob = ""
        for raid in raids:
            participant_link = self.text.make_chatcmd(
                "Log", "/tell <myname> raid logentry %d" % raid.raid_id)
            timestamp = self.util.format_datetime(raid.raid_start)
            leader_name = self.character_service.resolve_char_to_name(
                raid.started_by)
            blob += "[%d] [%s] <highlight>%s<end> started by <highlight>%s<end> [%s]\n" % (
                raid.raid_id, timestamp, raid.raid_name, leader_name,
                participant_link)

        return ChatBlob("Raid history", blob)

    def is_in_raid(self, main_id: int):
        if self.raid is None:
            return None

        for raider in self.raid.raiders:
            if raider.main_id == main_id:
                return raider

    def get_raid_join_blob(self, link_txt: str):
        blob = "<header2>1. Join the raid<end>\n" \
               "To join the current raid <highlight>%s<end>, send the following tell to <myname>\n" \
               "<tab><tab><a href='chatcmd:///tell <myname> <symbol>raid join'>/tell <myname> raid " \
               "join</a>\n\n<header2>2. Enable LFT<end>\nWhen you have joined the raid, go lft " \
               "with \"<myname>\" as description\n<tab><tab><a href='chatcmd:///lft <myname>'>/lft <myname></a>\n\n" \
               "<header2>3. Announce<end>\nYou could announce to the raid leader, that you have enabled " \
               "LFT\n<tab><tab><a href='chatcmd:///group <myname> I am on lft'>Announce</a> that you have enabled " \
               "lft\n\n<header2>4. Rally with yer mateys<end>\nFinally, move towards the starting location of " \
               "the raid.\n<highlight>Ask for help<end> if you're in doubt of where to go." % self.raid.raid_name

        return self.text.paginate_single(ChatBlob(link_txt, blob))
Exemple #3
0
class RecipeController:
    def __init__(self):
        self.logger = Logger(__name__)

        self.recipe_name_regex = re.compile(r"(\d+)\.json")
        self.recipe_item_regex = re.compile(r"#L \"([^\"]+)\" \"([\d+]+)\"")
        self.recipe_link_regex = re.compile(r"#L \"([^\"]+)\" \"([^\"]+)\"")

    def inject(self, registry):
        self.db = registry.get_instance("db")
        self.text = registry.get_instance("text")
        self.items_controller = registry.get_instance("items_controller")
        self.command_alias_service = registry.get_instance("command_alias_service")

    def start(self):
        self.command_alias_service.add_alias("r", "recipe")
        self.command_alias_service.add_alias("tradeskill", "recipe")

        self.db.exec("CREATE TABLE IF NOT EXISTS recipe (id INT NOT NULL PRIMARY KEY, name VARCHAR(50) NOT NULL, author VARCHAR(50) NOT NULL, recipe TEXT NOT NULL, dt INT NOT NULL DEFAULT 0)")

        recipe_dir = os.path.dirname(os.path.realpath(__file__)) + "/recipes/"
        recipes = self.db.query("SELECT id, dt FROM recipe")

        for file in os.listdir(recipe_dir):
            if file.startswith("_"):
                continue

            m = self.recipe_name_regex.match(file)
            if m:
                recipe_id = m.group(1)
                dt = int(os.path.getmtime(recipe_dir+file))

                recipe = self.find_recipe(recipe_id, recipes)
                if recipe:
                    recipes.remove(recipe)
                    if recipe.dt == dt:
                        continue

                self.update_recipe(recipe_dir, recipe_id, dt)
            else:
                raise Exception("Unknown recipe format for '%s'" % file)

    @command(command="recipe", params=[Int("recipe_id")], access_level="all", description="Show a recipe")
    def recipe_show_cmd(self, request, recipe_id):
        recipe = self.get_recipe(recipe_id)
        if not recipe:
            return "Could not find recipe with ID <highlight>%d</highlight>." % recipe_id

        return self.format_recipe(recipe)

    @command(command="recipe", params=[Item("item"), NamedParameters(["page"])], access_level="all", description="Search for a recipe")
    def recipe_search_item_cmd(self, request, item, named_params):
        page_number = int(named_params.page or "1")

        return self.get_search_results(item.name, page_number)

    @command(command="recipe", params=[Any("search"), NamedParameters(["page"])], access_level="all", description="Search for a recipe")
    def recipe_search_cmd(self, request, search, named_params):
        page_number = int(named_params.page or "1")

        return self.get_search_results(search, page_number)

    def get_search_results(self, search, page_number):
        page_size = 30
        offset = (page_number - 1) * page_size

        data = self.db.query("SELECT * FROM recipe WHERE recipe <EXTENDED_LIKE=0> ? ORDER BY name ASC", [search], extended_like=True)
        count = len(data)
        paged_data = data[offset:offset + page_size]

        blob = ""

        if len(data) == 0:
            return "No recipe matching <highlight>%s</highlight>." % search

        elif len(data) == 1:
            return self.format_recipe(data[0])

        else:
            blob += self.text.get_paging_links(f"recipe {search}", page_number, offset + page_size < len(data))
            blob += "\n\n"

            for row in paged_data:
                blob += self.text.make_tellcmd(row.name, "recipe %d" % row.id) + "\n"

            return ChatBlob("Recipes Matching '%s' (%d - %d of %d)" % (search, offset + 1, min(offset + page_size, count), count), blob)

    def get_recipe(self, recipe_id):
        return self.db.query_single("SELECT * FROM recipe WHERE id = ?", [recipe_id])

    def format_recipe(self, recipe):
        blob = "Recipe ID: <highlight>%d</highlight>\n" % recipe.id
        blob += "Author: <highlight>%s</highlight>\n\n" % (recipe.author or "Unknown")
        blob += self.format_recipe_text(recipe.recipe)

        return ChatBlob("Recipe for '%s'" % recipe.name, blob)

    def format_recipe_text(self, recipe_text):
        recipe_text = recipe_text.replace("\\n", "\n")
        recipe_text = self.recipe_item_regex.sub(self.lookup_item, recipe_text)
        recipe_text = self.recipe_link_regex.sub("<a href='chatcmd://\\2'>\\1</a>", recipe_text)
        return recipe_text

    def lookup_item(self, m):
        name = m.group(1)
        item_id = m.group(2)

        item = self.items_controller.get_by_item_id(item_id)
        if item:
            return self.text.make_item(item.lowid, item.highid, item.highql, item.name)
        else:
            return name

    def find_recipe(self,recipe_id,recipes):
        for row in recipes:
            if str(row.id) == recipe_id:
                return row
        return None

    def update_recipe(self, recipe_dir, recipe_id, dt):
        with open(recipe_dir + recipe_id + ".json", mode="r", encoding="UTF-8") as f:
            recipe = json.load(f)

        name = recipe["name"]
        author = recipe["author"]

        if "raw" in recipe:
            content = recipe["raw"]
        else:
            content = self.format_json_recipe(recipe_id, recipe)

        self.db.exec("REPLACE INTO recipe (id, name, author, recipe, dt) VALUES (?, ?, ?, ?, ?)", [recipe_id, name, author, content, dt])

    def format_json_recipe(self, recipe_id, recipe):
        items = {}
        for i in recipe["items"]:
            item = self.items_controller.get_by_item_id(i["item_id"], i.get("ql"))
            if not item:
                raise Exception("Could not find recipe item '%d' for recipe id %s" % (i["item_id"], recipe_id))

            item.ql = i.get("ql") or (item.highql if i["item_id"] == item.highid else item.lowql)
            items[i["alias"]] = item

        content = ""

        ingredients = items.copy()
        for step in recipe["steps"]:
            del ingredients[step["result"]]

        content += self.format_ingredients(ingredients.items())
        content += "\n"
        content += self.format_steps(items, recipe["steps"])

        if "details" in recipe:
            content += self.format_details(recipe["details"])

        return content

    def format_ingredients(self, ingredients):
        content = "<font color=#FFFF00>------------------------------</font>\n"
        content += "<font color=#FF0000>Ingredients</font>\n"
        content += "<font color=#FFFF00>------------------------------</font>\n\n"

        for _, ingredient in ingredients:
            content += self.text.make_image(ingredient["icon"]) + "<tab>"
            content += self.text.make_item(ingredient["lowid"], ingredient["highid"], ingredient["ql"], ingredient["name"]) + "\n"

        return content

    def format_steps(self, items, steps):
        content = ""
        content += "<font color=#FFFF00>------------------------------</font>\n"
        content += "<font color=#FF0000>Recipe</font>\n"
        content += "<font color=#FFFF00>------------------------------</font>\n\n"

        for step in steps:
            source = items[step["source"]]
            target = items[step["target"]]
            result = items[step["result"]]
            content += "<a href='itemref://%d/%d/%d'>%s</a>" % \
                       (source["lowid"], source["highid"], source["ql"],
                        self.text.make_image(source["icon"])) + ""
            content += "<font color=#FFFFFF><tab>+<tab></font> "
            content += "<a href='itemref://%d/%d/%d'>%s</a>" % \
                       (target["lowid"], target["highid"], target["ql"],
                        self.text.make_image(target["icon"])) + ""
            content += "<font color=#FFFFFF><tab>=<tab></font> "
            content += "<a href='itemref://%d/%d/%d'>%s</a>" % \
                       (result["lowid"], result["highid"], result["ql"],
                        self.text.make_image(result["icon"]))
            content += "\n<tab><tab>" + self.text.make_item(source["lowid"], source["highid"], source["ql"], source["name"])
            content += "\n + <tab>" + self.text.make_item(target["lowid"], target["highid"], target["ql"], target["name"])
            content += "\n = <tab>" + self.text.make_item(result["lowid"], result["highid"], result["ql"], result["name"]) + "\n"

            if "skills" in step:
                content += "<font color=#FFFF00>Skills: | %s |</font>\n" % step["skills"]
            content += "\n\n"

        return content

    def format_details(self, details):
        content = ""
        content += "<font color=#FFFF00>------------------------------</font>\n"
        content += "<font color=#FF0000>Details</font>\n"
        content += "<font color=#FFFF00>------------------------------</font>\n\n"

        last_type = ""
        for detail in details:
            if "item" in detail:
                last_type = "item"
                i = detail["item"]

                item = None
                if "ql" in i:
                    item = self.items_controller.get_by_item_id(i["id"], i["ql"])
                else:
                    item = self.items_controller.get_by_item_id(i["id"])
                    item["ql"] = item["highql"]

                content += "<font color=#009B00>%s</font>" % \
                           self.text.make_item(item["lowid"],
                                               item["highid"],
                                               item["ql"],
                                               item["name"])

                if "comment" in i:
                    content += " - " + i["comment"]

                content += "\n"

            elif "text" in detail:
                if last_type == "item":
                    content += "\n"

                last_type = "text"
                content += "<font color=#FFFFFF>%s</font>\n" % detail["text"]

        return content
Exemple #4
0
class PremadeImplantController:
    def __init__(self):
        self.slots = ["head", "eye", "ear", "rarm", "chest", "larm", "rwrist", "waist", "lwrist", "rhand", "legs", "lhand", "feet"]

    def inject(self, registry):
        self.db = registry.get_instance("db")
        self.util = registry.get_instance("util")
        self.text = registry.get_instance("text")

    @command(command="premade", params=[], access_level="all",
             description="Search for implants in the premade implant booths")
    def premade_list_cmd(self, request):
        blob = "<header2>Professions<end>\n"
        for row in self.db.query("SELECT Name FROM Profession WHERE ID IN (SELECT ProfessionID FROM premade_implant) ORDER BY Name ASC"):
            blob += self.text.make_chatcmd(row.Name, "/tell <myname> premade %s" % row.Name) + "\n"

        blob += "\n<header2>Slots<end>\n"
        for row in self.db.query("SELECT * FROM ImplantType ORDER BY ImplantTypeID ASC"):
            blob += self.text.make_chatcmd(row.Name, "/tell <myname> premade %s" % row.ShortName) + "\n"

        blob += "\n<header2>Modifiers<end>\n"
        sql = "SELECT LongName FROM Cluster WHERE ClusterID IN " \
              "(SELECT ShinyClusterID From premade_implant UNION SELECT BrightClusterID FROM premade_implant UNION SELECT FadedClusterID FROM premade_implant) " \
              "AND ClusterID != 0 " \
              "ORDER BY LongName ASC"
        for row in self.db.query(sql):
            blob += self.text.make_chatcmd(row.LongName, "/tell <myname> premade %s" % row.LongName) + "\n"

        return ChatBlob("Premade Implant", blob)

    @command(command="premade", params=[Any("search")], access_level="all",
             description="Search for implants in the premade implant booths", extended_description="Search can be a profession, implant slot, or modifier (ability/skill)")
    def premade_show_cmd(self, request, search):
        search = search.lower()

        prof = self.util.get_profession(search)
        slot = self.get_slot(search)
        if prof:
            blob = "Search by profession: <highlight>%s<end>\n\n" % prof
            results = self.search_by_profession(prof)
        elif slot:
            blob = "Search by slot: <highlight>%s<end>\n\n" % slot.ShortName
            results = self.search_by_slot(slot.ShortName)
        else:
            blob = "Search by modifier: <highlight>%s<end>\n\n" % search
            results = self.search_by_modifier(search)

        for row in results:
            blob += "<header2>%s<end> %s <highlight>%s<end> %s, %s, %s\n" % (row.profession, row.slot, row.ability, row.shiny, row.bright, row.faded)

        return ChatBlob("Premade Implant Search Results (%d)" % len(results), blob)

    def search_by_profession(self, profession):
        sql = """SELECT
                i.Name AS slot,
                p2.Name AS profession,
                a.Name AS ability,
                CASE WHEN c1.ClusterID = 0 THEN 'N/A' ELSE c1.LongName END AS shiny,
                CASE WHEN c2.ClusterID = 0 THEN 'N/A' ELSE c2.LongName END AS bright,
                CASE WHEN c3.ClusterID = 0 THEN 'N/A' ELSE c3.LongName END AS faded
            FROM premade_implant p
            JOIN ImplantType i ON p.ImplantTypeID = i.ImplantTypeID
            JOIN Profession p2 ON p.ProfessionID = p2.ID
            JOIN Ability a ON p.AbilityID = a.AbilityID
            JOIN Cluster c1 ON p.ShinyClusterID = c1.ClusterID
            JOIN Cluster c2 ON p.BrightClusterID = c2.ClusterID
            JOIN Cluster c3 ON p.FadedClusterID = c3.ClusterID
            WHERE p2.Name = ?
            ORDER BY slot"""

        return self.db.query(sql, [profession])

    def search_by_slot(self, slot):
        sql = """SELECT
                i.Name AS slot,
                p2.Name AS profession,
                a.Name AS ability,
                CASE WHEN c1.ClusterID = 0 THEN 'N/A' ELSE c1.LongName END AS shiny,
                CASE WHEN c2.ClusterID = 0 THEN 'N/A' ELSE c2.LongName END AS bright,
                CASE WHEN c3.ClusterID = 0 THEN 'N/A' ELSE c3.LongName END AS faded
            FROM premade_implant p
            JOIN ImplantType i ON p.ImplantTypeID = i.ImplantTypeID
            JOIN Profession p2 ON p.ProfessionID = p2.ID
            JOIN Ability a ON p.AbilityID = a.AbilityID
            JOIN Cluster c1 ON p.ShinyClusterID = c1.ClusterID
            JOIN Cluster c2 ON p.BrightClusterID = c2.ClusterID
            JOIN Cluster c3 ON p.FadedClusterID = c3.ClusterID
            WHERE i.ShortName = ?
            ORDER BY shiny, bright, faded"""

        return self.db.query(sql, [slot])

    def search_by_modifier(self, modifier):
        sql = """SELECT
                i.Name AS slot,
                p2.Name AS profession,
                a.Name AS ability,
                CASE WHEN c1.ClusterID = 0 THEN 'N/A' ELSE c1.LongName END AS shiny,
                CASE WHEN c2.ClusterID = 0 THEN 'N/A' ELSE c2.LongName END AS bright,
                CASE WHEN c3.ClusterID = 0 THEN 'N/A' ELSE c3.LongName END AS faded
            FROM premade_implant p
            JOIN ImplantType i ON p.ImplantTypeID = i.ImplantTypeID
            JOIN Profession p2 ON p.ProfessionID = p2.ID
            JOIN Ability a ON p.AbilityID = a.AbilityID
            JOIN Cluster c1 ON p.ShinyClusterID = c1.ClusterID
            JOIN Cluster c2 ON p.BrightClusterID = c2.ClusterID
            JOIN Cluster c3 ON p.FadedClusterID = c3.ClusterID
            WHERE c1.LongName <EXTENDED_LIKE=0> ? OR c2.LongName <EXTENDED_LIKE=1> ? OR c3.LongName <EXTENDED_LIKE=2> ?"""

        return self.db.query(*self.db.handle_extended_like(sql, [modifier, modifier, modifier]))

    def get_slot(self, search):
        return self.db.query_single("SELECT * FROM ImplantType WHERE ShortName LIKE ?", [search])
Exemple #5
0
class BuddyController:
    def __init__(self):
        self.logger = Logger(__name__)

    def inject(self, registry):
        self.bot = registry.get_instance("bot")
        self.character_service = registry.get_instance("character_service")
        self.buddy_service = registry.get_instance("buddy_service")

    @command(command="buddylist",
             params=[],
             access_level="admin",
             description="Show characters on the buddy list")
    def buddylist_cmd(self, request):
        buddy_list = []
        for char_id, buddy in self.buddy_service.get_all_buddies().items():
            char_name = self.character_service.resolve_char_to_name(
                char_id, "Unknown(%d)" % char_id)
            buddy_list.append(
                [char_name, buddy["online"], ",".join(buddy["types"])])

        blob = self.format_buddies(buddy_list)

        return ChatBlob("Buddy List (%d)" % len(buddy_list), blob)

    @command(command="buddylist",
             params=[Const("add"),
                     Character("character"),
                     Any("type")],
             access_level="admin",
             description="Add a character to the buddy list")
    def buddylist_add_cmd(self, request, _, char, buddy_type):
        buddy_type = buddy_type.lower()

        if char.char_id:
            self.buddy_service.add_buddy(char.char_id, buddy_type)
            return "Character <highlight>%s<end> has been added to the buddy list for type <highlight>%s<end>." % (
                char.name, buddy_type)
        else:
            return "Could not find character <highlight>%s<end>." % char.name

    @command(command="buddylist",
             params=[Options(["rem", "remove"]),
                     Const("all")],
             access_level="admin",
             description="Remove all characters from the buddy list")
    def buddylist_remove_all_cmd(self, request, _1, _2):
        count = 0
        for char_id, buddy in self.buddy_service.get_all_buddies().items():
            self.buddy_service.remove_buddy(char_id, None, True)
            count += 1

        return "Removed all <highlight>%d<end> buddies from the buddy list." % count

    @command(command="buddylist",
             params=[
                 Options(["rem", "remove"]),
                 Character("character"),
                 Any("type")
             ],
             access_level="admin",
             description="Remove a character from the buddy list")
    def buddylist_remove_cmd(self, request, _, char, buddy_type):
        buddy_type = buddy_type.lower()

        if char.char_id:
            self.buddy_service.remove_buddy(char.char_id, buddy_type)
            return "Character <highlight>%s<end> has been removed from the buddy list for type <highlight>%s<end>." % (
                char.name, buddy_type)
        else:
            return "Could not find character <highlight>%s<end>." % char.name

    @command(command="buddylist",
             params=[Const("clean")],
             access_level="admin",
             description="Remove all orphaned buddies from the buddy list")
    def buddylist_clean_cmd(self, request, _):
        return "Removed <highlight>%d<end> orphaned buddies from the buddy list." % self.remove_orphaned_buddies(
        )

    @command(command="buddylist",
             params=[Const("search"), Any("character")],
             access_level="admin",
             description="Search for characters on the buddy list")
    def buddylist_search_cmd(self, request, _, search):
        search = search.lower()

        buddy_list = []
        for char_id, buddy in self.buddy_service.get_all_buddies().items():
            char_name = self.character_service.resolve_char_to_name(
                char_id, "Unknown(%d)" % char_id)
            if search in char_name.lower():
                buddy_list.append(
                    [char_name, buddy["online"], ",".join(buddy["types"])])

        blob = self.format_buddies(buddy_list)

        return ChatBlob("Buddy List Search Results (%d)" % len(buddy_list),
                        blob)

    @timerevent(budatime="24h", description="Remove orphaned buddies")
    def remove_orphaned_buddies_event(self, event_type, event_data):
        self.logger.debug("removing %d orphaned buddies" %
                          self.remove_orphaned_buddies())

    def remove_orphaned_buddies(self):
        count = 0
        for char_id, buddy in self.buddy_service.get_all_buddies().items():
            if len(buddy["types"]) == 0:
                self.buddy_service.remove_buddy(char_id, None, True)
                count += 1
        return count

    def format_buddies(self, buddy_list):
        buddy_list = sorted(buddy_list, key=lambda x: x[0])

        blob = ""
        for name, online, types in buddy_list:
            blob += "%s - %s\n" % (name, types)

        return blob
Exemple #6
0
class TrickleController:
    def __init__(self):
        self.ability_first = re.compile(" ([a-z]+) ([0-9]+)")
        self.amount_first = re.compile(" ([0-9]+) ([a-z]+)")

    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 pre_start(self):
        self.db.load_sql_file(self.module_dir + "/" + "trickle.sql")

    @command(command="trickle",
             params=[TrickleParam()],
             access_level="all",
             description="Show skill increases due to trickle")
    def trickle_ability_cmd(self, request, trickle_params):
        abilities_map = {}

        # initialize map with 0s
        for ability in self.util.get_all_abilities():
            abilities_map[ability] = 0

        for p in trickle_params:
            ability = self.util.get_ability(p.ability)
            if not ability:
                return "Unknown ability <highlight>%s</highlight>." % p.ability

            abilities_map[ability] = p.amount

        trickle_amounts = self.get_trickle_amounts(abilities_map)
        return self.format_trickle_output(abilities_map, trickle_amounts)

    @command(command="trickle",
             params=[Any("skill")],
             access_level="all",
             description="Show how much ability is needed to trickle a skill")
    def trickle_skill_cmd(self, request, search):
        data = self.db.query(
            "SELECT * FROM trickle WHERE name <EXTENDED_LIKE=0> ?", [search],
            extended_like=True)
        count = len(data)

        if count == 0:
            return "Could not find any skills for <highlight>%s</highlight>." % search
        elif count == 1:
            row = data[0]
            return self.format_trickle_amounts(row)
        else:
            blob = ""
            for row in data:
                blob += self.format_trickle_amounts(row) + "\n"
            return ChatBlob(
                "Trickle Info for <highlight>%s</highlight>" % search, blob)

    def format_trickle_amounts(self, row):
        msg = "<highlight>%s</highlight> " % row.name
        for ability in self.util.get_all_abilities():
            amount = row["amount_" + ability.lower()]
            if amount > 0:
                value = 4 / amount
                msg += "(%s: %s) " % (ability, round(value, 2))
        return msg

    def get_abilities_map(self, search, is_reversed):
        if is_reversed:
            matches = self.amount_first.findall(search)
        else:
            matches = self.ability_first.findall(search)

        m = {}

        # initialize map with 0s
        for ability in self.util.get_all_abilities():
            m[ability] = 0

        # add values that for abilities that were passed in
        for val in matches:
            if is_reversed:
                ability = self.util.get_ability(val[1])
                amount = int(val[0])
            else:
                ability = self.util.get_ability(val[0])
                amount = int(val[1])
            m[ability] += amount

        return m

    def get_trickle_amounts(self, abilities_map):
        sql = """
            SELECT
                group_name,
                name,
                amount_agility,
                amount_intelligence,
                amount_psychic,
                amount_stamina,
                amount_strength,
                amount_sense,
                (amount_agility * %d
                    + amount_intelligence * %d
                    + amount_psychic * %d
                    + amount_stamina * %d
                    + amount_strength * %d
                    + amount_sense * %d) AS amount
            FROM
                trickle
            GROUP BY
                id,
                group_name,
                name,
                amount_agility,
                amount_intelligence,
                amount_psychic,
                amount_stamina,
                amount_strength,
                amount_sense
            HAVING
                amount > 0
            ORDER BY
                id""" % \
              (abilities_map["Agility"], abilities_map["Intelligence"], abilities_map["Psychic"], abilities_map["Stamina"], abilities_map["Strength"], abilities_map["Sense"])

        return self.db.query(sql)

    def format_trickle_output(self, abilities_map, trickle_amounts):
        # create blob
        blob = ""
        group_name = ""
        for row in trickle_amounts:
            if row.group_name != group_name:
                blob += "\n<header2>%s</header2>\n" % row.group_name
                group_name = row.group_name

            blob += "%s <highlight>%g</highlight>\n" % (row.name,
                                                        row.amount / 4)

        # create title
        title = "Trickle Results: " + ", ".join(
            map(lambda x: "%s <highlight>%d</highlight>" % (x[0], x[1]),
                filter(lambda x: x[1] > 0, abilities_map.items())))

        return ChatBlob(title, blob)
class TranslateController:
    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")

    @setting(name="setting_azure_token",
             value="None",
             description="Enter your Azure Translation Token here")
    def setting_azure_token(self):
        return TextSettingType(["None"])

    @setting(name="setting_azure_region",
             value="None",
             description="Enter your Azure Translation Region here")
    def setting_azure_region(self):
        return TextSettingType(["None", "westeurope"])

    @setting(name="setting_translate_language",
             value="en",
             description="Enter your default output language")
    def setting_translate_language(self):
        return TextSettingType(["en"])

    @command(command="translate",
             params=[
                 Options([
                     "en", "de", "fr", "fi", "es", "nl", "nb", "ru", "sv",
                     "el", "da", "et", "it", "hu", "hr", "id", "bs", "ko",
                     "ja", "lt", "lv", "pt", "pl", "tlh-Lat"
                 ]),
                 Any("text")
             ],
             access_level="member",
             description="Translate text to a specific language")
    def translate_to_cmd(self, request, opt_language, query):
        opt_language = opt_language.lower()
        if self.setting_azure_region().get_value() == "None":
            return "Translation is not enabled, no region set"
        if self.setting_azure_token().get_value() == "None":
            return "Translation is not enabled, no token set"
        azure_url = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=%s" % opt_language
        payload_data = [{'Text': query}]
        header_data = {
            'Ocp-Apim-Subscription-Key':
            self.setting_azure_token().get_value(),
            'Ocp-Apim-Subscription-Region':
            self.setting_azure_region().get_value(),
            'Content-Type': 'application/json'
        }
        try:
            r = requests.post(azure_url,
                              headers=header_data,
                              json=payload_data,
                              timeout=2)
            response = json.loads(r.content)
            return "(%s) %s >> (%s) %s" % (
                response[0]['detectedLanguage']['language'], query,
                response[0]['translations'][0]['to'],
                response[0]['translations'][0]['text'])
        except Exception as e:
            self.logger.warning('Exception occured: ' + e)
            self.logger.warning(r.content)
            return "Something went wrong, try again later."

    @command(command="translate",
             params=[Any("text")],
             access_level="member",
             description="Translate text")
    def translate_cmd(self, request, query):
        if self.setting_azure_region().get_value() == "None":
            return "Translation is not enabled, no region set"
        if self.setting_azure_token().get_value() == "None":
            return "Translation is not enabled, no token set"
        azure_url = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=%s" % self.setting_translate_language(
        ).get_value()
        payload_data = [{'Text': query}]
        header_data = {
            'Ocp-Apim-Subscription-Key':
            self.setting_azure_token().get_value(),
            'Ocp-Apim-Subscription-Region':
            self.setting_azure_region().get_value(),
            'Content-Type': 'application/json'
        }
        try:
            r = requests.post(azure_url,
                              headers=header_data,
                              json=payload_data,
                              timeout=2)
            response = json.loads(r.content)
            return "(%s) %s >> (%s) %s" % (
                response[0]['detectedLanguage']['language'], query,
                response[0]['translations'][0]['to'],
                response[0]['translations'][0]['text'])
        except Exception as e:
            self.logger.warning('Exception occured: ' + e)
            self.logger.warning(r.content)
            return "Something went wrong, try again later."
Exemple #8
0
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")
        self.command_alias_service = registry.get_instance(
            "command_alias_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 f"Unknown command channel <highlight>{cmd_channel}</highlight>."

        if not self.has_sufficient_access_level(request.sender.char_id,
                                                command_str, sub_command_str,
                                                cmd_channel):
            return "You do not have the required access level to change this command."

        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 f"Could not find command <highlight>{cmd_name}</highlight> for channel <highlight>{cmd_channel}</highlight>."
        else:
            if cmd_channel == "all":
                return f"Command <highlight>{cmd_name}<end> has been <highlight>{action}d<end> successfully."
            else:
                return f"Command <highlight>{cmd_name}<end> for channel <highlight>{cmd_channel}<end> has been <highlight>{action}d<end> successfully."

    @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 f"Unknown command channel <highlight>{cmd_channel}</highlight>."

        if self.access_service.get_access_level_by_label(access_level) is None:
            return f"Unknown access level <highlight>{access_level}</highlight>."

        if not self.has_sufficient_access_level(request.sender.char_id,
                                                command_str, sub_command_str,
                                                cmd_channel):
            return "You do not have the required access level to change this command."

        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 f"Could not find command <highlight>{cmd_name}</highlight> for channel <highlight>{cmd_channel}</highlight>."
        else:
            if cmd_channel == "all":
                return f"Access level <highlight>{access_level}</highlight> for command <highlight>{cmd_name}</highlight> has been set successfully."
            else:
                return f"Access level <highlight>{access_level}</highlight> for command <highlight>{cmd_name}</highlight> " \
                       f"on channel <highlight>{cmd_channel}</highlight> has been set successfully."

    @command(command="config",
             params=[
                 Const("cmd"),
                 Any("cmd_name"),
                 NamedFlagParameters(["show_channels"])
             ],
             access_level="admin",
             description="Show command configuration")
    def config_cmd_show_cmd(self, request, _, cmd_name, flag_params):
        cmd_name = cmd_name.lower()

        alias = self.command_alias_service.check_for_alias(cmd_name)
        if alias:
            cmd_name = alias

        command_str, sub_command_str = self.command_service.get_command_key_parts(
            cmd_name)

        cmd_channel_configs = self.get_command_channel_config(
            command_str, sub_command_str)

        if not cmd_channel_configs:
            return f"Could not find command <highlight>{cmd_name}</highlight>."

        blob = ""
        if flag_params.show_channels or not self.are_command_channel_configs_same(
                cmd_channel_configs):
            blob += self.format_cmd_channel_configs_separate_channels(
                cmd_name, cmd_channel_configs)
        else:
            blob += self.format_cmd_channel_configs_consolidated(
                cmd_name, cmd_channel_configs)

        sub_commands = self.get_sub_commands(command_str, sub_command_str)
        if sub_commands:
            blob += "<header2>Subcommands</header2>\n"
            for row in sub_commands:
                command_name = self.command_service.get_command_key(
                    row.command, row.sub_command)
                blob += self.text.make_tellcmd(
                    command_name, f"config cmd {command_name}") + "\n\n"

        # 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)

    def get_sub_commands(self, command_str, sub_command_str):
        return self.db.query(
            "SELECT DISTINCT command, sub_command FROM command_config WHERE command = ? AND sub_command != ?",
            [command_str, sub_command_str])

    def get_command_channel_config(self, command_str, sub_command_str):
        result = []
        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:
                result.append(
                    DictObject({
                        "channel": command_channel,
                        "channel_label": channel_label,
                        "cmd_config": cmd_configs[0]
                    }))

        return result

    def are_command_channel_configs_same(self, cmd_channel_configs):
        if len(cmd_channel_configs) < 2:
            return True

        access_level = cmd_channel_configs[0].cmd_config.access_level
        enabled = cmd_channel_configs[0].cmd_config.enabled
        for cmd_channel_config in cmd_channel_configs[1:]:
            if cmd_channel_config.cmd_config.access_level != access_level or cmd_channel_config.cmd_config.enabled != enabled:
                return False

        return True

    def format_cmd_channel_configs_separate_channels(self, cmd_name,
                                                     cmd_channel_configs):
        blob = ""
        for obj in cmd_channel_configs:
            cmd_config = obj.cmd_config

            blob += "<header2>%s</header2> " % obj.channel_label
            blob += self.format_cmd_config(cmd_name, cmd_config.enabled,
                                           cmd_config.access_level,
                                           obj.channel)

        return blob

    def format_cmd_channel_configs_consolidated(self, cmd_name,
                                                cmd_channel_configs):
        cmd_config = cmd_channel_configs[0].cmd_config
        channel = "all"

        blob = ""
        blob += self.format_cmd_config(cmd_name, cmd_config.enabled,
                                       cmd_config.access_level, channel)
        blob += self.text.make_tellcmd(
            "Configure command channels individually",
            f"config cmd {cmd_name} --show_channels")
        blob += "\n\n"

        return blob

    def format_cmd_config(self, cmd_name, enabled, access_level, channel):
        blob = ""
        status = "<green>Enabled</green>" if enabled == 1 else "<red>Disabled</red>"

        blob += "%s (%s: %s)\n" % (status, "Access Level",
                                   access_level.capitalize())

        # show status config
        blob += "Status:"
        enable_link = self.text.make_tellcmd(
            "Enable", f"config cmd {cmd_name} enable {channel}")
        disable_link = self.text.make_tellcmd(
            "Disable", f"config cmd {cmd_name} disable {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_tellcmd(
                label.capitalize(),
                f"config cmd {cmd_name} access_level {channel} {label}")
            blob += "  " + link
        blob += "\n\n"

        return blob

    def has_sufficient_access_level(self, char_id, command_str,
                                    sub_command_str, channel):
        access_level = self.access_service.get_access_level(char_id)

        params = [command_str, sub_command_str]
        sql = "SELECT access_level FROM command_config WHERE command = ? AND sub_command = ?"
        if channel != "all":
            sql += " AND channel = ?"
            params.append(channel)

        data = self.db.query(sql, params)
        for row in data:
            if self.access_service.compare_access_levels(
                    row.access_level, access_level["label"]) > 0:
                return False

        return True
Exemple #9
0
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 \n" % (
                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",
             sub_command="modify")
    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",
             sub_command="modify")
    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 MessageHubController:
    def inject(self, registry):
        self.bot = registry.get_instance("bot")
        self.db = registry.get_instance("db")
        self.text = registry.get_instance("text")
        self.message_hub_service = registry.get_instance("message_hub_service")
        self.getresp = partial(
            registry.get_instance("translation_service").get_response,
            "module/system")

    def start(self):
        pass

    @command(command="messagehub",
             params=[],
             access_level="admin",
             description="Show the current message hub subscriptions")
    def messagehub_cmd(self, request):
        blob = self.getresp("messagehub_info") + "\n"
        subscriptions = self.message_hub_service.hub
        for destination, obj in subscriptions.items():
            edit_subs_link = self.text.make_tellcmd(
                destination, "messagehub edit %s" % destination)
            blob += "\n%s\n" % edit_subs_link
            for source in obj.sources:
                blob += " └ %s\n" % source

        return ChatBlob(
            self.getresp("messagehub_title", {"count": len(subscriptions)}),
            blob)

    @command(command="messagehub",
             params=[Const("edit"), Any("destination")],
             access_level="admin",
             description="Edit subscriptions for a destination")
    def messagehub_edit_cmd(self, request, _, destination):
        obj = self.message_hub_service.hub[destination]
        if not obj:
            return self.getresp("destination_not_exist",
                                {"destination": destination})

        blob = ""
        count = 0
        for source in self.message_hub_service.sources:
            if source in obj.invalid_sources:
                continue

            sub_link = self.text.make_tellcmd(
                "Subscribe",
                "messagehub subscribe %s %s" % (destination, source))
            unsub_link = self.text.make_tellcmd(
                "Unsubscribe",
                "messagehub unsubscribe %s %s" % (destination, source))
            status = ""
            if source in obj.sources:
                count += 1
                status = "<green>%s</green>" % self.getresp("subscribed")
            blob += "%s [%s] [%s] %s\n\n" % (source, sub_link, unsub_link,
                                             status)

        return ChatBlob(
            self.getresp("messagehub_edit_title", {
                "destination": destination.capitalize(),
                "count": count
            }), blob)

    @command(command="messagehub",
             params=[Const("subscribe"),
                     Any("destination"),
                     Any("source")],
             access_level="admin",
             description="Subscribe a destination to a source")
    def messagehub_subscribe_cmd(self, request, _, destination, source):
        obj = self.message_hub_service.hub[destination]
        if not obj:
            return self.getresp("module/system", "destination_not_exist",
                                {"destination": destination})

        if source in obj.sources:
            return self.getresp("messagehub_already_subscribed", {
                "destination": destination,
                "source": source
            })

        if source in obj.invalid_sources:
            return self.getresp("messagehub_invalid_subscription", {
                "destination": destination,
                "source": source
            })

        self.message_hub_service.subscribe_to_source(destination, source)
        return self.getresp("messagehub_subscribe_success", {
            "destination": destination,
            "source": source
        })

    @command(command="messagehub",
             params=[Const("unsubscribe"),
                     Any("destination"),
                     Any("source")],
             access_level="admin",
             description="Unsubscribe a destination to a source")
    def messagehub_unsubscribe_cmd(self, request, _, destination, source):
        obj = self.message_hub_service.hub[destination]
        if not obj:
            return self.getresp("module/system", "destination_not_exist",
                                {"destination": destination})

        if source not in obj.sources:
            return self.getresp("messagehub_not_subscribed", {
                "destination": destination,
                "source": source
            })

        self.message_hub_service.unsubscribe_from_source(destination, source)
        return self.getresp("messagehub_unsubscribe_success", {
            "destination": destination,
            "source": source
        })
class PlayfieldController:
    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 start(self):
        self.command_alias_service.add_alias("playfields", "playfield")

    @command(command="playfield",
             params=[Const("all", is_optional=True)],
             access_level="all",
             description="Show a list of playfields")
    def playfield_list_command(self, request, all):
        if all:
            data = self.db.query("SELECT * FROM playfields ORDER BY long_name")
        else:
            data = self.db.query(
                "SELECT * FROM playfields WHERE short_name != '' ORDER BY long_name"
            )

        blob = ""
        for row in data:
            blob += "[<highlight>%d<end>] %s (%s)\n" % (row.id, row.long_name,
                                                        row.short_name)

        return ChatBlob("Playfields", blob)

    @command(
        command="waypoint",
        params=[
            Regex(
                "waypoint_data",
                "\s+.*?Pos: ([0-9.]+), ([0-9.]+), ([0-9.]+), Area: ([a-zA-Z ]+).*",
                num_groups=4)
        ],
        access_level="all",
        description="Create a waypoint link from F9 output",
        extended_description=
        "Example: <symbol>waypoint Pos: 123.1, 456.1, 789.1, Area: Perpetual Wastelands"
    )
    def waypoint1_command(self, request, regex):
        x_coords, y_coords, _, playfield_arg = regex

        return self.create_waypoint_blob(x_coords, y_coords, playfield_arg)

    @command(
        command="waypoint",
        params=[
            Regex("waypoint_data",
                  "\s+.*?([0-9.]+) ([0-9.]+) y ([0-9.]+) ([0-9]+).*",
                  num_groups=4)
        ],
        access_level="all",
        description="Create a waypoint link from Shift + F9 output",
        extended_description="Example: <symbol>waypoint 123.1 456.1 y 789.1 570"
    )
    def waypoint2_command(self, request, regex):
        x_coords, y_coords, _, playfield_arg = regex

        return self.create_waypoint_blob(x_coords, y_coords, playfield_arg)

    @command(command="waypoint",
             params=[Int("x_coords"),
                     Int("y_coords"),
                     Any("playfield")],
             access_level="all",
             description="Manually create a waypoint link",
             extended_description="Example: !waypoint 123 456 PW")
    def waypoint3_command(self, request, x_coords, y_coords, playfield_arg):
        return self.create_waypoint_blob(x_coords, y_coords, playfield_arg)

    def create_waypoint_blob(self, x_coords, y_coords, playfield_arg):
        x_coords = int(float(x_coords))
        y_coords = int(float(y_coords))
        playfield = self.get_playfield_by_name(
            playfield_arg) or self.get_playfield_by_id(playfield_arg)

        if not playfield:
            return "Could not find playfield <highlight>%s<end>." % playfield_arg
        else:
            title = "waypoint: %sx%s %s" % (x_coords, y_coords,
                                            playfield.long_name)
            blob = "Zone: %s (%s)\n" % (playfield.long_name, playfield.id)
            blob += "Coords: %s x %s\n\n" % (x_coords, y_coords)
            blob += "<center>%s\n" % self.text.make_chatcmd(
                self.text.make_image(11336), "/waypoint %s %s %d" %
                (x_coords, y_coords, playfield.id))
            blob += "%s" % self.text.make_chatcmd(
                "Click for waypoint", "/waypoint %s %s %d" %
                (x_coords, y_coords, playfield.id))

            blob += "\n\n%s\n" % self.text.make_chatcmd(
                self.text.make_image(275054),
                "/start https://yeets.org/map/#%s,%s,%s,8" %
                (x_coords, y_coords, playfield.id))
            blob += "%s</center>" % self.text.make_chatcmd(
                "View interactive map",
                "/start https://yeets.org/map/#%s,%s,%s,8" %
                (x_coords, y_coords, playfield.id))
            return ChatBlob(title, blob)

    def get_playfield_by_name(self, name):
        return self.db.query_single(
            "SELECT * FROM playfields WHERE long_name LIKE ? OR short_name LIKE ? LIMIT 1",
            [name, name])

    def get_playfield_by_id(self, playfield_id):
        return self.db.query_single("SELECT * FROM playfields WHERE id = ?",
                                    [playfield_id])
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"), NamedParameters(["event_type"])], access_level="admin",
             description="List all events")
    def config_eventlist_cmd(self, request, _, named_params):
        params = []
        sql = "SELECT module, event_type, event_sub_type, handler, description, enabled, is_hidden FROM event_config"
        #sql += " WHERE is_hidden = 0"
        if named_params.event_type:
            sql += " WHERE event_type = ?"
            params.append(named_params.event_type)
        sql += " ORDER BY module, is_hidden, event_type, event_sub_type, handler"
        data = self.db.query(sql, params)

        blob = "Asterisk (*) denotes a hidden event. Only change these events if you understand the implications.\n"
        current_module = ""
        for row in data:
            if current_module != row.module:
                blob += "\n<pagebreak><header2>%s</header2>\n" % row.module
                current_module = row.module

            event_type_key = self.format_event_type(row)

            on_link = self.text.make_tellcmd("On", "config event %s %s enable" % (event_type_key, row.handler))
            off_link = self.text.make_tellcmd("Off", "config event %s %s disable" % (event_type_key, row.handler))

            if row.is_hidden == 1:
                blob += "*"
            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</green>" if enabled else "<red>D</red>"

    def format_event_type(self, row):
        if row.event_sub_type:
            return row.event_type + ":" + row.event_sub_type
        else:
            return row.event_type
Exemple #13
0
class TimerController:
    def __init__(self):
        self.alerts = [60 * 60, 60 * 15, 60 * 1]

    def inject(self, registry):
        self.bot = registry.get_instance("bot")
        self.db = registry.get_instance("db")
        self.util = registry.get_instance("util")
        self.job_scheduler = registry.get_instance("job_scheduler")
        self.command_alias_service = registry.get_instance(
            "command_alias_service")
        self.access_service = registry.get_instance("access_service")

    def start(self):
        self.db.exec(
            "CREATE TABLE IF NOT EXISTS timer (name VARCHAR(255) NOT NULL, char_id INT NOT NULL, channel VARCHAR(10) NOT NULL, "
            "duration INT NOT NULL, created_at INT NOT NULL, finished_at INT NOT NULL, repeating_every INT NOT NULL, job_id INT NOT NULL)"
        )

        # add scheduled jobs for timers that are already running
        data = self.db.query("SELECT * FROM timer")
        for row in data:
            job_id = self.job_scheduler.scheduled_job(self.timer_alert,
                                                      row.finished_at,
                                                      row.name)
            self.db.exec("UPDATE timer SET job_id = ? WHERE name = ?",
                         [job_id, row.name])

        self.command_alias_service.add_alias("timers", "timer")

    @command(command="timer",
             params=[],
             access_level="all",
             description="Show current timers")
    def timer_list_cmd(self, request):
        t = int(time.time())
        data = self.db.query(
            "SELECT t.*, p.name AS char_name FROM timer t LEFT JOIN player p ON t.char_id = p.char_id ORDER BY t.finished_at ASC"
        )
        blob = ""
        for timer in data:
            repeats = (" (Repeats every %s)" %
                       self.util.time_to_readable(timer.repeating_every)
                       ) if timer.repeating_every > 0 else ""
            blob += "<pagebreak>Name: <highlight>%s</highlight>\n" % timer.name
            blob += "Time left: <highlight>%s</highlight>%s\n" % (
                self.util.time_to_readable(
                    timer.created_at + timer.duration - t,
                    max_levels=None), repeats)
            blob += "Owner: <highlight>%s</highlight>\n\n" % timer.char_name

        return ChatBlob("Timers (%d)" % len(data), blob)

    @command(command="timer",
             params=[
                 Const("add", is_optional=True),
                 TimerTime("time"),
                 Any("name", is_optional=True)
             ],
             access_level="all",
             description="Add a timer")
    def timer_add_cmd(self, request, _, duration, timer_name):
        timer_name = timer_name or self.get_timer_name(request.sender.name)

        if self.get_timer(timer_name):
            return "A timer named <highlight>%s</highlight> is already running." % timer_name

        t = int(time.time())
        self.add_timer(timer_name, request.sender.char_id, request.channel, t,
                       duration)

        return "Timer <highlight>%s</highlight> has been set for %s." % (
            timer_name, self.util.time_to_readable(duration, max_levels=None))

    @command(command="timer",
             params=[Options(["rem", "remove"]),
                     Any("name")],
             access_level="all",
             description="Remove a timer")
    def timer_remove_cmd(self, request, _, timer_name):
        timer = self.get_timer(timer_name)
        if not timer:
            return "There is no timer named <highlight>%s</highlight>." % timer_name

        if self.access_service.has_sufficient_access_level(
                request.sender.char_id, timer.char_id):
            self.remove_timer(timer_name)
            return "Timer <highlight>%s</highlight> has been removed." % timer.name
        else:
            return "Error! Insufficient access level to remove timer <highlight>%s</highlight>." % timer.name

    @command(command="rtimer",
             params=[
                 Const("add", is_optional=True),
                 TimerTime("start_time"),
                 TimerTime("repeating_time"),
                 Any("name", is_optional=True)
             ],
             access_level="all",
             description="Add a timer")
    def rtimer_add_cmd(self, request, _, start_time, repeating_time,
                       timer_name):
        timer_name = timer_name or self.get_timer_name(request.sender.name)
        if repeating_time < 60:
            return "The timer named <highlight>%s</highlight> has not been created, because there is an <highlight>minimum repeating time of 1 minute</highlight>." % timer_name

        if self.get_timer(timer_name):
            return "A timer named <highlight>%s</highlight> is already running." % timer_name

        t = int(time.time())
        self.add_timer(timer_name, request.sender.char_id, request.channel, t,
                       start_time, repeating_time)

        return "Repeating timer <highlight>%s</highlight> will go off in <highlight>%s</highlight> and repeat every <highlight>%s</highlight>." % \
               (timer_name, self.util.time_to_readable(start_time), self.util.time_to_readable(repeating_time))

    def get_timer_name(self, base_name):
        # attempt base name first
        name = base_name

        idx = 1
        while self.get_timer(name):
            idx += 1
            name = base_name + str(idx)

        return name

    def get_timer(self, name):
        return self.db.query_single("SELECT * FROM timer WHERE name LIKE ?",
                                    [name])

    def add_timer(self,
                  timer_name,
                  char_id,
                  channel,
                  t,
                  duration,
                  repeating_time=0):
        alert_duration = self.get_next_alert(duration)
        job_id = self.job_scheduler.scheduled_job(self.timer_alert,
                                                  t + alert_duration,
                                                  timer_name)

        self.db.exec(
            "INSERT INTO timer (name, char_id, channel, duration, created_at, finished_at, repeating_every, job_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
            [
                timer_name, char_id, channel, duration, t, t + duration,
                repeating_time, job_id
            ])

    def remove_timer(self, timer_name):
        timer = self.get_timer(timer_name)
        self.job_scheduler.cancel_job(timer.job_id)

        self.db.exec("DELETE FROM timer WHERE name LIKE ?", [timer_name])

    def get_next_alert(self, duration):
        for alert in self.alerts:
            if duration > alert:
                return duration - alert
        return duration

    def timer_alert(self, t, timer_name):
        timer = self.get_timer(timer_name)

        if timer.finished_at > t:
            msg = "Timer <highlight>%s</highlight> has <highlight>%s</highlight> left." % (
                timer.name, self.util.time_to_readable(timer.finished_at - t))

            alert_duration = self.get_next_alert(timer.finished_at - t)
            job_id = self.job_scheduler.scheduled_job(self.timer_alert,
                                                      t + alert_duration,
                                                      timer.name)

            self.db.exec("UPDATE timer SET job_id = ? WHERE name = ?",
                         [job_id, timer.name])
        else:
            msg = "Timer <highlight>%s</highlight> has gone off." % timer.name

            self.remove_timer(timer.name)

            if timer.repeating_every > 0:
                # skip scheduling jobs in the past to prevent backlog of jobs when bot goes offline
                current_t = int(time.time()) - timer.repeating_every
                new_t = t
                while new_t < current_t:
                    new_t += timer.repeating_every
                self.add_timer(timer.name, timer.char_id, timer.channel, new_t,
                               timer.repeating_every, timer.repeating_every)

        if timer.channel == "org":
            self.bot.send_org_message(msg)
        elif timer.channel == "priv":
            self.bot.send_private_channel_message(msg)
        else:
            self.bot.send_private_message(timer.char_id, msg)
Exemple #14
0
class RandomController:
    def inject(self, registry):
        self.db: DB = registry.get_instance("db")
        self.text: Text = registry.get_instance("text")
        self.util = registry.get_instance("util")
        self.character_service = registry.get_instance("character_service")
        self.command_alias_service = registry.get_instance(
            "command_alias_service")

    def start(self):
        self.command_alias_service.add_alias("verify", "roll verify")
        self.command_alias_service.add_alias("lootorder", "random")

    @command(
        command="random",
        params=[Any("items")],
        access_level="all",
        description="Randomly order a list of elements",
        extended_description="Enter a space-delimited list of items to randomize"
    )
    def random_command(self, request, items):
        items = items.split(" ")
        random.shuffle(items)
        return " ".join(items)

    @command(command="roll",
             params=[Int("start_value", is_optional=True),
                     Int("end_value")],
             access_level="all",
             description="Roll a number between 1 and a number")
    def roll_command(self, request, start_value, end_value):
        start_value = start_value or 1

        if start_value > end_value:
            start_value, end = end_value, start_value

        result = random.randint(start_value, end_value)

        self.db.exec(
            "INSERT INTO roll (char_id, min_value, max_value, result, created_at) VALUES (?, ?, ?, ?, ?)",
            [
                request.sender.char_id, start_value, end_value, result,
                int(time.time())
            ])

        return "Rolling between %d and %d: <highlight>%d<end>. /tell <myname> roll verify %d" % (
            start_value, end_value, result, self.db.last_insert_id())

    @command(command="roll",
             params=[Const("verify"), Int("roll_id")],
             access_level="all",
             description="Verify a roll that happened")
    def roll_verify_command(self, request, _, roll_id):
        row = self.db.query_single("SELECT * FROM roll WHERE id = ?",
                                   [roll_id])

        if not row:
            return "Could not find roll with id <highlight>%d<end>." % roll_id
        else:
            time_string = self.util.time_to_readable(
                int(time.time()) - row.created_at)
            name = self.character_service.resolve_char_to_name(row.char_id)
            return "Rolling between %d and %d: <highlight>%d<end>. %s ago for %s." % (
                row.min_value, row.max_value, row.result, time_string, name)
Exemple #15
0
class InfoController:
    FILE_EXT = ".txt"
    CUSTOM_DATA_DIRECTORY = "./data/info"

    def __init__(self):
        self.paths = []
        self.paths.append(
            os.path.dirname(os.path.realpath(__file__)) + os.sep + "info")
        self.paths.append(self.CUSTOM_DATA_DIRECTORY)

    def inject(self, registry):
        self.text = registry.get_instance("text")
        self.command_alias_service = registry.get_instance(
            "command_alias_service")

    def start(self):
        self.command_alias_service.add_alias("guides", "info")
        self.command_alias_service.add_alias("breed", "info breed")
        self.command_alias_service.add_alias("healdelta", "info healdelta")
        self.command_alias_service.add_alias("nanodelta", "info nanodelta")
        self.command_alias_service.add_alias("lag", "info lag")
        self.command_alias_service.add_alias("light", "info light")
        self.command_alias_service.add_alias("stats", "info stats")
        self.command_alias_service.add_alias("light", "info light")
        self.command_alias_service.add_alias("doja", "info doja")

        pathlib.Path(self.CUSTOM_DATA_DIRECTORY).mkdir(parents=True,
                                                       exist_ok=True)

    @command(command="info",
             params=[],
             access_level="all",
             description="Show the list of info topics")
    def info_list_cmd(self, request):
        topics = self.get_all_topics()

        blob = ""
        for topic in topics:
            blob += self.text.make_chatcmd(
                topic, "/tell <myname> info " + topic) + "\n"

        return ChatBlob("Info Topics (%d)" % len(topics), blob)

    @command(command="info",
             params=[Any("topic")],
             access_level="all",
             description="Show the info topic details")
    def info_show_cmd(self, request, topic_name):
        topic = self.get_topic_info(topic_name)

        if topic:
            return ChatBlob(topic_name.capitalize(), topic)
        else:
            return "Could not find info topic <highlight>%s<end>." % topic_name

    def register_path(self, path):
        self.paths.append(path)

    def get_topic_info(self, name):
        name = name.lower()
        for base in reversed(self.paths):
            file_path = base + os.sep + name + self.FILE_EXT
            try:
                with open(file_path, "r") as f:
                    return f.read()
            except FileNotFoundError:
                pass

        return None

    def get_all_topics(self):
        topics = []
        for base in reversed(self.paths):
            topics += [
                f[:-len(self.FILE_EXT)] for f in os.listdir(base)
                if f.endswith(self.FILE_EXT)
            ]
        return sorted(set(topics))
Exemple #16
0
class LootController:
    def __init__(self):
        self.loot_list = OrderedDict()
        self.last_modify = None

    def inject(self, registry):
        self.bot = registry.get_instance("bot")
        self.db: DB = registry.get_instance("db")
        self.settings_service: SettingService = registry.get_instance("setting_service")
        self.text: Text = registry.get_instance("text")
        self.leader_controller: LeaderController = registry.get_instance("leader_controller")
        self.setting_service: SettingService = registry.get_instance("setting_service")
        self.command_alias_service = registry.get_instance("command_alias_service")

    def start(self):
        self.command_alias_service.add_alias("aries", "pande aries")
        self.command_alias_service.add_alias("aquarius", "pande aquarius")
        self.command_alias_service.add_alias("leo", "pande leo")
        self.command_alias_service.add_alias("virgo", "pande virgo")
        self.command_alias_service.add_alias("cancer", "pande cancer")
        self.command_alias_service.add_alias("gemini", "pande gemini")
        self.command_alias_service.add_alias("libra", "pande libra")
        self.command_alias_service.add_alias("pisces", "pande pisces")
        self.command_alias_service.add_alias("capricorn", "pande capricorn")
        self.command_alias_service.add_alias("scorpio", "pande scorpio")
        self.command_alias_service.add_alias("taurus", "pande taurus")
        self.command_alias_service.add_alias("sagittarius", "pande sagittarius")

        self.command_alias_service.add_alias("s7", "apf s7")
        self.command_alias_service.add_alias("s13", "apf s13")
        self.command_alias_service.add_alias("s28", "apf s28")
        self.command_alias_service.add_alias("s35", "apf s35")

        self.command_alias_service.add_alias("mitaar", "xan mitaar")
        self.command_alias_service.add_alias("12m", "xan 12m")
        self.command_alias_service.add_alias("vortexx", "xan vortexx")

    @setting(name="use_item_icons", value="True", description="Use icons when building loot list")
    def use_item_icons(self):
        return BooleanSettingType()

    @command(command="loot", params=[], description="Show the list of added items", access_level="all")
    def loot_cmd(self, request):
        if not self.loot_list:
            return "Loot list is empty."

        if isinstance(list(self.loot_list.values())[0], AuctionItem):
            return self.get_auction_list()
        if isinstance(list(self.loot_list.values())[0], LootItem):
            return self.get_loot_list()

        return "Error when generating list (loot type is unsupported)."

    @command(command="loot", params=[Const("clear")], description="Clear all loot", access_level="all", sub_command="modify")
    def loot_clear_cmd(self, request, _):
        if not self.leader_controller.can_use_command(request.sender.char_id):
            return LeaderController.NOT_LEADER_MSG

        if self.loot_list:
            self.loot_list.clear()
            self.last_modify = None
            return "Loot list cleared."
        else:
            return "Loot list is already empty."

    @command(command="loot", params=[Const("remitem"), Int("item_index")],
             description="Remove an existing loot item", access_level="all", sub_command="modify")
    def loot_rem_item_cmd(self, request, _, item_index: int):
        if not self.leader_controller.can_use_command(request.sender.char_id):
            return LeaderController.NOT_LEADER_MSG

        if not self.loot_list:
            return "Loot list is empty."

        try:
            if self.loot_list[item_index]:
                self.last_modify = int(time.time())
                return "Removed %s from loot list." % self.loot_list.pop(item_index).item.name
            else:
                return "Item error."
        except KeyError:
            return "Wrong index given."

    @command(command="loot", params=[Const("additem"), Item("item"), Int("item_count", is_optional=True)],
             description="Add an item to loot list", access_level="all", sub_command="modify")
    def loot_add_item_cmd(self, request, _, item, item_count: int):
        if not self.leader_controller.can_use_command(request.sender.char_id):
            return LeaderController.NOT_LEADER_MSG

        if item_count is None:
            item_count = 1

        self.add_item_to_loot(item["low_id"], item["high_id"], item["ql"], item["name"], None, item_count)

        return "%s was added to loot list." % item["name"]

    @command(command="loot", params=[Const("increase"), Int("item_index")], description="Increase item count",
             access_level="all", sub_command="modify")
    def loot_increase_item_cmd(self, request, _, item_index: int):
        if not self.leader_controller.can_use_command(request.sender.char_id):
            return LeaderController.NOT_LEADER_MSG

        if not self.loot_list:
            return "Loot list is empty."

        try:
            loot_item = self.loot_list[item_index]

            if loot_item:
                loot_item.count += 1
                self.last_modify = int(time.time())
                return "Increased item count for %s to %d." % (loot_item.item.name, loot_item.count)
            else:
                return "Item error."
        except KeyError:
            return "Wrong index given."

    @command(command="loot", params=[Const("decrease"), Int("item_index")], description="Decrease item count",
             access_level="all", sub_command="modify")
    def loot_decrease_item_cmd(self, request, _, item_index: int):
        if not self.leader_controller.can_use_command(request.sender.char_id):
            return LeaderController.NOT_LEADER_MSG

        if not self.loot_list:
            return "Loot list is empty."

        try:
            loot_item = self.loot_list[item_index]

            if loot_item:
                loot_item.count = loot_item.count - 1 if loot_item.count > 1 else 1
                self.last_modify = int(time.time())
                return "Decreased item count for %s to %d." % (loot_item.item.name, loot_item.count)
            else:
                return "Item error."
        except KeyError:
            return "Wrong index given."

    @command(command="loot", params=[Const("add"), Int("item_index")], description="Add yourself to item",
             access_level="all")
    def loot_add_to_cmd(self, request, _, item_index: int):
        try:
            loot_item = self.loot_list[item_index]
            old_item = self.is_already_added(request.sender.name)

            if loot_item:
                if old_item is not None:
                    if old_item.item.name == loot_item.item.name:
                        name = "You have" if request.channel == "msg" else request.sender.name
                        return "%s already added to %s." % (name, loot_item.item.name)

                    old_item.bidders.remove(request.sender.name)

                name = "You have" if request.channel == "msg" else request.sender.name
                loot_item.bidders.append(request.sender.name)

                self.last_modify = int(time.time())

                return "%s moved from %s to %s." % (name, old_item.item.name, loot_item.item.name) \
                    if old_item is not None \
                    else "%s added to %s." % (name, loot_item.item.name)
            else:
                return "Item error."

        except KeyError:
            return "Wrong index given."

    @command(command="loot", params=[Const("rem")], description="Remove yourself from item", access_level="all")
    def loot_rem_from_cmd(self, request, _):
        try:
            loot_item = self.is_already_added(request.sender.name)

            if loot_item is not None:
                name = "You were" if request.channel == "msg" else "%s was" % request.sender.name
                loot_item.bidders.remove(request.sender.name)

                self.last_modify = int(time.time())

                return "%s removed from %s." % (name, loot_item.item.name)
            else:
                return "You are not added to any loot."
        except KeyError:
            return "Wrong index given."

    @command(command="loot", params=[Const("roll")], description="Roll all loot", access_level="all", sub_command="modify")
    def loot_roll_cmd(self, request, _):
        if not self.leader_controller.can_use_command(request.sender.char_id):
            return LeaderController.NOT_LEADER_MSG

        if self.loot_list:
            blob = ""

            for i, loot_item in self.loot_list.items():
                winners = []

                if loot_item.bidders:
                    if len(loot_item.bidders) <= loot_item.count:
                        winners = loot_item.bidders.copy()
                        loot_item.count = loot_item.count - len(loot_item.bidders)
                        loot_item.bidders = []
                    else:
                        for j in range(0, loot_item.count):
                            winner = secrets.choice(loot_item.bidders)
                            winners.append(winner)
                            loot_item.bidders.remove(winner)
                            loot_item.count = loot_item.count - 1 if loot_item.count > 0 else 0

                    item = loot_item.item
                    blob += "%s. %s (ql%s)\n" % (i, self.text.make_item(item.low_id, item.high_id,
                                                                        item.ql, item.name), item.ql)
                    blob += " | Winners: <red>%s<end>\n\n" % '<end>, <red>'.join(winners)

            return ChatBlob("Roll results", blob) if len(blob) > 0 else "No one was added to any loot"
        else:
            return "No loot to roll."

    @command(command="loot", params=[Const("reroll")], description="Rebuild loot list", access_level="all", sub_command="modify")
    def loot_reroll_cmd(self, request, _):
        if not self.leader_controller.can_use_command(request.sender.char_id):
            return LeaderController.NOT_LEADER_MSG

        if self.loot_list:
            count = 1
            for key in sorted(list(self.loot_list.keys())):
                if self.loot_list[key].count <= 0:
                    del self.loot_list[key]
                else:
                    loot_item = self.loot_list[key]
                    del self.loot_list[key]
                    self.loot_list[count] = loot_item
                    count += 1

            self.last_modify = int(time.time())

            return "List has been rebuilt." if len(self.loot_list) > 0 else "No items left to roll."
        else:
            return "Loot list is empty."

    @command(command="loot", params=[Const("addraiditem"), Int("item_id"), Int("item_count")],
             description="Add item from pre-defined raid to loot list", access_level="all", sub_command="modify")
    def loot_add_raid_item(self, request, _, item_id: int, item_count: int):
        if not self.leader_controller.can_use_command(request.sender.char_id):
            return LeaderController.NOT_LEADER_MSG

        sql = "SELECT * FROM aodb a LEFT JOIN raid_loot r ON (a.name = r.name AND a.highql >= r.ql) " \
              "WHERE r.id = ? LIMIT 1"
        item = self.db.query_single(sql, [item_id])

        if item:
            self.add_item_to_loot(item.lowid, item.highid, item.ql, item.name, item.comment, item_count)

            return "Added %s to loot list." % item.name
        else:
            return "Failed to add item with ID %s." % item_id

    @command(command="loot", params=[Const("addraid"), Any("raid"), Any("category")],
             description="Add all loot from pre-defined raid", access_level="all", sub_command="modify")
    def loot_add_raid_loot(self, request, _, raid: str, category: str):
        if not self.leader_controller.can_use_command(request.sender.char_id):
            return LeaderController.NOT_LEADER_MSG

        items = self.db.query(
            "SELECT r.raid, r.category, r.id, r.ql, r.name, r.comment, r.multiloot, a.lowid, a.highid, a.icon "
            "FROM raid_loot r "
            "LEFT JOIN aodb a "
            "ON (r.name = a.name AND r.ql <= a.highql) "
            "WHERE r.raid = ? AND r.category = ? "
            "ORDER BY r.name",
            [raid, category]
        )

        if items:
            for item in items:
                self.add_item_to_loot(item.lowid, item.highid, item.ql, item.name, item.comment, item.multiloot)

            return "%s table was added to loot." % category
        else:
            return "%s does not have any items registered in loot table." % category

    @command(command="apf",
             params=[Options(["s7", "s13", "s28", "s35"], is_optional=True)],
             description="Get list of items from APF", access_level="all")
    def apf_cmd(self, _, category):
        if category is None:
            blob = ""
            sql = "SELECT category FROM raid_loot WHERE raid = 'APF' GROUP BY category"
            raids = self.db.query(sql)

            for raid in raids:
                add_loot = self.text.make_chatcmd("Add loot", "/tell <myname> loot addraid APF %s" % raid.category)
                show_loot = self.text.make_chatcmd(
                    "Loot table", "/tell <myname> apf %s" % self.get_real_category_name(raid.category, True))

                sql = "SELECT COUNT(*) AS count FROM raid_loot WHERE category = ?"
                count = self.db.query_single(sql, [raid.category]).count

                blob += "%s - %s items\n" % (raid.category, count)
                blob += " | [%s] [%s]\n\n" % (show_loot, add_loot)

            return ChatBlob("APF loot tables", blob)

        add_all = True if category != "s7" else False
        category = self.get_real_category_name(category)

        items = self.get_items("APF", category)

        if items:
            return ChatBlob("%s loot table" % category, self.build_list(items, "APF", category, add_all))
        else:
            return "No loot registered for <highlight>%s<end>." % category

    @command(command="pande",
             params=[
                 Options(["bweapons", "barmor", "bstars", "aries", "aquarius", "leo",
                          "virgo", "cancer", "gemini", "libra", "pisces", "capricorn",
                          "scorpio", "taurus", "sagittarius"], is_optional=True)
             ],
             description="Get list of items from Pandemonium", access_level="all")
    def pande_cmd(self, _, category_name):
        if category_name is None:
            blob = ""
            sql = "SELECT category FROM raid_loot WHERE raid = 'Pande' GROUP BY category"
            raids = self.db.query(sql)

            for raid in raids:
                show_loot = self.text.make_chatcmd(
                    "Loot table", "/tell <myname> pande %s" % self.get_real_category_name(raid.category, True))

                sql = "SELECT COUNT(*) AS count FROM raid_loot WHERE category = ?"
                count = self.db.query_single(sql, [raid.category]).count

                blob += "%s - %s items\n" % (raid.category, count)
                blob += " | [%s]\n\n" % show_loot

            return ChatBlob("Pandemonium loot tables", blob)

        category = self.get_real_category_name(category_name)

        items = self.get_items("Pande", category)

        if items:
            return ChatBlob("%s loot table" % category, self.build_list(items, "Pande", category))
        else:
            return "No loot registered for <highlight>%s<end>." % category_name

    @command(command="db", params=[Options(["db1", "db2", "db3", "dbarmor", "util"], is_optional=True)],
             description="Get list of items from DustBrigade", access_level="all")
    def db_cmd(self, _, category):
        if category is None:
            blob = ""
            sql = "SELECT category FROM raid_loot WHERE raid = 'DustBrigade' GROUP BY category"
            raids = self.db.query(sql)

            for raid in raids:
                show_loot = self.text.make_chatcmd("Loot table", "/tell <myname> db %s" % self.get_real_category_name(raid.category, True))

                sql = "SELECT COUNT(*) AS count FROM raid_loot WHERE category = ?"
                count = self.db.query_single(sql, [raid.category]).count

                blob += "%s - %s items\n" % (raid.category, count)
                blob += " | [%s]\n\n" % show_loot

            return ChatBlob("DustBrigade loot tables", blob)

        category = self.get_real_category_name(category)

        items = self.get_items("DustBrigade", category)

        if items:
            return ChatBlob("%s loot table" % category, self.build_list(items, "DustBrigade", category))
        else:
            return "No loot registered for <highlight>%s<end>." % category

    @command(command="xan", params=[Options(["mitaar", "12m", "vortexx"], is_optional=True)],
             description="Get list of items from Xan", access_level="all")
    def xan_cmd(self, _, category):
        if category is None:
            blob = ""
            raids = ["Mitaar", "Vortexx", "12Man"]

            for raid in raids:
                show_loot = self.text.make_chatcmd(
                    "Loot table", "/tell <myname> xan %s" % self.get_real_category_name(category, True))

                sql = "SELECT COUNT(*) AS count FROM raid_loot WHERE raid = ?"
                count = self.db.query_single(sql, [raid]).count

                blob += "%s - %s items\n" % (raid, count)
                blob += " | [%s]\n\n" % show_loot

            return ChatBlob("Xan loot tables", blob)

        category = self.get_real_category_name(category)

        blob = ""

        blob += self.build_list(self.get_items(category, "General"), category, "General")

        blob += self.build_list(self.get_items(category, "Symbiants"), category, "Symbiants")

        blob += self.build_list(self.get_items(category, "Spirits"), category, "Spirits")

        if category == "12Man":
            blob += self.build_list(self.get_items(category, "Profession Gems"), category, "Profession Gems")

        return ChatBlob("%s loot table" % category, blob)

    @timerevent(budatime="1h", description="Periodically check when loot list was last modified, and clear it if last modification was done 1+ hours ago")
    def loot_clear_event(self, _1, _2):
        if self.loot_list and self.last_modify:
            if int(time.time()) - self.last_modify > 3600 and self.loot_list:
                self.last_modify = None
                self.loot_list = OrderedDict()
                self.bot.send_org_message("Loot was last modified more than 1 hour ago, list has been cleared.")
                self.bot.send_private_channel_message("Loot was last modified more than 1 hour ago, list has been cleared.")

    def is_already_added(self, name: str):
        for i, loot_item in self.loot_list.items():
            if name in loot_item.bidders:
                return loot_item
        return None

    def add_item_to_loot(self, low_id: int, high_id: int, ql: int, name: str, comment=None, item_count=1):
        end_index = list(self.loot_list.keys())[-1] + 1 if len(self.loot_list) > 0 else 1

        item_name = "%s (%s)" % (name, comment) if comment is not None and comment != "" else name

        item_ref = {
            "low_id": low_id,
            "high_id": high_id,
            "ql": ql,
            "name": item_name
        }

        self.loot_list[end_index] = LootItem(item_ref, None, None, item_count)
        self.last_modify = int(time.time())

    def get_loot_list(self):
        blob = ""

        for i, loot_item in self.loot_list.items():
            item = loot_item.item
            bidders = loot_item.bidders

            increase_link = self.text.make_chatcmd("+", "/tell <myname> loot increase %d" % i)
            decrease_link = self.text.make_chatcmd("-", "/tell <myname> loot decrease %d" % i)

            blob += "%d. %s " % (i, self.text.make_item(item.low_id, item.high_id, item.ql, item.name))
            blob += "x%s [%s|%s]\n" % (loot_item.count, increase_link, decrease_link)

            if len(bidders) > 0:
                blob += " | %s\n" % ', '.join(bidders)
            else:
                blob += " | No bidders\n"

            add_to_loot = self.text.make_chatcmd("Add to", "/tell <myname> loot add %d" % i)
            remove_from_loot = self.text.make_chatcmd("Remove from", "/tell <myname> loot rem")
            remove_item = self.text.make_chatcmd("Remove item", "/tell <myname> loot remitem %d" % i)
            blob += " | [%s] [%s] [%s]\n\n" % (add_to_loot, remove_from_loot, remove_item)

        return ChatBlob("Loot (%d)" % len(self.loot_list), blob)

    def get_auction_list(self):
        blob = ""
        item = None

        for i, loot_item in self.loot_list.items():
            item = loot_item.item
            bidders = loot_item.bidders

            item_ref = self.text.make_item(item.low_id, item.high_id, item.ql, item.name)
            prefix = "" if loot_item.prefix is None else "%s " % loot_item.prefix
            suffix = "" if loot_item.suffix is None else " %s" % loot_item.suffix
            blob += "%d. %s%s%s\n" % (i, prefix, item_ref, suffix)

            if len(bidders) > 0:
                blob += " | <red>%s<end> bidder%s\n" % (len(bidders), "s" if len(bidders) > 1 else "")
            else:
                blob += " | <green>No bidders<end>\n"

            if len(self.loot_list) > 1:
                bid_link = self.text.make_chatcmd("Bid", "/tell <myname> bid item %d" % i)
                blob += " | [%s]\n\n" % bid_link

        if len(self.loot_list) == 1:
            min_bid = self.setting_service.get("minimum_bid").get_value()
            blob += "\n"
            blob += "<header2>Bid info<end>\n" \
                    "To bid for <yellow>%s<end>, you must send a tell to <myname> with\n\n" \
                    "<tab><highlight>/tell <myname> auction bid &lt;amount&gt;<end>\n\n" \
                    "Where you replace &lt;amount&gt; with the amount of points you are welling to bid " \
                    "for the item.\n\nMinimum bid is %d, and you can also use \"all\" as bid, to bid " \
                    "all your available points.\n\n" % (item.name, min_bid)
            if self.setting_service.get("vickrey_auction").get_value():
                blob += "<header2>This is a Vickrey auction<end>\n" \
                        " - In a Vickrey auction, you only get to bid twice on the same item.\n" \
                        " - You won't be notified of the outcome of your bid, as all bids will be anonymous.\n" \
                        " - The highest anonymous bid will win, and pay the second-highest bid.\n" \
                        " - Bids are anonymous, meaning you do not share your bid with others, or write this command " \
                        "in the raid channel.\n - Bidding is done with the command described above. You'll be " \
                        "notified when/if the bid has been accepted."

        return ChatBlob("Auction list (%d)" % len(self.loot_list), blob)

    # Raids available in AO:
    # s7, s10, s13, s28, s35, s42, aquarius, virgo, sagittarius, beastweapons, beastarmor,
    # beaststars, tnh, aries, leo, cancer, gemini, libra, pisces, taurus,
    # capricorn, scorpio, tara, vortexx, mitaar, 12m, db1, db2, db3, poh,
    # biodome, manex (collector), hollow islands, mercenaries

    def build_list(self, items, raid=None, category=None, add_all=False):
        blob = ""

        if add_all:
            blob += "%s items to loot list\n\n" % self.text.make_chatcmd(
                "Add all", "/tell <myname> loot addraid %s %s" % (raid, category))

        blob += "<header2>%s<end>\n" % category if category is not None else ""

        for item in items:
            if item.multiloot > 1:
                single_link = self.text.make_chatcmd("Add x1", "/tell <myname> loot addraiditem %s 1" % item.id)
                multi_link = self.text.make_chatcmd(
                    "Add x%d" % item.multiloot, "/tell <myname> loot addraiditem %s %d" % (item.id, item.multiloot))
                add_links = "[%s] [%s]" % (single_link, multi_link)
            else:
                add_links = "[%s]" % self.text.make_chatcmd("Add x1", "/tell <myname> loot addraiditem %s 1" % item.id)

            comment = " (%s)" % item.comment if item.comment != "" else ""

            if self.settings_service.get("use_item_icons").get_value():
                item_link = self.text.make_item(item.lowid, item.highid, item.ql, "<img src=rdb://%s>" % item.icon)
                blob += "%s\n%s%s\n | %s\n\n" % (item_link, item.name, comment, add_links)
            else:
                item_link = self.text.make_item(item.lowid, item.highid, item.ql, item.name)
                blob += "%s%s\n | %s\n\n" % (item_link, comment, add_links)

        return blob

    def get_items(self, raid, category):
        return self.db.query(
            "SELECT r.raid, r.category, r.id, r.ql, r.name, r.comment, r.multiloot, a.lowid, a.highid, a.icon "
            "FROM raid_loot r "
            "LEFT JOIN aodb a "
            "ON (r.name = a.name AND r.ql <= a.highql) "
            "WHERE r.raid = ? AND r.category = ? "
            "ORDER BY r.name",
            [raid, category]
        )

    def get_real_category_name(self, category, reverse=False):
        real_names = {
            "s7": "Sector 7", "s13": "Sector 13", "s28": "Sector 28", "s35": "Sector 35",
            "s42": "Sector 42", "db": "DustBrigade", "aquarius": "Aquarius", "sagittarius": "Sagittarius",
            "taurus": "Taurus", "libra": "Libra", "capricorn": "Capricorn",
            "gemini": "Gemini", "virgo": "Virgo", "cancer": "Cancer", "pisces": "Pisces",
            "scorpio": "Scorpio", "aries": "Aries", "leo": "Leo", "tnh": "The Night Heart",
            "barmor": "Beast Armor", "bweapons": "Beast Weapons", "bstars": "Stars",
            "alba": "Albtraum", "symbs": "Symbiants", "spirits": "Spirits",
            "pgems": "Profession Gems", "gen": "General", "db1": "DB1", "db2": "DB2",
            "db3": "DB3", "ncu": "HUD/NCU", "gaunt": "Bastion", "mitaar": "Mitaar",
            "12m": "12Man", "vortexx": "Vortexx", "dbarmor": "DB Armor", "util": "Util"
        }

        if reverse:
            return next((name for name, real_name in real_names.items() if real_name == category), None)
        else:
            return real_names[category] if category in list(real_names.keys()) else None
Exemple #17
0
class NewsController:
    def __init__(self):
        self.logger = Logger(__name__)

    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.setting_service: SettingService = registry.get_instance("setting_service")
        self.util: Util = registry.get_instance("util")

    @setting(name="number_news_shown", value="10", description="Maximum number of news items shown")
    def number_news_shown(self):
        return NumberSettingType()

    @setting(name="unread_color", value="#ffff00", description="Color for unread news text")
    def unread_color(self):
        return ColorSettingType()

    @setting(name="sticky_color", value="#ffff00", description="Color for sticky news text")
    def sticky_color(self):
        return ColorSettingType()

    @setting(name="news_color", value="#ffffff", description="Color for news text")
    def news_color(self):
        return ColorSettingType()

    @command(command="news", params=[], description="Show list of news", access_level="member")
    def news_cmd(self, request):
        row = self.db.query_single("SELECT created_at FROM news WHERE deleted_at = 0 ORDER BY created_at DESC LIMIT 1")
        if row:
            return ChatBlob("News [Last updated %s]" % self.util.format_datetime(row.created_at), self.build_news_list())
        else:
            return "No news."
    
    @command(command="news", params=[Const("add"), Any("news")], description="Add news entry", access_level="moderator", sub_command="update")
    def news_add_cmd(self, request, _, news):
        sql = "INSERT INTO news (char_id, news, sticky, created_at, deleted_at) VALUES (?,?,?,?,?)"
        success = self.db.exec(sql, [request.sender.char_id, news, 0, int(time.time()), 0])

        if success > 0:
            return "Successfully added news entry with ID <highlight>%d<end>." % self.db.last_insert_id()
        else:
            return "Failed to add news entry."

    @command(command="news", params=[Const("rem"), Int("news_id")], description="Remove a news entry", access_level="moderator", sub_command="update")
    def news_rem_cmd(self, request, _, news_id):
        sql = "UPDATE news SET deleted_at = ? WHERE id = ? AND deleted_at = 0"
        success = self.db.exec(sql, [int(time.time()), news_id])

        if success > 0:
            return "Successfully deleted news entry with ID <highlight>%d<end>." % news_id
        else:
            return "Could not find news entry with ID <highlight>%d<end>." % news_id

    @command(command="news", params=[Const("sticky"), Int("news_id")], description="Sticky a news entry", access_level="moderator", sub_command="update")
    def news_sticky_cmd(self, request, _, news_id):
        sql = "UPDATE news SET sticky = 1 WHERE id = ? AND deleted_at = 0"
        success = self.db.exec(sql, [news_id])

        if success > 0:
            return "Successfully updated news entry with ID <highlight>%d<end> to a sticky." % news_id
        else:
            return "Could not find news entry with ID <highlight>%d<end>." % news_id

    @command(command="news", params=[Const("unsticky"), Int("news_id")], description="Unsticky a news entry", access_level="moderator", sub_command="update")
    def news_unsticky_cmd(self, request, _, news_id):
        sql = "UPDATE news SET sticky = 0 WHERE id = ?"
        success = self.db.exec(sql, [news_id])

        if success > 0:
            return "Successfully removed news entry with ID <highlight>%d<end> as a sticky." % news_id
        else:
            return "Could not find news entry with ID <highlight>%d<end>." % news_id

    @command(command="news", params=[Const("markasread"), Int("news_id")], description="Mark a news entry as read", access_level="member")
    def news_markasread_cmd(self, request, _, news_id):
        if not self.get_news_entry(news_id):
            return "Could not find news entry with ID <highlight>%d<end>." % news_id

        sql = "INSERT INTO news_read (char_id, news_id) VALUES (?,?)"
        self.db.exec(sql, [request.sender.char_id, news_id])

        return "Successfully marked news entry with ID <highlight>%d<end> as read." % news_id

    @command(command="news", params=[Const("markasread"), Const("all")], description="Mark all news entries as read", access_level="member")
    def news_markasread_all_cmd(self, request, _1, _2):
        sql = "INSERT INTO news_read (char_id, news_id) SELECT ?, n.id FROM news n WHERE n.id NOT IN ( " \
              "SELECT r.news_id FROM news_read r WHERE char_id = ? ) AND n.deleted_at = 0 "

        num_rows = self.db.exec(sql, [request.sender.char_id, request.sender.char_id])

        return "Successfully marked <highlight>%d<end> news entries as read." % num_rows
    
    @event(event_type=OrgMemberController.ORG_MEMBER_LOGON_EVENT, description="Send news list when org member logs on")
    def orgmember_logon_event(self, event_type, event_data):
        if not self.bot.is_ready():
            return

        unread_news = self.has_unread_news(event_data.char_id)

        if unread_news is None:
            # No news at all
            return
        elif not unread_news:
            # No new unread entries
            return

        news = self.build_news_list(False, event_data.char_id)
        
        if news:
            self.bot.send_private_message(event_data.char_id, ChatBlob("News", news))

    @event(event_type=PrivateChannelService.JOINED_PRIVATE_CHANNEL_EVENT, description="Send news list when someone joins private channel")
    def priv_logon_event(self, event_type, event_data):
        unread_news = self.has_unread_news(event_data.char_id)

        if unread_news is None:
            # No news at all
            return
        elif not unread_news:
            # No new unread entries
            return
        
        news = self.build_news_list(False, event_data.char_id)
        
        if news:
            self.bot.send_private_message(event_data.char_id, ChatBlob("News", news))

    def build_news_list(self, include_read=True, char_id=None):
        blob = ""

        if not include_read and char_id is not None:
            blob += self.get_unread_news(char_id)
        else:
            stickies = self.get_sticky_news()
            news = self.get_news()

            if stickies:
                blob += "<header2>Stickies<end>\n"
                blob += stickies
                blob += "____________________________\n\n"

            blob += news or "No news"

        return blob if len(blob) > 0 else None

    def has_unread_news(self, char_id):
        sql = "SELECT COUNT(*) as count FROM news n WHERE n.id NOT IN ( SELECT r.news_id FROM news_read r WHERE r.char_id = ? ) AND n.deleted_at = 0"
        news_unread_count = self.db.query_single(sql, [char_id]).count

        if news_unread_count < 1:
            sql = "SELECT COUNT(*) as count FROM news n WHERE n.deleted_at = 0"
            news_count = self.db.query_single(sql).count

            if news_count < 1:
                return None

        return news_unread_count > 0

    def get_unread_news(self, char_id):
        number_news_shown = self.setting_service.get("number_news_shown").get_value()
        sql = "SELECT n.*, p.name AS author " \
              "FROM news n " \
              "LEFT JOIN alts a ON n.char_id = a.char_id " \
              "LEFT JOIN alts a2 ON (a.group_id = a2.group_id AND a2.status = ?) " \
              "LEFT JOIN player p ON p.char_id = COALESCE(a2.char_id, n.char_id) " \
              "WHERE n.id NOT IN ( SELECT r.news_id FROM news_read r WHERE char_id = ? ) " \
              "AND n.deleted_at = 0 ORDER BY n.created_at ASC LIMIT ?"
        news = self.db.query(sql, [AltsService.MAIN, char_id, number_news_shown])

        blob = "%s\n\n" % self.text.make_chatcmd("Mark as all read", "/tell <myname> news markasread all")

        if news:
            unread_color = self.setting_service.get("unread_color").get_font_color()
            for item in news:
                read_link = self.text.make_chatcmd("Mark as read", "/tell <myname> news markasread %s" % item.id)
                timestamp = self.util.format_datetime(item.created_at)

                blob += "%s%s<end>\n" % (unread_color, item.news)
                blob += "By %s [%s] [%s] ID %d\n\n" % (item.author, timestamp, read_link, item.id)

            return blob

        return None

    def get_sticky_news(self):
        sql = "SELECT n.*, p.name AS author " \
              "FROM news n " \
              "LEFT JOIN alts a ON n.char_id = a.char_id " \
              "LEFT JOIN alts a2 ON (a.group_id = a2.group_id AND a2.status = ?) " \
              "LEFT JOIN player p ON p.char_id = COALESCE(a2.char_id, n.char_id) " \
              "WHERE n.deleted_at = 0 AND n.sticky = 1 ORDER BY n.created_at DESC"
        news = self.db.query(sql, [AltsService.MAIN])

        blob = ""

        if news:
            sticky_color = self.setting_service.get("sticky_color").get_font_color()
            for item in news:
                # remove_link = self.text.make_chatcmd("Remove", "/tell <myname> news rem %s" % item.id)
                # sticky_link = self.text.make_chatcmd("Unsticky", "/tell <myname> news unsticky %s" % item.id)
                timestamp = self.util.format_datetime(item.created_at)

                blob += "%s%s<end>\n" % (sticky_color, item.news)
                blob += "By %s [%s] ID %d\n\n" % (item.author, timestamp, item.id)

            return blob

        return None

    def get_news(self):
        number_news_shown = self.setting_service.get("number_news_shown").get_value()
        sql = "SELECT n.*, p.name AS author " \
              "FROM news n " \
              "LEFT JOIN alts a ON n.char_id = a.char_id " \
              "LEFT JOIN alts a2 ON (a.group_id = a2.group_id AND a2.status = ?) " \
              "LEFT JOIN player p ON p.char_id = COALESCE(a2.char_id, n.char_id) " \
              "WHERE n.deleted_at = 0 AND n.sticky = 0 ORDER BY n.created_at DESC LIMIT ?"
        news = self.db.query(sql, [AltsService.MAIN, number_news_shown])

        blob = ""

        if news:
            news_color = self.setting_service.get("news_color").get_font_color()
            for item in news:
                # remove_link = self.text.make_chatcmd("Remove", "/tell <myname> news rem %s" % item.id)
                # sticky_link = self.text.make_chatcmd("Sticky", "/tell <myname> news sticky %s" % item.id)
                timestamp = self.util.format_datetime(item.created_at)

                blob += "%s%s<end>\n" % (news_color, item.news)
                blob += "By %s [%s] ID %d\n\n" % (item.author, timestamp, item.id)

            return blob

        return None

    def get_news_entry(self, news_id):
        return self.db.query_single("SELECT * FROM news WHERE id = ?", [news_id])
Exemple #18
0
class TowerController:
    TOWER_ATTACK_EVENT = "tower_attack"
    TOWER_VICTORY_EVENT = "tower_victory"

    TOWER_BATTLE_OUTCOME_ID = 42949672962
    ALL_TOWERS_ID = 42949672960

    ATTACK_1 = [
        506, 12753364
    ]  # The %s organization %s just entered a state of war! %s attacked the %s organization %s's tower in %s at location (%d,%d).
    ATTACK_2 = re.compile(
        r"^(.+) just attacked the (clan|neutral|omni) organization (.+)'s tower in (.+) at location \((\d+), (\d+)\).\n$"
    )

    VICTORY_1 = re.compile(
        r"^Notum Wars Update: Victory to the (Clan|Neutral|Omni)s!!!$")
    VICTORY_2 = re.compile(
        r"^The (Clan|Neutral|Omni) organization (.+) attacked the (Clan|Neutral|Omni) (.+) at their base in (.+). The attackers won!!$"
    )
    VICTORY_3 = [
        506, 147506468
    ]  # 'Notum Wars Update: The %s organization %s lost their base in %s.'

    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.event_service: EventService = registry.get_instance(
            "event_service")
        self.pork_service: PorkService = registry.get_instance("pork_service")
        self.playfield_controller: PlayfieldController = registry.get_instance(
            "playfield_controller")
        self.public_channel_service: PublicChannelService = registry.get_instance(
            "public_channel_service")

    def pre_start(self):
        self.event_service.register_event_type(self.TOWER_ATTACK_EVENT)
        self.event_service.register_event_type(self.TOWER_VICTORY_EVENT)
        self.bot.register_packet_handler(
            server_packets.PublicChannelMessage.id,
            self.handle_public_channel_message)

    @command(command="lc",
             params=[],
             access_level="all",
             description=
             "See a list of playfields containing land control tower sites")
    def lc_list_cmd(self, request):
        data = self.db.query(
            "SELECT * FROM playfields WHERE id IN (SELECT DISTINCT playfield_id FROM tower_site) ORDER BY short_name"
        )

        blob = ""
        for row in data:
            blob += "%s <highlight>%s</highlight>\n" % (self.text.make_tellcmd(
                row.long_name, "lc %s" % row.short_name), row.short_name)

        return ChatBlob("Land Control Playfields", blob)

    @command(command="lc",
             params=[Any("playfield"),
                     Int("site_number", is_optional=True)],
             access_level="all",
             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(
            playfield_name)
        if not playfield:
            return "Could not find playfield <highlight>%s</highlight>." % playfield_name

        if site_number:
            data = self.db.query(
                "SELECT t.*, p.short_name, p.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, p.long_name FROM tower_site t JOIN playfields p ON t.playfield_id = p.id WHERE t.playfield_id = ?",
                [playfield.id])

        if not data:
            if site_number:
                return "Could not find tower info for <highlight>%s %d</highlight>." % (
                    playfield.long_name, site_number)
            else:
                return "Could not find tower info for <highlight>%s</highlight>." % playfield.long_name

        blob = ""
        for row in data:
            blob += "<pagebreak>" + self.format_site_info(row) + "\n\n"

        if site_number:
            title = "Tower Info: %s %d" % (playfield.long_name, site_number)
        else:
            title = "Tower Info: %s" % playfield.long_name

        return ChatBlob(title, blob)

    @event(event_type="connect",
           description="Check if All Towers channel is available",
           is_hidden=True)
    def handle_connect_event(self, event_type, event_data):
        if self.public_channel_service.org_id and not self.public_channel_service.get_channel_id(
                "All Towers"):
            self.logger.warning(
                "This bot is a member of an org but does not have access to 'All Towers' channel and therefore will not receive tower attack messages"
            )

    def format_site_info(self, row):
        blob = "Short name: <highlight>%s %d</highlight>\n" % (row.short_name,
                                                               row.site_number)
        blob += "Long name: <highlight>%s, %s</highlight>\n" % (row.site_name,
                                                                row.long_name)
        blob += "Level range: <highlight>%d-%d</highlight>\n" % (row.min_ql,
                                                                 row.max_ql)
        blob += "Coordinates: %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))

        return blob

    def handle_public_channel_message(
            self, conn: Conn, packet: server_packets.PublicChannelMessage):
        if conn.id != "main":
            return

        if packet.channel_id == self.TOWER_BATTLE_OUTCOME_ID:
            victory = self.get_victory_event(packet)

            if victory:
                # self.logger.debug("tower victory packet: %s" % str(packet))

                # lookup playfield
                playfield_name = victory.location.playfield.long_name
                victory.location.playfield = self.playfield_controller.get_playfield_by_name(
                    playfield_name) or DictObject()
                victory.location.playfield.long_name = playfield_name

                self.event_service.fire_event(self.TOWER_VICTORY_EVENT,
                                              victory)
        elif packet.channel_id == self.ALL_TOWERS_ID:
            attack = self.get_attack_event(packet)

            if attack:
                # self.logger.debug("tower attack packet: %s" % str(packet))

                # lookup playfield
                playfield_name = attack.location.playfield.long_name
                attack.location.playfield = self.playfield_controller.get_playfield_by_name(
                    playfield_name) or DictObject()
                attack.location.playfield.long_name = playfield_name

                # lookup attacker
                name = attack.attacker.name
                faction = attack.attacker.faction
                org_name = attack.attacker.org_name
                char_info = self.pork_service.get_character_info(name)
                attack.attacker = char_info or DictObject()
                attack.attacker.name = name
                attack.attacker.faction = faction or attack.attacker.get(
                    "faction", "Unknown")
                attack.attacker.org_name = org_name

                self.event_service.fire_event(self.TOWER_ATTACK_EVENT, attack)

    def get_attack_event(self, packet: server_packets.PublicChannelMessage):
        if packet.extended_message and [
                packet.extended_message.category_id,
                packet.extended_message.instance_id
        ] == self.ATTACK_1:
            params = packet.extended_message.params
            return DictObject({
                "attacker": {
                    "name": params[2],
                    "faction": params[0].capitalize(),
                    "org_name": params[1]
                },
                "defender": {
                    "faction": params[3].capitalize(),
                    "org_name": params[4]
                },
                "location": {
                    "playfield": {
                        "long_name": params[5]
                    },
                    "x_coord": params[6],
                    "y_coord": params[7]
                }
            })
        else:
            match = self.ATTACK_2.match(packet.message)
            if match:
                return DictObject({
                    "attacker": {
                        "name": match.group(1),
                        "faction": "",
                        "org_name": ""
                    },
                    "defender": {
                        "faction": match.group(2).capitalize(),
                        "org_name": match.group(3)
                    },
                    "location": {
                        "playfield": {
                            "long_name": match.group(4)
                        },
                        "x_coord": match.group(5),
                        "y_coord": match.group(6)
                    }
                })

        # Unknown attack
        self.logger.warning("Unknown tower attack: " + str(packet))
        return None

    def get_victory_event(self, packet: server_packets.PublicChannelMessage):
        match = self.VICTORY_1.match(packet.message)
        if match:
            return None

        match = self.VICTORY_2.match(packet.message)
        if match:
            return DictObject({
                "type": "attack",
                "winner": {
                    "faction": match.group(1).capitalize(),
                    "org_name": match.group(2)
                },
                "loser": {
                    "faction": match.group(3).capitalize(),
                    "org_name": match.group(4)
                },
                "location": {
                    "playfield": {
                        "long_name": match.group(5)
                    }
                }
            })

        if packet.extended_message and [
                packet.extended_message.category_id,
                packet.extended_message.instance_id
        ] == self.VICTORY_3:
            params = packet.extended_message.params
            return DictObject({
                # TODO might be terminated or un-orged player
                "type": "terminated",
                "winner": {
                    "faction": params[0].capitalize(),
                    "org_name": params[1]
                },
                "loser": {
                    "faction": params[0].capitalize(),
                    "org_name": params[1]
                },
                "location": {
                    "playfield": {
                        "long_name": params[2]
                    }
                }
            })

        # Unknown victory
        self.logger.warning("Unknown tower victory: " + str(packet))
        return None
Exemple #19
0
class ConfigCommandController:
    def __init__(self):
        pass

    def inject(self, registry):
        self.db: DB = registry.get_instance("db")
        self.text: Text = registry.get_instance("text")
        self.access_manager = registry.get_instance("access_manager")
        self.command_manager = registry.get_instance("command_manager")

    def start(self):
        pass

    @command(command="config", params=[Const("cmd"), Any("cmd_name"), Options(["enable", "disable"]), Any("channel")], access_level="superadmin",
             description="Enable or disable a command")
    def config_cmd_status_cmd(self, channel, sender, reply, args):
        cmd_name = args[1].lower()
        action = args[2].lower()
        cmd_channel = args[3].lower()
        command_str, sub_command_str = self.command_manager.get_command_key_parts(cmd_name)
        enabled = 1 if action == "enable" else 0

        if cmd_channel != "all" and not self.command_manager.is_command_channel(cmd_channel):
            reply("Unknown command channel <highlight>%s<end>." % cmd_channel)
            return

        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:
            reply("Could not find command <highlight>%s<end> for channel <highlight>%s<end>." % (cmd_name, cmd_channel))
        else:
            if cmd_channel == "all":
                reply("Command <highlight>%s<end> has been <highlight>%sd<end> successfully." % (cmd_name, action))
            else:
                reply("Command <highlight>%s<end> for channel <highlight>%s<end> has been <highlight>%sd<end> successfully." % (cmd_name, channel, action))

    @command(command="config", params=[Const("cmd"), Any("cmd_name"), Const("access_level"), Any("channel"), Any("access_level")], access_level="superadmin",
             description="Change access_level for a command")
    def config_cmd_access_level_cmd(self, channel, sender, reply, args):
        cmd_name = args[1].lower()
        cmd_channel = args[3].lower()
        access_level = args[4].lower()
        command_str, sub_command_str = self.command_manager.get_command_key_parts(cmd_name)

        if cmd_channel != "all" and not self.command_manager.is_command_channel(cmd_channel):
            reply("Unknown command channel <highlight>%s<end>." % cmd_channel)
            return

        if self.access_manager.get_access_level_by_label(access_level) is None:
            reply("Unknown access level <highlight>%s<end>." % access_level)
            return

        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:
            reply("Could not find command <highlight>%s<end> for channel <highlight>%s<end>." % (cmd_name, cmd_channel))
        else:
            if cmd_channel == "all":
                reply("Access level <highlight>%s<end> for command <highlight>%s<end> has been set successfully." % (access_level, cmd_name))
            else:
                reply("Access level <highlight>%s<end> for command <highlight>%s<end> on channel <highlight>%s<end> has been set successfully." % (access_level, cmd_name, channel))

    @command(command="config", params=[Const("cmd"), Any("cmd_name")], access_level="superadmin",
             description="Show command configuration")
    def config_cmd_show_cmd(self, channel, sender, reply, args):
        cmd_name = args[1].lower()
        command_str, sub_command_str = self.command_manager.get_command_key_parts(cmd_name)

        blob = ""
        for command_channel, channel_label in self.command_manager.channels.items():
            cmd_configs = self.command_manager.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_manager.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"
            blob += "\n\n"

        if blob:
            # include help text
            blob += "\n\n".join(map(lambda handler: handler["help"], self.command_manager.get_handlers(cmd_name)))

        reply(ChatBlob("Command (%s)" % cmd_name, blob))
class RelayGcrController:
    relay_channel_id = None
    relay_name = None
    org = None

    def __init__(self):
        self.logger = Logger(__name__)

    def inject(self, registry):
        self.bot: Tyrbot = registry.get_instance("bot")
        self.setting_service: SettingService = registry.get_instance("setting_service")
        self.character_service: CharacterService = registry.get_instance("character_service")
        self.public_channel_service: PublicChannelService = registry.get_instance("public_channel_service")
        self.db: DB = registry.get_instance("db")
        self.character_service = registry.get_instance("character_service")
        self.online_controller = registry.get_instance("online_controller")
        self.pork_service = registry.get_instance("pork_service")

    def start(self):
        self.setting_service.register(self.module_name, "relaygcr_type", "private_channel", TextSettingType(["tell", "private_channel"]), "Type of relay")
        self.setting_service.register(self.module_name, "relaygcr_symbol", "@", TextSettingType(["!", "#", "*", "@", "$", "+", "-"]), "Symbol for external relay")
        self.setting_service.register(self.module_name, "relaygcr_symbol_method", "with_symbol", TextSettingType(["Always", "with_symbol", "unless_symbol"]), "When to relay messages")
        self.setting_service.register(self.module_name, "relaygcr_bot", "Relay", TextSettingType(), "Bot for Guildrelay")
        self.setting_service.register(self.module_name, "relaygcr_enabled", False, BooleanSettingType(), "Is the Module Enabled?")
        self.setting_service.register(self.module_name, "relaygcr_color_guild", "#C3C3C3", ColorSettingType(), "Color of messages from relay to guild channel")
        self.setting_service.register(self.module_name, "relaygcr_color_priv", "#C3C3C3", ColorSettingType(), "Color of messages from relay to priv channel")
        self.setting_service.register(self.module_name, "relaygcr_guest", False, BooleanSettingType(), "Relay the Private/Guest Channel")
        self.setting_service.register(self.module_name, "relaygcr_guild_abbreviation", "ORG_TAG", TextSettingType(), "Abbreviation to use for org name")
        self.setting_service.register(self.module_name, "relaygcr_share", False, BooleanSettingType(), "Do we share online lists with relayed partners?")
        self.setting_service.register(self.module_name, "relaygcr_others", "", TextSettingType(allow_empty=True), "Online wanted Bot(s) unspaced list ; separated (example: Bot1;Bot2;Bot3)")

        self.bot.register_packet_handler(server_packets.PrivateChannelInvited.id, self.handle_private_channel_invite)
        self.bot.register_packet_handler(server_packets.PrivateChannelMessage.id, self.handle_private_channel_message)
        self.bot.register_packet_handler(server_packets.PublicChannelMessage.id, self.handle_org_channel_message)

    def handle_private_channel_invite(self, conn, packet: server_packets.PrivateChannelInvited):
        channel_name = self.character_service.get_char_name(packet.private_channel_id)

        if self.setting_service.get_value("relaygcr_enabled") == "0":
            self.logger.info(f"Denied private Channel invite: {channel_name} - Relay Module not active")
        elif self.setting_service.get_value("relaygcr_bot") != channel_name:
            self.logger.info(f"Denied private Channel invite: {channel_name} - not the Relaybot")
        else:
            conn.send_packet(client_packets.PrivateChannelJoin(packet.private_channel_id))
            self.logger.info(f"Joined private channel {channel_name}")
            self.relay_channel_id = packet.private_channel_id
            self.relay_name = channel_name
            self.online_send()
            self.send("!gcrc onlinereq")

    def handle_private_channel_message(self, conn, packet: server_packets.PrivateChannelMessage):
        if self.setting_service.get_value("relaygcr_enabled") == "0":
            return

        if packet.private_channel_id != conn.char_id:
            if conn.char_id == packet.char_id:
                return

            if len(packet.message) < 2:
                return

            channel_name = self.character_service.get_char_name(packet.private_channel_id)
            char_name = self.character_service.get_char_name(packet.char_id)
            message = packet.message.lstrip()

            self.process_incoming_relay_message(channel_name, char_name, message)
        elif packet.private_channel_id == conn.char_id and self.setting_service.get_value("relaygcr_guest") == "1":
            self.process_outgoing_relay_message(packet, conn)

    def handle_org_channel_message(self, conn, packet: server_packets.PublicChannelMessage):
        if self.setting_service.get_value("relaygcr_enabled") == "0":
            return

        if self.public_channel_service.is_org_channel_id(packet.channel_id) and packet.char_id != conn.char_id:
            self.process_outgoing_relay_message(packet, conn)

    @event(event_type="connect", description="Initialize online with relay tell partner", is_enabled=True)
    def connect_event(self, event_type, event_data):
        if self.setting_service.get_value("relaygcr_share") == "1" and self.setting_service.get_value("relaygcr_type") == "tell":
            self.relay_name = self.setting_service.get_value("relaygcr_bot")
            self.online_send()
            self.send("!gcrc onlinereq")

    @event(event_type=PrivateChannelService.JOINED_PRIVATE_CHANNEL_EVENT, description="Send to relay when someone joins private channel", is_enabled=True)
    def private_channel_joined_event(self, event_type, event_data):
        if self.setting_service.get_value("relaygcr_share") == "1":
            name = self.character_service.resolve_char_to_name(event_data.char_id)
            self.send("!gcrc buddy 1 " + name + " pg 0")

    @event(event_type=PrivateChannelService.LEFT_PRIVATE_CHANNEL_EVENT, description="Send to relay when someone joins private channel", is_enabled=True)
    def private_channel_left_event(self, event_type, event_data):
        if self.setting_service.get_value("relaygcr_share") == "1":
            name = self.character_service.resolve_char_to_name(event_data.char_id)
            self.send("!gcrc buddy 0 " + name + " pg 0")

    @event(event_type=OrgMemberController.ORG_MEMBER_LOGON_EVENT, description="Send to relay when org member logs on", is_enabled=True)
    def org_member_logon_event(self, event_type, event_data):
        if self.setting_service.get_value("relaygcr_share") == "1":
            name = self.character_service.resolve_char_to_name(event_data.char_id)
            self.send("!gcrc buddy 1 " + name + " gc 0")

    @event(event_type=OrgMemberController.ORG_MEMBER_LOGOFF_EVENT, description="Send to relay when org member logs off", is_enabled=True)
    def org_member_logoff_event(self, event_type, event_data):
        if self.setting_service.get_value("relaygcr_share") == "1":
            name = self.character_service.resolve_char_to_name(event_data.char_id)
            self.send("!gcrc buddy 0 " + name + " gc 0")

    @command(command="gcr", params=[Any("msg")], description="Incoming gcr tells", access_level="all")
    def gcr_inc_tell(self, request, msg):
        name = self.character_service.resolve_char_to_name(request.sender.char_id)
        self.process_incoming_relay_message(name, name, "!gcr " + msg)

    @command(command="gcrc", params=[Any("msg")], description="Incoming gcrc tells", access_level="all")
    def gcrc_inc_tell(self, request, msg):
        name = self.character_service.resolve_char_to_name(request.sender.char_id)
        self.process_incoming_relay_message(name, name, "!gcrc " + msg)

    def process_incoming_relay_message(self, channel, sender, message):
        if self.setting_service.get_value("relaygcr_enabled") == "0":
            return

        if message[:5] == "!gcrc" and self.setting_service.get_value("relaygcr_share") == "1":
            message = message[6:]
            others = self.setting_service.get_value("relaygcr_others")
            if len(others) > 0:
                bots = others.split(";")
                t = int(time.time())
                for bot in bots:
                    if bot.capitalize() == sender:
                        self.online_controller.register_online_channel(sender)
                        if message[:9] == "onlinereq":
                            self.online_send()
                        elif message[:6] == "online":
                            message = message[7:]
                            onliners = message.split(";")
                            self.db.exec("DELETE FROM online WHERE channel = ?", [sender])
                            for onliner in onliners:
                                info = onliner.split(",")
                                self.add_to_online(sender, info[0], t)
                        elif message[:5] == "buddy":
                            message = message[6:]
                            info = message.split(" ")
                            if info[0] == "0":
                                char_id = self.character_service.resolve_char_to_id(info[1])
                                self.db.exec("DELETE FROM online WHERE char_id = ?", [char_id])
                            elif info[0] == "1":
                                self.add_to_online(sender, info[1], t)
        elif message[:4] == "!gcr":
            message = message[5:]
            message = message.replace("##relay_channel##", "")
            message = re.sub(r'##relay_name##([^:]+):##end##', r'<a href="user://\1">\1</a>:', message)
            message = message.replace("##relay_name##", "")
            colorom = self.setting_service.get_value("relaygcr_color_guild")
            messago = re.sub(r'##relay_message##([^#]+)##end##', r'<font color="{color}">\1</font>'.format(color=colorom), message)
            messago = messago.replace("##end##", "")
            colorpm = self.setting_service.get_value("relaygcr_color_priv")
            messagp = re.sub(r'##relay_message##([^#]+)##end##', r'<font color="{color}">\1</font>'.format(color=colorpm), message)
            messagp = messagp.replace("##end##", "")
            if channel == self.setting_service.get_value("relaygcr_bot"):
                self.bot.send_org_message(messago)
                if self.setting_service.get_value("relaygcr_guest") == "1":
                    self.bot.send_private_channel_message(messagp)

    def process_outgoing_relay_message(self, packet, conn):
        if self.setting_service.get_value("relaygcr_enabled") == "0":
            return

        if packet.char_id == conn.char_id:
            return

        method = self.setting_service.get_value("relaygcr_symbol_method")
        sender = self.character_service.get_char_name(packet.char_id)

        plain_msg = packet.message
        symbol = self.setting_service.get_value("relaygcr_symbol")
        # TODO handle when not set
        org = self.setting_service.get_value("relaygcr_guild_abbreviation")
        msg = None
        if method == "Always":
            msg = "!gcr [##relay_channel##{org}##end##] ##relay_name##{char}:##end## ##relay_message##{msg}##end##".format(org=org, char=sender, msg=plain_msg)
        elif method == "with_symbol":
            if plain_msg[:len(symbol)] == symbol:
                msg = "!gcr [##relay_channel##{org}##end##] ##relay_name##{char}:##end## ##relay_message##{msg}##end##".format(org=org, char=sender, msg=plain_msg[len(symbol):])
        elif method == "unless_symbol":
            if plain_msg[:len(symbol)] != symbol:
                msg = "!gcr [##relay_channel##{org}##end##] ##relay_name##{char}:##end## ##relay_message##{msg}##end##".format(org=org, char=sender, msg=plain_msg)
        self.send(msg)

    def send(self, msg):
        if not msg:
            return

        if self.setting_service.get_value("relaygcr_type") == "private_channel":
            if self.relay_channel_id:
                self.bot.send_private_channel_message(private_channel_id=self.relay_channel_id, msg=msg, add_color=False)
            else:
                self.logger.info("Not a member of a relay, ignoring message")
        else:
            self.bot.send_private_message(char_id=self.character_service.resolve_char_to_id(self.relay_name), msg=msg)

    def online_send(self):
        blob = "!gcrc online "
        sql = "SELECT char_id as id, channel as ch FROM online WHERE (channel = ? OR channel = ?)"
        data = self.db.query(sql, [OnlineController.ORG_CHANNEL, OnlineController.PRIVATE_CHANNEL])
        for char in data:
            name = self.character_service.resolve_char_to_name(char.id)
            if char.ch == "Org":
                blob += name + ",gc,0;"
            else:
                blob += name + ",pg,0;"
        blob = blob[:-1]
        self.send(blob)

    def add_to_online(self, sender, name, t):
        char_id = self.character_service.resolve_char_to_id(name)
        self.pork_service.load_character_info(char_id, name)

        sql = "SELECT 1 FROM online WHERE char_id = ? LIMIT 1"
        check = self.db.query_single(sql, [char_id])
        if not check:
            self.db.exec("INSERT INTO online (char_id, afk_dt, afk_reason, channel, dt) VALUES (?, ?, ?, ?, ?)", [char_id, 0, "", sender, t])
class OfabWeaponsController:
    def inject(self, registry):
        self.db = registry.get_instance("db")
        self.text: Text = registry.get_instance("text")
        self.items_controller = registry.get_instance("items_controller")
        self.command_alias_service = registry.get_instance(
            "command_alias_service")

    def start(self):
        self.command_alias_service.add_alias("ofabweapon", "ofabweapons")

    @command(command="ofabweapons",
             params=[],
             access_level="all",
             description="Show ofab weapons")
    def ofabweapons_list_command(self, request):
        data = self.db.query(
            "SELECT type, name FROM ofab_weapons ORDER BY name ASC")

        blob = ""
        for row in data:
            blob += "<pagebreak>%s - Type %d\n" % (self.text.make_chatcmd(
                row.name,
                "/tell <myname> ofabweapons %s" % row.name), row.type)

        return ChatBlob("Ofab Weapons", blob)

    @command(command="ofabweapons",
             params=[
                 Int("ql", is_optional=True),
                 Any("weapon"),
                 Int("ql", is_optional=True)
             ],
             access_level="all",
             description="Show info about an ofab weapon",
             extended_description=
             "QL is optional and can come before or after the weapon")
    def ofabweapons_show_command(self, request, ql1, weapon_name, ql2):
        weapon_name = weapon_name.capitalize()
        ql = ql1 or ql2 or 300

        weapon = self.db.query_single(
            "SELECT type, vp FROM ofab_weapons w, ofab_weapons_cost c WHERE w.name LIKE ? AND c.ql = ?",
            [weapon_name, ql])

        if not weapon:
            return "Could not find Ofab Weapon <highlight>%s<end> for QL <highlight>%d<end>." % (
                weapon_name, ql)

        type_ql = round(ql * 0.8)
        type_link = self.text.make_chatcmd(
            "Kyr'Ozch Bio-Material - Type %d" % weapon.type,
            "/tell <myname> bioinfo %d %d" % (weapon.type, type_ql))

        blob = "Upgrade with %s (minimum QL %d)\n\n" % (type_link, type_ql)

        data = self.db.query(
            "SELECT ql FROM ofab_weapons_cost ORDER BY ql ASC")
        for row in data:
            blob += self.text.make_chatcmd(
                row.ql, "/tell <myname> ofabweapons %s %d" %
                (weapon_name, row.ql)) + " "
        blob += "\n\n"

        for i in range(1, 7):
            item = self.items_controller.find_by_name("Ofab %s Mk %d" %
                                                      (weapon_name, i))
            blob += "<pagebreak>" + self.text.format_item(item)
            if i == 1:
                blob += "  (<highlight>%d<end> VP)" % weapon.vp
            blob += "\n"

        return ChatBlob("Ofab %s (QL %d)" % (weapon_name, ql), blob)
class DiscordController:
    def __init__(self):
        self.servers = []
        self.channels = {}
        self.ignore = []
        self.dthread = None
        self.dqueue = []
        self.aoqueue = []
        self.logger = Logger(__name__)
        self.client = DiscordWrapper(self.channels, self.servers, self.dqueue,
                                     self.aoqueue)
        self.command_handlers = []

        logging.getLogger("discord").setLevel(logging.INFO)

    def inject(self, registry):
        self.bot = registry.get_instance("bot")
        self.db = registry.get_instance("db")
        self.setting_service = registry.get_instance("setting_service")
        self.event_service = registry.get_instance("event_service")
        self.character_service: CharacterService = registry.get_instance(
            "character_service")
        self.text: Text = registry.get_instance("text")
        self.command_service = registry.get_instance("command_service")
        self.client.register(registry)

    def pre_start(self):
        self.event_service.register_event_type("discord_ready")
        self.event_service.register_event_type("discord_message")
        self.event_service.register_event_type("discord_channels")
        self.event_service.register_event_type("discord_command")
        self.event_service.register_event_type("discord_invites")
        self.event_service.register_event_type("discord_exception")

        channels = self.db.query("SELECT * FROM discord")

        if channels is not None:
            for row in channels:
                a = True if row.relay_ao == 1 else False
                d = True if row.relay_dc == 1 else False
                self.channels[row.channel_id] = DiscordChannel(
                    row.channel_id, row.server_name, row.channel_name, a, d)

    @setting(name="discord_bot_token",
             value="",
             description="Discord bot token")
    def discord_bot_token(self):
        return HiddenSettingType()

    @setting(name="discord_relay_format",
             value="color",
             description="Format of message relayed to Discord")
    def discord_relay_format(self):
        return TextSettingType(options=["embed", "color", "plain"])

    @setting(name="discord_embed_color",
             value="#00FF00",
             description="Discord embedded message color")
    def discord_embed_color(self):
        return ColorSettingType()

    @setting(
        name="relay_to_private",
        value="true",
        description=
        "Global setting for relaying of Discord messages to the private channel"
    )
    def relay_to_private(self):
        return BooleanSettingType()

    @setting(
        name="relay_to_org",
        value="true",
        description=
        "Global setting for relaying of Discord message to the org channel")
    def relay_to_org(self):
        return BooleanSettingType()

    @setting(name="relay_color_prefix",
             value="#FCA712",
             description=
             "Set the prefix color for relayed messages in org/private channel"
             )
    def relay_color_prefix(self):
        return ColorSettingType()

    @setting(
        name="relay_color_name",
        value="#808080",
        description=
        "Set the color of the name in the relayed message in org/private channel"
    )
    def relay_color_name(self):
        return ColorSettingType()

    @setting(
        name="relay_color_message",
        value="#00DE42",
        description=
        "Set the color of the content of the relayed message in org/private channel"
    )
    def relay_color_message(self):
        return ColorSettingType()

    @command(command="discord",
             params=[Const("connect")],
             access_level="moderator",
             sub_command="manage",
             description="Manually connect to Discord")
    def discord_connect_cmd(self, request, _):
        if self.client.is_logged_in:
            return "Already connected to Discord."
        else:
            token = self.setting_service.get("discord_bot_token").get_value()
            if token:
                self.connect_discord_client(token)
                return "Connecting to discord..."
            else:
                return "Cannot connect to discord, no bot token is set."

    @command(command="discord",
             params=[Const("disconnect")],
             access_level="moderator",
             sub_command="manage",
             description="Manually disconnect from Discord")
    def discord_disconnect_cmd(self, request, _):
        pass

    @command(command="discord",
             params=[],
             access_level="member",
             description="See discord info")
    def discord_cmd(self, request):
        counter = 0
        for cid, channel in self.channels.items():
            if channel.relay_ao or channel.relay_dc:
                counter += 1

        blob = "<header2>Info<end>\n"
        blob += "Status: "
        blob += "<green>Connected<end>\n" if self.client.is_logged_in else "<red>disconnected<end>\n"
        blob += "Channels available: <highlight>%d<end>\n\n" % counter

        blob += "<header2>Servers<end>\n"
        if self.servers:
            for server in self.servers:
                invites = self.text.make_chatcmd(
                    "get invite",
                    "/tell <myname> discord getinvite %s" % server.id)
                owner = server.owner.nick if server.owner.nick is not None else "Insufficient permissions"
                blob += "%s [%s]\n" % (server.name, invites)
                blob += " | member count: %s\n" % (str(len(server.members)))
                blob += " | owner: %s\n\n" % owner
        else:
            blob += "None\n\n"

        blob += "<header2>Subscribed channels<end>\n"
        for cid, channel in self.channels.items():
            if channel.relay_ao or channel.relay_dc:
                a = "<green>On<end>" if channel.relay_ao else "<red>Off<end>"
                d = "<green>On<end>" if channel.relay_dc else "<red>Off<end>"
                blob += "<highlight>%s<end> :: <highlight>%s<end>\n" % (
                    channel.server_name, channel.channel_name)
                blob += " | relaying from AO [%s]\n" % a
                blob += " | relaying from Discord [%s]\n" % d

        blob += "\n\nDiscord Module written by <highlight>Vladimirovna<end>"

        return ChatBlob("Discord info", blob)

    @command(command="discord",
             params=[Const("relay")],
             access_level="moderator",
             sub_command="manage",
             description="Setup relaying of channels")
    def discord_relay_setup_cmd(self, request, _):
        logtext = "logout" if self.client.is_logged_in else "login"
        logcmdt = "discord disconnect" if self.client.is_logged_in else "discord connect"
        loglink = self.text.make_chatcmd(logtext,
                                         "/tell <myname> %s" % logcmdt)
        constatus = "<green>Connected<end>" if self.client.is_logged_in else "<red>disconnected<end>"

        blob = "<header2>Info<end>\n"
        blob += "Status: %s [%s]\n" % (constatus, loglink)
        blob += "Channels available: <highlight>%d<end>\n\n" % len(
            self.channels)

        blob += "<header2>Subscription setup<end>\n"
        for cid, channel in self.channels.items():
            a = "<green>on<end>" if channel.relay_ao else "<red>off<end>"
            d = "<green>on<end>" if channel.relay_dc else "<red>off<end>"

            arelay = "off" if channel.relay_ao else "on"
            drelay = "off" if channel.relay_dc else "on"

            alink = self.text.make_chatcmd(
                arelay, "/tell <myname> discord relay %s %s %s" %
                (channel.channel_id, "ao", arelay))
            dlink = self.text.make_chatcmd(
                drelay, "/tell <myname> discord relay %s %s %s" %
                (channel.channel_id, "discord", drelay))

            blob += "<highlight>%s<end> :: <highlight>%s<end>\n" % (
                channel.server_name, channel.channel_name)
            blob += " | relaying from AO [%s] [%s]\n" % (a, alink)
            blob += " | relaying from Discord [%s] [%s]\n" % (d, dlink)

        blob += "\n\nDiscord Module written by <highlight>Vladimirovna<end>"

        return ChatBlob("Discord setup", blob)

    @command(command="discord",
             params=[
                 Const("relay"),
                 Any("channel_id"),
                 Options(["ao", "discord"]),
                 Options(["on", "off"])
             ],
             access_level="moderator",
             description="Changes relay setting for specific channel",
             sub_command="manage")
    def discord_relay_change_cmd(self, request, _, channel_id, relay_type,
                                 relay):
        channel = self.channels[channel_id]

        if relay_type == "ao":
            if channel is not None:
                channel.relay_ao = True if relay == "on" else False
        elif relay_type == "discord":
            if channel is not None:
                channel.relay_dc = True if relay == "on" else False
        else:
            return "Unknown relay type."

        self.update_discord_channels()

        return "Changed relay for %s to %s." % (channel.channel_name, relay)

    @command(command="discord",
             params=[Const("ignore"),
                     Const("add"),
                     Any("char_id")],
             access_level="moderator",
             description="Add char id to relay ignore list",
             sub_command="manage")
    def discord_ignore_add_cmd(self, request, _1, _2, char_id):
        if char_id not in self.ignore:
            self.ignore.append(char_id)
            self.update_discord_ignore()

            return "Added char id %s to ignore list." % char_id
        else:
            return "Char id already in ignore list."

    @command(
        command="discord",
        params=[Const("ignore"), Const("rem"),
                Any("char_id")],
        access_level="moderator",
        description="Remove char id from relay ignore list",
        sub_command="manage",
    )
    def discord_ignore_remove_cmd(self, request, _1, _2, char_id):
        if char_id not in self.ignore:
            return "Char id is not in ignore list."
        else:
            self.ignore.remove(char_id)
            return "Removed char id from ignore list."

    @command(command="discord",
             params=[Const("ignore")],
             access_level="moderator",
             description="See list of ignored characters",
             sub_command="manage")
    def discord_ignore_list_cmd(self, request, _):
        blob = "Characters ignored: <highlight>%d<end>\n\n" % len(self.ignore)

        if len(self.ignore) > 0:
            blob += "<header2>Character list<end>\n"

            for char_id in self.ignore:
                remove = self.text.make_chatcmd(
                    "remove", "/tell <myname> discord ignore rem %s" % char_id)
                name = self.character_service.resolve_char_to_name(char_id)
                blob += "<highlight>%s<end> - %s [%s]\n" % (name, char_id,
                                                            remove)

        return ChatBlob("Ignore list", blob)

    @command(command="discord",
             params=[Const("getinvite"), Int("server_id")],
             access_level="moderator",
             description="Get an invite for specified server",
             sub_command="manage")
    def discord_getinvite_cmd(self, request, _, server_id):
        if self.servers:
            for server in self.servers:
                if server.id == server_id:
                    self.aoqueue.append(
                        ("get_invite", (request.sender.name, server)))
        else:
            return "No such server."

    @event(event_type="org_message",
           description="Relay messages to Discord from org channel")
    def handle_org_message_event(self, event_type, event_data):
        if event_data.char_id not in self.ignore:
            if event_data.message[:1] != "!":
                msgtype = self.setting_service.get(
                    "discord_relay_format").get_value()
                msgcolor = self.setting_service.get(
                    "discord_embed_color").get_int_value()
                name = self.character_service.resolve_char_to_name(
                    event_data.char_id)
                message = DiscordMessage(
                    msgtype, "Org", name,
                    self.strip_html_tags(event_data.message), False, msgcolor)
                self.aoqueue.append(("org", message))

    @event(event_type="private_channel_message",
           description="Relay messages to Discord from private channel")
    def handle_private_message_event(self, event_type, event_data):
        if event_data.char_id not in self.ignore:
            if event_data.message[:1] != "!":
                msgtype = self.setting_service.get(
                    "discord_relay_format").get_value()
                msgcolor = self.setting_service.get(
                    "discord_embed_color").get_int_value()
                name = self.character_service.resolve_char_to_name(
                    event_data.char_id)
                message = DiscordMessage(
                    msgtype, "Private", name,
                    self.strip_html_tags(event_data.message), False, msgcolor)
                self.aoqueue.append(("priv", message))

    @timerevent(budatime="1s", description="Discord relay queue handler")
    def handle_discord_queue_event(self, event_type, event_data):
        if self.dqueue:
            dtype, message = self.dqueue.pop(0)
            self.event_service.fire_event(dtype, message)

    @event(
        event_type="connect",
        description=
        "Connects the Discord client automatically on startup, if a token exists"
    )
    def handle_connect_event(self, event_type, event_data):
        self.ignore.append(self.bot.char_id)
        ignores = self.db.query("SELECT * FROM discord_ignore")

        for row in ignores:
            if row.char_id not in self.ignore:
                self.ignore.append(row.char_id)

        self.update_discord_ignore()

        token = self.setting_service.get("discord_bot_token").get_value()
        if token:
            self.connect_discord_client(token)

    @event(event_type="discord_channels",
           description="Updates the list of channels available for relaying")
    def handle_discord_channels_event(self, event_type, message):
        for channel in message:
            if channel.type is ChannelType.text:
                cid = channel.id
                if cid not in self.channels:
                    self.channels[cid] = DiscordChannel(
                        cid, channel.server.name, channel.name, False, False)
                else:
                    self.channels[cid].server_name = channel.server.name
                    self.channels[cid].channel_name = channel.name

        self.update_discord_channels()

    @event(event_type="discord_command",
           description="Handles discord commands")
    def handle_discord_command_event(self, event_type, message):
        msgtype = self.setting_service.get("discord_relay_format").get_value()
        msgcolor = self.setting_service.get(
            "discord_embed_color").get_int_value()

        command_str, command_args = self.command_service.get_command_parts(
            message)
        for handler in self.command_handlers:
            if handler.command == command_str:
                matches = handler.regex.search(command_args)

                def reply(content, title="Command"):
                    self.aoqueue.append(
                        ("command_reply",
                         DiscordMessage(msgtype, title, self.bot.char_name,
                                        content, True, msgcolor)))

                if matches:
                    handler.callback(
                        reply,
                        self.command_service.process_matches(
                            matches, handler.params))
                else:
                    reply(self.generate_help(command_str, handler.params),
                          "Command Help")
                break

    def generate_help(self, command_str, params):
        return "!" + command_str + " " + " ".join(
            map(
                lambda x: x.get_name().replace("<highlight>", "").replace(
                    "<end>", ""), params))

    @event(event_type="discord_message",
           description="Handles relaying of discord messages")
    def handle_discord_message_event(self, event_type, message):
        if isinstance(message.author, Member):
            name = message.author.nick or message.author.name
        else:
            name = message.author.name

        chanclr = self.setting_service.get(
            "relay_color_prefix").get_font_color()
        nameclr = self.setting_service.get("relay_color_name").get_font_color()
        mesgclr = self.setting_service.get(
            "relay_color_message").get_font_color()

        content = "<grey>[<end>%sDiscord<end><grey>][<end>%s%s<end><grey>]<end> %s%s<end><grey>:<end> %s%s<end>" % (
            chanclr, chanclr, message.channel.name, nameclr, name, mesgclr,
            message.content)

        if self.setting_service.get("relay_to_private").get_value():
            self.bot.send_private_channel_message(content)

        if self.setting_service.get("relay_to_org").get_value():
            self.bot.send_org_message(content)

    @event(event_type="discord_invites", description="Handles invite requests")
    def handle_discord_invite_event(self, event_type, event_data):
        sender = event_data[0]
        invites = event_data[1]

        blob = "<header2>Available invites<end>\n"

        if len(invites) > 0:
            for invite in invites:
                link = self.text.make_chatcmd("join", "/start %s" % invite.url)
                timeleft = "Permanent" if invite.max_age == 0 else str(
                    datetime.timedelta(seconds=invite.max_age))
                used = str(invite.uses) if invite.uses is not None else "N/A"
                useleft = str(
                    invite.max_uses) if invite.max_uses is not None else "N/A"
                channel = " | for channel: %s\n" % invite.channel.name if invite.channel is not None else None

                blob += "%s [%s]\n" % (invite.server.name, link)
                blob += " | life time: %s\n" % timeleft
                blob += " | used: %s\n" % used
                blob += " | uses left: %s\n" % useleft
                blob += channel
                blob += "\n"
        else:
            blob += "None available, maybe the bot user does not have sufficient permissions to see invites, or no invites exists.\n\n"

        self.bot.send_private_message(sender, ChatBlob("Discord invites",
                                                       blob))

    @event(event_type="discord_exception",
           description="Handles discord exceptions")
    def handle_discord_exception_event(self, event_type, event_data):
        self.bot.send_private_channel_message("Exception raised: %s" %
                                              event_data)
        # TODO expand... use DiscordMessage as a general case wrapper for all info that would be needed in the different relays

    def register_discord_command_handler(self, callback, command_str, params):
        r = re.compile(self.command_service.get_regex_from_params(params),
                       re.IGNORECASE | re.DOTALL)
        self.command_handlers.append(
            DictObject({
                "callback": callback,
                "command": command_str,
                "params": params,
                "regex": r
            }))

    def connect_discord_client(self, token):
        self.dthread = threading.Thread(target=self.client.run,
                                        args=(token, ),
                                        daemon=True)
        self.dthread.start()
        self.client.loop.create_task(self.client.relay_message())

    def update_discord_channels(self):
        result = self.db.query("SELECT * FROM discord")
        worked = []

        if result is not None:
            for row in result:
                if row.channel_id in self.channels:
                    channel = self.channels[row.channel_id]
                    self.db.exec(
                        "UPDATE discord SET server_name = ?, channel_name = ?, relay_ao = ?, relay_dc = ? WHERE channel_id = ?",
                        [
                            channel.server_name, channel.channel_name,
                            channel.relay_ao, channel.relay_dc, row.channel_id
                        ])
                    worked.append(row.channel_id)

        for cid, channel in self.channels.items():
            if channel.channel_id not in worked:
                self.db.exec(
                    "INSERT INTO discord (channel_id, server_name, channel_name, relay_ao, relay_dc) VALUES (?, ?, ?, ?, ?)",
                    [
                        channel.channel_id, channel.server_name,
                        channel.channel_name, channel.relay_ao,
                        channel.relay_dc
                    ])

    def update_discord_ignore(self):
        ignores = self.db.query("SELECT * FROM discord_ignore")
        skip = []

        for row in ignores:
            skip.append(row.char_id)
            if row.char_id not in self.ignore:
                self.db.exec("DELETE FROM discord_ignore WHERE char_id = ?",
                             [row.char_id])

        for cid in self.ignore:
            if cid not in skip:
                self.db.exec("INSERT INTO discord_ignore (char_id) VALUES (?)",
                             [cid])

    def strip_html_tags(self, html):
        s = MLStripper()
        s.feed(html)
        return s.get_data()
Exemple #23
0
 def test_any(self):
     param = Any("test")
     self.assertEqual("10", self.param_test_helper(param, "10"))
     self.assertEqual("ten", self.param_test_helper(param, "ten"))
     self.assertEqual("?this is a test.", self.param_test_helper(param, "?this is a test."))
     self.assertIsNone(self.param_test_helper(param, ""))
Exemple #24
0
class ConfigController:
    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.config_events_controller = registry.get_instance(
            "config_events_controller")
        self.ts: TranslationService = registry.get_instance(
            "translation_service")
        self.getresp = self.ts.get_response

    def start(self):
        self.ts.register_translation("module/config", self.load_config_msg)

    def load_config_msg(self):
        with open("modules/core/config/config.msg", mode="r",
                  encoding="UTF-8") as f:
            return hjson.load(f)

    @command(command="config",
             params=[],
             access_level="admin",
             description="Show configuration options for the bot")
    def config_list_cmd(self, request):
        sql = """SELECT
                module,
                SUM(CASE WHEN enabled = 1 THEN 1 ELSE 0 END) count_enabled,
                SUM(CASE WHEN enabled = 0 THEN 1 ELSE 0 END) count_disabled
            FROM
                (SELECT module, enabled FROM command_config
                UNION
                SELECT module, enabled FROM event_config WHERE is_hidden = 0
                UNION
                SELECT module, 2 FROM setting) t
            GROUP BY
                module
            ORDER BY
                module ASC"""

        data = self.db.query(sql)
        count = len(data)
        blob = ""
        current_group = ""
        for row in data:
            parts = row.module.split(".")
            group = parts[0]
            module = parts[1]
            if group != current_group:
                current_group = group
                blob += "\n<header2>" + current_group + "<end>\n"

            blob += self.text.make_chatcmd(
                module, "/tell <myname> config mod " + row.module) + " "
            if row.count_enabled > 0 and row.count_disabled > 0:
                blob += self.getresp("module/config", "partial")
            else:
                blob += self.getresp(
                    "module/config", "enabled_high"
                    if row.count_disabled == 0 else "disabled_high")
            blob += "\n"

        return ChatBlob(
            self.getresp("module/config", "config", {"count": count}), blob)

    @command(command="config",
             params=[Options(["mod", "module"]),
                     Any("module_name")],
             access_level="admin",
             description="Show configuration options for a specific module")
    def config_module_list_cmd(self, request, _, module):
        module = module.lower()

        blob = ""

        data = self.db.query(
            "SELECT name FROM setting WHERE module = ? ORDER BY name ASC",
            [module])
        if data:
            blob += self.getresp("module/config", "settings")
            for row in data:
                setting = self.setting_service.get(row.name)
                blob += "%s: %s (%s)\n" % (
                    setting.get_description(), setting.get_display_value(),
                    self.text.make_chatcmd(
                        "change", "/tell <myname> config setting " + row.name))

        data = self.db.query(
            "SELECT DISTINCT command, sub_command FROM command_config WHERE module = ? ORDER BY command ASC",
            [module])
        if data:
            blob += self.getresp("module/config", "commands")
            for row in data:
                command_key = self.command_service.get_command_key(
                    row.command, row.sub_command)
                blob += self.text.make_chatcmd(
                    command_key,
                    "/tell <myname> config cmd " + command_key) + "\n"

        data = self.db.query(
            "SELECT event_type, event_sub_type, handler, description, enabled "
            "FROM event_config WHERE module = ? AND is_hidden = 0 "
            "ORDER BY event_type, handler ASC", [module])
        if data:
            blob += self.getresp("module/config", "events")
            for row in data:
                event_type_key = self.event_service.get_event_type_key(
                    row.event_type, row.event_sub_type)
                enabled = self.getresp(
                    "module/config",
                    "enabled_high" if row.enabled == 1 else "disabled_high")
                blob += "%s - %s [%s]" % (
                    self.config_events_controller.format_event_type(row),
                    row.description, enabled)
                blob += " " + self.text.make_chatcmd(
                    "On", "/tell <myname> config event %s %s enable" %
                    (event_type_key, row.handler))
                blob += " " + self.text.make_chatcmd(
                    "Off", "/tell <myname> config event %s %s disable" %
                    (event_type_key, row.handler))
                if row.event_type == 'timer':
                    blob += " " + self.text.make_chatcmd(
                        "Run Now", "/tell <myname> config event %s %s run" %
                        (event_type_key, row.handler))
                blob += "\n"

        if blob:
            return ChatBlob(
                self.getresp("module/config", "mod_title", {"mod": module}),
                blob)
        else:
            return self.getresp("module/config", "mod_not_found",
                                {"mod": module})

    @command(command="config",
             params=[
                 Const("setting"),
                 Any("setting_name"),
                 Options(["set", "clear"]),
                 Any("new_value", is_optional=True)
             ],
             access_level="admin",
             description="Change a setting value")
    def config_setting_update_cmd(self, request, _, setting_name, op,
                                  new_value):
        setting_name = setting_name.lower()

        if op == "clear":
            new_value = ""
        elif not new_value:
            return self.getresp("module/config", "no_new_value")
        setting = self.setting_service.get(setting_name)

        if setting:
            try:
                setting.set_value(new_value)
                if op == "clear":
                    return self.getresp("module/config", "set_clr",
                                        {"setting": setting_name})
                else:
                    return self.getresp(
                        "module/config", "set_new", {
                            "setting": setting_name,
                            "value": setting.get_display_value()
                        })
            except Exception as e:
                return "Error! %s" % str(e)
        else:
            return self.getresp("module/config", "setting_not_found",
                                {"setting": setting_name})

    @command(command="config",
             params=[Const("setting"), Any("setting_name")],
             access_level="admin",
             description="Show configuration options for a setting")
    def config_setting_show_cmd(self, request, _, setting_name):
        setting_name = setting_name.lower()

        blob = ""

        setting = self.setting_service.get(setting_name)

        if setting:
            blob += self.getresp("module/config", "current_value",
                                 {"value": str(setting.get_display_value())})
            blob += self.getresp("module/config", "description",
                                 {"desc": setting.get_description()})
            if setting.get_extended_description():
                blob += setting.get_extended_description() + "\n\n"
            blob += setting.get_display()
            return ChatBlob(
                self.getresp("module/config", "setting",
                             {"setting": setting_name}), blob)
        else:
            return self.getresp("module/config", "setting_not_found",
                                {"setting": setting_name})
Exemple #25
0
class LogController:
    def inject(self, registry):
        self.db: DB = registry.get_instance("db")

    @command(command="logon",
             params=[],
             access_level="member",
             description="Check your current logon message")
    def check_current_logon(self, request):
        current_logon = self.get_logon(request.sender.char_id)
        if current_logon:
            return "%s's logon message is: %s" % (request.sender.name,
                                                  current_logon)
        else:
            return "Your logon message has not been set."

    @command(command="logon",
             params=[Const("clear")],
             access_level="member",
             description="Clear your logon message")
    def clear_logon(self, request, params):
        if self.db.query_single(
                "SELECT logon FROM log_messages WHERE char_id=?;",
            [request.sender.char_id]):
            self.db.exec("UPDATE log_messages SET logon=NULL WHERE char_id=?;",
                         [request.sender.char_id])
        return "Your logon message has been cleared."

    @command(command="logon",
             params=[Any("logon_message")],
             access_level="member",
             description="Set your logon message")
    def set_logon(self, request: CommandRequest, logon_message):
        if self.db.query_single(
                "SELECT logon FROM log_messages WHERE char_id=?;",
            [request.sender.char_id]):
            self.db.exec("UPDATE log_messages SET logon=? WHERE char_id=?;",
                         [logon_message, request.sender.char_id])
        else:
            self.db.exec(
                "INSERT INTO log_messages (char_id, logon) VALUES(?, ?);",
                [request.sender.char_id, logon_message])
        return "Your new logon message is: %s" % logon_message

    @command(command="logoff",
             params=[],
             access_level="member",
             description="Check your current logoff message")
    def check_current_logoff(self, request):
        current_logoff = self.get_logoff(request.sender.char_id)
        if current_logoff:
            return "%s's logoff message is: %s" % (request.sender.name,
                                                   current_logoff)
        else:
            return "Your logoff message has not been set."

    @command(command="logoff",
             params=[Const("clear")],
             access_level="member",
             description="Clear your logoff message")
    def clear_logoff(self, request, params):
        if self.db.query_single(
                "SELECT logoff FROM log_messages WHERE char_id=?;",
            [request.sender.char_id]):
            self.db.exec(
                "UPDATE log_messages SET logoff=NULL WHERE char_id=?;",
                [request.sender.char_id])
        return "Your logoff message has been cleared."

    @command(command="logoff",
             params=[Any("logoff_message")],
             access_level="member",
             description="Set your logoff message")
    def set_logoff(self, request: CommandRequest, logoff_message):
        if self.db.query_single(
                "SELECT logoff FROM log_messages WHERE char_id=?;",
            [request.sender.char_id]):
            self.db.exec("UPDATE log_messages SET logoff=? WHERE char_id=?;",
                         [logoff_message, request.sender.char_id])
        else:
            self.db.exec(
                "INSERT INTO log_messages (char_id, logoff) VALUES(?, ?);",
                [request.sender.char_id, logoff_message])
        return "Your new logoff message is: %s" % logoff_message

    def get_logon(self, char_id):
        row = self.db.query_single(
            "SELECT * FROM log_messages WHERE char_id=?", [char_id])
        if row and row.logon:
            return "<grey>" + row.logon + "<end>"
        return ""

    def get_logoff(self, char_id):
        row = self.db.query_single(
            "SELECT * FROM log_messages WHERE char_id=?", [char_id])
        if row and row.logoff:
            return "<grey>" + row.logoff + "<end>"
        return ""
Exemple #26
0
class AOUController:
    AOU_URL = "https://www.ao-universe.com/mobile/parser.php?bot=tyrbot"

    CACHE_GROUP = "aou"
    CACHE_MAX_AGE = 604800

    def __init__(self):
        self.guide_id_regex = re.compile(r"pid=(\d+)", re.IGNORECASE)

        # initialize bbcode parser
        self.parser = bbcode.Parser(install_defaults=False,
                                    newline="\n",
                                    replace_links=False,
                                    replace_cosmetic=False,
                                    drop_unrecognized=True)
        self.parser.add_simple_formatter("i", "<i>%(value)s</i>")
        self.parser.add_simple_formatter("b",
                                         "<highlight>%(value)s</highlight>")
        self.parser.add_simple_formatter("ts_ts", " + ", standalone=True)
        self.parser.add_simple_formatter("ts_ts2", " = ", standalone=True)
        self.parser.add_simple_formatter("ct", " | ", standalone=True)
        self.parser.add_simple_formatter("cttd", " | ", standalone=True)
        self.parser.add_simple_formatter("cttr", "\n | ", standalone=True)
        self.parser.add_simple_formatter("br", "\n", standalone=True)
        self.parser.add_formatter("img", self.bbcode_render_image)
        self.parser.add_formatter("url", self.bbcode_render_url)
        self.parser.add_formatter("item", self.bbcode_render_item)
        self.parser.add_formatter("itemname", self.bbcode_render_item)
        self.parser.add_formatter("itemicon", self.bbcode_render_item)
        self.parser.add_formatter("waypoint", self.bbcode_render_waypoint)

    def inject(self, registry):
        self.text = registry.get_instance("text")
        self.items_controller = registry.get_instance("items_controller")
        self.cache_service = registry.get_instance("cache_service")
        self.command_alias_service = registry.get_instance(
            "command_alias_service")
        self.ts: TranslationService = registry.get_instance(
            "translation_service")
        self.getresp = self.ts.get_response

    def start(self):
        self.command_alias_service.add_alias("title", "aou 11")
        self.command_alias_service.add_alias("totw", "macro aou 171|aou 172")
        self.command_alias_service.add_alias("som", "macro aou 169|aou 383")
        self.command_alias_service.add_alias("reck", "aou 629")
        self.command_alias_service.add_alias("pets", "aou 2")
        self.ts.register_translation("module/aou", self.load_aou_msg)

    def load_aou_msg(self):
        with open("modules/standard/aou/aou.msg", mode="r",
                  encoding="utf-8") as f:
            return hjson.load(f)

    @command(command="aou",
             params=[Int("guide_id")],
             access_level="all",
             description="Show an AO-Universe guide")
    def aou_show_cmd(self, request, guide_id):
        guide_info = self.retrieve_guide(guide_id)

        if not guide_info:
            return self.getresp("module/aou", "no_guide_id", {"id": guide_id})

        obj = DictObject()
        obj.id = self.text.make_chatcmd(
            guide_info.id,
            "/start https://www.ao-universe.com/main.php?site=knowledge&id=%s"
            % guide_info.id)
        obj.raw = self.text.make_chatcmd(
            "Raw", "/start %s" %
            (self.AOU_URL + "&mode=view&id=" + str(guide_info.id)))
        obj.updated = guide_info.updated
        obj.profession = guide_info.profession
        obj.faction = guide_info.faction
        obj.level = guide_info.level
        obj.author = self.format_bbcode_code(guide_info.author)
        obj.aou = self.text.make_chatcmd("AO-Universe.com",
                                         "/start https://www.ao-universe.com")
        obj.text = self.format_bbcode_code(guide_info.text)

        return ChatBlob(guide_info.name,
                        self.getresp("module/aou", "guide", {**obj}))

    @command(command="aou",
             params=[Const("all", is_optional=True),
                     Any("search")],
             access_level="all",
             description="Search for an AO-Universe guides")
    def aou_search_cmd(self, request, include_all_matches, search):
        include_all_matches = include_all_matches or False

        r = requests.get(self.AOU_URL + "&mode=search&search=" + search,
                         timeout=5)
        xml = ElementTree.fromstring(r.content)

        blob = ""
        count = 0
        for section in xml.iter("section"):
            category = self.get_category(section)
            found = False
            for guide in self.get_guides(section):
                if include_all_matches or self.check_matches(
                        category + " " + guide["name"] + " " +
                    (guide["description"] or ""), search):
                    # don't show category unless we have at least one guide for it
                    if not found:
                        blob += "\n<header2>%s</header2>\n" % category
                        found = True

                    count += 1
                    blob += "%s - %s\n" % (self.text.make_tellcmd(
                        guide["name"],
                        "aou %s" % guide["id"]), guide["description"])
        blob += "\n\nPowered by %s" % self.text.make_chatcmd(
            "AO-Universe.com", "/start https://www.ao-universe.com")

        if count == 0:
            return self.getresp("module/aou", "no_guide_search",
                                {"search": search})
        else:
            return ChatBlob(
                self.getresp(
                    "module/aou", "search_guide_title" +
                    ("_all" if include_all_matches else ""), {
                        "search": search,
                        "count": count
                    }), blob)

    def retrieve_guide(self, guide_id):
        cache_key = "%d.xml" % guide_id

        t = int(time.time())

        # check cache for fresh value
        cache_result = self.cache_service.retrieve(self.CACHE_GROUP, cache_key)

        if cache_result and cache_result.last_modified > (t -
                                                          self.CACHE_MAX_AGE):
            result = ElementTree.fromstring(cache_result.data)
        else:
            response = requests.get(self.AOU_URL + "&mode=view&id=" +
                                    str(guide_id),
                                    timeout=5)
            result = ElementTree.fromstring(response.content)

            if result.findall("./error"):
                result = None

            if result:
                # store result in cache
                self.cache_service.store(
                    self.CACHE_GROUP, cache_key,
                    ElementTree.tostring(result, encoding="unicode"))
            elif cache_result:
                # check cache for any value, even expired
                result = ElementTree.fromstring(cache_result.data)

        if result:
            return self.get_guide_info(result)
        else:
            return None

    def get_guide_info(self, xml):
        content = self.get_xml_child(xml, "section/content")
        return DictObject({
            "id":
            self.get_xml_child(content, "id").text,
            "category":
            self.get_category(self.get_xml_child(xml, "section")),
            "name":
            self.get_xml_child(content, "name").text,
            "updated":
            self.get_xml_child(content, "update").text,
            "profession":
            self.get_xml_child(content, "class").text,
            "faction":
            self.get_xml_child(content, "faction").text,
            "level":
            self.get_xml_child(content, "level").text,
            "author":
            self.get_xml_child(content, "author").text,
            "text":
            self.get_xml_child(content, "text").text
        })

    def check_matches(self, haystack, needle):
        haystack = haystack.lower()
        for n in needle.split():
            if n in haystack:
                return True
        return False

    def get_guides(self, section):
        result = []
        for guide in section.findall("./guidelist/guide"):
            result.append({
                "id": guide[0].text,
                "name": guide[1].text,
                "description": guide[2].text
            })
        return result

    def get_category(self, section):
        result = []
        for folder_names in section.findall("./folderlist/folder/name"):
            result.append(folder_names.text)
        return " - ".join(reversed(result))

    def get_xml_child(self, xml, child_tag):
        return xml.findall("./%s" % child_tag)[0]

    def format_bbcode_code(self, bbcode_str):
        return self.parser.format(bbcode_str or "")

    # BBCode formatters
    def bbcode_render_image(self, tag_name, value, options, parent, context):
        return self.text.make_chatcmd(
            "Image", "/start https://www.ao-universe.com/" + value)

    def bbcode_render_url(self, tag_name, value, options, parent, context):
        url = options.get("url") or value
        guide_id_match = self.guide_id_regex.search(url)
        if guide_id_match:
            return self.text.make_tellcmd(value,
                                          "aou " + guide_id_match.group(1))
        else:
            return self.text.make_chatcmd(value, "/start " + url)

    def bbcode_render_item(self, tag_name, value, options, parent, context):
        item = self.items_controller.get_by_item_id(value)
        if not item:
            return "Unknown Item(%s)" % value
        else:
            include_icon = tag_name == "item" or tag_name == "itemicon"
            return self.text.format_item(item, with_icon=include_icon)

    def bbcode_render_waypoint(self, tag_name, value, options, parent,
                               context):
        x_coord = options["x"]
        y_coord = options["y"]
        pf_id = options["pf"]

        return self.text.make_chatcmd(
            "%s (%sx%s)" % (value, x_coord, y_coord),
            "/waypoint %s %s %s" % (x_coord, y_coord, pf_id))
Exemple #27
0
class RaffleController:
    def __init__(self):
        self.raffle = None

    def inject(self, registry):
        self.bot = registry.get_instance("bot")
        self.db = registry.get_instance("db")
        self.text = registry.get_instance("text")
        self.util = registry.get_instance("util")
        self.job_scheduler = registry.get_instance("job_scheduler")

    @command(command="raffle",
             params=[],
             access_level="all",
             description="Show current raffle")
    def raffle_show_cmd(self, request):
        if not self.raffle:
            return "There is no active raffle."

        t = int(time.time())

        return self.get_raffle_display(t)

    @command(command="raffle",
             params=[Const("cancel")],
             access_level="all",
             description="Cancel the raffle")
    def raffle_cancel_cmd(self, request, _):
        if not self.raffle:
            return "There is no active raffle."

        self.job_scheduler.cancel_job(self.raffle.scheduled_job_id)
        self.raffle = None

        msg = "The raffle has been cancelled."
        self.spam_raffle_channels(msg)

    @command(command="raffle",
             params=[Const("join")],
             access_level="all",
             description="Join the raffle")
    def raffle_join_cmd(self, request, _):
        if not self.raffle:
            return "There is no active raffle."

        if request.sender.name in self.raffle.members:
            return "You are already in the raffle."

        self.raffle.members.append(request.sender.name)
        return "You have joined the raffle."

    @command(command="raffle",
             params=[Const("leave")],
             access_level="all",
             description="Leave the raffle")
    def raffle_leave_cmd(self, request, _):
        if not self.raffle:
            return "There is no active raffle."

        if request.sender.name not in self.raffle.members:
            return "You are not in the raffle."

        self.raffle.members.remove(request.sender.name)
        return "You have been removed from the raffle."

    @command(command="raffle",
             params=[
                 Const("start", is_optional=True),
                 Any("item"),
                 Time("duration", is_optional=True)
             ],
             access_level="all",
             description="Raffle an item")
    def raffle_start_cmd(self, request, _, item, duration):
        duration = duration or 180  # 3 minutes

        if self.raffle:
            return "There is already a raffle in progress."

        t = int(time.time())
        finished_at = t + duration

        self.raffle = DictObject({
            "owner":
            request.sender,
            "item":
            self.get_item_name(item),
            "started_at":
            t,
            "duration":
            duration,
            "finished_at":
            finished_at,
            "members": [],
            "reply":
            request.reply,
            "scheduled_job_id":
            self.job_scheduler.scheduled_job(
                self.alert_raffle_status,
                self.get_next_alert_time(t, finished_at))
        })

        chatblob = self.get_raffle_display(t)

        self.spam_raffle_channels(chatblob)

    def get_raffle_display(self, t):
        time_left_str = self.util.time_to_readable(self.raffle.finished_at - t)

        blob = "Item: <highlight>%s<end>\n" % self.raffle.item
        blob += "By: <highlight>%s<end>\n" % self.raffle.owner.name
        blob += "Time left: <highlight>%s<end>\n" % time_left_str
        blob += "Members (%d): <highlight>%s<end>\n\n" % (len(
            self.raffle.members), ", ".join(self.raffle.members))
        blob += "Click %s to join the raffle!\n\n" % self.text.make_chatcmd(
            "here", "/tell <myname> raffle join")
        blob += "Click %s if you wish to leave the raffle." % self.text.make_chatcmd(
            "here", "/tell <myname> raffle leave")

        return ChatBlob(
            "Raffle for %s! (ends in %s)" % (self.raffle.item, time_left_str),
            blob)

    def get_item_name(self, item):
        m = re.match(r"<a href=\"itemref://\d+/\d+/\d+\">(.+)</a>", item)
        if m:
            return m.group(1)
        else:
            return item

    def alert_raffle_status(self, t):
        if not self.raffle:
            pass

        if t >= self.raffle.finished_at:
            if len(self.raffle.members) == 0:
                self.spam_raffle_channels(
                    "The raffle has ended and there is no winner because no one entered the raffle."
                )
            else:
                self.spam_raffle_channels(
                    "Congrations <highlight>%s<end>! You have won the raffle for <highlight>%s<end>."
                    % (self.get_raffle_winner(), self.raffle.item))
            self.raffle = None
        else:
            self.spam_raffle_channels(self.get_raffle_display(t))
            self.raffle.scheduled_job_id = self.job_scheduler.scheduled_job(
                self.alert_raffle_status,
                self.get_next_alert_time(t, self.raffle.finished_at))

    def spam_raffle_channels(self, msg):
        self.bot.send_private_channel_message(msg, fire_outgoing_event=False)
        self.bot.send_org_message(msg, fire_outgoing_event=False)

    def get_raffle_winner(self):
        return random.choice(self.raffle.members)

    def get_next_alert_time(self, current_time, finished_at):
        time_left = finished_at - current_time
        if time_left > 60:
            return current_time + 60
        else:
            return current_time + time_left
Exemple #28
0
class ItemsController:
    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 start(self):
        self.command_alias_service.add_alias("item", "items")
        self.command_alias_service.add_alias("i", "items")

    @command(command="items",
             params=[
                 Int("ql", is_optional=True),
                 Any("search"),
                 NamedParameters(["page"])
             ],
             access_level="all",
             description="Search for an item")
    def items_cmd(self, request, ql, search, named_params):
        page = int(named_params.page or "1")

        search = html.unescape(search)

        page_size = 30
        offset = (page - 1) * page_size

        all_items = self.find_items(search, ql)
        if search.isdigit():
            all_items += [self.get_by_item_id(int(search))] + all_items
        items = self.sort_items(search, all_items)[offset:offset + page_size]
        cnt = len(items)

        if cnt == 0:
            if ql:
                return "No QL <highlight>%d<end> items found matching <highlight>%s<end>." % (
                    ql, search)
            else:
                return "No items found matching <highlight>%s<end>." % search
        else:
            blob = ""
            blob += "Version: <highlight>%s<end>\n" % "unknown"
            if ql:
                blob += "Search: <highlight>QL %d %s<end>\n" % (ql, search)
            else:
                blob += "Search: <highlight>%s<end>\n" % search
            blob += "\n"

            if page > 1:
                blob += "   " + self.text.make_chatcmd(
                    "<< Page %d" %
                    (page - 1), self.get_chat_command(ql, search, page - 1))
            if offset + page_size < len(all_items):
                blob += "   Page " + str(page)
                blob += "   " + self.text.make_chatcmd(
                    "Page %d >>" %
                    (page + 1), self.get_chat_command(ql, search, page + 1))
            blob += "\n"

            blob += self.format_items(items, ql)
            blob += "\nItem DB rips created using the %s tool." % self.text.make_chatcmd(
                "Budabot Items Extractor",
                "/start https://github.com/Budabot/ItemsExtractor")

            return ChatBlob(
                "Item Search Results (%d - %d of %d)" %
                (offset + 1, min(offset + page_size,
                                 len(all_items)), len(all_items)), blob)

    def format_items(self, items, ql=None):
        blob = ""
        for item in items:
            blob += "<pagebreak>"
            blob += self.text.make_image(item.icon) + "\n"
            if ql:
                blob += "QL %d " % ql
                blob += self.text.make_item(item.lowid, item.highid, ql,
                                            item.name)
            else:
                blob += self.text.make_item(item.lowid, item.highid,
                                            item.highql, item.name)

            if item.lowql != item.highql:
                blob += " (QL%d - %d)\n" % (item.lowql, item.highql)
            else:
                blob += " (QL%d)\n" % item.highql

            blob += "\n"

        return blob

    def find_items(self, name, ql=None):
        params = [name]
        sql = "SELECT * FROM aodb WHERE name LIKE ? "
        if ql:
            sql += " AND lowql <= ? AND highql >= ?"
            params.append(ql)
            params.append(ql)

        sql += " UNION SELECT * FROM aodb WHERE name <EXTENDED_LIKE=%d> ?" % len(
            params)
        params.append(name)

        if ql:
            sql += " AND lowql <= ? AND highql >= ?"
            params.append(ql)
            params.append(ql)

        sql += " ORDER BY name ASC, highql DESC"

        return self.db.query(sql, params, extended_like=True)

    def sort_items(self, search, items):
        search = search.lower()
        search_parts = search.split(" ")

        # if item name matches search exactly (case-insensitive) then priority = 0
        # if item name contains every whole word from search (case-insensitive) then priority = 1
        # +1 priority for each whole word from search that item name does not contain

        for row in items:
            row.priority = 0
            row_name = row.name.lower()
            if row_name != search:
                row.priority += 1
                row_parts = row_name.split(" ")
                for search_part in search_parts:
                    if search_part not in row_parts:
                        row.priority += 1

        items.sort(key=lambda x: x.priority, reverse=False)

        return items

    def get_by_item_id(self, item_id):
        return self.db.query_single(
            "SELECT * FROM aodb WHERE highid = ? OR lowid = ? ORDER BY highid = ?",
            [item_id, item_id, item_id])

    def find_by_name(self, name, ql=None):
        if ql:
            return self.db.query_single(
                "SELECT * FROM aodb WHERE name = ? AND lowql <= ? AND highql >= ? ORDER BY highid DESC",
                [name, ql, ql])
        else:
            return self.db.query_single(
                "SELECT * FROM aodb WHERE name = ? ORDER BY highql DESC, highid DESC",
                [name])

    def get_chat_command(self, ql, search, page):
        if ql:
            return "/tell <myname> items %d %s --page=%d" % (ql, search, page)
        else:
            return "/tell <myname> items %s --page=%d" % (search, page)
Exemple #29
0
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")
        self.ts: TranslationService = registry.get_instance(
            "translation_service")
        self.getresp = self.ts.get_response

    @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 self.getresp("module/config", "cmd_unknown_channel",
                                {"channel": 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 self.getresp("module/config", "cmd_unknown_for_channel", {
                "channel": cmd_channel,
                "cmd": cmd_name
            })
        else:
            action = self.getresp(
                "module/config",
                "enabled_low" if action == "enable" else "disabled_low")
            if cmd_channel == "all":
                return self.getresp("module/config", "cmd_toggle_success", {
                    "cmd": cmd_name,
                    "changedto": action
                })
            else:
                return self.getresp("module/config",
                                    "cmd_toggle_channel_success", {
                                        "channel": cmd_channel,
                                        "cmd": cmd_name,
                                        "changedto": 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 self.getresp("module/config", "cmd_unknown_channel",
                                {"channel": cmd_channel})

        if self.access_service.get_access_level_by_label(access_level) is None:
            return self.getresp("module/config", "unknown_accesslevel",
                                {"al": 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 self.getresp("module/config", "cmd_unknown_for_channel", {
                "channel": cmd_channel,
                "cmd": cmd_name
            })
        else:
            if cmd_channel == "all":
                return self.getresp("module/config", "set_accesslevel_success",
                                    {
                                        "cmd": cmd_name,
                                        "al": access_level
                                    })
            else:
                return self.getresp("module/config", "set_accesslevel_fail", {
                    "channel": cmd_channel,
                    "cmd": cmd_name,
                    "al": access_level
                })

    @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]
                status = self.getresp(
                    "module/config", "enabled_high"
                    if cmd_config.enabled == 1 else "disabled_high")

                blob += "<header2>%s<end> %s (%s: %s)\n" % (
                    channel_label, status,
                    self.getresp("module/config", "access_level"),
                    cmd_config.access_level.capitalize())

                # show status config
                blob += "Status:"
                enable_link = self.text.make_chatcmd(
                    self.getresp("module/config", "enable"),
                    "/tell <myname> config cmd %s enable %s" %
                    (cmd_name, command_channel))
                disable_link = self.text.make_chatcmd(
                    self.getresp("module/config", "disable"),
                    "/tell <myname> config cmd %s disable %s" %
                    (cmd_name, command_channel))

                blob += "  " + enable_link + "  " + disable_link

                # show access level config
                blob += "\n" + self.getresp("module/config", "access_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 self.getresp("module/config", "no_cmd", {"cmd": cmd_name})
Exemple #30
0
class HelpController:
    def inject(self, registry):
        self.bot = registry.get_instance("bot")
        self.text = registry.get_instance("text")
        self.db = registry.get_instance("db")
        self.access_service = registry.get_instance("access_service")
        self.command_service = registry.get_instance("command_service")
        self.command_alias_service = registry.get_instance(
            "command_alias_service")

    def start(self):
        self.command_alias_service.add_alias("version", "about")

    @command(command="about",
             params=[],
             access_level="all",
             description="Show information about the development of this bot")
    def about_cmd(self, request):
        with open(os.path.dirname(os.path.realpath(__file__)) + os.sep +
                  "about.txt",
                  mode="r",
                  encoding="UTF-8") as f:
            return ChatBlob(f"About Tyrbot {self.bot.version}", f.read())

    @command(command="help",
             params=[],
             access_level="all",
             description="Show a list of commands to get help with")
    def help_list_cmd(self, request):
        data = self.db.query(
            "SELECT command, module, access_level FROM command_config "
            "WHERE enabled = 1 "
            "ORDER BY module ASC, command ASC")
        blob = ""
        current_group = ""
        current_module = ""
        current_command = ""
        access_level = self.access_service.get_access_level(
            request.sender.char_id)
        for row in data:
            if access_level[
                    "level"] > self.access_service.get_access_level_by_label(
                        row.access_level)["level"]:
                continue

            parts = row.module.split(".")
            group = parts[0]
            module = parts[1]
            if group != current_group:
                current_group = group
                blob += "\n\n<header2>" + current_group + "</header2>"

            if module != current_module:
                current_module = module
                blob += "\n" + module + ":"

            if row.command != current_command:
                current_command = row.command
                blob += " " + self.text.make_tellcmd(row.command,
                                                     "help " + row.command)

        return ChatBlob("Help (main)", blob)

    @command(
        command="help",
        params=[Any("search"),
                NamedFlagParameters(["show_regex"])],
        access_level="all",
        description="Show help for a specific command",
        extended_description=
        "Search param can be either a command name or a module name (eg. 'standard.online')"
    )
    def help_detail_cmd(self, request, help_topic, named_params):
        help_topic = help_topic.lower()

        # check for alias
        alias = self.command_alias_service.check_for_alias(help_topic)
        if alias:
            help_topic = alias

        # check if help topic matches a command
        data = self.db.query(
            "SELECT command, sub_command, access_level FROM command_config "
            "WHERE command = ? AND channel = ? AND enabled = 1",
            [help_topic, request.channel])

        help_text = self.command_service.format_help_text(
            data, request.sender.char_id, named_params.show_regex)
        if help_text:
            return self.command_service.format_help_text_blob(
                help_topic, help_text)

        # check if help topic matches a module
        data = self.db.query(
            "SELECT command, sub_command, access_level FROM command_config "
            "WHERE module = ? AND channel = ? AND enabled = 1",
            [help_topic, request.channel])

        help_text = self.command_service.format_help_text(
            data, request.sender.char_id, named_params.show_regex)
        if help_text:
            return self.command_service.format_help_text_blob(
                help_topic, help_text)

        return f"Could not find help on <highlight>{help_topic}</highlight>."