Exemple #1
0
 def test_named_parameters(self):
     param = NamedParameters(["test1", "test2", "test3"])
     self.assertEqual({
         'test1': '1',
         'test2': '2',
         'test3': '3'
     }, self.param_test(param, "--test1=1 --test2=2 --test3=3"))
     self.assertEqual({
         'test1': '1',
         'test2': '2',
         'test3': '3'
     }, self.param_test(param, "--test3=3 --test2=2 --test1=1"))
     self.assertEqual({
         'test1': '',
         'test2': '2',
         'test3': ''
     }, self.param_test(param, "--test2=2"))
     self.assertEqual(
         {
             'test1': 'one and two and three',
             'test2': '',
             'test3': ''
         }, self.param_test(param, "--test1=one and two and three"))
     self.assertEqual(
         {
             'test1': 'one and two',
             'test2': '',
             'test3': 'three and four'
         },
         self.param_test(param,
                         "--test1=one and two --test3=three and four"))
     self.assertIsNone(self.param_test(param, ""))
class CommandListController:
    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.ts: TranslationService = registry.get_instance("translation_service")
        self.getresp = self.ts.get_response

    @command(command="config", params=[Const("cmdlist"), NamedParameters(["access_level"])], access_level="admin",
             description="List all commands")
    def config_cmdlist_cmd(self, request, _, named_params):
        sql = "SELECT access_level, channel, enabled, command, module, sub_command FROM command_config"
        params = []
        if named_params.access_level:
            sql += " WHERE access_level = ?"
            params.append(named_params.access_level)
        sql += " ORDER BY module, command, sub_command, channel"
        data = self.db.query(sql, params)

        blob = ""
        current_module = ""
        current_command_key = ""
        count = 0
        temp_rows = []
        for row in data:
            if current_module != row.module:
                if temp_rows:
                    blob += self.display_row_data(temp_rows)
                    temp_rows = []
                blob += "\n<pagebreak><header2>%s</header2>\n" % row.module
                current_module = row.module
                current_command_key = ""

            command_key = self.command_service.get_command_key(row.command, row.sub_command)
            if current_command_key != command_key:
                if temp_rows:
                    blob += self.display_row_data(temp_rows)
                    temp_rows = []
                count += 1
                blob += "%s - " % (self.text.make_tellcmd(command_key, "config cmd " + command_key))
                current_command_key = command_key

            temp_rows.append(row)

        if temp_rows:
            blob += self.display_row_data(temp_rows)

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

    def display_row_data(self, rows):
        return "[%s %s]\n" % (self.get_enabled_str(rows), self.get_access_levels_str(rows))

    def get_access_levels_str(self, rows):
        access_levels = list(map(lambda x: x.access_level, rows))
        if all(x == access_levels[0] for x in access_levels):
            return access_levels[0]
        else:
            return ",".join(access_levels)

    def get_enabled_str(self, rows):
        enabled = list(map(lambda x: x.enabled, rows))

        blob = ""
        if all(x == enabled[0] for x in enabled):
            blob += self.format_enabled(enabled[0])
        else:
            for x in enabled:
                blob += self.format_enabled(x)

        return blob

    def format_enabled(self, enabled):
        return "<green>E</green>" if enabled else "<red>D</red>"
Exemple #3
0
class TowerMessagesController:
    MESSAGE_SOURCE = "tower_attacks"

    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 = registry.get_instance("bot")
        self.db = registry.get_instance("db")
        self.text = registry.get_instance("text")
        self.util = registry.get_instance("util")
        self.event_service = registry.get_instance("event_service")
        self.command_alias_service = registry.get_instance(
            "command_alias_service")
        self.pork_service = registry.get_instance("pork_service")
        self.message_hub_service = registry.get_instance("message_hub_service")
        self.playfield_controller: PlayfieldController = registry.get_instance(
            "playfield_controller")

    def pre_start(self):
        self.message_hub_service.register_message_source(self.MESSAGE_SOURCE)

        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)

    def start(self):
        self.db.exec(
            "CREATE TABLE IF NOT EXISTS tower_attacker (id INT PRIMARY KEY AUTO_INCREMENT, att_org_name VARCHAR(50) NOT NULL, att_faction VARCHAR(10) NOT NULL, "
            "att_char_id INT, att_char_name VARCHAR(20) NOT NULL, att_level INT NOT NULL, att_ai_level INT NOT NULL, att_profession VARCHAR(15) NOT NULL, "
            "x_coord INT NOT NULL, y_coord INT NOT NULL, is_victory SMALLINT NOT NULL, "
            "tower_battle_id INT NOT NULL, created_at INT NOT NULL)")
        self.db.exec(
            "CREATE TABLE IF NOT EXISTS tower_battle (id INT PRIMARY KEY AUTO_INCREMENT, playfield_id INT NOT NULL, site_number INT NOT NULL, "
            "def_org_name VARCHAR(50) NOT NULL, def_faction VARCHAR(10) NOT NULL, is_finished INT NOT NULL, battle_type VARCHAR(20) NOT NULL, last_updated INT NOT NULL)"
        )

        self.command_alias_service.add_alias("victory", "attacks")
        self.command_alias_service.add_alias("battles", "attacks")

    @command(command="attacks",
             params=[Const("battle"), Int("battle_id")],
             access_level="all",
             description="Show battle info for a specific battle")
    def attacks_battle_cmd(self, request, _, battle_id):
        battle = self.get_battle(battle_id)
        if not battle:
            return "Could not find battle with ID <highlight>%d</highlight>." % battle_id

        blob = self.check_for_all_towers_channel() + self.get_battle_blob(
            battle)

        return ChatBlob("Battle Info %d" % battle_id, blob)

    @command(command="attacks",
             params=[
                 Any("playfield", is_optional=True, allowed_chars="[a-z0-9 ]"),
                 Int("site_number", is_optional=True),
                 NamedParameters([
                     "defender_org", "attacker_org", "attacker", "victory",
                     "page"
                 ])
             ],
             access_level="all",
             description="Show recent tower attacks and victories",
             extended_description=
             "Victory param can be 'true', 'false', or 'all' (default)")
    def attacks_cmd(self, request, playfield_name, site_number, named_params):
        playfield = None
        if playfield_name:
            playfield = self.playfield_controller.get_playfield_by_name_or_id(
                playfield_name)
            if not playfield:
                return f"Could not find playfield <highlight>{playfield_name}</highlight>."

        page_number = int(named_params.page or "1")
        victory = "all" if named_params.victory == "" else named_params.victory

        command_str = "attacks"
        if playfield_name:
            command_str += " " + playfield_name
            if site_number:
                command_str += " " + str(site_number)

        page_size = 10
        offset = (page_number - 1) * page_size

        sql = "SELECT DISTINCT b.*, p.long_name, p.short_name FROM tower_battle b " \
              "LEFT JOIN playfields p ON b.playfield_id = p.id " \
              "LEFT JOIN tower_attacker a ON b.id = a.tower_battle_id " \
              "WHERE 1=1"
        params = []

        if playfield:
            sql += " AND b.playfield_id = ?"
            params.append(playfield.id)
            if site_number:
                sql += " AND b.site_number = ?"
                params.append(site_number)

        if victory != "all":
            command_str += f" --victory={victory}"
            sql += " AND b.is_finished = ?"
            if victory == "true":
                params.append(1)
            elif victory == "false":
                params.append(0)
            else:
                return "Named param <highlight>--victory</highlight> must be one of: 'true', 'false', or 'all' (default)."

        if named_params.defender_org:
            defender_org = named_params.defender_org
            command_str += f" --defender_org={defender_org}"
            sql += f" AND b.def_org_name <EXTENDED_LIKE={len(params)}> ?"
            params.append(defender_org)

        if named_params.attacker_org:
            attacker_org = named_params.attacker_org
            command_str += f" --attacker_org={attacker_org}"
            sql += f" AND a.att_org_name <EXTENDED_LIKE={len(params)}> ?"
            params.append(attacker_org)

        if named_params.attacker:
            attacker = named_params.attacker
            command_str += f" --attacker={attacker}"
            sql += f" AND a.att_char_name LIKE ? OR a.att_char_id = ?"
            params.append(attacker)
            params.append(attacker)

        sql += " ORDER BY b.last_updated DESC LIMIT ?, ?"
        params.append(offset)
        params.append(page_size)

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

        t = int(time.time())

        blob = self.check_for_all_towers_channel()
        blob += self.text.get_paging_links(command_str, page_number,
                                           page_size == len(data))
        blob += "\n\n"
        for row in data:
            blob += "\n<pagebreak>"
            blob += self.format_battle_info(row, t)
            blob += "<header2>Attackers:</header2>\n"
            sql2 = """SELECT a.*, COALESCE(a.att_level, 0) AS att_level, COALESCE(a.att_ai_level, 0) AS att_ai_level
                    FROM tower_attacker a
                    WHERE a.tower_battle_id = ?
                    ORDER BY created_at DESC"""
            data2 = self.db.query(sql2, [row.id])
            for row2 in data2:
                blob += "<tab>" + self.format_attacker(row2)
                if row2.is_victory:
                    blob += " - <notice>Winner!</notice>"
                blob += "\n"
            if not data2:
                blob += "<tab>Unknown attacker\n"

        title = "Tower Attacks"
        if playfield:
            title += f" ({playfield.long_name}"
            if site_number:
                title += " " + str(site_number)
            title += ")"

        return ChatBlob(title, blob)

    @event(event_type="connect",
           description="Check if All Towers channel is available",
           is_system=True)
    def handle_connect_event(self, event_type, event_data):
        conn = self.bot.get_primary_conn()
        if conn.org_id and self.ALL_TOWERS_ID not in conn.channels:
            self.logger.warning(
                "The primary bot is a member of an org but does not have access to 'All Towers' channel and therefore will not be able to record tower attacks"
            )

    def handle_public_channel_message(
            self, conn: Conn, packet: server_packets.PublicChannelMessage):
        # only listen to tower packets from first bot, to avoid triggering multiple times
        if conn != self.bot.get_primary_conn():
            return

        if packet.channel_id == self.TOWER_BATTLE_OUTCOME_ID:
            victory = self.get_victory_event(packet)
            if victory:
                self.fire_victory_event(victory)
        elif packet.channel_id == self.ALL_TOWERS_ID:
            attack = self.get_attack_event(packet)
            if attack:
                self.fire_attack_event(attack)

    def fire_victory_event(self, obj):
        # self.logger.debug("tower victory packet: %s" % str(packet))

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

        t = int(time.time())
        is_victory = 1
        is_finished = 1

        if obj.type == "attack":
            # get battle id and site_number
            last_updated = t - (6 * 3600)
            row = self.get_last_attack(obj.winner.faction, obj.winner.org_name,
                                       obj.loser.faction, obj.loser.org_name,
                                       obj.location.playfield.id, last_updated)

            if row:
                obj.battle_id = row.battle_id
                obj.location.site_number = row.site_number

                self.db.exec(
                    "UPDATE tower_attacker SET is_victory = ? WHERE id = ?",
                    [is_victory, row.attack_id])
                self.db.exec(
                    "UPDATE tower_battle SET is_finished = ?, last_updated = ? WHERE id = ?",
                    [is_finished, t, row.battle_id])
            else:
                obj.location.site_number = 0
                self.db.exec(
                    "INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)",
                    [
                        obj.location.playfield.id, obj.location.site_number,
                        obj.loser.org_name, obj.loser.faction, is_finished,
                        obj.type, t
                    ])
                obj.battle_id = self.db.last_insert_id()

                attacker = obj.winner or {}
                self.db.exec(
                    "INSERT INTO tower_attacker (att_org_name, att_faction, att_char_id, att_char_name, att_level, att_ai_level, att_profession, "
                    "x_coord, y_coord, is_victory, tower_battle_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                    [
                        attacker.get("org_name", ""),
                        attacker.get("faction", ""),
                        attacker.get("char_id", 0),
                        attacker.get("name", ""),
                        attacker.get("level", 0),
                        attacker.get("ai_level", 0),
                        attacker.get("profession", ""), 0, 0, is_victory,
                        obj.battle_id, t
                    ])

        elif obj.type == "terminated":
            obj.location.site_number = 0
            self.db.exec(
                "INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)",
                [
                    obj.location.playfield.id, obj.location.site_number,
                    obj.loser.org_name, obj.loser.faction, is_finished,
                    obj.type, t
                ])
            obj.battle_id = self.db.last_insert_id()
        else:
            raise Exception("Unknown victory event type: '%s'" % obj.type)

        self.event_service.fire_event(self.TOWER_VICTORY_EVENT, obj)

    def fire_attack_event(self, obj):
        # self.logger.debug("tower attack packet: %s" % str(packet))

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

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

        obj.location.site_number = self.find_closest_site_number(
            obj.location.playfield.id, obj.location.x_coord,
            obj.location.y_coord)

        attacker = obj.attacker or {}
        defender = obj.defender
        location = obj.location

        t = int(time.time())
        battle = self.find_or_create_battle(obj.location.playfield.id,
                                            obj.location.site_number,
                                            defender.org_name,
                                            defender.faction, "attack", t)
        obj.battle_id = battle.id

        self.db.exec(
            "INSERT INTO tower_attacker (att_org_name, att_faction, att_char_id, att_char_name, att_level, att_ai_level, att_profession, "
            "x_coord, y_coord, is_victory, tower_battle_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
            [
                attacker.get("org_name", ""),
                attacker.get("faction", ""),
                attacker.get("char_id", 0),
                attacker.get("name", ""),
                attacker.get("level", 0),
                attacker.get("ai_level", 0),
                attacker.get("profession", ""), location.x_coord,
                location.y_coord, 0, battle.id, t
            ])
        attacker_id = self.db.last_insert_id()

        attacker_row = self.db.query_single(
            "SELECT * FROM tower_attacker WHERE id = ?", [attacker_id])
        more_info = self.text.paginate_single(
            ChatBlob(
                "More Info",
                self.text.make_tellcmd("More Info",
                                       f"attacks battle {battle.id}")),
            self.bot.get_primary_conn())
        msg = "%s attacked %s [%s] at %s %s %s" % (
            self.format_attacker(attacker_row), defender.org_name,
            self.text.get_formatted_faction(defender.faction),
            location.playfield.get("short_name",
                                   location.playfield.get("long_name")),
            obj.location.site_number or "?", more_info)
        self.message_hub_service.send_message(self.MESSAGE_SOURCE, None, None,
                                              msg)

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

    def get_attack_event(self, packet: server_packets.PublicChannelMessage):
        t = int(time.time())

        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({
                "timestamp": t,
                "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({
                    "timestamp": t,
                    "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

        t = int(time.time())

        match = self.VICTORY_2.match(packet.message)
        if match:
            return DictObject({
                "type": "attack",
                "timestamp": t,
                "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({
                "type": "terminated",
                "timestamp": t,
                "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

    def format_attacker(self, row):
        level = ("%d/<green>%d</green>" % (row.att_level, row.att_ai_level)
                 ) if row.att_ai_level > 0 else "%d" % row.att_level
        org = row.att_org_name + " " if row.att_org_name else ""
        return "%s (%s %s) %s[%s]" % (
            row.att_char_name or "Unknown attacker", level, row.att_profession
            or "Unknown", org, self.text.get_formatted_faction(
                row.att_faction))

    def find_closest_site_number(self, playfield_id, x_coord, y_coord):
        sql = "SELECT site_number FROM tower_site_bounds " \
              "WHERE playfield_id = ? AND x_coord1 <= ? AND x_coord2 >= ? AND y_coord1 >= ? AND y_coord2 <= ? " \
              "LIMIT 1"
        row = self.db.query_single(
            sql, [playfield_id, x_coord, x_coord, y_coord, y_coord])
        if row:
            return row.site_number

        sql = """
            SELECT
                site_number,
                ((x_distance * x_distance) + (y_distance * y_distance)) radius
            FROM
                (SELECT
                    playfield_id,
                    site_number,
                    min_ql,
                    max_ql,
                    x_coord,
                    y_coord,
                    site_name,
                    (x_coord - ?) as x_distance,
                    (y_coord - ?) as y_distance
                FROM
                    tower_site
                WHERE
                    playfield_id = ?) t
            ORDER BY
                radius ASC
            LIMIT 1"""

        row = self.db.query_single(sql, [x_coord, y_coord, playfield_id])
        if row:
            return row.site_number
        else:
            return 0

    def find_or_create_battle(self, playfield_id, site_number, org_name,
                              faction, battle_type, t):
        last_updated = t - (8 * 3600)
        is_finished = 0

        sql = """
            SELECT
                id
            FROM
                tower_battle
            WHERE
                playfield_id = ?
                AND site_number = ?
                AND is_finished = ?
                AND def_org_name = ?
                AND def_faction = ?
                AND last_updated >= ?
        """

        battle = self.db.query_single(sql, [
            playfield_id, site_number, is_finished, org_name, faction,
            last_updated
        ])

        if battle:
            self.db.exec(
                "UPDATE tower_battle SET last_updated = ? WHERE id = ?",
                [t, battle.id])
            battle_id = battle.id
        else:
            self.db.exec(
                "INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)",
                [
                    playfield_id, site_number, org_name, faction, is_finished,
                    battle_type, t
                ])
            battle_id = self.db.last_insert_id()

        return self.get_battle(battle_id)

    def get_last_attack(self, att_faction, att_org_name, def_faction,
                        def_org_name, playfield_id, last_updated):
        is_finished = 0

        sql = """
            SELECT
                b.id AS battle_id,
                a.id AS attack_id,
                b.site_number
            FROM
                tower_battle b
                JOIN tower_attacker a ON
                    a.tower_battle_id = b.id 
            WHERE
                a.att_faction = ?
                AND a.att_org_name = ?
                AND b.def_faction = ?
                AND b.def_org_name = ?
                AND b.playfield_id = ?
                AND b.is_finished = ?
                AND b.last_updated >= ?
            ORDER BY
                last_updated DESC
            LIMIT 1"""

        return self.db.query_single(sql, [
            att_faction, att_org_name, def_faction, def_org_name, playfield_id,
            is_finished, last_updated
        ])

    def format_battle_info(self, row, t, verbose=False):
        blob = ""
        defeated = " - <notice>Defeated!</notice>" if row.is_finished else ""
        blob += f"Site: %s " % self.text.make_tellcmd(
            f"{row.short_name} {row.site_number}",
            f"lc {row.short_name} {row.site_number}")
        if not verbose:
            blob += self.text.make_tellcmd("More Info",
                                           "attacks battle %d" % row.id)
        blob += "\n"
        if verbose:
            if row.site_number:
                blob += f"Long name: <highlight>{row.site_name}, {row.long_name}</highlight>\n"
                blob += f"Level range: <highlight>{row.min_ql}-{row.max_ql}</highlight>\n"
                blob += "Coordinates: %s\n" % self.text.make_chatcmd(
                    f"{row.x_coord}x{row.y_coord}",
                    f"/waypoint {row.x_coord} {row.y_coord} {row.playfield_id}"
                )
            else:
                blob += f"Long name: Unknown\n"
                blob += f"Level range: Unknown\n"
                blob += "Coordinates: Unknown\n"
        blob += f"Defender: %s [%s]%s\n" % (row.def_org_name,
                                            self.text.get_formatted_faction(
                                                row.def_faction), defeated)
        blob += "Last Activity: %s\n" % self.format_timestamp(
            row.last_updated, t)
        return blob

    def format_timestamp(self, t, current_t):
        return "<highlight>%s</highlight> (%s ago)" % (
            self.util.format_datetime(t),
            self.util.time_to_readable(current_t - t))

    def check_for_all_towers_channel(self):
        if self.ALL_TOWERS_ID not in self.bot.get_primary_conn().channels:
            return "Notice: The primary bot must belong to an org and be promoted to a rank that is high enough to have the All Towers channel (e.g., Squad Commander) in order for this command to work correctly.\n\n"
        else:
            return ""

    def get_battle_blob(self, battle):
        t = int(time.time())

        attackers = self.db.query(
            "SELECT * FROM tower_attacker WHERE tower_battle_id = ? ORDER BY created_at DESC",
            [battle.id])

        first_activity = attackers[-1].created_at if len(
            attackers) > 0 else battle.last_updated

        blob = ""
        blob += self.format_battle_info(battle, t, verbose=True)
        blob += "Duration: <highlight>%s</highlight>\n\n" % self.util.time_to_readable(
            battle.last_updated - first_activity)
        blob += "<header2>Attackers:</header2>\n"

        for row in attackers:
            blob += "<tab>" + self.format_attacker(row)
            blob += " " + self.format_timestamp(row.created_at, t)
            if row.is_victory:
                blob += " - <notice>Winner!</notice>"
            blob += "\n"

        return blob

    def get_battle(self, battle_id):
        return self.db.query_single(
            "SELECT b.*, p.short_name, p.long_name, t.site_name, t.x_coord, t.y_coord, t.min_ql, t.max_ql "
            "FROM tower_battle b "
            "LEFT JOIN playfields p ON p.id = b.playfield_id "
            "LEFT JOIN tower_site t ON b.playfield_id = t.playfield_id AND b.site_number = t.site_number "
            "WHERE b.id = ?", [battle_id])
Exemple #4
0
class UsageController:

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

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

    @command(command="usage", params=[Time("max_time", is_optional=True), NamedParameters(["cmd", "page"])], access_level="moderator",
             description="Show command usage summary")
    def usage_command(self, request, max_time, named_params):
        page_size = 20
        page_number = int(named_params.page or "1")

        params = []
        sql = "SELECT command, count(1) AS count FROM command_usage WHERE 1=1"
        if max_time:
            sql += " AND created_at > ?"
            t = int(time.time())
            params.append(t - max_time)

        if named_params.cmd:
            sql += " AND command LIKE ?"
            params.append(named_params.cmd)

        sql += " GROUP BY command ORDER BY count(1) DESC LIMIT ?, ?"
        offset, limit = self.util.get_offset_limit(page_size, page_number)
        params.append(offset)
        params.append(limit)
        data = self.db.query(sql, params)

        rows = []
        for row in data:
            rows.append([f"<highlight>{row.count}</highlight>", row.command])

        display_table = self.text.pad_table(rows, " ", pad_right=False)

        cmd_string = "usage"
        if max_time:
            cmd_string += f" {max_time}"
        if named_params.cmd:
            cmd_string += f" --cmd={named_params.cmd}"

        blob = ""
        blob += self.text.get_paging_links(cmd_string, page_number, len(data) == page_size) + "\n\n"
        for cols in display_table:
            blob += "  ".join(cols) + "\n"

        if max_time:
            time_str = self.util.time_to_readable(max_time)
            title = f"Command Usage for {time_str}"
        else:
            title = "Command Usage"

        return ChatBlob(title, blob)

    @command(command="usage", params=[Const("history"), NamedParameters(["cmd", "char", "page"])], access_level="moderator",
             description="Show command usage history")
    def usage_history_command(self, request, _, named_params):
        page_size = 20
        page_number = int(named_params.page or "1")

        params = []
        sql = "SELECT channel, command, created_at, handler, p.name, c.char_id FROM command_usage c " \
              "LEFT JOIN player p ON c.char_id = p.char_id " \
              "WHERE 1=1"

        if named_params.cmd:
            sql += " AND (command LIKE ? OR handler LIKE ?)"
            params.append(named_params.cmd)
            params.append(named_params.cmd)

        if named_params.char:
            char_id = self.character_service.resolve_char_to_id(named_params.char)
            if not char_id:
                return StandardMessage.char_not_found(named_params.char)

            sql += " AND c.char_id = ?"
            params.append(char_id)

        sql += " ORDER BY created_at DESC LIMIT ?, ?"
        offset, limit = self.util.get_offset_limit(page_size, page_number)
        params.append(offset)
        params.append(limit)
        data = self.db.query(sql, params)

        rows = []
        for row in data:
            rows.append([row.command, row.channel, row.handler, (row.name or "Unknown(%s)" % row.char_id), self.util.format_datetime(row.created_at)])

        display_table = self.text.pad_table(rows, " ", pad_right=False)

        blob = ""
        cmd_string = "usage history"
        if named_params.cmd:
            cmd_string += f" --cmd={named_params.cmd}"
        if named_params.char:
            cmd_string += f" --char={named_params.char}"

        blob += self.text.get_paging_links(cmd_string, page_number, len(data) == page_size) + "\n\n"
        for cols in display_table:
            blob += "  ".join(cols) + "\n"

        title = "Command History"

        return ChatBlob(title, blob)
Exemple #5
0
class RandomController:
    def inject(self, registry):
        self.db: DB = registry.get_instance("db")
        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.db.exec(
            "CREATE TABLE IF NOT EXISTS roll (id INT PRIMARY KEY AUTO_INCREMENT, created_at INT NOT NULL, char_id INT NOT NULL, options VARCHAR(2048), result VARCHAR(255))"
        )

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

    @command(command="random",
             params=[Any("items"),
                     NamedParameters(["separator"])],
             access_level="all",
             description="Randomly order a list of elements",
             extended_description="Default separator is a space.")
    def random_command(self, request, items, named_params):
        separator = named_params.separator if named_params.separator else " "
        items = items.split(separator)
        random.shuffle(items)
        return separator.join(items)

    @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</highlight>." % 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 "<highlight>%s</highlight> rolled by <highlight>%s</highlight> %s ago. Possible options: %s." % (
                row.result, name, time_string, row.options)

    @command(
        command="roll",
        params=[Int("start_value", is_optional=True),
                Int("end_value")],
        access_level="all",
        description=
        "Roll a number between start_value and end_value number (inclusive)",
        extended_description="Start_value is assumed 1 if not provided.")
    def roll_number_command(self, request, start_value, end_value):
        start_value = start_value or 1
        if start_value > end_value:
            end = start_value
            start = end_value
        else:
            start = start_value
            end = end_value
        result = random.randint(start, end)
        options = "value between %d and %d" % (start, end)
        self.db.exec(
            "INSERT INTO roll (created_at, char_id, options, result) VALUES (?, ?, ?, ?)",
            [int(time.time()), request.sender.char_id, options, result])
        return "The roll is <highlight>%d</highlight> out of values between %d and %d. To verify do /tell <myname> verify %d" % (
            result, start, end, self.db.last_insert_id())

    # Keep this method at the bottom of file otherwise it will precede over all other commands
    @command(
        command="roll",
        params=[Any("items")],
        access_level="all",
        description="Roll a random value from a list of space-delimited values"
    )
    def roll_text_variables_command(self, request, items):
        options = items.split(" ")
        result = random.choice(options)
        self.db.exec(
            "INSERT INTO roll (created_at, char_id, options, result) VALUES (?, ?, ?, ?)",
            [int(time.time()), request.sender.char_id, items, result])
        return "The roll is <highlight>%s</highlight> out of possible options: %s. To verify do /tell <myname> verify %d" % (
            result, items, self.db.last_insert_id())
class TowerAttackController:
    MESSAGE_SOURCE = "tower_attacks"

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

    def inject(self, registry):
        self.bot = registry.get_instance("bot")
        self.db = registry.get_instance("db")
        self.text = registry.get_instance("text")
        self.util = registry.get_instance("util")
        self.setting_service = registry.get_instance("setting_service")
        self.event_service = registry.get_instance("event_service")
        self.command_alias_service = registry.get_instance(
            "command_alias_service")
        self.public_channel_service = registry.get_instance(
            "public_channel_service")
        self.message_hub_service = registry.get_instance("message_hub_service")

    def pre_start(self):
        self.message_hub_service.register_message_source(self.MESSAGE_SOURCE)

    def start(self):
        self.db.exec(
            "CREATE TABLE IF NOT EXISTS tower_attacker (id INT PRIMARY KEY AUTO_INCREMENT, att_org_name VARCHAR(50) NOT NULL, att_faction VARCHAR(10) NOT NULL, "
            "att_char_id INT, att_char_name VARCHAR(20) NOT NULL, att_level INT NOT NULL, att_ai_level INT NOT NULL, att_profession VARCHAR(15) NOT NULL, "
            "x_coord INT NOT NULL, y_coord INT NOT NULL, is_victory SMALLINT NOT NULL, "
            "tower_battle_id INT NOT NULL, created_at INT NOT NULL)")
        self.db.exec(
            "CREATE TABLE IF NOT EXISTS tower_battle (id INT PRIMARY KEY AUTO_INCREMENT, playfield_id INT NOT NULL, site_number INT NOT NULL, "
            "def_org_name VARCHAR(50) NOT NULL, def_faction VARCHAR(10) NOT NULL, is_finished INT NOT NULL, battle_type VARCHAR(20) NOT NULL, last_updated INT NOT NULL)"
        )

        self.command_alias_service.add_alias("victory", "attacks")

        self.setting_service.register(self.module_name,
                                      "show_tower_attack_messages", True,
                                      BooleanSettingType(),
                                      "Show tower attack messages")

    @command(command="attacks",
             params=[NamedParameters(["page"])],
             access_level="all",
             description="Show recent tower attacks and victories")
    def attacks_cmd(self, request, named_params):
        page_number = int(named_params.page or "1")

        page_size = 10
        offset = (page_number - 1) * page_size

        sql = """SELECT b.*, p.long_name, p.short_name 
            FROM tower_battle b LEFT JOIN playfields p ON b.playfield_id = p.id 
            ORDER bY b.last_updated DESC LIMIT ?, ?"""
        data = self.db.query(sql, [offset, page_size])

        t = int(time.time())

        blob = self.check_for_all_towers_channel()
        blob += self.text.get_paging_links(f"attacks", page_number,
                                           page_size == len(data))
        blob += "\n\n"
        for row in data:
            blob += "\n<pagebreak>"
            blob += self.format_battle_info(row, t)
            blob += self.text.make_tellcmd("More Info",
                                           "attacks battle %d" % row.id) + "\n"
            blob += "<header2>Attackers:</header2>\n"
            sql2 = """SELECT a.*, COALESCE(a.att_level, 0) AS att_level, COALESCE(a.att_ai_level, 0) AS att_ai_level
                FROM tower_attacker a
                WHERE a.tower_battle_id = ?
                ORDER BY created_at DESC"""
            data2 = self.db.query(sql2, [row.id])
            for row2 in data2:
                blob += "<tab>" + self.format_attacker(row2) + "\n"
            if not data2:
                blob += "<tab>Unknown attacker\n"

        return ChatBlob("Tower Attacks", blob)

    @command(command="attacks",
             params=[Const("battle"), Int("battle_id")],
             access_level="all",
             description="Show battle info for a specific battle")
    def attacks_battle_cmd(self, request, _, battle_id):
        battle = self.get_battle(battle_id)
        if not battle:
            return "Could not find battle with ID <highlight>%d</highlight>." % battle_id

        blob = self.check_for_all_towers_channel() + self.get_battle_blob(
            battle)

        return ChatBlob("Battle Info %d" % battle_id, blob)

    @event(event_type=TowerController.TOWER_ATTACK_EVENT,
           description="Record tower attacks",
           is_hidden=True)
    def tower_attack_event(self, event_type, event_data):
        t = int(time.time())
        site_number = self.find_closest_site_number(
            event_data.location.playfield.id, event_data.location.x_coord,
            event_data.location.y_coord)

        attacker = event_data.attacker or {}
        defender = event_data.defender
        location = event_data.location

        battle = self.find_or_create_battle(event_data.location.playfield.id,
                                            site_number, defender.org_name,
                                            defender.faction, "attack", t)

        self.db.exec(
            "INSERT INTO tower_attacker (att_org_name, att_faction, att_char_id, att_char_name, att_level, att_ai_level, att_profession, "
            "x_coord, y_coord, is_victory, tower_battle_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
            [
                attacker.get("org_name", ""),
                attacker.get("faction", ""),
                attacker.get("char_id", 0),
                attacker.get("name", ""),
                attacker.get("level", 0),
                attacker.get("ai_level", 0),
                attacker.get("profession", ""), location.x_coord,
                location.y_coord, 0, battle.id, t
            ])
        attacker_id = self.db.last_insert_id()

        if self.setting_service.get("show_tower_attack_messages").get_value():
            attacker_row = self.db.query_single(
                "SELECT * FROM tower_attacker WHERE id = ?", [attacker_id])
            more_info = self.text.paginate_single(
                ChatBlob(
                    "More Info",
                    self.text.make_tellcmd("More Info",
                                           f"attacks battle {battle.id}")),
                self.bot.get_primary_conn())
            msg = "%s attacked %s [%s] at %s %s %s" % (
                self.format_attacker(attacker_row), defender.org_name,
                self.text.get_formatted_faction(defender.faction),
                location.playfield.get("short_name",
                                       location.playfield.get("long_name")),
                site_number or "?", more_info)
            self.message_hub_service.send_message(self.MESSAGE_SOURCE, None,
                                                  None, msg)

    @event(event_type=TowerController.TOWER_VICTORY_EVENT,
           description="Record tower victories",
           is_hidden=True)
    def tower_victory_event(self, event_type, event_data):
        t = int(time.time())

        if event_data.type == "attack":
            row = self.get_last_attack(event_data.winner.faction,
                                       event_data.winner.org_name,
                                       event_data.loser.faction,
                                       event_data.loser.org_name,
                                       event_data.location.playfield.id, t)

            if not row:
                site_number = 0
                is_finished = 1
                self.db.exec(
                    "INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)",
                    [
                        event_data.location.playfield.id, site_number,
                        event_data.loser.org_name, event_data.loser.faction,
                        is_finished, event_data.type, t
                    ])
                battle_id = self.db.last_insert_id()

                attacker = event_data.winner or {}
                self.db.exec(
                    "INSERT INTO tower_attacker (att_org_name, att_faction, att_char_id, att_char_name, att_level, att_ai_level, att_profession, "
                    "x_coord, y_coord, is_victory, tower_battle_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                    [
                        attacker.get("org_name", ""),
                        attacker.get("faction", ""),
                        attacker.get("char_id", 0),
                        attacker.get("name", ""),
                        attacker.get("level", 0),
                        attacker.get("ai_level", 0),
                        attacker.get("profession", ""), 0, 0, 0, battle_id, t
                    ])
            else:
                is_victory = 1
                self.db.exec(
                    "UPDATE tower_attacker SET is_victory = ? WHERE id = ?",
                    [is_victory, row.attack_id])

                is_finished = 1
                self.db.exec(
                    "UPDATE tower_battle SET is_finished = ?, last_updated = ? WHERE id = ?",
                    [is_finished, t, row.battle_id])
        elif event_data.type == "terminated":
            site_number = 0
            is_finished = 1
            self.db.exec(
                "INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)",
                [
                    event_data.location.playfield.id, site_number,
                    event_data.loser.org_name, event_data.loser.faction,
                    is_finished, event_data.type, t
                ])
        else:
            raise Exception("Unknown victory event type: '%s'" %
                            event_data.type)

    def format_attacker(self, row):
        level = ("%d/<green>%d</green>" % (row.att_level, row.att_ai_level)
                 ) if row.att_ai_level > 0 else "%d" % row.att_level
        org = row.att_org_name + " " if row.att_org_name else ""
        victor = " - <notice>Winner!</notice>" if row.is_victory else ""
        return "%s (%s %s) %s[%s]%s" % (
            row.att_char_name or "Unknown attacker", level, row.att_profession,
            org, self.text.get_formatted_faction(row.att_faction), victor)

    def find_closest_site_number(self, playfield_id, x_coord, y_coord):
        sql = """
            SELECT
                site_number,
                ((x_distance * x_distance) + (y_distance * y_distance)) radius
            FROM
                (SELECT
                    playfield_id,
                    site_number,
                    min_ql,
                    max_ql,
                    x_coord,
                    y_coord,
                    site_name,
                    (x_coord - ?) as x_distance,
                    (y_coord - ?) as y_distance
                FROM
                    tower_site
                WHERE
                    playfield_id = ?) t
            ORDER BY
                radius ASC
            LIMIT 1"""

        row = self.db.query_single(sql, [x_coord, y_coord, playfield_id])
        if row:
            return row.site_number
        else:
            return 0

    def find_or_create_battle(self, playfield_id, site_number, org_name,
                              faction, battle_type, t):
        last_updated = t - (8 * 3600)
        is_finished = 0

        sql = """
            SELECT
                id
            FROM
                tower_battle
            WHERE
                playfield_id = ?
                AND site_number = ?
                AND is_finished = ?
                AND def_org_name = ?
                AND def_faction = ?
                AND last_updated >= ?
        """

        battle = self.db.query_single(sql, [
            playfield_id, site_number, is_finished, org_name, faction,
            last_updated
        ])

        if battle:
            self.db.exec(
                "UPDATE tower_battle SET last_updated = ? WHERE id = ?",
                [t, battle.id])
            battle_id = battle.id
        else:
            self.db.exec(
                "INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)",
                [
                    playfield_id, site_number, org_name, faction, is_finished,
                    battle_type, t
                ])
            battle_id = self.db.last_insert_id()

        return self.get_battle(battle_id)

    def get_last_attack(self, att_faction, att_org_name, def_faction,
                        def_org_name, playfield_id, t):
        last_updated = t - (8 * 3600)
        is_finished = 0

        sql = """
            SELECT
                b.id AS battle_id,
                a.id AS attack_id
            FROM
                tower_battle b
                JOIN tower_attacker a ON
                    a.tower_battle_id = b.id 
            WHERE
                a.att_faction = ?
                AND a.att_org_name = ?
                AND b.def_faction = ?
                AND b.def_org_name = ?
                AND b.playfield_id = ?
                AND b.is_finished = ?
                AND b.last_updated >= ?
            ORDER BY
                last_updated DESC
            LIMIT 1"""

        return self.db.query_single(sql, [
            att_faction, att_org_name, def_faction, def_org_name, playfield_id,
            is_finished, last_updated
        ])

    def format_battle_info(self, row, t, verbose=False):
        blob = ""
        defeated = " - <notice>Defeated!</notice>" if row.is_finished else ""
        blob += "Site: <highlight>%s %s</highlight>\n" % (
            row.short_name, row.site_number or "?")
        if verbose:
            if row.site_number:
                blob += f"Long name: <highlight>{row.site_name}, {row.long_name}</highlight>\n"
                blob += f"Level range: <highlight>{row.min_ql}-{row.max_ql}</highlight>\n"
                blob += "Coordinates: %s\n" % self.text.make_chatcmd(
                    f"{row.x_coord}x{row.y_coord}",
                    f"/waypoint {row.x_coord} {row.y_coord} {row.playfield_id}"
                )
            else:
                blob += f"Long name: Unknown\n"
                blob += f"Level range: Unknown\n"
                blob += "Coordinates: Unknown\n"
        blob += f"Defender: %s [%s]%s\n" % (row.def_org_name,
                                            self.text.get_formatted_faction(
                                                row.def_faction), defeated)
        blob += "Last Activity: %s\n" % self.format_timestamp(
            row.last_updated, t)
        return blob

    def format_timestamp(self, t, current_t):
        return "<highlight>%s</highlight> (%s ago)" % (
            self.util.format_datetime(t),
            self.util.time_to_readable(current_t - t))

    def get_chat_command(self, page):
        return "/tell <myname> attacks --page=%d" % page

    def check_for_all_towers_channel(self):
        if TowerController.ALL_TOWERS_ID not in self.bot.get_primary_conn(
        ).channels:
            return "Notice: The primary bot must belong to an org and be promoted to a rank that is high enough to have the All Towers channel (e.g., Squad Commander) in order for the <symbol>attacks command to work correctly.\n\n"
        else:
            return ""

    def get_battle_blob(self, battle):
        t = int(time.time())

        attackers = self.db.query(
            "SELECT * FROM tower_attacker WHERE tower_battle_id = ? ORDER BY created_at DESC",
            [battle.id])

        first_activity = attackers[-1].created_at if len(
            attackers) > 0 else battle.last_updated

        blob = ""
        blob += self.format_battle_info(battle, t, verbose=True)
        blob += "Duration: <highlight>%s</highlight>\n\n" % self.util.time_to_readable(
            battle.last_updated - first_activity)
        blob += "<header2>Attackers:</header2>\n"

        for row in attackers:
            blob += "<tab>" + self.format_attacker(row)
            blob += " " + self.format_timestamp(row.created_at, t)
            blob += "\n"

        return blob

    def get_battle(self, battle_id):
        return self.db.query_single(
            "SELECT b.*, p.short_name, p.long_name, t.site_name, t.x_coord, t.y_coord, t.min_ql, t.max_ql "
            "FROM tower_battle b "
            "LEFT JOIN playfields p ON p.id = b.playfield_id "
            "LEFT JOIN tower_site t ON b.playfield_id = t.playfield_id AND b.site_number = t.site_number "
            "WHERE b.id = ?", [battle_id])
Exemple #7
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")
        self.ts: TranslationService = registry.get_instance(
            "translation_service")
        self.getresp = self.ts.get_response

    def start(self):
        self.ts.register_translation("module/help", self.load_help_msg)
        self.command_alias_service.add_alias("version", "about")

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

    @command(command="about",
             params=[],
             access_level="all",
             description="Show information about the development of this bot")
    def about_cmd(self, request):
        blob = self.getresp("module/help", "about_head")
        blob += self.getresp("module/help", "about_body")
        blob += self.getresp("module/help", "about_special_ones")
        blob += self.getresp("module/help", "about_improvers")
        blob += self.getresp("module/help", "about_bottom")
        return ChatBlob(
            self.getresp("module/help", "blob_title",
                         {"ver": self.bot.version}), blob)

    @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 + "<end>"

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

            if row.command != current_command:
                current_command = row.command
                blob += " " + self.text.make_chatcmd(
                    row.command, "/tell <myname> help " + row.command)

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

    @command(command="help",
             params=[Any("command"),
                     NamedParameters(["show_regex"])],
             access_level="all",
             description="Show help for a specific command")
    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

        show_regex = named_params.show_regex and named_params.show_regex.lower(
        ) == "true"

        help_text = self.command_service.get_help_text(request.sender.char_id,
                                                       help_topic,
                                                       request.channel,
                                                       show_regex)
        if help_text:
            return self.command_service.format_help_text(help_topic, help_text)
        else:
            return self.getresp("module/help", "no_help",
                                {"topic": help_topic})
Exemple #8
0
class NanoController:
    def inject(self, registry):
        self.db = registry.get_instance("db")
        self.util = registry.get_instance("util")
        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("nl", "nanolines")
        self.command_alias_service.add_alias("nanoline", "nanolines")

    @command(command="nano",
             params=[Any("search"), NamedParameters(["page"])],
             access_level="all",
             description="Search for a nano")
    def nano_cmd(self, request, search, named_params):
        page = int(named_params.page or "1")
        page_size = 30
        offset = (page - 1) * page_size

        sql = "SELECT n1.lowid, n1.lowql, n1.name, n1.location, n1.profession, n3.id AS nanoline_id, n3.name AS nanoline_name " \
              "FROM nanos n1 " \
              "LEFT JOIN nanos_nanolines_ref n2 ON n1.lowid = n2.lowid " \
              "LEFT JOIN nanolines n3 ON n2.nanolines_id = n3.id " \
              "WHERE n1.name <EXTENDED_LIKE=0> ? " \
              "ORDER BY n1.profession, n3.name, n1.lowql DESC, n1.name ASC"
        data = self.db.query(sql, [search], extended_like=True)
        count = len(data)
        paged_data = data[offset:offset + page_size]

        blob = ""

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

        current_nanoline = -1
        for row in paged_data:
            if current_nanoline != row.nanoline_id:
                if row.nanoline_name:
                    blob += "\n<header2>%s<end> - %s\n" % (
                        row.profession,
                        self.text.make_chatcmd(
                            row.nanoline_name,
                            "/tell <myname> nanolines %d" % row.nanoline_id))
                else:
                    blob += "\n<header2>Unknown/General<end>\n"
                current_nanoline = row.nanoline_id

            blob += "%s [%d] %s\n" % (self.text.make_item(
                row.lowid, row.lowid, row.lowql,
                row.name), row.lowql, row.location)
        blob += self.get_footer()

        return ChatBlob(
            "Nano Search Results for '%s' (%d - %d of %d)" %
            (search, offset + 1, min(offset + page_size, count), count), blob)

    @command(command="nanoloc",
             params=[],
             access_level="all",
             description="Show all nano locations")
    def nanoloc_list_cmd(self, request):
        data = self.db.query(
            "SELECT location, COUNT(location) AS cnt FROM nanos GROUP BY location ORDER BY location ASC"
        )

        blob = ""
        for row in data:
            blob += "%s (%d)\n" % (self.text.make_chatcmd(
                row.location,
                "/tell <myname> nanoloc %s" % row.location), row.cnt)
        blob += self.get_footer()

        return ChatBlob("Nano Locations", blob)

    @command(command="nanoloc",
             params=[Any("location")],
             access_level="all",
             description="Show nanos by location")
    def nanoloc_show_cmd(self, request, location):
        sql = "SELECT n1.lowid, n1.lowql, n1.name, n1.location, n3.profession " \
              "FROM nanos n1 LEFT JOIN nanos_nanolines_ref n2 ON n1.lowid = n2.lowid LEFT JOIN nanolines n3 ON n2.nanolines_id = n3.id " \
              "WHERE n1.location LIKE ? " \
              "ORDER BY n1.profession ASC, n1.name ASC"
        data = self.db.query(sql, [location])
        cnt = len(data)

        blob = ""
        for row in data:
            blob += "%s [%d] %s" % (self.text.make_item(
                row.lowid, row.lowid, row.lowql,
                row.name), row.lowql, row.location)
            if row.profession:
                blob += " - <highight>%s<end>" % row.profession
            blob += "\n"

        return ChatBlob("Nanos for Location '%s' (%d)" % (location, cnt), blob)

    @command(command="nanolines",
             params=[],
             access_level="all",
             description="Show nanos by nanoline")
    def nanolines_list_cmd(self, request):
        data = self.db.query(
            "SELECT DISTINCT profession FROM nanolines ORDER BY profession ASC"
        )

        blob = ""
        for row in data:
            blob += self.text.make_chatcmd(
                row.profession,
                "/tell <myname> nanolines %s" % row.profession) + "\n"
        blob += self.get_footer()

        return ChatBlob("Nanolines", blob)

    @command(command="nanolines",
             params=[Int("nanoline_id")],
             access_level="all",
             description="Show nanos by nanoline id")
    def nanolines_id_cmd(self, request, nanoline_id):
        nanoline = self.db.query_single("SELECT * FROM nanolines WHERE id = ?",
                                        [nanoline_id])

        if not nanoline:
            return "Could not find nanoline with ID <highlight>%d<end>." % nanoline_id

        data = self.db.query(
            "SELECT n1.lowid, n1.lowql, n1.name, n1.location "
            "FROM nanos n1 JOIN nanos_nanolines_ref n2 ON n1.lowid = n2.lowid "
            "WHERE n2.nanolines_id = ? "
            "ORDER BY n1.lowql DESC, n1.name ASC", [nanoline_id])

        blob = ""
        for row in data:
            blob += "%s [%d] %s\n" % (self.text.make_item(
                row.lowid, row.lowid, row.lowql,
                row.name), row.lowql, row.location)
        blob += self.get_footer()

        return ChatBlob("%s %s Nanos" % (nanoline.profession, nanoline.name),
                        blob)

    @command(command="nanolines",
             params=[Any("profession")],
             access_level="all",
             description="Show nanolines by profession")
    def nanolines_profession_cmd(self, request, prof_name):
        profession = self.util.get_profession(prof_name)
        if not profession:
            return "Could not find profession <highlight>%s<end>." % prof_name

        data = self.db.query(
            "SELECT * FROM nanolines WHERE profession = ? ORDER BY name ASC",
            [profession])

        blob = ""
        for row in data:
            blob += self.text.make_chatcmd(
                row.name, "/tell <myname> nanolines %d" % row.id) + "\n"
        blob += self.get_footer()

        return ChatBlob("%s Nanolines" % profession, blob)

    def get_footer(self):
        return "\n\nNanos DB provided by Saavick & Lucier"

    def get_chat_command(self, search, page):
        return "/tell <myname> nano %s --page=%d" % (search, page)
Exemple #9
0
class OrgActivityController:
    def __init__(self):
        self.logger = Logger(__name__)

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

    def start(self):
        self.db.exec("CREATE TABLE IF NOT EXISTS org_activity (id INT PRIMARY KEY AUTO_INCREMENT, actor_char_id INT NOT NULL, actee_char_id INT NOT NULL, "
                     "action VARCHAR(20) NOT NULL, created_at INT NOT NULL, org_id INT NOT NULL)")

        self.command_alias_service.add_alias("orghistory", "orgactivity")

    @command(command="orgactivity", params=[Character("char", is_optional=True), NamedParameters(["action", "page"])], access_level="org_member",
             description="Show org member activity")
    def orgactivity_cmd(self, request, char, named_params):
        page_size = 20
        page_number = int(named_params.page or "1")

        params = []
        filter_sql = "WHERE 1=1"
        if char:
            if not char.char_id:
                return StandardMessage.char_not_found(char.name)
            else:
                filter_sql += " AND (o.actor_char_id = ? OR o.actee_char_id = ?)"
                params.append(char.char_id)
                params.append(char.char_id)

        if named_params.action:
            named_params.action = named_params.action.lower()
            filter_sql += " AND o.action LIKE ?"
            params.append(named_params.action)

        offset, limit = self.util.get_offset_limit(page_size, page_number)
        params.append(offset)
        params.append(limit)

        sql = f"""
            SELECT
                p1.name AS actor,
                p2.name AS actee, o.action,
                o.created_at,
                o.org_id
            FROM
                org_activity o
                LEFT JOIN player p1 ON o.actor_char_id = p1.char_id
                LEFT JOIN player p2 ON o.actee_char_id = p2.char_id
            {filter_sql}
            ORDER BY
                o.created_at DESC
            LIMIT ?, ?
        """
        data = self.db.query(sql, params)

        command_str = "orgactivity"
        if char:
            command_str += " " + char.name
        if named_params.action:
            command_str += " --action=" + named_params.action

        blob = self.text.get_paging_links(command_str, page_number, len(data) == page_size) + "\n\n"
        for row in data:
            blob += self.format_org_action(row) + "\n"

        title = "Org Activity"
        if char:
            title += " for " + char.name
        if named_params.action:
            title += " [" + named_params.action + "]"

        return ChatBlob(title, blob)

    @event(PublicChannelService.ORG_MSG_EVENT, "Record org member activity", is_system=True)
    def org_msg_event(self, event_type, event_data):
        ext_msg = event_data.extended_message
        org_id = event_data.conn.org_id
        if [ext_msg.category_id, ext_msg.instance_id] == OrgMemberController.LEFT_ORG:
            self.save_activity(ext_msg.params[0], ext_msg.params[0], "left", org_id)
        elif [ext_msg.category_id, ext_msg.instance_id] == OrgMemberController.KICKED_FROM_ORG:
            self.save_activity(ext_msg.params[0], ext_msg.params[1], "kicked", org_id)
        elif [ext_msg.category_id, ext_msg.instance_id] == OrgMemberController.INVITED_TO_ORG:
            self.save_activity(ext_msg.params[0], ext_msg.params[1], "invited", org_id)
        elif [ext_msg.category_id, ext_msg.instance_id] == OrgMemberController.KICKED_INACTIVE_FROM_ORG:
            self.save_activity(ext_msg.params[0], ext_msg.params[1], "removed", org_id)
        elif [ext_msg.category_id, ext_msg.instance_id] == OrgMemberController.KICKED_ALIGNMENT_CHANGED:
            self.save_activity(ext_msg.params[0], ext_msg.params[0], "alignment changed", org_id)
        elif [ext_msg.category_id, ext_msg.instance_id] == OrgMemberController.JOINED_ORG:
            self.save_activity(ext_msg.params[0], ext_msg.params[0], "joined", org_id)

    def save_activity(self, actor, actee, action, org_id):
        actor_id = self.character_service.resolve_char_to_id(actor)
        actee_id = self.character_service.resolve_char_to_id(actee) if actee else 0

        if not actor_id:
            self.logger.error("Could not get char_id for actor '%s'" % actor)

        if not actee_id:
            self.logger.error("Could not get char_id for actee '%s'" % actee)

        t = int(time.time())
        self.db.exec("INSERT INTO org_activity (actor_char_id, actee_char_id, action, created_at, org_id) VALUES (?, ?, ?, ?, ?)",
                     [actor_id, actee_id, action, t, org_id])

    def format_org_action(self, row):
        org_name = self.get_org_name(row.org_id)
        created_at_str = self.util.format_datetime(row.created_at)
        if row.action == "left" or row.action == "alignment changed" or row.action == "joined":
            return f"<highlight>{row.actor}</highlight> {row.action}. [{org_name}] {created_at_str}"
        else:
            return f"<highlight>{row.actor}</highlight> {row.action} <highlight>{row.actee}</highlight>. [{org_name}] {created_at_str}"

    def get_org_name(self, org_id):
        conn: Conn = self.bot.get_conn_by_org_id(org_id)
        if conn:
            return conn.get_org_name()
        else:
            return f"UnknownOrg({org_id})"
Exemple #10
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 #11
0
class AltsController:
    SORT_OPTIONS = ["level", "name", "profession"]

    def inject(self, registry):
        self.bot = registry.get_instance("bot")
        self.alts_service: AltsService = registry.get_instance("alts_service")
        self.buddy_service = registry.get_instance("buddy_service")
        self.util = registry.get_instance("util")

    @command(command="alts",
             params=[NamedParameters(["sort_by"])],
             access_level="all",
             description="Show your alts",
             extended_description="Sort_by param can be one of: " +
             ", ".join(SORT_OPTIONS))
    def alts_list_cmd(self, request, named_params):
        if named_params.sort_by:
            if named_params.sort_by not in self.SORT_OPTIONS:
                return "Sort_by parameter must be one of: " + ", ".join(
                    self.SORT_OPTIONS)
        else:
            named_params.sort_by = "level"

        alts = self.alts_service.get_alts(request.sender.char_id,
                                          named_params.sort_by)
        blob = self.format_alt_list(alts)

        return ChatBlob(f"Alts of {alts[0].name} ({len(alts)})", blob)

    def get_alt_status(self, status):
        if status == AltsService.MAIN:
            return " - [main]"
        else:
            return ""

    @command(
        command="alts",
        params=[Const("setmain")],
        access_level="all",
        description="Set a new main",
        extended_description=
        "You must run this from the character you want to be your new main")
    def alts_setmain_cmd(self, request, _):
        msg, result = self.alts_service.set_as_main(request.sender.char_id)

        if result:
            return f"Character <highlight>{request.sender.name}</highlight> has been set as your main."
        elif msg == "not_an_alt":
            return "Error! Character <highlight>{request.sender.name}</highlight> cannot be set as your main since you do not have any alts."
        elif msg == "already_main":
            return "Error! Character <highlight>{request.sender.name}</highlight> is already set as your main."
        else:
            raise Exception("Unknown msg: " + msg)

    @command(command="alts",
             params=[Const("add"),
                     Multiple(Character("character"))],
             access_level="all",
             description="Add one or more alts")
    def alts_add_cmd(self, request, _, alt_chars):
        responses = []
        for alt_char in alt_chars:
            if not alt_char.char_id:
                responses.append(StandardMessage.char_not_found(alt_char.name))
            elif alt_char.char_id == request.sender.char_id:
                responses.append(
                    "Error! You cannot register yourself as an alt.")
            else:
                msg, result = self.alts_service.add_alt(
                    request.sender.char_id, alt_char.char_id)
                if result:
                    self.bot.send_private_message(
                        alt_char.char_id,
                        f"Character <highlight>{request.sender.name}</highlight> has added you as an alt.",
                        conn=request.conn)
                    responses.append(
                        f"Character <highlight>{alt_char.name}</highlight> has been added as your alt."
                    )
                elif msg == "another_main":
                    responses.append(
                        f"Error! Character <highlight>{alt_char.name}</highlight> already has alts."
                    )
                else:
                    raise Exception("Unknown msg: " + msg)

        return "\n".join(responses)

    @command(command="alts",
             params=[Options(["rem", "remove"]),
                     Character("character")],
             access_level="all",
             description="Remove an alt")
    def alts_remove_cmd(self, request, _, alt_char):
        if not alt_char.char_id:
            return StandardMessage.char_not_found(alt_char.name)

        msg, result = self.alts_service.remove_alt(request.sender.char_id,
                                                   alt_char.char_id)
        if result:
            return f"Character <highlight>{alt_char.name}</highlight> has been removed as your alt."
        elif msg == "not_alt":
            return f"Error! Character <highlight>{alt_char.name}</highlight> is not your alt."
        elif msg == "remove_main":
            return "Error! You cannot remove your main."
        else:
            raise Exception("Unknown msg: " + msg)

    @command(command="alts",
             params=[Character("character"),
                     NamedParameters(["sort_by"])],
             access_level="member",
             description="Show alts of another character",
             sub_command="show",
             extended_description="Sort_by param can be one of: " +
             ", ".join(SORT_OPTIONS))
    def alts_list_other_cmd(self, request, char, named_params):
        if not char.char_id:
            return StandardMessage.char_not_found(char.name)

        if named_params.sort_by:
            if named_params.sort_by not in self.SORT_OPTIONS:
                return "Sort_by parameter must be one of: " + ", ".join(
                    self.SORT_OPTIONS)
        else:
            named_params.sort_by = "level"

        alts = self.alts_service.get_alts(char.char_id, named_params.sort_by)
        blob = self.format_alt_list(alts)

        return ChatBlob(f"Alts of {alts[0].name} ({len(alts)})", blob)

    @command(command="altadmin",
             params=[Const("add"),
                     Character("main"),
                     Character("alt")],
             access_level="moderator",
             description="Add alts to main")
    def altadmin_add_cmd(self, request, _, main, alt):
        if not main.char_id:
            return StandardMessage.char_not_found(main.name)
        if not alt.char_id:
            return StandardMessage.char_not_found(alt.name)

        elif main.char_id == alt.char_id:
            return "Error! Alt and main are identical."

        msg, result = self.alts_service.add_alt(main.char_id, alt.char_id)
        if result:
            return f"Character <highlight>{alt.name}</highlight> was added as an alt of <highlight>{main.name}</highlight> successfully."
        elif msg == "another_main":
            return f"Error! Character <highlight>{alt.name}</highlight> already has alts."
        else:
            raise Exception("Unknown msg: " + msg)

    @command(command="altadmin",
             params=[
                 Options(["rem", "remove"]),
                 Character("main"),
                 Character("alt")
             ],
             access_level="moderator",
             description="Remove alts from main")
    def altadmin_remove_cmd(self, request, _, main, alt):
        if not main.char_id:
            return StandardMessage.char_not_found(main.name)
        if not alt.char_id:
            return StandardMessage.char_not_found(alt.name)

        msg, result = self.alts_service.remove_alt(main.char_id, alt.char_id)

        if result:
            return f"Character <highlight>{alt.name}</highlight> was added as an alt of <highlight>{main.name}</highlight> successfully."
        elif msg == "not_alt":
            return f"Error! Character <highlight>{alt.name}</highlight> is not an alt of <highlight>{main.name}</highlight>."
        elif msg == "remove_main":
            return "Error! Main characters may not be removed from their alt list."
        else:
            raise Exception("Unknown msg: " + msg)

    def format_alt_list(self, alts):
        blob = ""
        for alt in alts:

            blob += "<highlight>%s</highlight> (%d/<green>%d</green>) %s %s" % (
                alt.name, alt.level, alt.ai_level, alt.faction, alt.profession)
            if self.buddy_service.is_online(alt.char_id):
                blob += " [<green>Online</green>]"
            blob += "\n"
        return blob
class ConfigEventsController:
    def inject(self, registry):
        self.db: DB = registry.get_instance("db")
        self.text: Text = registry.get_instance("text")
        self.command_service = registry.get_instance("command_service")
        self.event_service = registry.get_instance("event_service")
        self.setting_service = registry.get_instance("setting_service")
        self.ts: TranslationService = registry.get_instance("translation_service")
        self.getresp = self.ts.get_response

    @command(command="config", params=[Const("event"), Any("event_type"), Any("event_handler"), Options(["enable", "disable"])], access_level="admin",
             description="Enable or disable an event")
    def config_event_status_cmd(self, request, _, event_type, event_handler, action):
        event_type = event_type.lower()
        event_handler = event_handler.lower()
        action = action.lower()
        event_base_type, event_sub_type = self.event_service.get_event_type_parts(event_type)
        enabled = 1 if action == "enable" else 0

        if not self.event_service.is_event_type(event_base_type):
            return self.getresp("module/config", "unknown event", {"type", event_type})

        count = self.event_service.update_event_status(event_base_type, event_sub_type, event_handler, enabled)

        if count == 0:
            return self.getresp("module/config", "event_enable_fail", {"type": event_type, "handler": event_handler})
        else:
            action = self.getresp("module/config", "enabled_high" if action == "enable" else "disabled_high")
            return self.getresp("module/config", "event_enable_success", {"type": event_type,
                                                                          "handler": event_handler,
                                                                          "changedto": action})

    @command(command="config", params=[Const("event"), Any("event_type"), Any("event_handler"), Const("run")], access_level="admin",
             description="Execute a timed event immediately")
    def config_event_run_cmd(self, request, _1, event_type, event_handler, _2):
        action = "run"
        event_type = event_type.lower()
        event_handler = event_handler.lower()
        event_base_type, event_sub_type = self.event_service.get_event_type_parts(event_type)

        if not self.event_service.is_event_type(event_base_type):
            return self.getresp("module/config", "unknown event", {"type", event_type})

        row = self.db.query_single("SELECT e.event_type, e.event_sub_type, e.handler, t.next_run FROM timer_event t "
                                   "JOIN event_config e ON t.event_type = e.event_type AND t.handler = e.handler "
                                   "WHERE e.event_type = ? AND e.event_sub_type = ? AND e.handler LIKE ?", [event_base_type, event_sub_type, event_handler])

        if not row:
            return self.getresp("module/config", "event_enable_fail", {"type": event_type, "handler": event_handler})
        elif row.event_type != "timer":
            return self.getresp("module/config", "event_manual")
        else:
            self.event_service.execute_timed_event(row, int(time.time()))
            action = self.getresp("module/config", "run")
            return self.getresp("module/config", "event_enable_success", {"type": event_type,
                                                                          "handler": event_handler,
                                                                          "changedto": action})

    @command(command="config", params=[Const("eventlist"), 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 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=[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 f"Character <highlight>{char.name}</highlight> has been added to the buddy list for type <highlight>{buddy_type}</highlight>."
        else:
            return StandardMessage.char_not_found(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):
        buddies = self.buddy_service.get_all_buddies().items()
        for char_id, buddy in buddies:
            self.buddy_service.remove_buddy(char_id, None, True)

        return f"Removed all <highlight>{len(buddies)}</highlight> buddies from the buddy list."

    @command(command="buddylist", params=[Options(["rem", "remove"]), Character("character"), Any("type")], access_level="admin",
             description="Remove a character from the buddy list by type")
    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 f"Character <highlight>{char.name}</highlight> has been removed from the buddy list for type <highlight>{buddy_type}</highlight>."
        else:
            return StandardMessage.char_not_found(char.name)

    @command(command="buddylist", params=[Options(["rem", "remove"]), Character("character")], access_level="admin",
             description="Remove a character from the buddy list forcefully")
    def buddylist_remove_force_cmd(self, request, _, char):
        if char.char_id:
            self.buddy_service.remove_buddy(char.char_id, None, force_remove=True)
            return f"Character <highlight>{char.name}</highlight> has been removed from the buddy list forcefully."
        else:
            return StandardMessage.char_not_found(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, _):
        num_removed = self.remove_orphaned_buddies()
        return f"Removed <highlight>{num_removed}</highlight> orphaned buddies from the buddy list."

    @command(command="buddylist", params=[Const("search", is_optional=True), Any("character", allowed_chars="[a-z0-9-]", is_optional=True), NamedParameters(["inactive"])],
             access_level="admin", description="Search for characters on the buddy list",
             extended_description="Use --inactive=include (default), --inactive=exclude, or --inactive=only to control if inactive buddies are shown")
    def buddylist_search_cmd(self, request, _, search, named_params):
        is_search = False
        if search:
            is_search = True
            search = search.lower()

        include_active = True
        include_inactive = True
        if named_params.inactive:
            if named_params.inactive.lower() == "exclude":
                include_inactive = False
            elif named_params.inactive.lower() == "only":
                include_active = False
            elif named_params.inactive.lower() != "include":
                return "Named parameter <highlight>--inactive</highlight> only allows values <highlight>include</highlight>, " \
                       "<highlight>exclude</highlight>, or <highlight>only</highlight>."
            is_search = True

        buddy_list = []
        for char_id, buddy in self.buddy_service.get_all_buddies().items():
            is_active = buddy["online"] is not None
            if not include_active and is_active:
                continue
            elif not include_inactive and not is_active:
                continue

            char_name = self.character_service.resolve_char_to_name(char_id, "Unknown(%d)" % char_id)
            if not search or search in char_name.lower():
                buddy_list.append([char_name, buddy])

        blob = self.format_buddies(buddy_list)

        if is_search:
            return ChatBlob(f"Buddy List Search Results ({len(buddy_list)})", blob)
        else:
            return ChatBlob(f"Buddy list ({len(buddy_list)})", blob)

    @timerevent(budatime="24h", description="Remove orphaned buddies", is_system=True)
    def remove_orphaned_buddies_event(self, event_type, event_data):
        if self.bot.is_ready():
            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, buddy in buddy_list:
            pending = "*" if buddy["online"] is None else ""
            blob += "%s%s [%s] - %s\n" % (name, pending, buddy["conn_id"], ",".join(buddy["types"]))

        blob += "\nAsterisk (*) indicates the buddy is pending and may not be active."

        return blob
Exemple #14
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", "r") as f:
            return ChatBlob("About Tyrbot %s" % 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 + "<end>"

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

            if row.command != current_command:
                current_command = row.command
                blob += " " + self.text.make_chatcmd(
                    row.command, "/tell <myname> help " + row.command)

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

    @command(command="help",
             params=[Any("command"),
                     NamedParameters(["show_regex"])],
             access_level="all",
             description="Show help for a specific command")
    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

        show_regex = named_params.show_regex and named_params.show_regex.lower(
        ) == "true"

        help_text = self.command_service.get_help_text(request.sender.char_id,
                                                       help_topic,
                                                       request.channel,
                                                       show_regex)
        if help_text:
            return self.command_service.format_help_text(help_topic, help_text)
        else:
            return "Could not find help on <highlight>" + help_topic + "<end>."
Exemple #15
0
class PointsController:
    def __init__(self):
        pass

    def inject(self, registry):
        self.bot = registry.get_instance("bot")
        self.db: DB = registry.get_instance("db")
        self.text: Text = registry.get_instance("text")
        self.character_service: CharacterService = registry.get_instance("character_service")
        self.util: Util = registry.get_instance("util")
        self.setting_service: SettingService = registry.get_instance("setting_service")
        self.alts_service: AltsService = registry.get_instance("alts_service")

    def start(self):
        self.db.exec("CREATE TABLE IF NOT EXISTS points (char_id BIGINT PRIMARY KEY, points INT DEFAULT 0, created_at INT NOT NULL, "
                     "disabled SMALLINT DEFAULT 0)")
        self.db.exec("CREATE TABLE IF NOT EXISTS points_log (log_id INT PRIMARY KEY, char_id BIGINT NOT NULL, audit INT NOT NULL, "
                     "leader_id BIGINT NOT NULL, reason VARCHAR(255), created_at INT NOT NULL)")
        self.db.exec("CREATE TABLE IF NOT EXISTS points_presets (preset_id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, "
                     "points INT DEFAULT 1, UNIQUE(name))")

        if self.db.query_single("SELECT COUNT(*) AS count FROM points_presets").count < 1:
            # Populate with pre-made presets if empty
            presets = ["s13", "s28", "s35", "s42", "zodiac", "zod",
                       "tnh", "beast", "12m", "tara", "pvp", "towers",
                       "wipe", "clanwipe", "clan", "omniwipe", "omni",
                       "bonus", "early"]
            sql = "INSERT INTO points_presets (name) VALUES (?)"
            for preset in presets:
                self.db.exec(sql, [preset])

    @command(command="account", params=[Const("create"), Character("char")], access_level="moderator",
             description="Create a new account for given character name", sub_command="modify")
    def bank_create_cmd(self, request, _, char: SenderObj):
        alts_info = self.alts_service.get_alts(char.char_id)

        for alt in alts_info:
            sql = "SELECT char_id, disabled FROM points WHERE char_id = ? LIMIT 1"
            row = self.db.query_single(sql, [alt.char_id])

            if row:
                was_disabled = False

                if row.disabled == 1:
                    if self.db.exec("UPDATE points SET disabled = 0 WHERE char_id = ?", [alt.char_id]):
                        was_disabled = True

                if alt.char_id == char.char_id:
                    if was_disabled:
                        self.add_log_entry(alt.char_id, request.sender.char_id, "Account was re-enabled by %s" % request.sender.name)
                        return "<highlight>%s</highlight>'s account has been re-enabled." % char.name
                    else:
                        return "<highlight>%s</highlight> already has an account." % char.name
                else:
                    if was_disabled:
                        self.add_log_entry(alt.char_id, request.sender.char_id, "Account was re-enabled by %s" % request.sender.name)
                        return "<highlight>%s</highlight>'s (%s) account has been re-enabled." % (char.name, alt.name)
                    else:
                        return "<highlight>%s</highlight> (%s) already has an account." % (char.name, alt.name)

        main_info = alts_info[0]
        changed_to_main = main_info.char_id == char.char_id

        self.create_account(main_info.char_id, request.sender)

        name_reference = "%s (%s)" % (char.name, main_info.name) if changed_to_main else char.name
        return "A new account has been created for <highlight>%s</highlight>." % name_reference

    @command(command="account", params=[Const("close"), Character("char")], access_level="moderator",
             description="Close the account for given character name", sub_command="modify")
    def close_account_cmd(self, request, _, char: SenderObj):
        main = self.alts_service.get_main(char.char_id)

        sql = "UPDATE points SET disabled = 1 WHERE char_id = ?"
        if self.db.exec(sql, [main.char_id]) > 0:
            reason = f"Account was closed by {request.sender.name}"
            self.add_log_entry(main.char_id, request.sender.char_id, reason)
            name_reference = "%s (%s)" % (char.name, main.name) if main.char_id != char.char_id else char.name
            return f"<highlight>{name_reference}</highlight> has had their account disabled. Logs have been preserved."
        else:
            return "<highlight>%s</highlight> does not have an open account." % char.name

    @command(command="account", params=[Const("history"), Int("log_id")], access_level="moderator",
             description="Look up specific account history record", sub_command="modify")
    def account_history_cmd(self, request, _, log_id: int):
        log_entry = self.db.query_single("SELECT log_id, char_id, audit, leader_id, reason, created_at FROM points_log WHERE log_id = ?", [log_id])

        if not log_entry:
            return "No account history record with given ID <highlight>%d</highlight>." % log_id

        char_name = self.character_service.resolve_char_to_name(log_entry.char_id)
        leader_name = self.character_service.resolve_char_to_name(log_entry.leader_id)

        blob = f"ID: <highlight>{log_entry.log_id}</highlight>\n"
        blob += f"Account: <highlight>{char_name}</highlight>\n"
        blob += f"Action by: <highlight>{leader_name}</highlight>\n"
        blob += "Type: <highlight>%s</highlight>\n" % ("Management" if log_entry.audit == 0 else "Altering of points")
        blob += f"Reason: <highlight>{log_entry.reason}</highlight>\n"
        blob += f"Audit: <highlight>{log_entry.audit}</highlight>\n"
        action_links = None
        if log_entry.audit == 0:
            if "closed" in log_entry.reason:
                action_links = self.text.make_tellcmd("Open the account", "account create %s" % char_name)
            elif "re-enabled" in log_entry.reason:
                action_links = self.text.make_tellcmd("Close the account", "account close %s" % char_name)
        else:
            reason = f"Points from event ({log_id}) have been retracted"
            if log_entry.audit < 0:
                action_links = self.text.make_tellcmd("Retract", f"account add {char_name} {-log_entry.audit} {reason}")
            else:
                action_links = self.text.make_tellcmd("Retract", f"account rem {char_name} {log_entry.audit} {reason}")

        blob += "Actions available: [%s]\n" % (action_links if action_links is not None else "No actions available")

        return ChatBlob(f"Account History Record ({log_id})", blob)

    @command(command="account", params=[Const("add"), Character("char"), Int("amount"), Any("reason")], access_level="moderator",
             description="Add points to an account", sub_command="modify")
    def account_add_cmd(self, request, _, char: SenderObj, amount: int, reason: str):
        main = self.alts_service.get_main(char.char_id)
        row = self.get_account(main.char_id, request.conn)

        if not row:
            return f"<highlight>{char.name}</highlight> does not have an account."

        if row.disabled == 1:
            return f"Account for <highlight>{char.name}</highlight> is disabled and cannot be altered."

        self.alter_points(main.char_id, request.sender.char_id, reason, amount)

        return f"<highlight>{char.name}</highlight> has had <highlight>{amount}</highlight> points added to their account."

    @command(command="account", params=[Options(["rem", "remove"]), Character("char"), Int("amount"), Any("reason")], access_level="moderator",
             description="Remove points from an account", sub_command="modify")
    def account_remove_cmd(self, request, _, char: SenderObj, amount: int, reason: str):
        main = self.alts_service.get_main(char.char_id)
        row = self.get_account(main.char_id, request.conn)

        if not row:
            return f"<highlight>{char.name}</highlight> does not have an account."

        if row.disabled == 1:
            return f"Account for <highlight>{char.name}</highlight> is disabled and cannot be altered."

        if amount > row.points:
            return f"<highlight>{char.name}</highlight> only has <highlight>{row.points}</highlight> points."

        self.alter_points(main.char_id, request.sender.char_id, reason, -amount)

        return f"<highlight>{char.name}</highlight> has had <highlight>{amount}</highlight> points removed from their account."

    @command(command="account", params=[NamedParameters(["page"])], access_level="all",
             description="Look up your account")
    def account_self_cmd(self, request, named_params):
        return self.get_account_display(request.sender, named_params.page)

    @command(command="account", params=[Character("char"), NamedParameters(["page"])], access_level="moderator",
             description="Look up account of another char", sub_command="modify")
    def account_other_cmd(self, request, char: SenderObj, named_params):
        return self.get_account_display(char, named_params.page)

    @command(command="raid", params=[Const("presets"), Const("add"), Any("name"), Int("points")], access_level="admin",
             description="Add new points preset", sub_command="manage_points")
    def presets_add_cmd(self, request, _1, _2, name: str, points: int):
        count = self.db.query_single("SELECT COUNT(*) AS count FROM points_presets WHERE name = ?", [name]).count

        if count > 0:
            return "A preset already exists with the name <highlight>%s</highlight>." % name

        sql = "INSERT INTO points_presets (name, points) VALUES (?,?)"
        self.db.exec(sql, [name, points])
        return "A preset with the name <highlight>%s</highlight> was added, worth <green>%d</green> points." % (name, points)

    @command(command="raid", params=[Const("presets"), Const("rem"), Int("preset_id")], access_level="admin",
             description="Delete preset", sub_command="manage_points")
    def presets_rem_cmd(self, request, _1, _2, preset_id: int):
        if self.db.exec("DELETE FROM points_presets WHERE preset_id = ?", [preset_id]) > 0:
            return "Successfully removed preset with ID <highlight>%d</highlight>." % preset_id
        else:
            return "No preset with given ID <highlight>%d</highlight>." % preset_id

    @command(command="raid", params=[Const("presets"), Const("alter"), Int("preset_id"), Int("new_points")], access_level="admin",
             description="Alter the points dished out by given preset", sub_command="manage_points")
    def presets_alter_cmd(self, request, _1, _2, preset_id: int, new_points: int):
        preset = self.db.query_single("SELECT * FROM points_presets WHERE preset_id = ?", [preset_id])

        if not preset:
            return f"Preset with ID <highlight>{preset_id}</highlight> does not exist."

        self.db.exec("UPDATE points_presets SET points = ? WHERE preset_id = ?", [new_points, preset_id])
        return "Successfully updated the preset, <highlight>%s</highlight>, to dish out " \
               "<green>%d</green> points instead of <red>%d</red>." % (preset.name, new_points, preset.points)

    @command(command="raid", params=[Options(["presets", "addpts"])], access_level="member",
             description="See list of points presets")
    def presets_cmd(self, request, _):
        return ChatBlob("Raid Points Presets", self.build_preset_list())

    def build_preset_list(self):
        presets = self.db.query("SELECT * FROM points_presets ORDER BY name ASC, points DESC")

        if presets:
            blob = ""

            for preset in presets:
                add_points_link = self.text.make_tellcmd("Add pts", "raid addpts %s" % preset.name)
                blob += "<highlight>%s</highlight> worth <green>%d</green> points %s [id: %d]\n\n" \
                        % (preset.name, preset.points, add_points_link, preset.preset_id)

            return blob

        return "No presets available. To add new presets use <highlight><symbol>presets add preset_name preset_points</highlight>."

    def add_log_entry(self, char_id: int, leader_id: int, reason: str, amount=0):
        sql = "INSERT INTO points_log (char_id, audit, leader_id, reason, created_at) VALUES (?,?,?,?,?)"
        return self.db.exec(sql, [char_id, amount, leader_id, reason, int(time.time())])

    def alter_points(self, char_id: int, leader_id: int, reason: str, amount: int):
        sql = "UPDATE points SET points = points + ? WHERE char_id = ?"
        self.db.exec(sql, [amount, char_id])

        self.add_log_entry(char_id, leader_id, reason, amount)

    def get_account(self, main_id, conn):
        sql = "SELECT p.char_id, p.points, p.disabled FROM points p WHERE p.char_id = ?"
        row = self.db.query_single(sql, [main_id])
        if not row:
            self.create_account(main_id, SenderObj(conn.get_char_id(),
                                                   conn.get_char_name(),
                                                   None))
            row = self.db.query_single(sql, [main_id])

        return row

    def create_account(self, main_id, sender):
        sql = "INSERT INTO points (char_id, points, created_at) VALUES (?,?,?)"
        self.db.exec(sql, [main_id, 0, int(time.time())])

        self.add_log_entry(main_id, sender.char_id, "Account opened by %s" % sender.name)

    def get_account_display(self, char: SenderObj, page):
        main = self.alts_service.get_main(char.char_id)
        if not main:
            return "Could not find character <highlight>%s</highlight>." % char.name

        page = int(page) if page else 1
        page_size = 20
        offset = (page - 1) * page_size

        points = self.db.query_single("SELECT points, disabled FROM points WHERE char_id = ?", [main.char_id])
        if not points:
            return "Could not find raid account for <highlight>%s</highlight>." % char.name

        alts_link = self.text.make_tellcmd("Alts", "alts %s" % main.name)
        blob = ""
        blob += "Account: %s [%s]\n" % (main.name, alts_link)
        blob += "Points: %d\n" % points.points
        blob += "Status: %s\n\n" % ("<green>Open</green>" if points.disabled == 0 else "<red>Disabled</red>")

        points_log = self.db.query("SELECT * FROM points_log WHERE char_id = ? ORDER BY created_at DESC LIMIT ?, ?",
                                   [main.char_id, offset, page_size])
        blob += "<header2>Account log</header2>\n"
        if points_log is None:
            blob += "No entries in log."
        else:
            for entry in points_log:
                if entry.audit > 0:
                    pts = "<green>+%d</green>" % entry.audit
                    blob += "<grey>[%s]</grey> %s points by <highlight>%s</highlight>; <orange>%s</orange>" \
                            % (self.util.format_datetime(entry.created_at), pts,
                               self.character_service.resolve_char_to_name(entry.leader_id), entry.reason)
                elif entry.audit < 0:
                    pts = "<red>-%d</red>" % (-1 * entry.audit)
                    blob += "<grey>[%s]</grey> %s points by <highlight>%s</highlight>; <orange>%s</orange>" \
                            % (self.util.format_datetime(entry.created_at), pts,
                               self.character_service.resolve_char_to_name(entry.leader_id),
                               entry.reason)
                else:
                    # If points is 0, then it's a general case log
                    blob += "<grey>[%s]</grey> <orange>%s</orange>" % (self.util.format_datetime(entry.created_at), entry.reason)

                log_entry_link = self.text.make_tellcmd(entry.log_id, f"account history {entry.log_id}")
                blob += " [%s]\n" % log_entry_link

        return ChatBlob("%s Account" % char.name, blob)
Exemple #16
0
class RecipeController:
    def __init__(self):
        self.recipe_name_regex = re.compile(r"(\d+)\.(txt|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")

        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):
            m = self.recipe_name_regex.match(file)
            if m:
                recipe_id = m.group(1)
                file_type = m.group(2)
                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, file, recipe_id, file_type, dt)

            elif file.startswith("_"):
                pass
            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<end>." % recipe_id

        return self.format_recipe(recipe)

    @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 = int(named_params.page or "1")
        page_size = 30
        offset = (page - 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 count > page_size:
            if page > 1 and len(paged_data) > 0:
                blob += "   " + self.text.make_chatcmd(
                    "<< Page %d" %
                    (page - 1), self.get_chat_command(search, page - 1))
            if offset + page_size < len(data):
                blob += "   Page " + str(page)
                blob += "   " + self.text.make_chatcmd(
                    "Page %d >>" %
                    (page + 1), self.get_chat_command(search, page + 1))
            blob += "\n\n"

        for row in paged_data:
            blob += self.text.make_chatcmd(
                row.name, "/tell <myname> 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<end>\n" % recipe.id
        blob += "Author: <highlight>%s<end>\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 get_chat_command(self, search, page):
        return "/tell <myname> recipe %s --page=%d" % (search, page)

    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, file, recipe_id, file_type, dt):
        if file_type == "txt":
            with open(recipe_dir + file) as f:
                lines = f.readlines()

            name = lines.pop(0).strip()[6:]
            author = lines.pop(0).strip()[8:]
            content = "".join(lines)

            #self.db.exec("INSERT INTO recipe (id, name, author, recipe) VALUES (?, ?, ?, ?)", [recipe_id, name, author, content])
        elif file_type == "json":
            with open(recipe_dir + file) as f:
                recipe = json.load(f)

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

            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 = "<font color=#FFFF00>------------------------------</font>\n"
            content += "<font color=#FF0000>Ingredients</font>\n"
            content += "<font color=#FFFF00>------------------------------</font>\n\n"

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

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

            content += "\n"
            content += "<font color=#FFFF00>------------------------------</font>\n"
            content += "<font color=#FF0000>Recipe</font>\n"
            content += "<font color=#FFFF00>------------------------------</font>\n\n"

            for step in recipe["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"

            if "details" in recipe:
                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 recipe["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"]

        self.db.exec(
            "REPLACE INTO recipe (id, name, author, recipe, dt) VALUES (?, ?, ?, ?, ?)",
            [recipe_id, name, author, content, dt])
Exemple #17
0
class ItemsController:
    PAGE_SIZE = 30

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

    def pre_start(self):
        self.db.load_sql_file(self.module_dir + "/" + "aodb.sql")

    def start(self):
        self.command_alias_service.add_alias("item", "items")
        self.command_alias_service.add_alias("i", "items")

    @command(command="items",
             params=[Int("item_id")],
             access_level="all",
             description="Search for an item by item id")
    def items_id_cmd(self, request, item_id):
        item = self.get_by_item_id(item_id)
        if item:
            return self.format_items_response(None, str(item_id), [item], 0, 1)
        else:
            return "Could not find item with ID <highlight>%d</highlight>." % item_id

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

        search = html.unescape(search)

        offset = (page - 1) * self.PAGE_SIZE

        all_items = self.find_items(search, ql)

        return self.format_items_response(ql, search, all_items, offset, page)

    def format_items_response(self, ql, search, all_items, offset,
                              page_number):
        items = self.sort_items(search,
                                all_items)[offset:offset + self.PAGE_SIZE]
        cnt = len(items)

        if cnt == 0:
            if ql:
                return "No QL <highlight>%d</highlight> items found matching <highlight>%s</highlight>." % (
                    ql, search)
            else:
                return "No items found matching <highlight>%s</highlight>." % search
        elif cnt == 1:
            item = items[0]
            return self.format_single_item([item], ql)
        else:
            blob = ""
            # blob += "Version: <highlight>%s</highlight>\n" % "unknown"
            if ql:
                blob += "Search: <highlight>QL %d %s</highlight>\n" % (ql,
                                                                       search)
            else:
                blob += "Search: <highlight>%s</highlight>\n" % search
            blob += "\n"

            blob += self.text.get_paging_links(
                self.get_chat_command(ql, search), page_number,
                (offset + self.PAGE_SIZE) < len(all_items))
            blob += "\n\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 + self.PAGE_SIZE,
                                 len(all_items)), len(all_items)), blob)

    def format_items(self, items, ql=None):
        blob = ""
        for item_group in ItemIter(items):
            blob += "<pagebreak>"
            blob += self.text.make_image(item_group[0].icon) + "\n"
            blob += self.format_single_item(item_group, ql)
            blob += "\n\n"

        return blob

    def format_single_item(self, item_group, ql):
        msg = ""
        msg += item_group[0].name

        for item in reversed(item_group):
            if ql:
                if item.lowql != item.highql:
                    msg += " %s" % self.text.make_item(item.lowid, item.highid,
                                                       ql, ql)
                    msg += " [%s - %s]" % (self.text.make_item(
                        item.lowid, item.highid, item.lowql, item.lowql),
                                           self.text.make_item(
                                               item.lowid, item.highid,
                                               item.highql, item.highql))
                elif item.lowql == item.highql:
                    msg += " [%s]" % self.text.make_item(
                        item.lowid, item.highid, item.highql, item.highql)
            elif item.lowql == item.highql:
                msg += " [%s]" % self.text.make_item(item.lowid, item.highid,
                                                     item.highql, item.highql)
            else:
                msg += " [%s - %s]" % (self.text.make_item(
                    item.lowid, item.highid, item.lowql, item.lowql),
                                       self.text.make_item(
                                           item.lowid, item.highid,
                                           item.highql, item.highql))

        return msg

    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, ql=None):
        if ql:
            return self.db.query_single(
                "SELECT * FROM aodb WHERE (highid = ? OR lowid = ?) AND (lowql <= ? AND highql >= ?) ORDER BY highid = ? DESC LIMIT 1",
                [item_id, item_id, ql, ql, item_id])
        else:
            return self.db.query_single(
                "SELECT * FROM aodb WHERE highid = ? OR lowid = ? ORDER BY highid = ? DESC LIMIT 1",
                [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 LIMIT 1",
                [name, ql, ql])
        else:
            return self.db.query_single(
                "SELECT * FROM aodb WHERE name = ? ORDER BY highql DESC, highid DESC LIMIT 1",
                [name])

    def get_chat_command(self, ql, search):
        if ql:
            return "items %d %s" % (ql, search)
        else:
            return "items %s" % search
Exemple #18
0
class RecipeController:
    def __init__(self):
        self.logger = Logger(__name__)

        self.recipe_name_regex = re.compile(r"(\d+)\.(txt|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)
                file_type = m.group(2)
                dt = int(os.path.getmtime(recipe_dir + file))

                # convert txt format to json
                if file_type == "txt":
                    if self.convert_to_json(recipe_dir, recipe_id, file):
                        file_type = "json"
                        dt = int(time.time())

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

                self.update_recipe(recipe_dir, recipe_id, file_type, 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_blob(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_blob(search, page_number)

    def get_search_results_blob(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:
            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 get_chat_command(self, search, page):
        return "/tell <myname> recipe %s --page=%d" % (search, page)

    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, file_type, 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

    def convert_to_json(self, recipe_dir, recipe_id, file):
        with open(recipe_dir + file, mode="r", encoding="UTF-8") as f:
            lines = f.readlines()

        recipe = {
            "name": lines.pop(0).strip()[6:],
            "author": lines.pop(0).strip()[8:],
            "items": list(),
            "steps": list(),
            "details": list(),
            "raw": None
        }

        content = "".join(lines)
        items = {}

        matches = self.recipe_item_regex.findall(content)
        for item_name, item_id in matches:
            item = self.items_controller.get_by_item_id(item_id)
            if not item:
                self.logger.warning(
                    "Could not find recipe item '%s - %s' for recipe id %s" %
                    (item_id, item_name, recipe_id))
            else:
                items[item.highid] = {
                    "alias": item.name,
                    "item_id": item.highid
                }

        recipe["items"].extend(items.values())
        recipe["raw"] = content

        with open(recipe_dir + recipe_id + ".json", mode="w",
                  encoding="UTF-8") as f:
            f.write(json.dumps(recipe, indent=4))

        # delete file
        os.remove(recipe_dir + file)

        return True
Exemple #19
0
class TowerAttackController:
    def __init__(self):
        self.logger = Logger(__name__)

    def inject(self, registry):
        self.bot = registry.get_instance("bot")
        self.db = registry.get_instance("db")
        self.text = registry.get_instance("text")
        self.util = registry.get_instance("util")
        self.event_service = registry.get_instance("event_service")
        self.playfield_controller = registry.get_instance(
            "playfield_controller")
        self.command_alias_service = registry.get_instance(
            "command_alias_service")
        self.public_channel_service = registry.get_instance(
            "public_channel_service")

    def start(self):
        self.db.exec(
            "CREATE TABLE IF NOT EXISTS tower_attacker (id INT PRIMARY KEY AUTO_INCREMENT, att_org_name VARCHAR(50) NOT NULL, att_faction VARCHAR(10) NOT NULL, "
            "att_char_id INT, att_char_name VARCHAR(20) NOT NULL, att_level INT NOT NULL, att_ai_level INT NOT NULL, att_profession VARCHAR(15) NOT NULL, "
            "x_coord INT NOT NULL, y_coord INT NOT NULL, is_victory SMALLINT NOT NULL, "
            "tower_battle_id INT NOT NULL, created_at INT NOT NULL)")
        self.db.exec(
            "CREATE TABLE IF NOT EXISTS tower_battle (id INT PRIMARY KEY AUTO_INCREMENT, playfield_id INT NOT NULL, site_number INT NOT NULL, "
            "def_org_name VARCHAR(50) NOT NULL, def_faction VARCHAR(10) NOT NULL, is_finished INT NOT NULL, battle_type VARCHAR(20) NOT NULL, last_updated INT NOT NULL)"
        )

        self.command_alias_service.add_alias("victory", "attacks")

    @command(command="attacks",
             params=[NamedParameters(["page"])],
             access_level="all",
             description="Show recent tower attacks and victories")
    def attacks_cmd(self, request, named_params):
        page = int(named_params.page or "1")

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

        sql = """
            SELECT
                b.*,
                a.*,
                COALESCE(a.att_level, 0) AS att_level,
                COALESCE(a.att_ai_level, 0) AS att_ai_level,
                p.short_name,
                b.id AS battle_id
            FROM
                tower_battle b
                LEFT JOIN tower_attacker a ON
                    a.tower_battle_id = b.id
                LEFT JOIN playfields p ON
                    p.id = b.playfield_id
            ORDER BY
                b.last_updated DESC,
                a.created_at DESC
            LIMIT %d, %d
        """ % (offset, page_size)

        data = self.db.query(sql)
        t = int(time.time())

        blob = self.check_for_all_towers_channel()

        if page > 1:
            blob += "   " + self.text.make_chatcmd(
                "<< Page %d" % (page - 1), self.get_chat_command(page - 1))
        if len(data) > 0:
            blob += "   Page " + str(page)
            blob += "   " + self.text.make_chatcmd(
                "Page %d >>" % (page + 1), self.get_chat_command(page + 1))
            blob += "\n"

        current_battle_id = -1
        for row in data:
            if current_battle_id != row.battle_id:
                blob += "\n<pagebreak>"
                current_battle_id = row.battle_id
                blob += self.format_battle_info(row, t)
                blob += self.text.make_chatcmd(
                    "More Info",
                    "/tell <myname> attacks battle %d" % row.battle_id) + "\n"
                blob += "<header2>Attackers:<end>\n"

            blob += "<tab>" + self.format_attacker(row) + "\n"

        return ChatBlob("Tower Attacks", blob)

    @command(command="attacks",
             params=[Const("battle"), Int("battle_id")],
             access_level="all",
             description="Show battle info for a specific battle")
    def attacks_battle_cmd(self, request, _, battle_id):
        battle = self.db.query_single(
            "SELECT b.*, p.short_name FROM tower_battle b LEFT JOIN playfields p ON p.id = b.playfield_id WHERE b.id = ?",
            [battle_id])
        if not battle:
            return "Could not find battle with ID <highlight>%d<end>." % battle_id

        t = int(time.time())

        attackers = self.db.query(
            "SELECT * FROM tower_attacker WHERE tower_battle_id = ? ORDER BY created_at DESC",
            [battle_id])

        first_activity = attackers[-1].created_at if len(
            attackers) > 0 else battle.last_updated

        blob = self.check_for_all_towers_channel()
        blob += self.format_battle_info(battle, t)
        blob += "Duration: <highlight>%s<end>\n\n" % self.util.time_to_readable(
            battle.last_updated - first_activity)
        blob += "<header2>Attackers:<end>\n"

        for row in attackers:
            blob += "<tab>" + self.format_attacker(row)
            blob += " " + self.format_timestamp(row.created_at, t)
            blob += "\n"

        return ChatBlob("Battle Info %d" % battle_id, blob)

    @event(event_type=TowerController.TOWER_ATTACK_EVENT,
           description="Record tower attacks",
           is_hidden=True)
    def tower_attack_event(self, event_type, event_data):
        t = int(time.time())
        site_number = self.find_closest_site_number(
            event_data.location.playfield.id, event_data.location.x_coord,
            event_data.location.y_coord)

        attacker = event_data.attacker or {}
        defender = event_data.defender

        battle = self.find_or_create_battle(event_data.location.playfield.id,
                                            site_number, defender.org_name,
                                            defender.faction, "attack", t)

        self.db.exec(
            "INSERT INTO tower_attacker (att_org_name, att_faction, att_char_id, att_char_name, att_level, att_ai_level, att_profession, "
            "x_coord, y_coord, is_victory, tower_battle_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
            [
                attacker.get("org_name", ""),
                attacker.get("faction", ""),
                attacker.get("char_id", 0),
                attacker.get("name", ""),
                attacker.get("level", 0),
                attacker.get("ai_level", 0),
                attacker.get("profession", ""), event_data.location.x_coord,
                event_data.location.y_coord, 0, battle.id, t
            ])

    @event(event_type=TowerController.TOWER_VICTORY_EVENT,
           description="Record tower victories",
           is_hidden=True)
    def tower_victory_event(self, event_type, event_data):
        t = int(time.time())

        if event_data.type == "attack":
            row = self.get_last_attack(event_data.winner.faction,
                                       event_data.winner.org_name,
                                       event_data.loser.faction,
                                       event_data.loser.org_name,
                                       event_data.location.playfield.id, t)

            if not row:
                site_number = 0
                is_finished = 1
                self.db.exec(
                    "INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)",
                    [
                        event_data.location.playfield.id, site_number,
                        event_data.loser.org_name, event_data.loser.faction,
                        is_finished, event_data.type, t
                    ])
                battle_id = self.db.last_insert_id()

                attacker = event_data.winner or {}
                self.db.exec(
                    "INSERT INTO tower_attacker (att_org_name, att_faction, att_char_id, att_char_name, att_level, att_ai_level, att_profession, "
                    "x_coord, y_coord, is_victory, tower_battle_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                    [
                        attacker.get("org_name", ""),
                        attacker.get("faction", ""),
                        attacker.get("char_id", 0),
                        attacker.get("name", ""),
                        attacker.get("level", 0),
                        attacker.get("ai_level", 0),
                        attacker.get("profession", ""), 0, 0, 0, battle_id, t
                    ])
            else:
                is_victory = 1
                self.db.exec(
                    "UPDATE tower_attacker SET is_victory = ? WHERE id = ?",
                    [is_victory, row.attack_id])

                is_finished = 1
                self.db.exec(
                    "UPDATE tower_battle SET is_finished = ?, last_updated = ? WHERE id = ?",
                    [is_finished, t, row.battle_id])
        elif event_data.type == "terminated":
            site_number = 0
            is_finished = 1
            self.db.exec(
                "INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)",
                [
                    event_data.location.playfield.id, site_number,
                    event_data.loser.org_name, event_data.loser.faction,
                    is_finished, event_data.type, t
                ])
        else:
            raise Exception("Unknown victory event type: '%s'" %
                            event_data.type)

    def format_attacker(self, row):
        if row.att_char_name:
            level = ("%d/<green>%d<end>" % (row.att_level, row.att_ai_level)
                     ) if row.att_ai_level > 0 else "%d" % row.att_level
            org = row.att_org_name + " " if row.att_org_name else ""
            victor = " - <notice>Winner!<end>" if row.is_victory else ""
            return "%s (%s %s) %s(%s)%s" % (row.att_char_name, level,
                                            row.att_profession, org,
                                            row.att_faction, victor)
        else:
            return "Unknown attacker"

    def find_closest_site_number(self, playfield_id, x_coord, y_coord):
        sql = """
            SELECT
                site_number,
                ((x_distance * x_distance) + (y_distance * y_distance)) radius
            FROM
                (SELECT
                    playfield_id,
                    site_number,
                    min_ql,
                    max_ql,
                    x_coord,
                    y_coord,
                    site_name,
                    (x_coord - ?) as x_distance,
                    (y_coord - ?) as y_distance
                FROM
                    tower_site
                WHERE
                    playfield_id = ?) t
            ORDER BY
                radius ASC
            LIMIT 1"""

        row = self.db.query_single(sql, [x_coord, y_coord, playfield_id])
        if row:
            return row.site_number
        else:
            return 0

    def find_or_create_battle(self, playfield_id, site_number, org_name,
                              faction, battle_type, t):
        last_updated = t - (8 * 3600)
        is_finished = 0

        sql = """
            SELECT
                *
            FROM
                tower_battle
            WHERE
                playfield_id = ?
                AND site_number = ?
                AND is_finished = ?
                AND def_org_name = ?
                AND def_faction = ?
                AND last_updated >= ?
        """

        battle = self.db.query_single(sql, [
            playfield_id, site_number, is_finished, org_name, faction,
            last_updated
        ])

        if battle:
            self.db.exec(
                "UPDATE tower_battle SET last_updated = ? WHERE id = ?",
                [t, battle.id])
            return battle
        else:
            self.db.exec(
                "INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)",
                [
                    playfield_id, site_number, org_name, faction, is_finished,
                    battle_type, t
                ])
            return self.db.query_single(
                "SELECT * FROM tower_battle WHERE id = ?",
                [self.db.last_insert_id()])

    def get_last_attack(self, att_faction, att_org_name, def_faction,
                        def_org_name, playfield_id, t):
        last_updated = t - (8 * 3600)
        is_finished = 0

        sql = """
            SELECT
                b.id AS battle_id,
                a.id AS attack_id
            FROM
                tower_battle b
                JOIN tower_attacker a ON
                    a.tower_battle_id = b.id 
            WHERE
                a.att_faction = ?
                AND a.att_org_name = ?
                AND b.def_faction = ?
                AND b.def_org_name = ?
                AND b.playfield_id = ?
                AND b.is_finished = ?
                AND b.last_updated >= ?
            ORDER BY
                last_updated DESC
            LIMIT 1"""

        return self.db.query_single(sql, [
            att_faction, att_org_name, def_faction, def_org_name, playfield_id,
            is_finished, last_updated
        ])

    def format_battle_info(self, row, t):
        blob = ""
        defeated = " - <notice>Defeated!<end>" if row.is_finished else ""
        blob += "Site: <highlight>%s %s<end>\n" % (row.short_name,
                                                   row.site_number or "?")
        blob += "Defender: <highlight>%s<end> (%s)%s\n" % (
            row.def_org_name, row.def_faction, defeated)
        blob += "Last Activity: %s\n" % self.format_timestamp(
            row.last_updated, t)
        return blob

    def format_timestamp(self, t, current_t):
        return "<highlight>%s<end> (%s ago)" % (self.util.format_datetime(
            t), self.util.time_to_readable(current_t - t))

    def get_chat_command(self, page):
        return "/tell <myname> attacks --page=%d" % page

    def check_for_all_towers_channel(self):
        if not self.public_channel_service.get_channel_name(
                TowerController.ALL_TOWERS_ID):
            return "Notice: The bot must belong to an org and be promoted to a rank that is high enough to have the All Towers channel (e.g., Squad Commander) in order for the <symbol>attacks command to work correctly.\n\n"
        else:
            return ""
Exemple #20
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")

    @setting(name="default_min_lvl", value="1", description="Default minimum level for joining raids")
    def default_min_lvl(self):
        return NumberSettingType()

    @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

        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(int(time.time()) - self.raid.started_at))
        blob += "Minimum Level: <highlight>%d<end>\n" % self.raid.raid_min_lvl
        blob += "Maximum Characters: <highlight>%s<end>\n" % (self.raid.raid_limit or "No Limit")
        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"), NamedParameters(["num_characters", "min_level"])],
             description="Start new raid", access_level="moderator", sub_command="manage")
    def raid_start_cmd(self, request, _, raid_name: str, named_params):
        if self.raid:
            return "The raid, <yellow>%s<end>, is already running." % self.raid.raid_name

        if named_params.min_level:
            raid_min_lvl = int(named_params.min_level)
        else:
            raid_min_lvl = self.setting_service.get("default_min_lvl").get_value()

        if named_params.num_characters:
            num_characters = int(named_params.num_characters)
        else:
            num_characters = None

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

        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<yellow>----------------------------------------<end>\n"
        msg += "<yellow>%s<end> has just started the raid <yellow>%s<end>.\n" % (request.sender.name, raid_name)
        msg += "Raider limit: <highlight>%s<end>\n" % self.raid.raid_limit
        msg += "Min lvl: <highlight>%d<end>\n" % self.raid.raid_min_lvl
        msg += "%s to join\n" % join_link
        msg += "<yellow>----------------------------------------<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("%s canceled the raid <yellow>%s<end> prematurely." % (request.sender.name, raid_name))
        self.bot.send_private_channel_message("%s canceled the raid <yellow>%s<end> 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)

        player_level = self.db.query_single("SELECT level FROM player WHERE char_id = ?", [request.sender.char_id])
        player_level = player_level.level \
            if player_level is not None \
            else self.setting_service.get("default_min_lvl").get_value()

        if player_level < self.raid.raid_min_lvl:
            return "Your level (%d) does not meet the requirements of the raid (%d)." % (player_level, self.raid.raid_min_lvl)

        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("%s joined the raid with a different alt, <yellow>%s<end>." % (former_active_name, request.sender.name))
                self.bot.send_org_message("%s joined the raid with a different alt, <yellow>%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, <yellow>%s<end>." % (former_active_name, request.sender.name))
                self.bot.send_org_message("%s returned to actively participate with a different alt, <yellow>%s<end>." % (former_active_name, request.sender.name))

        elif self.raid.raid_limit is not None and len(self.raid.raiders) >= self.raid.raid_limit:
            return "Raid is full."
        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("<yellow>%s<end> joined the raid." % request.sender.name)
            self.bot.send_org_message("<yellow>%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("<yellow>%s<end> left the raid." % request.sender.name)
            self.bot.send_org_message("<yellow>%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 "<green>%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 "%s 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 "%s has been kicked from the raid with reason \"%s\"" % (char.name, reason)
        else:
            return "%s 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_limit, raid_min_lvl, " \
              "raid_start, raid_end) VALUES (?,?,?,?,?,?)"
        num_rows = self.db.exec(sql, [self.raid.raid_name, self.raid.started_by.char_id, self.raid.raid_limit, self.raid.raid_min_lvl, 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
            with_errors = len(self.raid.raiders)

            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 (?,?,?,?,?,?)"
                with_errors -= 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%s." % ("" if with_errors == 0 else " with %d errors when logging participants" % with_errors)
        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 += "Raid limit: <highlight>%s<end>\n" % log_entry[0].raid_limit
        blob += "Raid min lvl: <highlight>%s<end>\n" % log_entry[0].raid_min_lvl
        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] <orange>%s<end> started by <yellow>%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 <yellow>%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(ChatBlob(link_txt, blob), 5000, 1)[0]