예제 #1
0
    def check(self, character: Character, target: Target) -> bool:
        attr = None

        if self.behavior['unit'] == 'Character':
            mapped_attr = ATTRIBUTES['Character'].get(self.behavior['attrs'],
                                                      '')
            attr = getattr(
                character,
                mapped_attr if mapped_attr else self.behavior['attrs'])
        elif self.behavior['unit'] == 'Target':
            mapped_attr = ATTRIBUTES['Target'].get(self.behavior['attrs'], '')
            attr = getattr(
                target, mapped_attr if mapped_attr else self.behavior['attrs'])
        elif self.behavior['unit'] == 'Tick':
            attr = time.time() - self._last_tick

        self._last_tick = time.time()
        comparable_value = VALUE_CONVERTER[self.behavior['attrs']](
            self.behavior['attr_value'])

        res = OPERATORS[self.behavior['attrs']][self.behavior['ops']](
            attr, comparable_value)
        Logger.debug("Attribute: {} {} {} = {}".format(attr,
                                                       self.behavior['ops'],
                                                       comparable_value, res))

        return res
예제 #2
0
    def extract_data_from_screen(self, screen: Image) -> ExtractedData or None:
        raw_data = self._color.image_to_string(self._crop_image(screen))

        data = self.parser.parse(raw_data)
        Logger.debug("Parsed data: {}".format(data))

        return data
예제 #3
0
    def turn(self, character: Character) -> (float, Direction) or None:
        current_trajectory = calculate_trajectory(character.position,
                                                  character.facing)
        waypoint_trajectory = Trajectory(
            character.position,
            self.waypoints.peek(character.current_waypoint))

        angle_difference, direction = current_trajectory.calculate_turn(
            waypoint_trajectory)
        # self._show_on_plot(current_trajectory, waypoint_trajectory)

        if angle_difference <= GlobalConfig.config.movement.turn_threshold:
            return None
        elif angle_difference >= GlobalConfig.config.movement.stop_threshold:
            if character.is_moving:
                self.controller.stop()
                character.is_moving = False

        Logger.debug('Current angle: {}'.format(character.facing))
        Logger.debug('Waypoint: {} - {} rad on the {}'.format(
            waypoint_trajectory.end_point, angle_difference, direction.name))

        if direction == Direction.left:
            self.controller.turn_left(transform_turn(angle_difference))
        else:
            self.controller.turn_right(transform_turn(angle_difference))

        return angle_difference, direction
예제 #4
0
class Channel(object):
    def __init__(self,
                 parent,
                 host='127.0.0.1',
                 port=6668,
                 name='undefined',
                 topic='undefined'):
        self.notify = Logger(
            'Channel')  # TODO - generate unique hashes for each channel?

        self.parent = parent

        self.host = host
        self.port = port
        self.name = name
        self.topic = topic

    def setup_channel(self):
        self.notify.warning('attempting to establish connection to server...')
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.connect((self.host, self.parent.port))
            s.sendall(bytes([1]))
            try:
                data = s.recv(4096)
                self.notify.debug('received data - {}'.format(data))
                self.handle_data(Packet(data))
            except Exception as e:
                raise Exception(e)

    def handle_data(self, packet):
        if packet.data[0] == 2:
            self.notify.info('successfully established connection to server!')
예제 #5
0
class OakleyDbImporter(object):
    def __init__(self, connection_pool):
        self.logger = Logger(self.__class__.__name__).get()
        self.connection_pool = connection_pool

    def import_table(self, table_name, import_file):

        self.logger.info('importing table [{}] from file [{}]'.format(
            table_name, import_file))
        with open(import_file, 'r') as my_file:
            rr = UnicodeReader(my_file)

            query = "INSERT INTO {} (".format(table_name)

            cnx = self.connection_pool.get_connection()

            # first line is header, column names
            # second line is header, column types
            processed_header = False
            processed_types = False

            field_types = []
            for line in rr:
                if not processed_header:
                    processed_header = True
                    query += ','.join(line)
                    query += ') VALUES ('

                    data_str = '%s,' * len(line)

                    query += data_str.rstrip(',')
                    query += ')'
                elif processed_header and not processed_types:
                    processed_types = True
                    field_types = line
                else:
                    cursor = cnx.cursor()

                    for count in range(0, len(line)):
                        if field_types[count] == 'INT' or field_types[
                                count] == 'BIT':
                            line[count] = int(line[count])
                        elif field_types[count] == 'TIMESTAMP' and line[
                                count] == '0':
                            line[count] = 0

                    self.logger.debug(
                        "Updating model with query [%s] and data [%s]", query,
                        line)

                    cursor.execute(query, line)

                    cnx.commit()

                    cursor.close()

            self.connection_pool.release_connection(cnx)

            print 'Done'
예제 #6
0
    def parse(self, raw: str) -> ExtractedData:
        raw = [r for r in raw.split('\n')]
        Logger.debug("Extracting raw data: {}".format(raw))

        try:
            clean_data = self._extract_value(raw)
            data = ExtractedData(
                player_health=int(clean_data[self.ADDON_DATA_POSITION[0]]),
                player_resource=int(clean_data[self.ADDON_DATA_POSITION[1]]),
                player_position=(
                    float(clean_data[self.ADDON_DATA_POSITION[2]]),
                    -float(clean_data[self.ADDON_DATA_POSITION[3]])),
                facing=float(clean_data[self.ADDON_DATA_POSITION[4]]),
                combat=bool(clean_data[self.ADDON_DATA_POSITION[5][0]]),
                casting=CastingState(
                    clean_data[self.ADDON_DATA_POSITION[5][1]]),
                last_ability=LastAbilityExecution(
                    clean_data[self.ADDON_DATA_POSITION[5][2]]),
                target_health=int(clean_data[self.ADDON_DATA_POSITION[6]]),
                target_distance=DistanceRange(
                    clean_data[self.ADDON_DATA_POSITION[7][0]]),
                target_in_combat=bool(
                    clean_data[self.ADDON_DATA_POSITION[7][1]]),
                target_id=int(
                    str(clean_data[self.ADDON_DATA_POSITION[8]])[:5], 16)
                if len(clean_data[self.ADDON_DATA_POSITION[8]]) > 2 else int(
                    clean_data[self.ADDON_DATA_POSITION[8]]),
                target_guid=int(str(clean_data[self.ADDON_DATA_POSITION[8]]),
                                16)
                if len(clean_data[self.ADDON_DATA_POSITION[8]]) > 2 else int(
                    clean_data[self.ADDON_DATA_POSITION[8]]),
                is_inventory_full=bool(
                    clean_data[self.ADDON_DATA_POSITION[5][3]]),
                player_has_pet=bool(
                    clean_data[self.ADDON_DATA_POSITION[5][4]]),
                player_first_resource_available=bool(
                    clean_data[self.ADDON_DATA_POSITION[5][5]]),
                pet_health=int(clean_data[self.ADDON_DATA_POSITION[9]]),
                pet_mana=int(clean_data[self.ADDON_DATA_POSITION[10]]),
            )

            return data
        except Exception as e:
            raise ExtractException(raw)
예제 #7
0
class AccessManager:
    def __init__(self):
        self.logger = Logger("access_manager")

    def inject(self, registry):
        self.db = registry.get_instance("db")
        self.character_manager = registry.get_instance("character_manager")
        self.alts_manager = registry.get_instance("alts_manager")

    def register_access_level(self, label, level, handler):
        self.logger.debug("Registering access level %d with label '%s'" % (level, label))
        self.access_levels.append({"label": label.lower(), "level": level, "handler": handler})
        self.access_levels = sorted(self.access_levels, key=lambda k: k["level"])

    def get_access_levels(self):
        acclvls = self.db.query("SELECT * FROM access_ranks ORDER BY access_level DESC");
        if len(acclvls) > 0:
            for row in acclvls:
                result.append(["name":row.name, "access_level":row.access_level, "name_short":row.name_short, "description":row.description])
        return result

    def get_access_level(self, char):
        return char.access_level

    def get_access_level_by_level(self, level):
        row = self.db.query_single("SELECT * FROM access_ranks WHERE access_level == ?", [level])
        if row:
            return row
        else: return None

    def get_access_level_by_label(self, label):
        row = self.db.query_single("SELECT access_level FROM access_ranks WHERE name LIKE ?", [label])
        if row:
            return row.access_level
        else: return None

    def check_access(self, char, access_level_label):
        return char.highest_access <= self.get_access_level_by_label(access_level_label)["level"]
예제 #8
0
파일: start.py 프로젝트: 9uapaw/bombshell
    def start(self, show_window=False):
        self._waypoints.parse(self.config.waypoint['grind'][0]['waypoints'])
        self._behavior.resolve_profile(GlobalConfig.config.behavior)

        for screen in self._screen_interceptor.capture():
            if show_window:
                self._show_window(screen)

            time_before = time.time()
            try:
                data = self._extractor.extract_data_from_screen(screen)
                self._data_sanitizer.sanitize_data(data)
                delta = time.time() - time_before
                Logger.debug("Elapsed time after extraction: {}".format(delta))
                time.sleep(0.05)
                self._state_handler.update(data, screen)
            except ExtractException as e:
                # screen.save(f"errorimages\\{str(uuid.uuid4().hex)}.bmp")
                Logger.error(
                    "Error while extracting data from addon. Data extracted: {}",
                    e.partial)
                if self._extract_error_count <= GlobalConfig.config.core.extract_error_threshold:
                    self._extract_error_count += 1
                    continue
                else:
                    self._recover_error_count += 1
                    self._state_handler = StateHandler(self._controller,
                                                       self._behavior,
                                                       self._waypoints)
            except RecoverableException as e:
                if self._recover_error_count <= GlobalConfig.config.core.recoverable_error_threshold:
                    self._recover_error_count += 1
                    self._state_handler = StateHandler(self._controller,
                                                       self._behavior,
                                                       self._waypoints)
                    continue
                else:
                    raise UnrecoverableException(str(e))
예제 #9
0
    def move(self, character: Character):
        Logger.debug(
            "Following waypoint {} out of {}. Character is currently moving: {}"
            .format(character.current_waypoint,
                    len(self.waypoints.waypoints) - 1, character.is_moving))
        if character.position.is_close_to(
                self.waypoints.waypoints[character.current_waypoint],
                GlobalConfig.config.movement.waypoint_difference_threshold):
            Logger.debug("Close to waypoint")

            if character.current_waypoint == len(self.waypoints.waypoints) - 1:
                character.current_waypoint = 0
                self.waypoints.reverse()
            else:
                character.current_waypoint += 1

        if not self.turn(character):
            if not character.is_moving:
                Logger.debug('Moving')
                self.controller.move_forward()
                character.is_moving = True
예제 #10
0
파일: loot.py 프로젝트: 9uapaw/bombshell
    def _check_through_screen(self, gen):
        Logger.debug("Entered loot state - Checking through screen")
        max_step_x = 20
        max_step_y = 15
        iter_x = int(
            (self.screen_res[2] - self.screen_res[0]) / max_step_x) + 1
        iter_y = int(
            (self.screen_res[3] - self.screen_res[1]) / max_step_y) + 1
        base_x = self.screen_res[0]
        base_y = self.screen_res[1]

        safe_zone = 3
        max_step_x -= safe_zone
        max_step_y -= safe_zone

        Logger.debug(
            "Checking through screen: x-stepsize: {} y-stepsize: {}".format(
                iter_x, iter_y))

        found = False

        while True:
            for i in range(safe_zone, max_step_x):
                x = base_x + iter_x * i
                for j in range(safe_zone * 2,
                               max_step_y):  # start from more downwards
                    y = base_y + iter_y * j
                    Logger.debug("Current x: {} current y: {}".format(x, y))
                    self.controller.move_mouse(x, y)
                    current_screen = next(gen)
                    found = self.scuttler.try_find(
                        current_screen, ScreenObjects.LOOT_ICON.value)
                    if found:
                        self.controller.right_click()
            if not found:
                break

        if not found:
            self.finished_looting = True
예제 #11
0
class SettingService:
    def __init__(self):
        self.logger = Logger(__name__)
        self.settings = {}
        self.db_cache = {}
        self.change_listeners = {}

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

    def start(self):
        # process decorators
        for _, inst in Registry.get_all_instances().items():
            for name, method in get_attrs(inst).items():
                if hasattr(method, "setting"):
                    setting_name, value, description, extended_description, obj = getattr(
                        method, "setting")
                    self.register(inst.module_name, setting_name, value, obj,
                                  description, extended_description)

    def register(self,
                 module,
                 name,
                 value,
                 setting: SettingType,
                 description,
                 extended_description=None):
        """Call during start"""
        name = name.lower()
        module = module.lower()
        setting.set_name(name)
        setting.set_description(description)
        setting.set_extended_description(extended_description)

        if not description:
            self.logger.warning("No description specified for setting '%s'" %
                                name)

        if " " in name:
            raise Exception(
                "One or more spaces found in setting name '%s' for module '%s'"
                % (name, module))

        row = self.db.query_single(
            "SELECT name, value, description FROM setting WHERE name = ?",
            [name])

        if row is None:
            self.logger.debug("Adding setting '%s'" % name)

            self.db.exec(
                "INSERT INTO setting (name, value, description, module, verified) VALUES (?, ?, ?, ?, ?)",
                [name, "", description, module, 1])

            # verify default value is a valid value, and is formatted appropriately
            setting.set_value(value)
        else:
            self.logger.debug("Updating setting '%s'" % name)
            self.db.exec(
                "UPDATE setting SET description = ?, verified = ?, module = ? WHERE name = ?",
                [description, 1, module, name])

        self.settings[name] = setting

    def register_change_listener(self, setting_name, handler):
        """
        Call during start

        Args:
            setting_name: str
            handler: (name: string, old_value, new_value) -> void
        """

        if len(inspect.signature(handler).parameters) != 3:
            raise Exception(
                "Incorrect number of arguments for handler '%s.%s()'" %
                (handler.__module__, handler.__name__))

        if setting_name in self.settings:
            if setting_name not in self.change_listeners:
                self.change_listeners[setting_name] = []
            self.change_listeners[setting_name].append(handler)
        else:
            raise Exception(
                "Could not register change_listener for setting '%s' since it does not exist"
                % setting_name)

    def get_value(self, name):
        # check cache first
        result = self.db_cache.get(name, None)
        if result:
            return result.value
        else:
            row = self.db.query_single(
                "SELECT value FROM setting WHERE name = ?", [name])

            # store result in cache
            self.db_cache[name] = row

            return row.value if row else None

    def set_value(self, name, value):
        old_value = self.get_value(name)

        # clear cache
        self.db_cache[name] = None

        self.db.exec("UPDATE setting SET value = ? WHERE name = ?",
                     [value, name])

        if name in self.change_listeners:
            for change_listener in self.change_listeners[name]:
                change_listener(name, old_value, value)

    def get(self, name):
        name = name.lower()
        setting = self.settings.get(name, None)
        if setting:
            return setting
        else:
            return None
예제 #12
0
def main(argv=None):
    parser = argparse.ArgumentParser(
        description="Client tool for changing boot order via Redfish API.")
    parser.add_argument("-H", help="iDRAC host address")
    parser.add_argument("-u", help="iDRAC username", required=True)
    parser.add_argument("-p", help="iDRAC password", required=True)
    parser.add_argument("-i",
                        help="Path to iDRAC interfaces yaml",
                        default=None)
    parser.add_argument("-t", help="Type of host. Accepts: foreman, director")
    parser.add_argument("-l",
                        "--log",
                        help="Optional argument for logging results to a file")
    parser.add_argument("-f",
                        "--force",
                        dest='force',
                        action='store_true',
                        help="Optional argument for forced clear-jobs")
    parser.add_argument("--host-list",
                        help="Path to a plain text file with a list of hosts.",
                        default=None)
    parser.add_argument("--pxe",
                        help="Set next boot to one-shot boot PXE",
                        action="store_true")
    parser.add_argument(
        "--boot-to",
        help="Set next boot to one-shot boot to a specific device")
    parser.add_argument(
        "--boot-to-type",
        help="Set next boot to one-shot boot to either director or foreman")
    parser.add_argument(
        "--boot-to-mac",
        help=
        "Set next boot to one-shot boot to a specific MAC address on the target"
    )
    parser.add_argument("--reboot-only",
                        help="Flag for only rebooting the host",
                        action="store_true")
    parser.add_argument(
        "--power-cycle",
        help="Flag for sending ForceOff instruction to the host",
        action="store_true")
    parser.add_argument("--racreset",
                        help="Flag for iDRAC reset",
                        action="store_true")
    parser.add_argument("--check-boot",
                        help="Flag for checking the host boot order",
                        action="store_true")
    parser.add_argument("--firmware-inventory",
                        help="Get firmware inventory",
                        action="store_true")
    parser.add_argument("--export-configuration",
                        help="Export system configuration to XML",
                        action="store_true")
    parser.add_argument("--clear-jobs",
                        help="Clear any schedule jobs from the queue",
                        action="store_true")
    parser.add_argument("-v",
                        "--verbose",
                        help="Verbose output",
                        action="store_true")
    parser.add_argument("-r",
                        "--retries",
                        help="Number of retries for executing actions.",
                        default=RETRIES)
    args = vars(parser.parse_args(argv))

    log_level = DEBUG if args["verbose"] else INFO

    logger = Logger()
    logger.start(level=log_level)

    if args["log"]:
        file_handler = FileHandler(args["log"])
        file_handler.setFormatter(Formatter(logger.LOGFMT))
        file_handler.setLevel(DEBUG)
        logger.addHandler(file_handler)

    host_list = args["host_list"]
    host = args["H"]

    if host_list:
        try:
            with open(host_list, "r") as _file:
                for _host in _file.readlines():
                    try:
                        execute_badfish(_host.strip(), args, logger)
                    except SystemExit:
                        continue
        except IOError as ex:
            logger.debug(ex)
            logger.error("There was something wrong reading from %s" %
                         host_list)
    elif not host:
        logger.error(
            "You must specify at least either a host (-H) or a host list (--host-list)."
        )
    else:
        execute_badfish(host, args, logger)
    return 0
예제 #13
0
class PorkService:
    def __init__(self):
        self.logger = Logger(__name__)

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

    def pre_start(self):
        self.bot.add_packet_handler(server_packets.CharacterLookup.id,
                                    self.update)
        self.bot.add_packet_handler(server_packets.CharacterName.id,
                                    self.update)

    def request_char_info(self, char_name, server_num):
        url = self.get_pork_url(server_num, char_name)

        try:
            r = requests.get(url, timeout=5)
            result = r.json()
        except ReadTimeout:
            self.logger.warning("Timeout while requesting '%s'" % url)
            result = None
        except ValueError as e:
            self.logger.debug(
                "Error marshalling value as json for url '%s': %s" %
                (url, r.text), e)
            result = None

        char_info = None
        if result:
            char_info_json = result[0]
            org_info_json = result[1] if result[1] else {}

            char_info = DictObject({
                "name":
                char_info_json["NAME"],
                "char_id":
                char_info_json["CHAR_INSTANCE"],
                "first_name":
                char_info_json["FIRSTNAME"],
                "last_name":
                char_info_json["LASTNAME"],
                "level":
                char_info_json["LEVELX"],
                "breed":
                char_info_json["BREED"],
                "dimension":
                char_info_json["CHAR_DIMENSION"],
                "gender":
                char_info_json["SEX"],
                "faction":
                char_info_json["SIDE"],
                "profession":
                char_info_json["PROF"],
                "profession_title":
                char_info_json["PROFNAME"],
                "ai_rank":
                char_info_json["RANK_name"],
                "ai_level":
                char_info_json["ALIENLEVEL"],
                "pvp_rating":
                char_info_json["PVPRATING"],
                "pvp_title":
                char_info_json["PVPTITLE"] or "",
                "head_id":
                char_info_json["HEADID"],
                "org_id":
                org_info_json.get("ORG_INSTANCE", 0),
                "org_name":
                org_info_json.get("NAME", ""),
                "org_rank_name":
                org_info_json.get("RANK_TITLE", ""),
                "org_rank_id":
                org_info_json.get("RANK", 0),
                "source":
                "people.anarchy-online.com",
                "cache_age":
                0
            })

        return char_info

    def get_character_info(self, char, max_cache_age=86400):
        char_id = self.character_service.resolve_char_to_id(char)
        char_name = self.character_service.resolve_char_to_name(char)

        t = int(time.time())

        # if there is an entry in database and it is within the cache time, use that
        db_char_info = self.get_from_database(char_id=char_id,
                                              char_name=char_name)
        if db_char_info:
            db_char_info.cache_age = t - db_char_info.last_updated

            if db_char_info.cache_age < max_cache_age and db_char_info.source != "chat_server":
                return db_char_info

        # if we can't resolve to a char_name, we can't make a call to pork
        if not char_name:
            return db_char_info

        char_info = self.request_char_info(char_name, self.bot.dimension)

        if char_info and char_info.char_id == char_id:
            self.save_character_info(char_info)

            return char_info
        else:
            # return cached info from database, even tho it's old, and set cache_age (if it exists)
            if db_char_info:
                db_char_info.cache_age = t - db_char_info.last_updated

            return db_char_info

    def load_character_info(self, char_id):
        char_info = self.get_character_info(char_id)
        if not char_info:
            char_info = DictObject({
                "name": "Unknown:" + str(char_id),
                "char_id": char_id,
                "first_name": "",
                "last_name": "",
                "level": 0,
                "breed": "",
                "dimension": self.bot.dimension,
                "gender": "",
                "faction": "",
                "profession": "",
                "profession_title": "",
                "ai_rank": "",
                "ai_level": 0,
                "pvp_rating": 0,
                "pvp_title": "",
                "head_id": 0,
                "org_id": 0,
                "org_name": "",
                "org_rank_name": "",
                "org_rank_id": 6,
                "source": "stub"
            })
            self.save_character_info(char_info)

    def save_character_info(self, char_info):
        if char_info["dimension"] != self.bot.dimension:
            return

        self.db.exec("DELETE FROM player WHERE char_id = ?",
                     [char_info["char_id"]])

        insert_sql = """
            INSERT INTO player ( char_id, name, first_name, last_name, level, breed, gender, faction, profession,
                profession_title, ai_rank, ai_level, org_id, org_name, org_rank_name, org_rank_id, dimension, head_id,
                pvp_rating, pvp_title, source, last_updated)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """

        self.db.exec(insert_sql, [
            char_info["char_id"], char_info["name"], char_info["first_name"],
            char_info["last_name"], char_info["level"], char_info["breed"],
            char_info["gender"], char_info["faction"], char_info["profession"],
            char_info["profession_title"], char_info["ai_rank"],
            char_info["ai_level"], char_info["org_id"], char_info["org_name"],
            char_info["org_rank_name"], char_info["org_rank_id"],
            char_info["dimension"], char_info["head_id"],
            char_info["pvp_rating"], char_info["pvp_title"],
            char_info["source"],
            int(time.time())
        ])

    def get_from_database(self, char_id=None, char_name=None):
        if char_id:
            return self.db.query_single(
                "SELECT char_id, name, first_name, last_name, level, breed, gender, faction, profession, "
                "profession_title, ai_rank, ai_level, org_id, org_name, org_rank_name, org_rank_id, "
                "dimension, head_id, pvp_rating, pvp_title, source, last_updated "
                "FROM player WHERE char_id = ?", [char_id])
        elif char_name:
            return self.db.query_single(
                "SELECT char_id, name, first_name, last_name, level, breed, gender, faction, profession, "
                "profession_title, ai_rank, ai_level, org_id, org_name, org_rank_name, org_rank_id, "
                "dimension, head_id, pvp_rating, pvp_title, source, last_updated "
                "FROM player WHERE name = ?", [char_name])
        else:
            return None

    def update(self, packet):
        # don't update if we didn't get a valid response
        if packet.char_id == 4294967295:
            return

        character = self.get_from_database(char_id=packet.char_id)

        if character:
            if character.name != packet.name:
                self.db.exec("UPDATE player SET name = ? WHERE char_id = ?",
                             [packet.name, packet.char_id])
        else:
            insert_sql = """
                INSERT INTO player ( char_id, name, first_name, last_name, level, breed, gender, faction, profession,
                profession_title, ai_rank, ai_level, org_id, org_name, org_rank_name, org_rank_id, dimension, head_id,
                pvp_rating, pvp_title, source, last_updated)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"""

            self.db.exec(insert_sql, [
                packet.char_id, packet.name, "", "", 0, "", "", "", "", "", "",
                0, 0, "", "", 6, self.bot.dimension, 0, 0, "", "chat_server",
                int(time.time())
            ])

    def find_orgs(self, search):
        return self.db.query(
            "SELECT DISTINCT org_name, org_id FROM player WHERE org_name <EXTENDED_LIKE=0> ?",
            [search],
            extended_like=True)

    def get_pork_url(self, dimension, char_name):
        return "http://people.anarchy-online.com/character/bio/d/%d/name/%s/bio.xml?data_type=json" % (
            dimension, char_name)
예제 #14
0
파일: db.py 프로젝트: Sliphs/Tyrbot
class DB:
    SQLITE = "sqlite"
    MYSQL = "mysql"

    def __init__(self):
        self.conn = None
        self.enhanced_like_regex = re.compile(
            r"(\s+)(\S+)\s+<EXTENDED_LIKE=(\d+)>\s+\?(\s*)", re.IGNORECASE)
        self.lastrowid = None
        self.logger = Logger(__name__)
        self.type = None
        self.transaction_level = 0

    def sqlite_row_factory(self, cursor: sqlite3.Cursor, row):
        d = {}
        for idx, col in enumerate(cursor.description):
            d[col[0]] = row[idx]
        return d

    def connect_mysql(self, host, port, username, password, database_name):
        self.type = self.MYSQL
        self.conn = mysql.connector.connect(user=username,
                                            password=password,
                                            host=host,
                                            port=port,
                                            database=database_name,
                                            charset="utf8",
                                            autocommit=True)
        self.exec("SET collation_connection = 'utf8_general_ci'")
        self.exec("SET sql_mode = 'TRADITIONAL,ANSI'")
        self.create_db_version_table()

    def connect_sqlite(self, filename):
        self.type = self.SQLITE
        self.conn = sqlite3.connect(filename,
                                    isolation_level=None,
                                    check_same_thread=False)
        self.conn.row_factory = self.sqlite_row_factory
        self.create_db_version_table()

    def create_db_version_table(self):
        self.exec(
            "CREATE TABLE IF NOT EXISTS db_version (file VARCHAR(255) NOT NULL, version VARCHAR(255) NOT NULL, verified SMALLINT NOT NULL)"
        )

    def _execute_wrapper(self, sql, params, callback):
        if self.type == self.MYSQL:
            # buffered=True - https://stackoverflow.com/a/33632767/280574
            cur = self.conn.cursor(dictionary=True, buffered=True)
        else:
            cur = self.conn.cursor()

        start_time = time.time()
        try:
            cur.execute(
                sql if self.type == self.SQLITE else sql.replace("?", "%s"),
                params)
        except Exception as e:
            raise SqlException(
                "SQL Error: '%s' for '%s' [%s]" %
                (str(e), sql, ", ".join(map(lambda x: str(x), params)))) from e

        elapsed = time.time() - start_time

        if elapsed > 0.5:
            self.logger.warning("slow query (%fs) '%s' for params: %s" %
                                (elapsed, sql, str(params)))

        result = callback(cur)
        cur.close()
        return result

    def query_single(self, sql, params=None, extended_like=False):
        if params is None:
            params = []

        if extended_like:
            sql, params = self.handle_extended_like(sql, params)

        sql, params = self.format_sql(sql, params)

        def map_result(cur):
            row = cur.fetchone()
            return DictObject(row) if row else None

        return self._execute_wrapper(sql, params, map_result)

    def query(self, sql, params=None, extended_like=False):
        if params is None:
            params = []

        if extended_like:
            sql, params = self.handle_extended_like(sql, params)

        sql, params = self.format_sql(sql, params)

        def map_result(cur):
            return list(map(lambda row: DictObject(row), cur.fetchall()))

        return self._execute_wrapper(sql, params, map_result)

    def exec(self, sql, params=None, extended_like=False):
        if params is None:
            params = []

        if extended_like:
            sql, params = self.handle_extended_like(sql, params)

        sql, params = self.format_sql(sql, params)

        def map_result(cur):
            return [cur.rowcount, cur.lastrowid]

        row_count, lastrowid = self._execute_wrapper(sql, params, map_result)
        self.lastrowid = lastrowid
        return row_count

    def last_insert_id(self):
        return self.lastrowid

    def format_sql(self, sql, params=None):
        # TODO check for AUTOINCREMENT in sql and log warning

        if self.type == self.SQLITE:
            sql = sql.replace("AUTO_INCREMENT", "AUTOINCREMENT")
            sql = sql.replace(" INT ", " INTEGER ")
            sql = sql.replace("INSERT IGNORE", "INSERT OR IGNORE")

        return sql, params

    def handle_extended_like(self, sql, params):
        original_params = params.copy()
        params = list(map(lambda x: [x], params))

        for match in self.enhanced_like_regex.finditer(sql):
            field = match.group(2)
            index = int(match.group(3))

            extra_sql, vals = self._get_extended_params(
                field, original_params[index].split(" "))

            sql = self.enhanced_like_regex.sub(
                match.group(1) + "(" + " AND ".join(extra_sql) + ")" +
                match.group(4), sql, 1)

            # remove current param and add generated params in its place
            del params[index]
            params.insert(index, vals)

        return sql, [item for sublist in params for item in sublist]

    def _get_extended_params(self, field, params):
        extra_sql = []
        vals = []
        for p in params:
            if p.startswith("-") and p != "-":
                vals.append("%" + p[1:] + "%")
                extra_sql.append(field + " NOT LIKE ?")
            else:
                vals.append("%" + p + "%")
                extra_sql.append(field + " LIKE ?")
        return extra_sql, vals

    def get_connection(self):
        return self.conn

    def load_sql_file(self, sqlfile, base_path):
        filename = base_path + os.sep + sqlfile

        db_version = self.get_db_version(filename)
        file_version = self.get_file_version(filename)

        if db_version:
            if parse_version(file_version) > parse_version(db_version):
                self.logger.debug("loading sql file '%s'" % sqlfile)
                self._load_file(filename)
            self.exec(
                "UPDATE db_version SET version = ?, verified = 1 WHERE file = ?",
                [int(file_version), filename])
        else:
            self.logger.debug("loading sql file '%s'" % sqlfile)
            self._load_file(filename)
            self.exec(
                "INSERT INTO db_version (file, version, verified) VALUES (?, ?, 1)",
                [filename, int(file_version)])

    def get_file_version(self, filename):
        return str(int(os.path.getmtime(filename)))

    def get_db_version(self, filename):
        row = self.query_single(
            "SELECT version FROM db_version WHERE file = ?", [filename])
        if row:
            return row.version
        else:
            return None

    def _load_file(self, filename):
        with open(filename, mode="r", encoding="UTF-8") as f:
            with self.transaction():
                cur = self.conn.cursor()
                line_num = 1
                for line in f.readlines():
                    try:
                        sql, _ = self.format_sql(line)
                        sql = sql.strip()
                        if sql and not sql.startswith("--"):
                            cur.execute(sql)
                    except Exception as e:
                        raise Exception(
                            "sql error in file '%s' on line %d: %s" %
                            (filename, line_num, str(e)))
                    line_num += 1
                cur.close()

    def get_type(self):
        return self.type

    # transaction support
    def transaction(self):
        return self

    def __enter__(self):
        # called when entering `with` code block
        self.begin_transaction()

    def __exit__(self, exc_type, exc_val, exc_tb):
        # called when exiting `with` code block
        # if exc_type, exc_val or exc_tb is not None, there was an exception
        # otherwise the code block exited normally
        if exc_type is None:
            self.commit_transaction()
        else:
            self.rollback_transaction()

        # False here indicates that if there was an exception, it should not be suppressed but instead propagated
        return False

    def begin_transaction(self):
        if self.transaction_level == 0:
            self.exec("BEGIN;")
        self.transaction_level += 1

    def commit_transaction(self):
        if self.transaction_level == 1:
            self.exec("COMMIT;")
        self.transaction_level -= 1

    def rollback_transaction(self):
        if self.transaction_level == 1:
            self.exec("ROLLBACK;")
        self.transaction_level -= 1
예제 #15
0
class AccessService:
    def __init__(self):
        self.access_levels = [{
            "label": "none",
            "level": 0,
            "handler": self.no_access
        }, {
            "label": "all",
            "level": 100,
            "handler": self.all_access
        }]
        self.logger = Logger(__name__)

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

    def register_access_level(self, label, level, handler):
        self.logger.debug("Registering access level %d with label '%s'" %
                          (level, label))
        self.access_levels.append({
            "label": label.lower(),
            "level": level,
            "handler": handler
        })
        self.access_levels = sorted(self.access_levels,
                                    key=lambda k: k["level"])

    def get_access_levels(self):
        return self.access_levels

    def get_access_level(self, char_id):
        access_level1 = self.get_single_access_level(char_id)

        alts = self.alts_service.get_alts(char_id)
        if not alts:
            return access_level1

        main = alts[0]
        if main.char_id == char_id:
            return access_level1
        else:
            access_level2 = self.get_single_access_level(main.char_id)
            if access_level1["level"] < access_level2["level"]:
                return access_level1
            else:
                return access_level2

    def compare_access_levels(self, access_level1, access_level2):
        """
        Returns a positive number if the access_level1 is greater than access_level2,
        a negative number if access_level1 is less than access_level2,
        and 0 if the access levels are equal.

        :param access_level1:
        :param access_level2:
        :return: int
        """
        a1 = self.get_access_level_by_label(access_level1)
        a2 = self.get_access_level_by_label(access_level2)

        return a2["level"] - a1["level"]

    def has_sufficient_access_level(self, char_id1, char_id2):
        """
        Returns True if char1 has a higher access level than char2 or if char1 is a verified alt of char2, and False otherwise.

        :param char_id1:
        :param char_id2:
        :return:
        """

        # return True if char_ids are the same
        if char_id1 == char_id2:
            return True

        # return True if both chars have the same main
        if self.alts_service.get_main(
                char_id1).char_id == self.alts_service.get_main(
                    char_id2).char_id:
            return True

        a1 = self.get_access_level(char_id1)
        a2 = self.get_access_level(char_id2)

        return a2["level"] - a1["level"] > 0

    def get_single_access_level(self, char):
        char_id = self.character_service.resolve_char_to_id(char)
        for access_level in self.access_levels:
            if access_level["handler"](char_id):
                return access_level

    def get_access_level_by_level(self, level):
        for access_level in self.access_levels:
            if access_level["level"] == level:
                return access_level
        return None

    def get_access_level_by_label(self, label):
        label = label.lower()
        for access_level in self.access_levels:
            if access_level["label"] == label:
                return access_level
        return None

    def check_access(self, char, access_level_label):
        char_id = self.character_service.resolve_char_to_id(char)
        if not char_id:
            return None

        return self.get_access_level(
            char)["level"] <= self.get_access_level_by_label(
                access_level_label)["level"]

    def no_access(self, char_id):
        return False

    def all_access(self, char_id):
        return True
예제 #16
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")
        self.ts: TranslationService = registry.get_instance("translation_service")
        self.getresp = self.ts.get_response

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

        blob = self.format_buddies(buddy_list)

        return ChatBlob(f"Buddy list ({len(buddy_list)})", blob)

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

        if char.char_id:
            self.buddy_service.add_buddy(char.char_id, buddy_type)
            return f"Character <highlight>{char.name}</highlight> has been added to the buddy list for type <highlight>{buddy_type}</highlight>."
        else:
            return self.getresp("global", "char_not_found", {"char": 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 self.getresp("global", "char_not_found", {"char": 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 self.getresp("global", "char_not_found", {"char": 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"), Any("character")], access_level="admin",
             description="Search for characters on the buddy list")
    def buddylist_search_cmd(self, request, _, search):
        search = search.lower()

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

        blob = self.format_buddies(buddy_list)
        return ChatBlob(f"Buddy List Search Results ({len(buddy_list)})", blob)

    @timerevent(budatime="24h", description="Remove orphaned buddies", is_hidden=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:
            blob += "%s(%s) - %s\n" % (name, buddy["conn_id"], ",".join(buddy["types"]))

        return blob
예제 #17
0
class Server(object):
    def __init__(self, host='127.0.0.1', port=6667, backlog=10000):
        self.notify = Logger('Server')

        self.host = host
        self.port = port
        self.backlog = backlog

        self.channels = {}  # store connected channels to the server
        self.nicknames = [
        ]  # registered nicknames across all channels on the server

    def start_server(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server_addr = (self.host, self.port)

        self.notify.info('starting server at {0}:{1}'.format(*server_addr))
        s.bind(server_addr)
        s.listen(self.backlog)

        while True:
            conn, client_addr = s.accept()
            try:
                self.notify.debug(
                    'incoming connection from {0}:{1}'.format(*client_addr))
                # TODO - determine whether new suggestion is a channel or a client
                while True:
                    data = conn.recv(1024)
                    self.notify.debug(
                        'incoming data received from {0}:{1} - {2}'.format(
                            client_addr[0], client_addr[1], data))
                    if data:
                        # TODO - handle packet data
                        self.handle_data(Packet(data), conn)
                    else:
                        self.notify.warning(
                            'invalid data received from {0}:{1} - {2}'.format(
                                client_addr[0], client_addr[1], data))
                        break
            finally:
                conn.close()

    def handle_data(self, packet, conn):
        if packet.data[0] == 1:
            try:
                self.register_channel(conn)
                self.notify.info(
                    'successfully registered new channel to server!')
                self.notify.debug(self.channels)
            except:
                raise Exception(
                    'unable to register new channel - is the client still connected to the server?'
                )

    def register_channel(self, conn):
        try:
            channel = self.channels[len(self.channels)] + 1
            self.channels[channel] = conn
            conn.sendall([2])
        except KeyError:
            channel = 1000000
            self.channels[channel] = conn
            conn.sendall([2])
예제 #18
0
class PorkService:
    def __init__(self):
        self.logger = Logger(__name__)

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

    def pre_start(self):
        self.bot.register_packet_handler(server_packets.CharacterLookup.id,
                                         self.update)
        self.bot.register_packet_handler(server_packets.CharacterName.id,
                                         self.update)

    def start(self):
        self.db.exec(
            "CREATE TABLE IF NOT EXISTS player ( char_id BIGINT PRIMARY KEY, first_name VARCHAR(30) NOT NULL, name VARCHAR(20) NOT NULL, last_name VARCHAR(30) NOT NULL, "
            "level SMALLINT NOT NULL, breed VARCHAR(20) NOT NULL, gender VARCHAR(20) NOT NULL, faction VARCHAR(20) NOT NULL, profession VARCHAR(20) NOT NULL, "
            "profession_title VARCHAR(50) NOT NULL, ai_rank VARCHAR(20) NOT NULL, ai_level SMALLINT, org_id INT DEFAULT NULL, org_name VARCHAR(255) NOT NULL, "
            "org_rank_name VARCHAR(20) NOT NULL, org_rank_id SMALLINT NOT NULL, dimension SMALLINT NOT NULL, head_id INT NOT NULL, pvp_rating SMALLINT NOT NULL, "
            "pvp_title VARCHAR(20) NOT NULL, source VARCHAR(50) NOT NULL, last_updated INT NOT NULL )"
        )

    # forces a lookup from remote PoRK server
    # this should not be called directly unless you are requesting info for a char on a different server
    # since cache will not be used and the result will not update the cache automatically
    def request_char_info(self, char_name, server_num):
        url = self.get_pork_url(server_num, char_name)

        try:
            r = requests.get(url, timeout=5)
            result = r.json()
        except ReadTimeout:
            self.logger.warning("Timeout while requesting '%s'" % url)
            result = None
        except ValueError as e:
            self.logger.debug(
                "Error marshalling value as json for url '%s': %s" %
                (url, r.text), e)
            result = None

        char_info = None
        if result:
            char_info_json = result[0]
            org_info_json = result[1] if result[1] else {}

            char_info = DictObject({
                "name":
                char_info_json["NAME"],
                "char_id":
                char_info_json["CHAR_INSTANCE"],
                "first_name":
                char_info_json["FIRSTNAME"],
                "last_name":
                char_info_json["LASTNAME"],
                "level":
                char_info_json["LEVELX"],
                "breed":
                char_info_json["BREED"],
                "dimension":
                char_info_json["CHAR_DIMENSION"],
                "gender":
                char_info_json["SEX"],
                "faction":
                char_info_json["SIDE"],
                "profession":
                char_info_json["PROF"],
                "profession_title":
                char_info_json["PROFNAME"],
                "ai_rank":
                char_info_json["RANK_name"],
                "ai_level":
                char_info_json["ALIENLEVEL"],
                "pvp_rating":
                char_info_json["PVPRATING"],
                "pvp_title":
                char_info_json["PVPTITLE"] or "",
                "head_id":
                char_info_json["HEADID"],
                "org_id":
                org_info_json.get("ORG_INSTANCE", 0),
                "org_name":
                org_info_json.get("NAME", ""),
                "org_rank_name":
                org_info_json.get("RANK_TITLE", ""),
                "org_rank_id":
                org_info_json.get("RANK", 0),
                "source":
                "people.anarchy-online.com",
                "cache_age":
                0
            })

        return char_info

    # standard method to get character pork data when character is on the same server
    def get_character_info(self, char_name_or_id, max_cache_age=86400):
        char_id = self.character_service.resolve_char_to_id(char_name_or_id)
        char_name = self.character_service.resolve_char_to_name(
            char_name_or_id)

        t = int(time.time())

        # if there is an entry in database and it is within the cache time, use that
        db_char_info = self.get_from_database(char_id=char_id,
                                              char_name=char_name)
        if db_char_info:
            db_char_info.cache_age = t - db_char_info.last_updated

            if db_char_info.cache_age < max_cache_age and db_char_info.source != "chat_server":
                return db_char_info

        # if we can't resolve to a char_name, we can't make a call to pork
        if not char_name:
            return db_char_info

        char_info = self.request_char_info(char_name, self.bot.dimension)

        if char_info and (char_id is None or char_info.char_id == char_id):
            self.save_character_info(char_info)

            return char_info
        else:
            # return cached info from database, even tho it's old, and set cache_age (if it exists)
            if db_char_info:
                db_char_info.cache_age = t - db_char_info.last_updated

            return db_char_info

    # forces a skeleton object into the player table in the case that PoRK does not return any data
    # call this method if you don't need the data now but want to ensure there is a record in the database
    def load_character_info(self, char_id, char_name=None):
        char_info = self.get_character_info(char_id)
        if not char_info and char_name:
            char_info = self.get_character_info(char_name)
        if not char_info:
            char_info = DictObject({
                "name": "Unknown:" + str(char_id),
                "char_id": char_id,
                "first_name": "",
                "last_name": "",
                "level": 0,
                "breed": "",
                "dimension": self.bot.dimension,
                "gender": "",
                "faction": "",
                "profession": "",
                "profession_title": "",
                "ai_rank": "",
                "ai_level": 0,
                "pvp_rating": 0,
                "pvp_title": "",
                "head_id": 0,
                "org_id": 0,
                "org_name": "",
                "org_rank_name": "",
                "org_rank_id": 6,
                "source": "stub"
            })
            self.save_character_info(char_info)

    def save_character_info(self, char_info):
        if char_info["dimension"] != self.bot.dimension:
            return

        self.db.exec("DELETE FROM player WHERE char_id = ?",
                     [char_info["char_id"]])

        insert_sql = """
            INSERT IGNORE INTO player ( char_id, name, first_name, last_name, level, breed, gender, faction, profession,
                profession_title, ai_rank, ai_level, org_id, org_name, org_rank_name, org_rank_id, dimension, head_id,
                pvp_rating, pvp_title, source, last_updated)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """

        self.db.exec(insert_sql, [
            char_info["char_id"], char_info["name"], char_info["first_name"],
            char_info["last_name"], char_info["level"], char_info["breed"],
            char_info["gender"], char_info["faction"], char_info["profession"],
            char_info["profession_title"], char_info["ai_rank"],
            char_info["ai_level"], char_info["org_id"], char_info["org_name"],
            char_info["org_rank_name"], char_info["org_rank_id"],
            char_info["dimension"], char_info["head_id"],
            char_info["pvp_rating"], char_info["pvp_title"],
            char_info["source"],
            int(time.time())
        ])

    def get_from_database(self, char_id=None, char_name=None):
        if char_id:
            return self.db.query_single(
                "SELECT char_id, name, first_name, last_name, level, breed, gender, faction, profession, "
                "profession_title, ai_rank, ai_level, org_id, org_name, org_rank_name, org_rank_id, "
                "dimension, head_id, pvp_rating, pvp_title, source, last_updated "
                "FROM player WHERE char_id = ?", [char_id])
        elif char_name:
            return self.db.query_single(
                "SELECT char_id, name, first_name, last_name, level, breed, gender, faction, profession, "
                "profession_title, ai_rank, ai_level, org_id, org_name, org_rank_name, org_rank_id, "
                "dimension, head_id, pvp_rating, pvp_title, source, last_updated "
                "FROM player WHERE name = ?", [char_name])
        else:
            return None

    def update(self, conn, packet):
        # don't update if we didn't get a valid response
        if packet.char_id == 4294967295:
            return

        character = self.get_from_database(char_id=packet.char_id)

        if character:
            if character.name != packet.name:
                self.db.exec("UPDATE player SET name = ? WHERE char_id = ?",
                             [packet.name, packet.char_id])
        else:
            insert_sql = """
                INSERT IGNORE INTO player ( char_id, name, first_name, last_name, level, breed, gender, faction, profession,
                profession_title, ai_rank, ai_level, org_id, org_name, org_rank_name, org_rank_id, dimension, head_id,
                pvp_rating, pvp_title, source, last_updated)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"""

            self.db.exec(insert_sql, [
                packet.char_id, packet.name, "", "", 0, "", "", "", "", "", "",
                0, 0, "", "", 6, self.bot.dimension, 0, 0, "", "chat_server",
                int(time.time())
            ])

    def find_orgs(self, search):
        return self.db.query(
            "SELECT DISTINCT org_name, org_id FROM player WHERE org_name <EXTENDED_LIKE=0> ?",
            [search],
            extended_like=True)

    def get_pork_url(self, dimension, char_name):
        return "http://people.anarchy-online.com/character/bio/d/%d/name/%s/bio.xml?data_type=json" % (
            dimension, char_name)
예제 #19
0
class EventService:
    def __init__(self):
        self.handlers = {}
        self.logger = Logger(__name__)
        self.event_types = []
        self.db_cache = {}

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

    def pre_start(self):
        self.register_event_type("timer")

    def start(self):
        # process decorators
        for _, inst in Registry.get_all_instances().items():
            for name, method in get_attrs(inst).items():
                if hasattr(method, "event"):
                    attrs = getattr(method, "event")
                    handler = getattr(inst, name)
                    self.register(handler, attrs.event_type, attrs.description,
                                  inst.module_name, attrs.is_hidden,
                                  attrs.is_enabled)

    def register_event_type(self, event_type):
        """
        Call during pre_start

        Args:
            event_type (str)
        """

        event_type = event_type.lower()

        if event_type in self.event_types:
            self.logger.error(
                "Could not register event type '%s': event type already registered"
                % event_type)
            return

        self.logger.debug("Registering event type '%s'" % event_type)
        self.event_types.append(event_type)

    def is_event_type(self, event_base_type):
        return event_base_type in self.event_types

    def register(self, handler, event_type, description, module, is_hidden,
                 is_enabled):
        """
        Call during pre_start

        Args:
            handler: (event_type, event_data) -> void
            event_type: str
            description: str
            module: str
            is_hidden: bool
            is_enabled: bool
        """

        if len(inspect.signature(handler).parameters) != 2:
            raise Exception(
                "Incorrect number of arguments for handler '%s.%s()'" %
                (handler.__module__, handler.__name__))

        event_base_type, event_sub_type = self.get_event_type_parts(event_type)
        module = module.lower()
        handler_name = self.util.get_handler_name(handler)
        is_hidden = 1 if is_hidden else 0
        is_enabled = 1 if is_enabled else 0

        if event_base_type not in self.event_types:
            self.logger.error(
                "Could not register handler '%s' for event type '%s': event type does not exist"
                % (handler_name, event_type))
            return

        if not description:
            self.logger.warning(
                "No description for event_type '%s' and handler '%s'" %
                (event_type, handler_name))

        row = self.db.query_single(
            "SELECT 1 FROM event_config WHERE event_type = ? AND handler = ?",
            [event_base_type, handler_name])

        if row is None:
            # add new event commands
            self.db.exec(
                "INSERT INTO event_config (event_type, event_sub_type, handler, description, module, enabled, verified, is_hidden) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
                [
                    event_base_type, event_sub_type, handler_name, description,
                    module, is_enabled, 1, is_hidden
                ])

            if event_base_type == "timer":
                self.db.exec(
                    "INSERT INTO timer_event (event_type, event_sub_type, handler, next_run) VALUES (?, ?, ?, ?)",
                    [
                        event_base_type, event_sub_type, handler_name,
                        int(time.time())
                    ])
        else:
            # mark command as verified
            self.db.exec(
                "UPDATE event_config SET verified = ?, module = ?, description = ?, event_sub_type = ?, is_hidden = ? WHERE event_type = ? AND handler = ?",
                [
                    1, module, description, event_sub_type, is_hidden,
                    event_base_type, handler_name
                ])

            if event_base_type == "timer":
                self.db.exec(
                    "UPDATE timer_event SET event_sub_type = ? WHERE event_type = ? AND handler = ?",
                    [event_sub_type, event_base_type, handler_name])

        # load command handler
        self.handlers[handler_name] = handler

    def fire_event(self, event_type, event_data=None):
        event_base_type, event_sub_type = self.get_event_type_parts(event_type)

        if event_base_type not in self.event_types:
            self.logger.error(
                "Could not fire event type '%s': event type does not exist" %
                event_type)
            return

        data = self.get_handlers(event_base_type, event_sub_type)
        for row in data:
            self.call_handler(row.handler, event_type, event_data)

    def call_handler(self, handler_method, event_type, event_data):
        handler = self.handlers.get(handler_method, None)
        if not handler:
            self.logger.error(
                "Could not find handler callback for event type '%s' and handler '%s'"
                % (event_type, handler_method))
            return

        try:
            handler(event_type, event_data)
        except Exception as e:
            self.logger.error("error processing event '%s'" % event_type, e)

    def get_event_type_parts(self, event_type):
        parts = event_type.lower().split(":", 1)
        if len(parts) == 2:
            return parts[0], parts[1]
        else:
            return parts[0], ""

    def get_event_type_key(self, event_base_type, event_sub_type):
        return event_base_type + ":" + event_sub_type

    def check_for_timer_events(self, current_timestamp):
        data = self.db.query(
            "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 t.next_run <= ? AND e.enabled = 1", [current_timestamp])
        for row in data:
            self.execute_timed_event(row, current_timestamp)

    def execute_timed_event(self, row, current_timestamp):
        event_type_key = self.get_event_type_key(row.event_type,
                                                 row.event_sub_type)

        # timer event run times should be consistent, so we base the next run time off the last run time,
        # instead of the current timestamp
        next_run = row.next_run + int(row.event_sub_type)

        # prevents timer events from getting too far behind, or having a large "catch-up" after
        # the bot has been offline for a time
        if next_run < current_timestamp:
            next_run = current_timestamp + int(row.event_sub_type)

        with self.db.transaction():
            self.db.exec(
                "UPDATE timer_event SET next_run = ? WHERE event_type = ? AND handler = ?",
                [next_run, row.event_type, row.handler])

        self.call_handler(row.handler, event_type_key, None)

    def update_event_status(self, event_base_type, event_sub_type,
                            event_handler, enabled_status):
        # clear cache
        self.db_cache[event_base_type + ":" + event_sub_type] = None

        return self.db.exec(
            "UPDATE event_config SET enabled = ? WHERE event_type = ? AND event_sub_type = ? AND handler LIKE ?",
            [enabled_status, event_base_type, event_sub_type, event_handler])

    def get_event_types(self):
        return self.event_types

    def get_handlers(self, event_base_type, event_sub_type):
        # check first in cache
        result = self.db_cache.get(event_base_type + ":" + event_sub_type,
                                   None)
        if result is not None:
            return result
        else:
            result = self.db.query(
                "SELECT handler FROM event_config WHERE event_type = ? AND event_sub_type = ? AND enabled = 1",
                [event_base_type, event_sub_type])

            # store result in cache
            self.db_cache[event_base_type + ":" + event_sub_type] = result

            return result

    def run_timer_events_at_startup(self):
        t = int(time.time())
        data = self.db.query(
            "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.enabled = 1", ["timer"])
        for row in data:
            handler = self.handlers[row.handler]
            attrs = getattr(handler, "event")
            if attrs.get("run_at_startup", False):
                self.execute_timed_event(row, t)
예제 #20
0
class MappingDal(object):
    def __init__(self, connection_pool):
        self.logger = Logger(self.__class__.__name__).get()
        self.connection_pool = connection_pool

    def style_family_mapping_exists(self, style_id, family_id):
        mapping_id = self.get_style_family_mapping_id(style_id, family_id)

        return mapping_id != -1

    def get_style_family_mapping_id(self, style_id, family_id):
        query = ("SELECT id, styleid, familyid FROM familystylemap "
                 "WHERE styleid = %s "
                 "AND familyid = %s "
                 "AND validfrom < %s "
                 "AND ((validto = '0000-00-00 00:00:00') OR (validto >= %s))")

        now = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime())

        cnx = self.connection_pool.get_connection()
        cursor = cnx.cursor()

        data = (style_id, family_id, now, now)
        self.logger.debug("Getting mapping with query [%s] and data [%s]",
                          query, data)

        cursor.execute(query, data)

        mapping_id = -1
        for (c_id, c_styleid, c_familyid) in cursor:
            if c_styleid == style_id and c_familyid == family_id:
                mapping_id = c_id

        cursor.close()
        self.connection_pool.release_connection(cnx)

        return mapping_id

    def get_last_style_family_mapping_id(self):
        style_query = "SELECT MAX(id) FROM familystylemap"

        cnx = self.connection_pool.get_connection()
        cursor = cnx.cursor()

        cursor.execute(style_query)

        ret_id = -1
        for c_id in cursor:
            if c_id is not None and c_id[0] is not None:
                ret_id = int(c_id[0])

        cursor.close()
        self.connection_pool.release_connection(cnx)

        return ret_id

    def insert_style_family_mapping(self, style_id, family_id, source_id):
        query = ("INSERT INTO familystylemap "
                 "(styleid, familyid, sourceid, validfrom) "
                 "VALUES (%s, %s, %s, %s)")

        now = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime())

        cnx = self.connection_pool.get_connection()
        cursor = cnx.cursor()

        data = (style_id, family_id, source_id, now)
        cursor.execute(query, data)

        cnx.commit()

        mapping_id = int(cursor.lastrowid)

        cursor.close()
        self.connection_pool.release_connection(cnx)

        return mapping_id
예제 #21
0
    if "features" in config:
        for k, v in config.features.items():
            k = k.upper()
            logger.info("Feature %s: %s" % (k, v))
            setattr(FeatureFlags, k, v)

    if platform.system() == "Windows":
        os.system("title %s.%d" %
                  (config.bots[0].character, config.server.dimension))

    # paths to search for instances: core + module_paths
    paths = ["core"]
    paths.extend(config.module_paths)

    # load instances
    logger.debug("Loading instances")
    Registry.load_instances(paths)
    Registry.inject_all()

    # configure database
    db = Registry.get_instance("db")
    if config.database.type == "sqlite":
        db.connect_sqlite("./data/" + config.database.name)
    elif config.database.type == "mysql":
        db.connect_mysql(config.database.host, config.database.port,
                         config.database.username, config.database.password,
                         config.database.name)
    else:
        raise Exception("Unknown database type '%s'" % config.database.type)

    # run db upgrade scripts
예제 #22
0
class CommandService:
    PRIVATE_MESSAGE_CHANNEL = "msg"

    def __init__(self):
        self.handlers = collections.defaultdict(list)
        self.logger = Logger(__name__)
        self.channels = {}
        self.pre_processors = []
        self.ignore_regexes = [
            re.compile(r" is AFK \(Away from keyboard\) since ",
                       re.IGNORECASE),
            re.compile(r"I am away from my keyboard right now", re.IGNORECASE),
            re.compile(r"Unknown command or access denied!", re.IGNORECASE),
            re.compile(r"I am responding", re.IGNORECASE),
            re.compile(r"I only listen", re.IGNORECASE),
            re.compile(r"Error!", re.IGNORECASE),
            re.compile(r"Unknown command input", re.IGNORECASE),
            re.compile(r"You have been auto invited", re.IGNORECASE),
            re.compile(r"^<font")
        ]

    def inject(self, registry):
        self.db = registry.get_instance("db")
        self.util = registry.get_instance("util")
        self.access_service: AccessService = registry.get_instance(
            "access_service")
        self.bot = registry.get_instance("bot")
        self.character_service: CharacterService = registry.get_instance(
            "character_service")
        self.event_service = registry.get_instance("event_service")
        self.setting_service: SettingService = registry.get_instance(
            "setting_service")
        self.command_alias_service = registry.get_instance(
            "command_alias_service")
        self.usage_service = registry.get_instance("usage_service")
        self.public_channel_service = registry.get_instance(
            "public_channel_service")
        self.ban_service = registry.get_instance("ban_service")

    def pre_start(self):
        self.bot.register_packet_handler(server_packets.PrivateMessage.id,
                                         self.handle_private_message)

        self.register_command_channel("Private Message",
                                      self.PRIVATE_MESSAGE_CHANNEL)

    def start(self):
        access_levels = {}

        # process decorators
        for _, inst in Registry.get_all_instances().items():
            for name, method in get_attrs(inst).items():
                if hasattr(method, "command"):
                    cmd_name, params, access_level, description, help_file, sub_command, extended_description = getattr(
                        method, "command")
                    handler = getattr(inst, name)
                    help_text = self.get_help_file(inst.module_name, help_file)

                    command_key = self.get_command_key(
                        cmd_name.lower(),
                        sub_command.lower() if sub_command else "")
                    al = access_levels.get(command_key, None)
                    if al is not None and al != access_level.lower():
                        raise Exception(
                            "Different access levels specified for forms of command '%s'"
                            % command_key)
                    access_levels[command_key] = access_level

                    self.register(handler, cmd_name, params, access_level,
                                  description, inst.module_name, help_text,
                                  sub_command, extended_description)

    def register(self,
                 handler,
                 command,
                 params,
                 access_level,
                 description,
                 module,
                 help_text=None,
                 sub_command=None,
                 extended_description=None,
                 check_access=None):
        """
        Call during pre_start

        Args:
            handler: (request, param1, param2, ...) -> str|ChatBlob|None
            command: str
            params: [CommandParam...]
            access_level: str
            description: str
            module: str
            help_text: str
            sub_command: str
            extended_description: str
            check_access: (char_id, access_level_label) -> bool
        """

        if len(inspect.signature(handler).parameters) != len(params) + 1:
            raise Exception(
                "Incorrect number of arguments for handler '%s.%s()'" %
                (handler.__module__, handler.__qualname__))

        command = command.lower()
        if sub_command:
            sub_command = sub_command.lower()
        else:
            sub_command = ""
        access_level = access_level.lower()
        module = module.lower()
        command_key = self.get_command_key(command, sub_command)

        if help_text is None:
            help_text = self.generate_help(command, description, params,
                                           extended_description)

        if check_access is None:
            check_access = self.access_service.check_access

        if not self.access_service.get_access_level_by_label(access_level):
            self.logger.error(
                "Could not add command '%s': could not find access level '%s'"
                % (command, access_level))
            return

        for channel, label in self.channels.items():
            row = self.db.query_single(
                "SELECT access_level, module, enabled, verified "
                "FROM command_config "
                "WHERE command = ? AND sub_command = ? AND channel = ?",
                [command, sub_command, channel])

            if row is None:
                # add new command
                self.db.exec(
                    "INSERT INTO command_config "
                    "(command, sub_command, access_level, channel, module, enabled, verified) "
                    "VALUES (?, ?, ?, ?, ?, 1, 1)",
                    [command, sub_command, access_level, channel, module])
            elif row.verified:
                if row.module != module:
                    self.logger.warning(
                        "module different for different forms of command '%s' and sub_command '%s'"
                        % (command, sub_command))
            else:
                # mark command as verified
                self.db.exec(
                    "UPDATE command_config SET verified = 1, module = ? "
                    "WHERE command = ? AND sub_command = ? AND channel = ?",
                    [module, command, sub_command, channel])

        # save reference to command handler
        r = re.compile(self.get_regex_from_params(params),
                       re.IGNORECASE | re.DOTALL)
        self.handlers[command_key].append({
            "regex": r,
            "callback": handler,
            "help": help_text,
            "description": description,
            "params": params,
            "check_access": check_access
        })

    def register_command_pre_processor(self, pre_processor):
        """
        Call during start

        Args:
            pre_processor: (context) -> bool
        """

        self.pre_processors.append(pre_processor)

    def register_command_channel(self, label, value):
        """
        Call during pre_start

        Args:
            label: str
            value: str
        """

        if value in self.channels:
            self.logger.error(
                "Could not register command channel '%s': command channel already registered"
                % value)
            return

        self.logger.debug("Registering command channel '%s'" % value)
        self.channels[value] = label

    def is_command_channel(self, channel):
        return channel in self.channels

    def process_command(self, message: str, channel: str, char_id, reply,
                        conn):
        try:
            context = DictObject({
                "message": message,
                "char_id": char_id,
                "channel": channel,
                "reply": reply
            })
            for pre_processor in self.pre_processors:
                if pre_processor(context) is False:
                    return

            for regex in self.ignore_regexes:
                if regex.search(message):
                    return

            # message = html.unescape(message)

            command_str, command_args = self.get_command_parts(message)

            # check for command alias
            command_alias_str = self.command_alias_service.get_alias_command_str(
                command_str, command_args)

            alias_depth_count = 0
            while command_alias_str:
                alias_depth_count += 1
                command_str, command_args = self.get_command_parts(
                    command_alias_str)
                command_alias_str = self.command_alias_service.get_alias_command_str(
                    command_str, command_args)

                if alias_depth_count > 20:
                    raise Exception(
                        "Command alias infinite recursion detected for command '%s'"
                        % message)

            cmd_configs = self.get_command_configs(command_str, channel, 1)
            access_level = self.access_service.get_access_level(char_id)
            sender = SenderObj(
                char_id,
                self.character_service.resolve_char_to_name(
                    char_id, "Unknown(%d)" % char_id), access_level)
            if cmd_configs:
                # given a list of cmd_configs that are enabled, see if one has regex that matches incoming command_str
                cmd_config, matches, handler = self.get_matches(
                    cmd_configs, command_args)
                if matches:
                    if handler["check_access"](char_id,
                                               cmd_config.access_level):
                        response = handler["callback"](
                            CommandRequest(conn, channel, sender, reply),
                            *self.process_matches(matches, handler["params"]))
                        if response is not None:
                            reply(response)

                        # record command usage
                        self.usage_service.add_usage(
                            command_str,
                            self.util.get_handler_name(handler["callback"]),
                            char_id, channel)
                    else:
                        self.access_denied_response(message, sender,
                                                    cmd_config, reply)
                else:
                    # handlers were found, but no handler regex matched
                    data = self.db.query(
                        "SELECT command, sub_command, access_level FROM command_config "
                        "WHERE command = ? AND channel = ? AND enabled = 1",
                        [command_str, channel])

                    help_text = self.format_help_text(data, char_id)
                    if help_text:
                        reply(
                            self.format_help_text_blob(command_str, help_text))
                    else:
                        # the command is known, but no help is returned, therefore character does not have access to command
                        reply("Access denied.")
            else:
                self.handle_unknown_command(command_str, command_args, channel,
                                            sender, reply)
        except Exception as e:
            self.logger.error("error processing command: %s" % message, e)
            reply("There was an error processing your request.")

    def handle_unknown_command(self, command_str, command_args, channel,
                               sender, reply):
        reply(f"Error! Unknown command <highlight>{command_str}</highlight>.")

    def access_denied_response(self, message, sender, cmd_config, reply):
        reply("Access denied.")

    def get_command_parts(self, message):
        parts = message.split(" ", 1)
        if len(parts) == 2:
            return parts[0].lower(), " " + parts[1]
        else:
            return parts[0].lower(), ""

    def get_command_configs(self,
                            command,
                            channel=None,
                            enabled=1,
                            sub_command=None):
        sql = "SELECT command, sub_command, access_level, channel, enabled FROM command_config WHERE command = ?"
        params = [command]
        if channel:
            sql += " AND channel = ?"
            params.append(channel)
        if enabled:
            sql += " AND enabled = ?"
            params.append(enabled)
        if sub_command:
            sql += " AND sub_command = ?"
            params.append(sub_command)

        sql += " ORDER BY sub_command, channel"

        return self.db.query(sql, params)

    def get_matches(self, cmd_configs, command_args):
        for row in cmd_configs:
            command_key = self.get_command_key(row.command, row.sub_command)
            handlers = self.handlers[command_key]
            for handler in handlers:
                matches = handler["regex"].search(command_args)
                if matches:
                    return row, matches, handler
        return None, None, None

    def process_matches(self, matches, params):
        groups = list(matches.groups())

        processed = []
        for param in params:
            processed.append(param.process_matches(groups))
        return processed

    def format_help_text(self, data, char_id, show_regex=False):
        # filter out commands that character does not have access level for
        data = filter(
            lambda row: self.access_service.check_access(
                char_id, row.access_level), data)

        def get_regex(params):
            if show_regex:
                return "\n" + self.get_regex_from_params(params)
            else:
                return ""

        def read_help_text(row):
            command_key = self.get_command_key(row.command, row.sub_command)
            return filter(
                lambda x: x is not None,
                map(
                    lambda handler: handler["help"] + get_regex(handler[
                        "params"]), self.handlers[command_key]))

        content = "\n\n".join(flatmap(read_help_text, data))
        return content if content else None

    def format_help_text_blob(self, topic, help_text):
        return ChatBlob("Help (" + topic + ")", help_text)

    def get_help_file(self, module, help_file):
        if help_file:
            try:
                help_file = "./" + module.replace(".", "/") + "/" + help_file
                with open(help_file, mode="r", encoding="UTF-8") as f:
                    return f.read().strip()
            except FileNotFoundError as e:
                self.logger.error("Error reading help file", e)
        return None

    def get_command_key(self, command, sub_command):
        if sub_command:
            return command + ":" + sub_command
        else:
            return command

    def get_command_key_parts(self, command_str):
        parts = command_str.split(":", 1)
        if len(parts) == 2:
            return parts[0], parts[1]
        else:
            return parts[0], ""

    def get_regex_from_params(self, params):
        # params must be wrapped with line-beginning and line-ending anchors in order to match
        # when no params are specified (eg. "^$")
        return "^" + "".join(map(lambda x: x.get_regex(), params)) + "$"

    def generate_help(self,
                      command,
                      description,
                      params,
                      extended_description=None):
        help_text = description + ":\n" + "<tab><symbol>" + command + " " + " ".join(
            map(lambda x: x.get_name(), params))
        if extended_description:
            help_text += "\n" + extended_description

        return help_text

    def get_handlers(self, command_key):
        return self.handlers.get(command_key, None)

    def handle_private_message(self, conn: Conn,
                               packet: server_packets.PrivateMessage):
        if not self.setting_service.get("accept_commands_from_slave_bots"
                                        ).get_value() and not conn.is_main:
            return

        # since the command symbol is not required for private messages,
        # the command_str must have length of at least 1 in order to be valid,
        # otherwise it is ignored
        if len(packet.message) < 1:
            return

        # ignore leading space
        message = packet.message.lstrip()

        def reply(msg):
            if self.bot.mass_message_queue and FeatureFlags.FORCE_LARGE_MESSAGES_FROM_SLAVES and \
                    isinstance(msg, ChatBlob) and len(msg.msg) > FeatureFlags.FORCE_LARGE_MESSAGES_FROM_SLAVES_THRESHOLD:
                self.bot.send_mass_message(packet.char_id, msg, conn=conn)
            else:
                self.bot.send_private_message(packet.char_id, msg, conn=conn)

        self.process_command(self.trim_command_symbol(message),
                             self.PRIVATE_MESSAGE_CHANNEL, packet.char_id,
                             reply, conn)

    def trim_command_symbol(self, s):
        symbol = self.setting_service.get("symbol").get_value()
        if s.startswith(symbol):
            s = s[len(symbol):]
        return s
예제 #23
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")
        self.ts: TranslationService = registry.get_instance("translation_service")
        self.getresp = self.ts.get_response

    def start(self):
        self.ts.register_translation("module/buddy", self.load_buddy_msg)

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

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

        blob = self.format_buddies(buddy_list)

        return ChatBlob(self.getresp("module/buddy", "blob_title", {"amount": len(buddy_list)}), blob)

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

        if char.char_id:
            self.buddy_service.add_buddy(char.char_id, buddy_type)
            return self.getresp("module/buddy", "add_success", {"char": char.name, "type": buddy_type})
        else:
            return self.getresp("global", "char_not_found", {"char": char.name})

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

            return self.getresp("module/buddy", "rem_all", {"count": count})

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

        if char.char_id:
            self.buddy_service.remove_buddy(char.char_id, buddy_type)
            return self.getresp("module/buddy", "rem_single", {"char": char.name, "type": buddy_type})
        else:
            return self.getresp("global", "char_not_found", {"char": char.name})

    @command(command="buddylist", params=[Const("clean")], access_level="admin",
             description="Remove all orphaned buddies from the buddy list")
    def buddylist_clean_cmd(self, request, _):
        return self.getresp("module/buddy", "rem_orphaned", {"count":self.remove_orphaned_buddies()})

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

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

        blob = self.format_buddies(buddy_list)
        return ChatBlob(self.getresp("module/buddy", "search_title", {"amount": len(buddy_list)}), blob)

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

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

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

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

        return blob
예제 #24
0
class SettingService:
    def __init__(self):
        self.logger = Logger(__name__)
        self.settings = {}
        self.db_cache = {}

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

    def start(self):
        # process decorators
        for _, inst in Registry.get_all_instances().items():
            for name, method in get_attrs(inst).items():
                if hasattr(method, "setting"):
                    setting_name, value, description, obj = getattr(method, "setting")
                    handler = getattr(inst, name)
                    module = self.util.get_module_name(handler)
                    self.register(setting_name, value, description, obj, module)

    def register(self, name, value, description, setting: SettingType, module):
        name = name.lower()
        module = module.lower()
        setting.set_name(name)
        setting.set_description(description)

        if not description:
            self.logger.warning("No description specified for setting '%s'" % name)

        if " " in name:
            raise Exception("One or more spaces found in setting name '%s' for module '%s'" % (name, module))

        row = self.db.query_single("SELECT name, value, description FROM setting WHERE name = ?",
                                   [name])

        if row is None:
            self.logger.debug("Adding setting '%s'" % name)

            self.db.exec(
                "INSERT INTO setting (name, value, description, module, verified) VALUES (?, ?, ?, ?, ?)",
                [name, "", description, module, 1])

            # verify default value is a valid value, and is formatted appropriately
            setting.set_value(value)
        else:
            self.logger.debug("Updating setting '%s'" % name)
            self.db.exec(
                "UPDATE setting SET description = ?, verified = ?, module = ? WHERE name = ?",
                [description, 1, module, name])

        self.settings[name] = setting

    def get_value(self, name):
        # check cache first
        result = self.db_cache.get(name, None)
        if result:
            return result.value
        else:
            row = self.db.query_single("SELECT value FROM setting WHERE name = ?", [name])

            # store result in cache
            self.db_cache[name] = row

            return row.value if row else None

    def set_value(self, name, value):
        # clear cache
        self.db_cache[name] = None

        self.db.exec("UPDATE setting SET value = ? WHERE name = ?", [value, name])

    def get(self, name):
        name = name.lower()
        setting = self.settings.get(name, None)
        if setting:
            return setting
        else:
            return None
예제 #25
0

def spawn(func, *args, **kwargs):
    thread_name = kwargs.pop('thread_name', None) or get_func_name(func)

    def wrapper(thread_name, args, kwargs):
        try:
            func(*args, **kwargs)
        except Exception, ex:
            log.error('Thread "%s" raised an exception: %s', thread_name, ex, exc_info=True)

    th = threading.Thread(target=wrapper, name=thread_name, args=(thread_name, args, kwargs))

    try:
        th.start()
        log.debug("Spawned thread with name '%s'" % thread_name)
    except thread.error, ex:
        log.error('Unable to spawn thread: %s', ex, exc_info=True, extra={
            'data': {
                'active_count': threading.active_count()
            }
        })
        return None

    return th


def schedule(func, seconds, *args, **kwargs):
    def schedule_sleep():
        time.sleep(seconds)
        func(*args, **kwargs)
예제 #26
0
class Tyrbot:
    CONNECT_EVENT = "connect"
    PRIVATE_MSG_EVENT = "private_msg"

    def __init__(self):
        super().__init__()
        self.logger = Logger(__name__)
        self.ready = False
        self.packet_handlers = {}
        self.superadmin = None
        self.status: BotStatus = BotStatus.SHUTDOWN
        self.dimension = None
        self.last_timer_event = 0
        self.start_time = int(time.time())
        self.version = "0.7-beta"
        self.incoming_queue = FifoQueue()
        self.mass_message_queue = None
        self.conns = DictObject()
        self.primary_conn_id = None

    def inject(self, registry):
        self.db = registry.get_instance("db")
        self.character_service: CharacterService = registry.get_instance(
            "character_service")
        self.public_channel_service: PublicChannelService = registry.get_instance(
            "public_channel_service")
        self.text: Text = registry.get_instance("text")
        self.setting_service: SettingService = registry.get_instance(
            "setting_service")
        self.access_service: AccessService = registry.get_instance(
            "access_service")
        self.event_service = registry.get_instance("event_service")
        self.job_scheduler = registry.get_instance("job_scheduler")

    def init(self, config, registry, mmdb_parser):
        self.mmdb_parser = mmdb_parser
        self.superadmin = config.superadmin.capitalize()
        self.dimension = config.server.dimension

        self.db.exec(
            "CREATE TABLE IF NOT EXISTS command_config (command VARCHAR(50) NOT NULL, sub_command VARCHAR(50) NOT NULL, access_level VARCHAR(50) NOT NULL, channel VARCHAR(50) NOT NULL, "
            "module VARCHAR(50) NOT NULL, enabled SMALLINT NOT NULL, verified SMALLINT NOT NULL)"
        )
        self.db.exec(
            "CREATE TABLE IF NOT EXISTS event_config (event_type VARCHAR(50) NOT NULL, event_sub_type VARCHAR(50) NOT NULL, handler VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, "
            "module VARCHAR(50) NOT NULL, enabled SMALLINT NOT NULL, verified SMALLINT NOT NULL, is_hidden SMALLINT NOT NULL)"
        )
        self.db.exec(
            "CREATE TABLE IF NOT EXISTS timer_event (event_type VARCHAR(50) NOT NULL, event_sub_type VARCHAR(50) NOT NULL, handler VARCHAR(255) NOT NULL, next_run INT NOT NULL)"
        )
        self.db.exec(
            "CREATE TABLE IF NOT EXISTS setting (name VARCHAR(50) NOT NULL, value VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, module VARCHAR(50) NOT NULL, verified SMALLINT NOT NULL)"
        )
        self.db.exec(
            "CREATE TABLE IF NOT EXISTS command_alias (alias VARCHAR(50) NOT NULL, command VARCHAR(1024) NOT NULL, enabled SMALLINT NOT NULL)"
        )
        self.db.exec(
            "CREATE TABLE IF NOT EXISTS command_usage (command VARCHAR(255) NOT NULL, handler VARCHAR(255) NOT NULL, char_id INT NOT NULL, channel VARCHAR(20) NOT NULL, created_at INT NOT NULL)"
        )
        self.db.exec(
            "CREATE TABLE IF NOT EXISTS ban_list (char_id INT NOT NULL, sender_char_id INT NOT NULL, created_at INT NOT NULL, finished_at INT NOT NULL, reason VARCHAR(255) NOT NULL, ended_early SMALLINT NOT NULL)"
        )

        self.db.exec("UPDATE db_version SET verified = 0")
        self.db.exec(
            "UPDATE db_version SET verified = 1 WHERE file = 'db_version'")

        # prepare commands, events, and settings
        self.db.exec("UPDATE command_config SET verified = 0")
        self.db.exec("UPDATE event_config SET verified = 0")
        self.db.exec("UPDATE setting SET verified = 0")

        with self.db.transaction():
            registry.pre_start_all()
            registry.start_all()

        # remove commands, events, and settings that are no longer registered
        self.db.exec("DELETE FROM db_version WHERE verified = 0")
        self.db.exec("DELETE FROM command_config WHERE verified = 0")
        self.db.exec("DELETE FROM event_config WHERE verified = 0")
        self.db.exec(
            "DELETE FROM timer_event WHERE handler NOT IN (SELECT handler FROM event_config WHERE event_type = ?)",
            ["timer"])
        self.db.exec("DELETE FROM setting WHERE verified = 0")

        self.status = BotStatus.RUN

    def pre_start(self):
        self.access_service.register_access_level("superadmin", 10,
                                                  self.check_superadmin)
        self.event_service.register_event_type(self.CONNECT_EVENT)
        self.event_service.register_event_type(self.PRIVATE_MSG_EVENT)

    def start(self):
        self.setting_service.register(
            "core.system", "symbol", "!",
            TextSettingType(["!", "#", "*", "@", "$", "+", "-"]),
            "Symbol for executing bot commands")

        self.setting_service.register(
            "core.system", "org_channel_max_page_length", 7500,
            NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]),
            "Maximum size of blobs in org channel")
        self.setting_service.register(
            "core.system", "private_message_max_page_length", 7500,
            NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]),
            "Maximum size of blobs in private messages")
        self.setting_service.register(
            "core.system", "private_channel_max_page_length", 7500,
            NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]),
            "Maximum size of blobs in private channel")

        self.setting_service.register(
            "core.system", "accept_commands_from_slave_bots", False,
            BooleanSettingType(),
            "Accept and respond to commands sent to slave bots (only applies if you have added slave bots in the config)"
        )

        self.setting_service.register("core.colors", "header_color", "#FFFF00",
                                      ColorSettingType(), "Color for headers")
        self.setting_service.register("core.colors", "header2_color",
                                      "#FCA712", ColorSettingType(),
                                      "Color for sub-headers")
        self.setting_service.register("core.colors", "highlight_color",
                                      "#00BFFF", ColorSettingType(),
                                      "Color for highlight")
        self.setting_service.register("core.colors", "notice_color", "#FF8C00",
                                      ColorSettingType(),
                                      "Color for important notices")

        self.setting_service.register("core.colors", "neutral_color",
                                      "#E6E1A6", ColorSettingType(),
                                      "Color for neutral faction")
        self.setting_service.register("core.colors", "omni_color", "#FA8484",
                                      ColorSettingType(),
                                      "Color for omni faction")
        self.setting_service.register("core.colors", "clan_color", "#F79410",
                                      ColorSettingType(),
                                      "Color for clan faction")
        self.setting_service.register("core.colors", "unknown_color",
                                      "#FF0000", ColorSettingType(),
                                      "Color for unknown faction")

        self.setting_service.register("core.colors", "org_channel_color",
                                      "#89D2E8", ColorSettingType(),
                                      "Default org channel color")
        self.setting_service.register("core.colors", "private_channel_color",
                                      "#89D2E8", ColorSettingType(),
                                      "Default private channel color")
        self.setting_service.register("core.colors", "private_message_color",
                                      "#89D2E8", ColorSettingType(),
                                      "Default private message color")
        self.setting_service.register("core.colors", "blob_color", "#FFFFFF",
                                      ColorSettingType(),
                                      "Default blob content color")

        self.register_packet_handler(server_packets.PrivateMessage.id,
                                     self.handle_private_message,
                                     priority=40)

    def check_superadmin(self, char_id):
        char_name = self.character_service.resolve_char_to_name(char_id)
        return char_name == self.superadmin

    def connect(self, config):
        for i, bot in enumerate(config.bots):
            if "id" in bot:
                _id = bot.id
            else:
                _id = "bot" + str(i)

            if i == 0:
                self.primary_conn_id = _id

            conn = self.create_conn(_id)
            conn.connect(config.server.host, config.server.port)

            # only create the mass_message_queue if there is at least 1 non-main bot
            if not bot.is_main and not self.mass_message_queue:
                self.mass_message_queue = FifoQueue()

            packet = conn.login(bot.username,
                                bot.password,
                                bot.character,
                                is_main=bot.is_main)
            if not packet:
                self.status = BotStatus.ERROR
                return False
            else:
                self.incoming_queue.put((conn, packet))

            self.create_conn_thread(
                conn, None if bot.is_main else self.mass_message_queue)

        return True

    def create_conn_thread(self, conn: Conn, mass_message_queue=None):
        def read_packets():
            try:
                while self.status == BotStatus.RUN:
                    packet = conn.read_packet(1)
                    if packet:
                        self.incoming_queue.put((conn, packet))

                    while mass_message_queue and not mass_message_queue.empty(
                    ) and conn.packet_queue.is_empty():
                        packet = mass_message_queue.get_or_default(block=False)
                        if packet:
                            conn.add_packet_to_queue(packet)

            except (EOFError, OSError) as e:
                self.status = BotStatus.ERROR
                self.logger.error("", e)
                raise e

        dthread = threading.Thread(target=read_packets, daemon=True)
        dthread.start()

    def create_conn(self, _id):
        if _id in self.conns:
            raise Exception(f"A connection with id {_id} already exists")

        def failure_callback():
            self.status = BotStatus.ERROR

        conn = Conn(_id, failure_callback)
        self.conns[_id] = conn
        return conn

    def disconnect(self):
        # wait for all threads to stop reading packets, then disconnect them all
        time.sleep(2)
        for _id, conn in self.get_conns():
            conn.disconnect()

    def run(self):
        start = time.time()

        # wait for flood of packets from login to stop sending
        time_waited = 0
        while time_waited < 2:
            if not self.iterate(1):
                time_waited += 1

        self.logger.info("Login complete (%fs)" % (time.time() - start))

        start = time.time()
        self.event_service.fire_event("connect", None)
        self.event_service.run_timer_events_at_startup()
        self.event_service.check_for_timer_events(int(start))
        self.logger.info("Connect events finished (%fs)" %
                         (time.time() - start))

        time_waited = 0
        while time_waited < 2:
            if not self.iterate(1):
                time_waited += 1

        self.ready = True
        timestamp = int(time.time())

        while self.status == BotStatus.RUN:
            try:
                timestamp = int(time.time())
                self.check_for_timer_events(timestamp)

                self.iterate()
            except Exception as e:
                self.logger.error("", e)

        # run any pending jobs/events
        self.check_for_timer_events(timestamp + 1)

        return self.status

    def check_for_timer_events(self, timestamp):
        # timer events will execute no more often than once per second
        if self.last_timer_event < timestamp:
            self.last_timer_event = timestamp
            self.job_scheduler.check_for_scheduled_jobs(timestamp)
            self.event_service.check_for_timer_events(timestamp)

    def register_packet_handler(self, packet_id: int, handler, priority=50):
        """
        Call during pre_start

        Args:
            packet_id: int
            handler: (conn, packet) -> void
            priority: int
        """

        if len(inspect.signature(handler).parameters) != 2:
            raise Exception(
                "Incorrect number of arguments for handler '%s.%s()'" %
                (handler.__module__, handler.__name__))

        handlers = self.packet_handlers.get(packet_id, [])
        handlers.append(DictObject({"priority": priority, "handler": handler}))
        self.packet_handlers[packet_id] = sorted(handlers,
                                                 key=lambda x: x.priority)

    def remove_packet_handler(self, packet_id, handler):
        handlers = self.packet_handlers.get(packet_id, [])
        for h in handlers:
            if h.handler == handler:
                handlers.remove(h)

    def iterate(self, timeout=0.1):
        conn, packet = self.incoming_queue.get_or_default(block=True,
                                                          timeout=timeout,
                                                          default=(None, None))
        if packet:
            if isinstance(packet, server_packets.SystemMessage):
                packet = self.system_message_ext_msg_handling(packet)
                self.logger.log_chat(conn, "SystemMessage", None,
                                     packet.extended_message.get_message())
            elif isinstance(packet, server_packets.PublicChannelMessage):
                packet = self.public_channel_message_ext_msg_handling(packet)
            elif isinstance(packet,
                            server_packets.BuddyAdded) and packet.char_id == 0:
                return

            for handler in self.packet_handlers.get(packet.id, []):
                handler.handler(conn, packet)

        return packet

    def public_channel_message_ext_msg_handling(
            self, packet: server_packets.PublicChannelMessage):
        msg = packet.message
        if msg.startswith("~&") and msg.endswith("~"):
            try:
                msg = msg[2:-1].encode("utf-8")
                category_id = self.mmdb_parser.read_base_85(msg[0:5])
                instance_id = self.mmdb_parser.read_base_85(msg[5:10])
                template = self.mmdb_parser.get_message_string(
                    category_id, instance_id)
                params = self.mmdb_parser.parse_params(msg[10:])
                packet.extended_message = ExtendedMessage(
                    category_id, instance_id, template, params)
            except Exception as e:
                self.logger.error(
                    "Error handling extended message for packet: " +
                    str(packet), e)

        return packet

    def system_message_ext_msg_handling(self,
                                        packet: server_packets.SystemMessage):
        try:
            category_id = 20000
            instance_id = packet.message_id
            template = self.mmdb_parser.get_message_string(
                category_id, instance_id)
            params = self.mmdb_parser.parse_params(packet.message_args)
            packet.extended_message = ExtendedMessage(category_id, instance_id,
                                                      template, params)
        except Exception as e:
            self.logger.error(
                "Error handling extended message: " + str(packet), e)

        return packet

    def send_org_message(self, msg, add_color=True, conn=None):
        if not conn:
            conn = self.get_primary_conn()

        if not conn.org_channel_id:
            self.logger.debug(
                f"Ignoring message to org channel for {conn.id} since the org_channel_id is unknown"
            )
        else:
            color = self.setting_service.get(
                "org_channel_color").get_font_color() if add_color else ""
            pages = self.get_text_pages(
                msg, conn,
                self.setting_service.get(
                    "org_channel_max_page_length").get_value())
            for page in pages:
                packet = client_packets.PublicChannelMessage(
                    conn.org_channel_id, color + page, "")
                conn.add_packet_to_queue(packet)

    def send_private_message(self, char_id, msg, add_color=True, conn=None):
        if not conn:
            conn = self.get_primary_conn()

        if char_id is None:
            raise Exception("Cannot send message, char_id is empty")
        else:
            color = self.setting_service.get(
                "private_message_color").get_font_color() if add_color else ""
            pages = self.get_text_pages(
                msg, conn,
                self.setting_service.get(
                    "private_message_max_page_length").get_value())
            for page in pages:
                self.logger.log_tell(
                    conn, "To", self.character_service.get_char_name(char_id),
                    page)
                packet = client_packets.PrivateMessage(char_id, color + page,
                                                       "\0")
                conn.add_packet_to_queue(packet)

    def send_private_channel_message(self,
                                     msg,
                                     private_channel_id=None,
                                     add_color=True,
                                     conn=None):
        if not conn:
            conn = self.get_primary_conn()

        if private_channel_id is None:
            private_channel_id = conn.get_char_id()

        color = self.setting_service.get(
            "private_channel_color").get_font_color() if add_color else ""
        pages = self.get_text_pages(
            msg, conn,
            self.setting_service.get(
                "private_channel_max_page_length").get_value())
        for page in pages:
            packet = client_packets.PrivateChannelMessage(
                private_channel_id, color + page, "\0")
            conn.send_packet(packet)

    def send_mass_message(self, char_id, msg, add_color=True, conn=None):
        if not conn:
            conn = self.get_primary_conn()

        if not char_id:
            self.logger.warning("Could not send message to empty char_id")
        else:
            color = self.setting_service.get(
                "private_message_color").get_font_color() if add_color else ""
            pages = self.get_text_pages(
                msg, conn,
                self.setting_service.get(
                    "private_message_max_page_length").get_value())
            for page in pages:
                if self.mass_message_queue:
                    packet = client_packets.PrivateMessage(
                        char_id, color + page, "\0")
                    self.mass_message_queue.put(packet)
                else:
                    packet = client_packets.PrivateMessage(
                        char_id, color + page, "spam")
                    self.get_primary_conn().send_packet(packet)

    def send_message_to_other_org_channels(self, msg, from_conn: Conn):
        for _id, conn in self.get_conns(
                lambda x: x.is_main and x.org_id and x != from_conn):
            self.send_org_message(msg, conn=conn)

    def handle_private_message(self, conn: Conn,
                               packet: server_packets.PrivateMessage):
        char_name = self.character_service.get_char_name(packet.char_id)
        self.logger.log_tell(conn, "From", char_name, packet.message)
        self.event_service.fire_event(
            self.PRIVATE_MSG_EVENT,
            DictObject({
                "char_id": packet.char_id,
                "name": char_name,
                "message": packet.message,
                "conn": conn
            }))

    def get_text_pages(self, msg, conn, max_page_length):
        if isinstance(msg, ChatBlob):
            return self.text.paginate(msg,
                                      conn,
                                      max_page_length=max_page_length)
        else:
            return [self.text.format_message(msg, conn)]

    def is_ready(self):
        return self.ready

    def shutdown(self):
        self.status = BotStatus.SHUTDOWN

    def restart(self):
        self.status = BotStatus.RESTART

    def get_primary_conn_id(self):
        return self.primary_conn_id

    def get_primary_conn(self):
        return self.conns[self.get_primary_conn_id()]

    def get_conn_by_char_id(self, char_id):
        for _id, conn in self.get_conns():
            if char_id == conn.get_char_id():
                return conn
        return None

    def get_conn_by_org_id(self, org_id):
        for _id, conn in self.get_conns():
            if conn.org_id == org_id:
                return conn
        return None

    # placeholder to keep track of things that need to be fixed/updated
    def get_temp_conn(self):
        return self.get_primary_conn()

    def get_conns(self, conn_filter=None):
        if conn_filter:
            return [(_id, conn) for _id, conn in self.conns.items()
                    if conn_filter(conn)]
        else:
            return self.conns.items()
예제 #27
0
파일: tyrbot.py 프로젝트: Sliphs/Tyrbot
class Tyrbot:
    CONNECT_EVENT = "connect"
    PACKET_EVENT = "packet"
    PRIVATE_MSG_EVENT = "private_msg"

    OUTGOING_ORG_MESSAGE_EVENT = "outgoing_org_message"
    OUTGOING_PRIVATE_MESSAGE_EVENT = "outgoing_private_message"
    OUTGOING_PRIVATE_CHANNEL_MESSAGE_EVENT = "outgoing_private_channel_message"

    def __init__(self):
        super().__init__()
        self.logger = Logger(__name__)
        self.ready = False
        self.packet_handlers = {}
        self.superadmin = None
        self.status: BotStatus = BotStatus.SHUTDOWN
        self.dimension = None
        self.last_timer_event = 0
        self.start_time = int(time.time())
        self.version = "0.5-beta"
        self.incoming_queue = FifoQueue()
        self.mass_message_queue = None
        self.conns = DictObject()

    def inject(self, registry):
        self.db = registry.get_instance("db")
        self.character_service: CharacterService = registry.get_instance("character_service")
        self.public_channel_service: PublicChannelService = registry.get_instance("public_channel_service")
        self.text: Text = registry.get_instance("text")
        self.setting_service: SettingService = registry.get_instance("setting_service")
        self.access_service: AccessService = registry.get_instance("access_service")
        self.event_service = registry.get_instance("event_service")
        self.job_scheduler = registry.get_instance("job_scheduler")

    def init(self, config, registry, paths, mmdb_parser):
        self.mmdb_parser = mmdb_parser
        self.superadmin = config.superadmin.capitalize()
        self.dimension = config.server.dimension

        self.db.exec("UPDATE db_version SET verified = 0")
        self.db.exec("UPDATE db_version SET verified = 1 WHERE file = 'db_version'")

        self.load_sql_files(paths)

        # prepare commands, events, and settings
        self.db.exec("UPDATE command_config SET verified = 0")
        self.db.exec("UPDATE event_config SET verified = 0")
        self.db.exec("UPDATE setting SET verified = 0")

        with self.db.transaction():
            registry.pre_start_all()
            registry.start_all()

        # remove commands, events, and settings that are no longer registered
        self.db.exec("DELETE FROM db_version WHERE verified = 0")
        self.db.exec("DELETE FROM command_config WHERE verified = 0")
        self.db.exec("DELETE FROM event_config WHERE verified = 0")
        self.db.exec("DELETE FROM timer_event WHERE handler NOT IN (SELECT handler FROM event_config WHERE event_type = ?)", ["timer"])
        self.db.exec("DELETE FROM setting WHERE verified = 0")

        self.status = BotStatus.RUN

    def pre_start(self):
        self.access_service.register_access_level("superadmin", 10, self.check_superadmin)
        self.event_service.register_event_type(self.CONNECT_EVENT)
        self.event_service.register_event_type(self.PACKET_EVENT)
        self.event_service.register_event_type(self.PRIVATE_MSG_EVENT)
        self.event_service.register_event_type(self.OUTGOING_ORG_MESSAGE_EVENT)
        self.event_service.register_event_type(self.OUTGOING_PRIVATE_MESSAGE_EVENT)
        self.event_service.register_event_type(self.OUTGOING_PRIVATE_CHANNEL_MESSAGE_EVENT)

    def start(self):
        self.setting_service.register_new("core.system", "symbol", "!", TextSettingType(["!", "#", "*", "@", "$", "+", "-"]), "Symbol for executing bot commands")

        self.setting_service.register_new("core.system", "org_channel_max_page_length", 7500,
                                          NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]),
                                          "Maximum size of blobs in org channel")
        self.setting_service.register_new("core.system", "private_message_max_page_length", 7500,
                                          NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]),
                                          "Maximum size of blobs in private messages")
        self.setting_service.register_new("core.system", "private_channel_max_page_length", 7500,
                                          NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]),
                                          "Maximum size of blobs in private channel")

        self.setting_service.register_new("core.system", "org_id", "", NumberSettingType(allow_empty=True), "Override the default org id",
                                          "This setting is is for development/debug purposes and should not be changed unless you understand the implications")
        self.setting_service.register_new("core.system", "org_name", "", TextSettingType(allow_empty=True), "The exact org name of the bot",
                                          "This setting is automatically set by the bot and should not be changed manually")

        self.setting_service.register_new("core.colors", "header_color", "#FFFF00", ColorSettingType(), "Color for headers")
        self.setting_service.register_new("core.colors", "header2_color", "#FCA712", ColorSettingType(), "Color for sub-headers")
        self.setting_service.register_new("core.colors", "highlight_color", "#00BFFF", ColorSettingType(), "Color for highlight")
        self.setting_service.register_new("core.colors", "notice_color", "#FF8C00", ColorSettingType(), "Color for important notices")

        self.setting_service.register_new("core.colors", "neutral_color", "#E6E1A6", ColorSettingType(), "Color for neutral faction")
        self.setting_service.register_new("core.colors", "omni_color", "#FA8484", ColorSettingType(), "Color for omni faction")
        self.setting_service.register_new("core.colors", "clan_color", "#F79410", ColorSettingType(), "Color for clan faction")
        self.setting_service.register_new("core.colors", "unknown_color", "#FF0000", ColorSettingType(), "Color for unknown faction")

        self.setting_service.register_new("core.colors", "org_channel_color", "#89D2E8", ColorSettingType(), "Default org channel color")
        self.setting_service.register_new("core.colors", "private_channel_color", "#89D2E8", ColorSettingType(), "Default private channel color")
        self.setting_service.register_new("core.colors", "private_message_color", "#89D2E8", ColorSettingType(), "Default private message color")
        self.setting_service.register_new("core.colors", "blob_color", "#FFFFFF", ColorSettingType(), "Default blob content color")

        self.register_packet_handler(server_packets.PrivateMessage.id, self.handle_private_message, priority=40)

    def check_superadmin(self, char_id):
        char_name = self.character_service.resolve_char_to_name(char_id)
        return char_name == self.superadmin

    def connect(self, config):
        conn = self.create_conn("main")
        conn.connect(config.server.host, config.server.port)
        packet = conn.login(config.username, config.password, config.character)
        if not packet:
            self.status = BotStatus.ERROR
            return False
        else:
            self.incoming_queue.put((conn, packet))

        self.mass_message_queue = FifoQueue()
        self.create_conn_thread(conn, self.mass_message_queue)

        if "slaves" in config:
            for i, slave in enumerate(config.slaves):
                conn = self.create_conn("slave" + str(i))
                conn.connect(config.server.host, config.server.port)

                packet = conn.login(slave.username, slave.password, slave.character)
                if not packet:
                    self.status = BotStatus.ERROR
                    return False
                else:
                    self.incoming_queue.put((conn, packet))

                self.create_conn_thread(conn, self.mass_message_queue)

        return True

    def create_conn_thread(self, conn: Conn, mass_message_queue=None):
        def read_packets():
            try:
                while self.status == BotStatus.RUN:
                    packet = conn.read_packet(1)
                    if packet:
                        self.incoming_queue.put((conn, packet))

                    while mass_message_queue and not mass_message_queue.empty() and conn.packet_queue.is_empty():
                        packet = mass_message_queue.get_or_default(block=False)
                        if packet:
                            conn.add_packet_to_queue(packet)

            except (EOFError, OSError) as e:
                self.status = BotStatus.ERROR
                self.logger.error("", e)
                raise e

        dthread = threading.Thread(target=read_packets, daemon=True)
        dthread.start()

    def create_conn(self, _id):
        if _id in self.conns:
            raise Exception(f"A connection with id {_id} already exists")

        conn = Conn(_id, self.disconnect)
        self.conns[_id] = conn
        return conn

    # passthrough
    def send_packet(self, packet):
        self.conns["main"].send_packet(packet)

    def disconnect(self):
        # wait for all threads to stop reading packets, then disconnect them all
        time.sleep(2)
        for _id, conn in self.conns.items():
            conn.disconnect()

    def run(self):
        start = time.time()

        # wait for flood of packets from login to stop sending
        time_waited = 0
        while time_waited < 5:
            if not self.iterate(1):
                time_waited += 1

        self.logger.info("Login complete (%fs)" % (time.time() - start))

        start = time.time()
        self.event_service.fire_event("connect", None)
        self.event_service.run_timer_events_at_startup()
        self.logger.info("Connect events finished (%fs)" % (time.time() - start))

        self.ready = True
        timestamp = int(time.time())

        while self.status == BotStatus.RUN:
            try:
                timestamp = int(time.time())
                self.check_for_timer_events(timestamp)

                self.iterate()
            except (EOFError, OSError) as e:
                raise e
            except Exception as e:
                self.logger.error("", e)

        # run any pending jobs/events
        self.check_for_timer_events(timestamp + 1)

        return self.status

    def check_for_timer_events(self, timestamp):
        # timer events will execute no more often than once per second
        if self.last_timer_event < timestamp:
            self.last_timer_event = timestamp
            self.job_scheduler.check_for_scheduled_jobs(timestamp)
            self.event_service.check_for_timer_events(timestamp)

    def register_packet_handler(self, packet_id: int, handler, priority=50):
        """
        Call during pre_start

        Args:
            packet_id: int
            handler: (conn, packet) -> void
            priority: int
        """

        if len(inspect.signature(handler).parameters) != 2:
            raise Exception("Incorrect number of arguments for handler '%s.%s()'" % (handler.__module__, handler.__name__))

        handlers = self.packet_handlers.get(packet_id, [])
        handlers.append(DictObject({"priority": priority, "handler": handler}))
        self.packet_handlers[packet_id] = sorted(handlers, key=lambda x: x.priority)

    def remove_packet_handler(self, packet_id, handler):
        handlers = self.packet_handlers.get(packet_id, [])
        for h in handlers:
            if h.handler == handler:
                handlers.remove(h)

    def iterate(self, timeout=0.1):
        conn, packet = self.incoming_queue.get_or_default(block=True, timeout=timeout, default=(None, None))
        if packet:
            if isinstance(packet, server_packets.SystemMessage):
                packet = self.system_message_ext_msg_handling(packet)
            elif isinstance(packet, server_packets.PublicChannelMessage):
                packet = self.public_channel_message_ext_msg_handling(packet)
            if isinstance(packet, server_packets.BuddyAdded):
                if packet.char_id == 0:
                    return

            for handler in self.packet_handlers.get(packet.id, []):
                handler.handler(conn, packet)

            self.event_service.fire_event("packet:" + str(packet.id), packet)

        return packet

    def public_channel_message_ext_msg_handling(self, packet: server_packets.PublicChannelMessage):
        msg = packet.message
        if msg.startswith("~&") and msg.endswith("~"):
            try:
                msg = msg[2:-1].encode("utf-8")
                category_id = self.mmdb_parser.read_base_85(msg[0:5])
                instance_id = self.mmdb_parser.read_base_85(msg[5: 10])
                template = self.mmdb_parser.get_message_string(category_id, instance_id)
                params = self.mmdb_parser.parse_params(msg[10:])
                packet.extended_message = ExtendedMessage(category_id, instance_id, template, params)
            except Exception as e:
                self.logger.error("Error handling extended message for packet: " + str(packet), e)

        return packet

    def system_message_ext_msg_handling(self, packet: server_packets.SystemMessage):
        try:
            category_id = 20000
            instance_id = packet.message_id
            template = self.mmdb_parser.get_message_string(category_id, instance_id)
            params = self.mmdb_parser.parse_params(packet.message_args)
            packet.extended_message = ExtendedMessage(category_id, instance_id, template, params)
            self.logger.log_chat("SystemMessage", None, packet.extended_message.get_message())
        except Exception as e:
            self.logger.error("Error handling extended message: " + str(packet), e)

        return packet

    def send_org_message(self, msg, add_color=True, fire_outgoing_event=True, conn_id="main"):
        org_channel_id = self.public_channel_service.org_channel_id
        if org_channel_id is None:
            self.logger.debug("ignoring message to org channel since the org_channel_id is unknown")
        else:
            color = self.setting_service.get("org_channel_color").get_font_color() if add_color else ""
            pages = self.get_text_pages(msg, self.setting_service.get("org_channel_max_page_length").get_value())
            for page in pages:
                packet = client_packets.PublicChannelMessage(org_channel_id, color + page, "")
                self.conns[conn_id].add_packet_to_queue(packet)

            if fire_outgoing_event:
                self.event_service.fire_event(self.OUTGOING_ORG_MESSAGE_EVENT, DictObject({"org_channel_id": org_channel_id,
                                                                                           "message": msg}))

    def send_private_message(self, char, msg, add_color=True, fire_outgoing_event=True, conn_id="main"):
        char_id = self.character_service.resolve_char_to_id(char)
        if char_id is None:
            self.logger.warning("Could not send message to %s, could not find char id" % char)
        else:
            color = self.setting_service.get("private_message_color").get_font_color() if add_color else ""
            pages = self.get_text_pages(msg, self.setting_service.get("private_message_max_page_length").get_value())
            for page in pages:
                self.logger.log_tell("To", self.character_service.get_char_name(char_id), page)
                packet = client_packets.PrivateMessage(char_id, color + page, "\0")
                self.conns[conn_id].add_packet_to_queue(packet)

            if fire_outgoing_event:
                self.event_service.fire_event(self.OUTGOING_PRIVATE_MESSAGE_EVENT, DictObject({"char_id": char_id,
                                                                                               "message": msg}))

    def send_private_channel_message(self, msg, private_channel=None, add_color=True, fire_outgoing_event=True, conn_id="main"):
        if private_channel is None:
            private_channel_id = self.get_char_id()
        else:
            private_channel_id = self.character_service.resolve_char_to_id(private_channel)

        if private_channel_id is None:
            self.logger.warning("Could not send message to private channel %s, could not find private channel" % private_channel)
        else:
            color = self.setting_service.get("private_channel_color").get_font_color() if add_color else ""
            pages = self.get_text_pages(msg, self.setting_service.get("private_channel_max_page_length").get_value())
            for page in pages:
                packet = client_packets.PrivateChannelMessage(private_channel_id, color + page, "\0")
                self.conns[conn_id].send_packet(packet)

            if fire_outgoing_event and private_channel_id == self.get_char_id():
                self.event_service.fire_event(self.OUTGOING_PRIVATE_CHANNEL_MESSAGE_EVENT, DictObject({"private_channel_id": private_channel_id,
                                                                                                       "message": msg}))

    def send_mass_message(self, char_id, msg, add_color=True):
        if not char_id:
            self.logger.warning("Could not send message to empty char_id")
        else:
            color = self.setting_service.get("private_message_color").get_font_color() if add_color else ""
            pages = self.get_text_pages(msg, self.setting_service.get("private_message_max_page_length").get_value())
            for page in pages:
                # self.logger.log_tell("To", self.character_service.get_char_name(char_id), page)
                if self.mass_message_queue:
                    packet = client_packets.PrivateMessage(char_id, color + page, "\0")
                    self.mass_message_queue.put(packet)
                else:
                    packet = client_packets.PrivateMessage(char_id, color + page, "spam")
                    self.conns["main"].send_packet(packet)

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

        self.logger.log_tell("From", self.character_service.get_char_name(packet.char_id), packet.message)
        self.event_service.fire_event(self.PRIVATE_MSG_EVENT, packet)

    def get_text_pages(self, msg, max_page_length):
        if isinstance(msg, ChatBlob):
            return self.text.paginate(msg, max_page_length=max_page_length)
        else:
            return [self.text.format_message(msg)]

    def is_ready(self):
        return self.ready

    def shutdown(self):
        self.status = BotStatus.SHUTDOWN

    def restart(self):
        self.status = BotStatus.RESTART

    def load_sql_files(self, paths):
        dirs = flatmap(lambda x: os.walk(x), paths)
        dirs = filter(lambda y: not y[0].endswith("__pycache__"), dirs)

        def get_files(tup):
            return map(lambda x: os.path.join(tup[0], x), tup[2])

        # get files from subdirectories
        files = flatmap(get_files, dirs)
        files = filter(lambda z: z.endswith(".sql"), files)

        base_path = os.getcwd()
        for file in files:
            self.db.load_sql_file(file, base_path)

    def get_char_name(self):
        return self.conns["main"].char_name

    def get_char_id(self):
        return self.conns["main"].char_id
예제 #28
0
        success = False

        try:
            success = handler.run(section=section, **kwargs)
        except CancelException, e:
            handler.update_status(False)

            log.info('Task "%s" was cancelled', key)
        except Exception, ex:
            handler.update_status(False)

            log.error('Exception raised in handler for %r: %s', key, ex, exc_info=True)

        log.debug(
            'Cache Statistics - len(matcher): %s, len(metadata): %s',
            len(CacheManager.get('matcher')),
            len(CacheManager.get('metadata'))
        )

        # Sync "matcher" cache (back to disk)
        CacheManager.get('matcher').sync()

        # Clear memory caches
        CacheManager.get('matcher').cache.clear()
        CacheManager.get('metadata').cache.clear()

        # Run garbage collection
        log.debug('[GC] Collected %d objects', gc.collect())
        log.debug('[GC] Count: %s', gc.get_count())
        log.debug('[GC] Garbage: %s', len(gc.garbage))
예제 #29
0
    # load logging configuration
    import conf.logging

    Registry.logger = Logger("core.registry")

    logger = Logger("core.bootstrap")
    logger.info("Starting Tyrbot...")
    config_file = "./conf/config.hjson"

    # start config wizard if config file does not exist
    if not os.path.exists(config_file):
        config_creator.create_new_cfg(config_file,
                                      "./conf/config.template.hjson")

    # load config
    logger.debug("Reading config file '%s'" % config_file)
    with open(config_file, "r") as cfg:
        config = DictObject(hjson.load(cfg))

    # paths to search for instances: core + module_paths
    paths = ["core"]
    paths.extend(config.module_paths)

    # load instances
    logger.debug("Loading instances")
    Registry.load_instances(paths)
    Registry.inject_all()

    # configure database
    db = Registry.get_instance("db")
    if config.database.type == "sqlite":
예제 #30
0
class StyleDal(object):
    def __init__(self, connection_pool):
        self.logger = Logger(self.__class__.__name__).get()
        self.connection_pool = connection_pool

    def style_exists(self, style_name):
        style = self.get_style(style_name)

        exists = False
        if 'name' in style and style['name'] == style_name:
            exists = True

        return exists

    def get_all_styles(self):
        style_query = (
            "SELECT s.id, s.name, s. url, f.name as familyname FROM style s "
            "JOIN familystylemap m on m.styleid = s.id "
            "JOIN family f on m.familyid = f.id "
            "WHERE s.validfrom < %s "
            "AND ((s.validto = '0000-00-00 00:00:00') OR (s.validto >= %s))")

        now = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime())

        cnx = self.connection_pool.get_connection()
        cursor = cnx.cursor()

        style_data = (now, now)
        self.logger.debug("Getting style with query [%s] and data [%s]",
                          style_query, style_data)

        cursor.execute(style_query, style_data)

        styles = []
        for (c_id, c_name, c_url, c_family) in cursor:
            style = {
                'id': c_id,
                'name': c_name,
                'url': c_url,
                'family': c_family
            }

            styles.append(style)

        cursor.close()
        self.connection_pool.release_connection(cnx)

        return styles

    def get_styles_for_family(self, family_name):
        style_query = (
            "SELECT s.id, s.name, s. url, f.name as familyname FROM style s "
            "JOIN familystylemap m on m.styleid = s.id "
            "JOIN family f on m.familyid = f.id "
            "WHERE f.name = %s "
            "AND s.validfrom < %s "
            "AND ((s.validto = '0000-00-00 00:00:00') OR (s.validto >= %s))")

        now = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime())

        cnx = self.connection_pool.get_connection()
        cursor = cnx.cursor()

        style_data = (family_name, now, now)
        self.logger.debug("Getting style with query [%s] and data [%s]",
                          style_query, style_data)

        cursor.execute(style_query, style_data)

        styles = []
        for (c_id, c_name, c_url, c_family) in cursor:
            style = {
                'id': c_id,
                'name': c_name,
                'url': c_url,
                'family': c_family
            }

            styles.append(style)

        cursor.close()
        self.connection_pool.release_connection(cnx)

        return styles

    def get_style(self, style_name):
        style_query = (
            "SELECT id, name, url FROM style "
            "WHERE name = %s "
            "AND validfrom < %s "
            "AND ((validto = '0000-00-00 00:00:00') OR (validto >= %s))")

        now = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime())

        cnx = self.connection_pool.get_connection()
        cursor = cnx.cursor()

        style_data = (style_name, now, now)
        self.logger.debug("Getting style with query [%s] and data [%s]",
                          style_query, style_data)

        cursor.execute(style_query, style_data)

        style = {}
        for (s_id, s_name, s_url) in cursor:
            if s_name == style_name:
                style['id'] = s_id
                style['name'] = s_name
                style['url'] = s_url

        cursor.close()
        self.connection_pool.release_connection(cnx)

        return style

    def get_style_id(self, style_name):
        style_query = (
            "SELECT id, name FROM style "
            "WHERE name = %s "
            "AND validfrom < %s "
            "AND ((validto = '0000-00-00 00:00:00') OR (validto >= %s))")

        now = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime())

        cnx = self.connection_pool.get_connection()
        cursor = cnx.cursor()

        style_data = (style_name, now, now)
        self.logger.debug("Getting style ID with query [%s] and data [%s]",
                          style_query, style_data)

        cursor.execute(style_query, style_data)

        style_id = -1
        for (s_id, s_name) in cursor:
            if s_name == style_name:
                style_id = s_id

        cursor.close()
        self.connection_pool.release_connection(cnx)

        return style_id

    def get_last_style_id(self):
        style_query = ("SELECT MAX(id) FROM style")

        cnx = self.connection_pool.get_connection()
        cursor = cnx.cursor()

        cursor.execute(style_query)

        ret_id = -1
        for c_id in cursor:
            if c_id is not None and c_id[0] is not None:
                ret_id = int(c_id[0])

        cursor.close()
        self.connection_pool.release_connection(cnx)

        return ret_id

    def insert_style(self, style_name, url, source_id):
        add_style = ("INSERT INTO style "
                     "(name, sourceid, url, validfrom) "
                     "VALUES (%s, %s, %s, %s)")

        now = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime())

        cnx = self.connection_pool.get_connection()
        cursor = cnx.cursor()

        data_style = (style_name, source_id, url, now)
        cursor.execute(add_style, data_style)

        cnx.commit()

        style_id = int(cursor.lastrowid)

        cursor.close()
        self.connection_pool.release_connection(cnx)

        return style_id
예제 #31
0
class CommandService:
    PRIVATE_CHANNEL = "priv"
    ORG_CHANNEL = "org"
    PRIVATE_MESSAGE = "msg"

    def __init__(self):
        self.handlers = collections.defaultdict(list)
        self.logger = Logger(__name__)
        self.channels = {}
        self.ignore_regexes = [
            re.compile(" is AFK \(Away from keyboard\) since ", re.IGNORECASE),
            re.compile("I am away from my keyboard right now", re.IGNORECASE),
            re.compile("Unknown command or access denied!", re.IGNORECASE),
            re.compile("I am responding", re.IGNORECASE),
            re.compile("I only listen", re.IGNORECASE),
            re.compile("Error!", re.IGNORECASE),
            re.compile("Unknown command input", re.IGNORECASE),
            re.compile("You have been auto invited", re.IGNORECASE),
        ]

    def inject(self, registry):
        self.db = registry.get_instance("db")
        self.util = registry.get_instance("util")
        self.access_service: AccessService = registry.get_instance(
            "access_service")
        self.bot: Tyrbot = registry.get_instance("bot")
        self.character_service: CharacterService = registry.get_instance(
            "character_service")
        self.setting_service: SettingService = registry.get_instance(
            "setting_service")
        self.command_alias_service = registry.get_instance(
            "command_alias_service")
        self.usage_service = registry.get_instance("usage_service")
        self.public_channel_service = registry.get_instance(
            "public_channel_service")
        self.ban_service = registry.get_instance("ban_service")

    def pre_start(self):
        self.bot.add_packet_handler(server_packets.PrivateMessage.id,
                                    self.handle_private_message)
        self.bot.add_packet_handler(server_packets.PrivateChannelMessage.id,
                                    self.handle_private_channel_message)
        self.bot.add_packet_handler(server_packets.PublicChannelMessage.id,
                                    self.handle_public_channel_message)
        self.register_command_channel("Private Message", self.PRIVATE_MESSAGE)
        self.register_command_channel("Org Channel", self.ORG_CHANNEL)
        self.register_command_channel("Private Channel", self.PRIVATE_CHANNEL)

    def start(self):
        # process decorators
        for _, inst in Registry.get_all_instances().items():
            for name, method in get_attrs(inst).items():
                if hasattr(method, "command"):
                    cmd_name, params, access_level, description, help_file, sub_command, extended_description, check_access, aliases = getattr(
                        method, "command")
                    handler = getattr(inst, name)
                    module = self.util.get_module_name(handler)
                    help_text = self.get_help_file(module, help_file)
                    self.register(handler, cmd_name, params, access_level,
                                  description, module, help_text, sub_command,
                                  extended_description, check_access)
                    if len(inspect.signature(
                            handler).parameters) != len(params) + 1:
                        raise Exception(
                            "Incorrect number of arguments for handler '%s.%s()'"
                            % (handler.__module__, handler.__name__))

                    if aliases:
                        for alias in aliases:
                            self.command_alias_service.add_alias(
                                alias, cmd_name)

    def register(self,
                 handler,
                 command,
                 params,
                 access_level,
                 description,
                 module,
                 help_text=None,
                 sub_command=None,
                 extended_description=None,
                 check_access=None):
        command = command.lower()
        if sub_command:
            sub_command = sub_command.lower()
        else:
            sub_command = ""
        access_level = access_level.lower()
        module = module.lower()
        command_key = self.get_command_key(command, sub_command)

        if help_text is None:
            help_text = self.generate_help(command, description, params,
                                           extended_description)

        if check_access is None:
            check_access = self.access_service.check_access

        if not self.access_service.get_access_level_by_label(access_level):
            self.logger.error(
                "Could not add command '%s': could not find access level '%s'"
                % (command, access_level))
            return

        for channel, label in self.channels.items():
            row = self.db.query_single(
                "SELECT access_level, module, enabled, verified "
                "FROM command_config "
                "WHERE command = ? AND sub_command = ? AND channel = ?",
                [command, sub_command, channel])

            if row is None:
                # add new command commands
                self.db.exec(
                    "INSERT INTO command_config "
                    "(command, sub_command, access_level, channel, module, enabled, verified) "
                    "VALUES (?, ?, ?, ?, ?, 1, 1)",
                    [command, sub_command, access_level, channel, module])
            elif row.verified:
                if row.module != module:
                    self.logger.warning(
                        "module different for different forms of command '%s' and sub_command '%s'"
                        % (command, sub_command))
            else:
                # mark command as verified
                self.db.exec(
                    "UPDATE command_config SET verified = 1, module = ? "
                    "WHERE command = ? AND sub_command = ? AND channel = ?",
                    [module, command, sub_command, channel])

        # save reference to command handler
        r = re.compile(self.get_regex_from_params(params),
                       re.IGNORECASE | re.DOTALL)
        self.handlers[command_key].append({
            "regex": r,
            "callback": handler,
            "help": help_text,
            "description": description,
            "params": params,
            "check_access": check_access
        })

    def register_command_channel(self, label, value):
        if value in self.channels:
            self.logger.error(
                "Could not register command channel '%s': command channel already registered"
                % value)
            return

        self.logger.debug("Registering command channel '%s'" % value)
        self.channels[value] = label

    def is_command_channel(self, channel):
        return channel in self.channels

    def process_command(self, message: str, channel: str, char_id, reply):
        try:
            if self.ban_service.get_ban(char_id):
                # do nothing if character is banned
                self.logger.info(
                    "ignored banned character %d for command '%s'" %
                    (char_id, message))
                return

            message = html.unescape(message)

            command_str, command_args = self.get_command_parts(message)

            # check for command alias
            command_alias = self.command_alias_service.check_for_alias(
                command_str)

            if command_alias:
                command_str, command_args = self.get_command_parts(
                    command_alias + command_args)

            cmd_configs = self.get_command_configs(command_str, channel, 1)
            if cmd_configs:
                # given a list of cmd_configs that are enabled, see if one has regex that matches incoming command_str
                cmd_config, matches, handler = self.get_matches(
                    cmd_configs, command_args)
                if matches:
                    if handler["check_access"](char_id,
                                               cmd_config.access_level):
                        sender = SenderObj(
                            char_id,
                            self.character_service.resolve_char_to_name(
                                char_id, "Unknown(%d)" % char_id))
                        response = handler["callback"](
                            CommandRequest(channel, sender, reply),
                            *self.process_matches(matches, handler["params"]))
                        if response is not None:
                            reply(response)

                        # record command usage
                        self.usage_service.add_usage(
                            command_str, handler["callback"].__qualname__,
                            char_id, channel)
                    else:
                        self.access_denied_response(char_id, cmd_config, reply)
                else:
                    # handlers were found, but no handler regex matched
                    help_text = self.get_help_text(char_id, command_str,
                                                   channel)
                    if help_text:
                        reply(self.format_help_text(command_str, help_text))
                    else:
                        reply("Error! Invalid syntax.")
            else:
                reply("Error! Unknown command <highlight>%s<end>." %
                      command_str)
        except Exception as e:
            self.logger.error("error processing command: %s" % message, e)
            reply("There was an error processing your request.")

    def access_denied_response(self, char_id, cmd_config, reply):
        reply("Error! Access denied.")

    def get_command_parts(self, message):
        parts = message.split(" ", 1)
        if len(parts) == 2:
            return parts[0].lower(), " " + parts[1]
        else:
            return parts[0].lower(), ""

    def get_command_configs(self,
                            command,
                            channel=None,
                            enabled=1,
                            sub_command=None):
        sql = "SELECT command, sub_command, access_level, enabled FROM command_config WHERE command = ?"
        params = [command]
        if channel:
            sql += " AND channel = ?"
            params.append(channel)
        if enabled:
            sql += " AND enabled = ?"
            params.append(enabled)
        if sub_command:
            sql += " AND sub_command = ?"
            params.append(sub_command)

        sql += " ORDER BY sub_command, channel"

        return self.db.query(sql, params)

    def get_matches(self, cmd_configs, command_args):
        for row in cmd_configs:
            command_key = self.get_command_key(row.command, row.sub_command)
            handlers = self.handlers[command_key]
            for handler in handlers:
                # add leading space to search string to normalize input for command params
                matches = handler["regex"].search(command_args)
                if matches:
                    return row, matches, handler
        return None, None, None

    def process_matches(self, matches, params):
        groups = list(matches.groups())

        processed = []
        for param in params:
            processed.append(param.process_matches(groups))
        return processed

    def get_help_text(self, char, command_str, channel):
        data = self.db.query(
            "SELECT command, sub_command, access_level FROM command_config "
            "WHERE command = ? AND channel = ? AND enabled = 1",
            [command_str, channel])

        # filter out commands that character does not have access level for
        data = filter(
            lambda row: self.access_service.check_access(
                char, row.access_level), data)

        def read_help_text(row):
            command_key = self.get_command_key(row.command, row.sub_command)
            return filter(
                lambda x: x is not None,
                map(lambda handler: handler["help"],
                    self.handlers[command_key]))

        content = "\n\n".join(flatmap(read_help_text, data))
        return content if content else None

    def format_help_text(self, topic, help_text):
        return ChatBlob("Help (" + topic + ")", help_text)

    def get_help_file(self, module, help_file):
        if help_file:
            try:
                help_file = "./" + module.replace(".", "/") + "/" + help_file
                with open(help_file) as f:
                    return f.read().strip()
            except FileNotFoundError as e:
                self.logger.error("Error reading help file", e)
        return None

    def get_command_key(self, command, sub_command):
        if sub_command:
            return command + ":" + sub_command
        else:
            return command

    def get_command_key_parts(self, command_str):
        parts = command_str.split(":", 1)
        if len(parts) == 2:
            return parts[0], parts[1]
        else:
            return parts[0], ""

    def get_regex_from_params(self, params):
        # params must be wrapped with line-beginning and line-ending anchors in order to match
        # when no params are specified (eg. "^$")
        return "^" + "".join(map(lambda x: x.get_regex(), params)) + "$"

    def generate_help(self,
                      command,
                      description,
                      params,
                      extended_description=None):
        help_text = description + ":\n" + "<tab><symbol>" + command + " " + " ".join(
            map(lambda x: x.get_name(), params))
        if extended_description:
            help_text += "\n" + extended_description

        return help_text

    def get_handlers(self, command_key):
        return self.handlers.get(command_key, None)

    def handle_private_message(self, packet: server_packets.PrivateMessage):
        # since the command symbol is not required for private messages,
        # the command_str must have length of at least 1 in order to be valid,
        # otherwise it is ignored
        if len(packet.message) < 1:
            return

        for regex in self.ignore_regexes:
            if regex.search(packet.message):
                return

        if packet.message[:1] == self.setting_service.get(
                "symbol").get_value():
            command_str = packet.message[1:]
        else:
            command_str = packet.message

        self.process_command(
            command_str, self.PRIVATE_MESSAGE, packet.char_id,
            lambda msg: self.bot.send_private_message(packet.char_id, msg))

    def handle_private_channel_message(
            self, packet: server_packets.PrivateChannelMessage):
        # since the command symbol is required in the private channel,
        # the command_str must have length of at least 2 in order to be valid,
        # otherwise it is ignored
        if len(packet.message) < 2:
            return

        symbol = packet.message[:1]
        command_str = packet.message[1:]
        if symbol == self.setting_service.get("symbol").get_value(
        ) and packet.private_channel_id == self.bot.char_id:
            self.process_command(
                command_str, self.PRIVATE_CHANNEL, packet.char_id,
                lambda msg: self.bot.send_private_channel_message(msg))

    def handle_public_channel_message(
            self, packet: server_packets.PublicChannelMessage):
        # since the command symbol is required in the org channel,
        # the command_str must have length of at least 2 in order to be valid,
        # otherwise it is ignored
        if len(packet.message) < 2:
            return

        symbol = packet.message[:1]
        command_str = packet.message[1:]
        if symbol == self.setting_service.get("symbol").get_value(
        ) and self.public_channel_service.is_org_channel_id(packet.channel_id):
            self.process_command(command_str, self.ORG_CHANNEL, packet.char_id,
                                 lambda msg: self.bot.send_org_message(msg))
예제 #32
0
class ModelDal(object):
    def __init__(self, connection_pool):
        self.logger = Logger(self.__class__.__name__).get()
        self.connection_pool = connection_pool

    def model_exists(self, model):
        ret_model = self.get_model(model['style'], model['name'], model['sku'])

        exists = False
        if ret_model is not None and 'name' in ret_model and model[
                'name'] == ret_model['name']:
            exists = True

        return exists

    def get_model(self, style_name, model_name, sku):
        query = (
            "SELECT m.id, m.name, m.sku, m.listprice, m.url FROM model m JOIN style s on m.styleid = s.id "
            "WHERE s.name = %s "
            "AND m.name = %s "
            "AND m.sku = %s "
            "AND m.validfrom < %s "
            "AND ((m.validto = 0) OR (m.validto >= %s))")

        now = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime())

        cnx = self.connection_pool.get_connection()
        cursor = cnx.cursor()

        data = (style_name, model_name, sku, now, now)

        self.logger.debug("Getting model with query [%s] and data [%s]", query,
                          data)

        cursor.execute(query, data)

        model = None
        for (m_id, m_name, m_sku, m_listprice, m_url) in cursor:
            if m_name == model_name and sku == m_sku:
                model = {
                    'id': m_id,
                    'name': m_name,
                    'sku': m_sku,
                    'listprice': m_listprice,
                    'url': m_url
                }

        cursor.close()
        self.connection_pool.release_connection(cnx)

        return model

    def get_model_id(self, style_id, model_name, sku):
        query = ("SELECT id, name, sku FROM model "
                 "WHERE name = %s "
                 "AND styleid = %s "
                 "AND sku = %s "
                 "AND validfrom < %s "
                 "AND ((validto = 0) OR (validto >= %s))")

        now = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime())

        cnx = self.connection_pool.get_connection()
        cursor = cnx.cursor()

        data = (model_name, style_id, sku, now, now)

        self.logger.debug("Getting model id with query [%s] and data [%s]",
                          query, data)

        cursor.execute(query, data)

        model_id = -1
        for (m_id, m_name, m_sku) in cursor:
            if m_name == model_name and sku == m_sku:
                model_id = m_id

        cursor.close()
        self.connection_pool.release_connection(cnx)

        return model_id

    def get_last_model_id(self):
        model_query = ("SELECT MAX(id) FROM model")

        cnx = self.connection_pool.get_connection()
        cursor = cnx.cursor()

        cursor.execute(model_query)

        ret_id = -1
        for c_id in cursor:
            if c_id is not None and c_id[0] is not None:
                ret_id = int(c_id[0])

        cursor.close()
        self.connection_pool.release_connection(cnx)

        return ret_id

    def get_fit_id(self, fit):
        query = "SELECT id, name FROM fit WHERE name = %s"

        cnx = self.connection_pool.get_connection()
        cursor = cnx.cursor()

        data = (fit, )
        cursor.execute(query, data)

        ret_id = -1
        for c_id, c_name in cursor:
            if c_name == fit:
                ret_id = c_id

        cursor.close()
        self.connection_pool.release_connection(cnx)

        return ret_id

    #style_id, model_name, model_sku, model_framecolour, model_lens, fit_id, model_listprice, model_url
    def insert_model(self, model, style_id, lens_id, fit_id, source_id):

        query = (
            "INSERT INTO model "
            "(name, styleid, sku, listprice, url, framecolour, lensid, fitid, sourceid, validfrom) "
            "VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)")

        now = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime())

        cnx = self.connection_pool.get_connection()
        cursor = cnx.cursor()

        data = (model['name'], style_id, model['sku'], model['listprice'],
                model['url'], model['frame'], lens_id, fit_id, source_id, now)

        self.logger.debug("Inserting model with query [%s] and data [%s]",
                          query, data)

        cursor.execute(query, data)

        cnx.commit()

        model_id = int(cursor.lastrowid)

        cursor.close()
        self.connection_pool.release_connection(cnx)

        # model_id = self.get_model_id(style_id, model['name'], model['sku'])
        return model_id

    def update_model(self, model, style_id, source_id):

        query = "UPDATE model SET "
        data = []

        continu = False
        if 'releasedate' in model and model['releasedate'] is not None:
            query += "releasedate=%s, "
            data.append(model['releasedate'])
            continu = True

        if 'retiredate' in model and model['retiredate'] is not None:
            query += "retiredate=%s, "
            data.append(model['retiredate'])
            continu = True

        if 'image' in model and model['image'] is not None:
            query += "image=%s, "
            data.append(model['image'])
            continu = True

        if 'imagesmall' in model and model['imagesmall'] is not None:
            query += "imagesmall=%s, "
            data.append(model['imagesmall'])
            continu = True

        if 'note' in model and model['note'] is not None:
            query += "note=%s, "
            data.append(model['note'])
            continu = True

        if 'signature' in model and model['signature'] is not None:
            query += "signature=%s, "
            data.append(model['signature'])
            continu = True

        if 'exclusive' in model and model['exclusive'] is not None:
            query += "exclusive=%s, "
            data.append(model['exclusive'])
            continu = True

        if 'upc' in model and model['upc'] is not None:
            query += "upc=%s "
            data.append(model['upc'])
            continu = True

        # no count? just return
        if not continu:
            return

        query = query.rstrip(' ,')

        # TODO: deal with valid froms etc
        query += " WHERE name=%s AND sku=%s AND styleid=%s"
        data.append(model['name'])
        data.append(model['sku'])
        data.append(style_id)

        cnx = self.connection_pool.get_connection()
        cursor = cnx.cursor()

        self.logger.info("Updating model with query [%s] and data [%s]", query,
                         data)

        cursor.execute(query, data)

        cnx.commit()

        cursor.close()
        self.connection_pool.release_connection(cnx)

        return