Esempio n. 1
0
    def load_ids(self):
        self.ipid_list = {}
        self.hdid_list = {}

        #load ipids
        try:
            with Constants.fopen('storage/ip_ids.json', 'r',
                                 encoding='utf-8') as whole_list:
                self.ipid_list = json.loads(whole_list.read())
        except Exception as ex:
            message = 'WARNING: Error loading storage/ip_ids.json. Will assume empty values.\n'
            message += '{}: {}'.format(type(ex).__name__, ex)

            logger.log_pdebug(message)

        #load hdids
        try:
            with Constants.fopen('storage/hd_ids.json', 'r',
                                 encoding='utf-8') as whole_list:
                self.hdid_list = json.loads(whole_list.read())
        except Exception as ex:
            message = 'WARNING: Error loading storage/hd_ids.json. Will assume empty values.\n'
            message += '{}: {}'.format(type(ex).__name__, ex)

            logger.log_pdebug(message)
Esempio n. 2
0
 def reload(self):
     with Constants.fopen('config/characters.yaml', 'r') as chars:
         self.char_list = Constants.yaml_load(chars)
     with Constants.fopen('config/music.yaml', 'r') as music:
         self.music_list = Constants.yaml_load(music)
     self.build_music_pages_ao1()
     self.build_music_list_ao2()
     with Constants.fopen('config/backgrounds.yaml', 'r') as bgs:
         self.backgrounds = Constants.yaml_load(bgs)
Esempio n. 3
0
    def load_ids(self):
        self.ipid_list = dict()
        self.hdid_list = dict()

        # load ipids
        try:
            with Constants.fopen('storage/ip_ids.json', 'r', encoding='utf-8') as whole_list:
                self.ipid_list = json.load(whole_list)
        except ServerError as exc:
            if exc.code != 'FileNotFound':
                raise exc
            with Constants.fopen('storage/ip_ids.json', 'w', encoding='utf-8') as whole_list:
                json.dump(dict(), whole_list)
            message = 'WARNING: File not found: storage/ip_ids.json. Creating a new one...'
            logger.log_pdebug(message)
        except Exception as ex:
            message = 'WARNING: Error loading storage/ip_ids.json. Will assume empty values.\n'
            message += '{}: {}'.format(type(ex).__name__, ex)

            logger.log_pdebug(message)

        # If the IPID list is not a dict, fix the file
        # Why on earth is it called an IPID list if it is a Python dict is beyond me.
        if not isinstance(self.ipid_list, dict):
            message = (f'WARNING: File storage/ip_ids.json had a structure of the wrong type: '
                       f'{self.ipid_list}. Replacing it with a proper type.')
            logger.log_pdebug(message)
            self.ipid_list = dict()
            self.dump_ipids()

        # load hdids
        try:
            with Constants.fopen('storage/hd_ids.json', 'r', encoding='utf-8') as whole_list:
                self.hdid_list = json.load(whole_list)
        except ServerError as exc:
            if exc.code != 'FileNotFound':
                raise exc
            with Constants.fopen('storage/hd_ids.json', 'w', encoding='utf-8') as whole_list:
                json.dump(dict(), whole_list)
            message = 'WARNING: File not found: storage/hd_ids.json. Creating a new one...'
            logger.log_pdebug(message)
        except Exception as ex:
            message = 'WARNING: Error loading storage/hd_ids.json. Will assume empty values.\n'
            message += '{}: {}'.format(type(ex).__name__, ex)

            logger.log_pdebug(message)

        # If the HDID list is not a dict, fix the file
        # Why on earth is it called an HDID list if it is a Python dict is beyond me.
        if not isinstance(self.hdid_list, dict):
            message = (f'WARNING: File storage/hd_ids.json had a structure of the wrong type: '
                       f'{self.hdid_list}. Replacing it with a proper type.')
            logger.log_pdebug(message)
            self.hdid_list = dict()
            self.dump_hdids()
Esempio n. 4
0
    def validate(self, file_name, extra_parameters=None):
        """
        Attempt to open the YAML file, parse its contents, then validate

        Parameters
        ----------
        file_name : str
            File to open.
        extra_parameters : dict
            Any extra parameters to pass to the validator when it wants to modify the contents.
            Structure of the dict depends on the class implementing validate_contents.

        Returns
        -------
        contents : Iterable
            Contents of the YAML file.

        """

        with Constants.fopen(file_name,
                             disallow_parent_folder=True,
                             mode='r',
                             encoding='utf-8') as file:
            contents = Constants.yaml_load(file)

        contents = self.validate_contents(contents,
                                          extra_parameters=extra_parameters)
        return contents
Esempio n. 5
0
    def load_gimp(self):
        try:
            gimp_list = ValidateGimp().validate('config/gimp.yaml')
        except ServerError.FileNotFoundError:
            gimp_list = [
                'ERP IS BAN',
                'HELP ME',
                '(((((case????)))))',
                'Anyone else a fan of MLP?',
                'does this server have sans from undertale?',
                'what does call mod do',
                'Join my discord server please',
                'can I have mod pls?',
                'why is everyone a missingo?',
                'how 2 change areas?',
                '19 years of perfection, i don\'t play games to f*****g lose',
                ('nah... your taunts are f*****g useless... only defeat angers me... by trying '
                 'to taunt just earns you my pitty'),
                'When do we remove dangits',
                'MODS STOP GIMPING ME',
                'PLAY NORMIES PLS',
                'share if you not afraid of herobrine',
                'New Killer Choosen! Hold On!!',
                'The cake killed Nether.',
                'How you win Class Trials is simple, call your opposition cucks.',
            ]
            with Constants.fopen('config/gimp.yaml', 'w') as gimp:
                Constants.yaml_dump(gimp_list, gimp)
            message = 'WARNING: File not found: config/gimp.yaml. Creating a new one...'
            logger.log_pdebug(message)

        self.gimp_list = gimp_list
        return gimp_list.copy()
Esempio n. 6
0
    def load_config(self):
        with Constants.fopen('config/config.yaml', 'r',
                             encoding='utf-8') as cfg:
            self.config = Constants.yaml_load(cfg)
            self.config['motd'] = self.config['motd'].replace('\\n', ' \n')

        for i in range(1, 8):
            daily_gmpass = '******'.format(i)
            if daily_gmpass not in self.config or not self.config[daily_gmpass]:
                self.config[daily_gmpass] = None

        # Default values to fill in config.yaml if not present
        defaults_for_tags = {
            'discord_link': None,
            'max_numdice': 20,
            'max_numfaces': 11037,
            'max_modifier_length': 12,
            'max_acceptable_term': 22074,
            'def_numdice': 1,
            'def_numfaces': 6,
            'def_modifier': '',
            'blackout_background': 'Blackout_HD',
            'default_area_description': 'No description.',
            'party_lights_timeout': 10,
            'showname_max_length': 30,
            'sneak_handicap': 5,
            'spectator_name': 'SPECTATOR',
            'music_change_floodguard': {
                'times_per_interval': 1,
                'interval_length': 0,
                'mute_length': 0
            }
        }

        for (tag, value) in defaults_for_tags.items():
            if tag not in self.config:
                self.config[tag] = value

        # Check that all passwords were generated and that they are unique
        passwords = [
            'guardpass', 'modpass', 'cmpass', 'gmpass', 'gmpass1', 'gmpass2',
            'gmpass3', 'gmpass4', 'gmpass5', 'gmpass6', 'gmpass7'
        ]
        for password_type in passwords:
            if password_type not in self.config:
                info = (
                    'Password "{}" not defined in server/config.yaml. Please make sure it is '
                    'set and try again.'.format(password_type))
                raise ServerError(info)

        for (i, password1) in enumerate(passwords):
            for (j, password2) in enumerate(passwords):
                if i != j and self.config[password1] == self.config[
                        password2] != None:
                    info = (
                        'Passwords "{}" and "{}" in server/config.yaml match. '
                        'Please change them so they are different and try again.'
                        .format(password1, password2))
                    raise ServerError(info)
Esempio n. 7
0
 def load_banlist(self):
     try:
         with Constants.fopen('storage/banlist.json', 'r') as banlist_file:
             self.bans = json.load(banlist_file)
     except ServerError as ex:
         if ex.code == 'FileNotFound':
             return
         raise
Esempio n. 8
0
    def load_iniswaps(self):
        try:
            with Constants.fopen('config/iniswaps.yaml', 'r', encoding='utf-8') as iniswaps:
                self.allowed_iniswaps = Constants.yaml_load(iniswaps)
        except Exception as ex:
            message = 'WARNING: Error loading config/iniswaps.yaml. Will assume empty values.\n'
            message += '{}: {}'.format(type(ex).__name__, ex)

            logger.log_pdebug(message)
Esempio n. 9
0
    def load_music(self, music_list_file='config/music.yaml', server_music_list=True):
        with Constants.fopen(music_list_file, 'r', encoding='utf-8') as music:
            music_list = Constants.yaml_load(music)

        if server_music_list:
            self.music_list = music_list
            self.build_music_pages_ao1()
            self.build_music_list_ao2(music_list=music_list)

        return music_list
Esempio n. 10
0
 def load_banlist(self):
     try:
         with Constants.fopen('storage/banlist.json', 'r') as banlist_file:
             self.bans = json.load(banlist_file)
     except ServerError as ex:
         if ex.code == 'FileNotFound':
             message = 'WARNING: File not found: storage/banlist.json. Creating a new one...'
             logger.log_pdebug(message)
             self.write_banlist()
         else:
             raise ex
     except Exception as ex:
         message = 'WARNING: Error loading storage/banlist.json. Will assume empty values.\n'
         message += '{}: {}'.format(type(ex).__name__, ex)
         logger.log_pdebug(message)
Esempio n. 11
0
    def load_commandhelp(self):
        with Constants.fopen('README.md', 'r', encoding='utf-8') as readme:
            lines = [x.rstrip() for x in readme.readlines()]

        self.linetorank = {
            '### User Commands': 'normie',
            '### GM Commands': 'gm',
            '### Community Manager Commands': 'cm',
            '### Moderator Commands': 'mod'}

        self.commandhelp = {
            'normie': dict(),
            'gm': dict(),
            'cm': dict(),
            'mod': dict()}

        # Look for the start of the command list
        try:
            start_index = lines.index('## Commands')
            end_index = lines.index('### Debug commands')
        except ValueError as error:
            error_mes = ", ".join([str(s) for s in error.args])
            message = ('Unable to generate help based on README.md: {}. Are you sure you have the '
                       'latest README.md?'.format(error_mes))
            raise ServerError(message)

        rank = None
        current_command = None

        for line in lines[start_index:end_index]:
            # Check if empty line
            if not line:
                continue

            # Check if this line defines the rank we are taking a look at right now
            if line in self.linetorank.keys():
                rank = self.linetorank[line]
                current_command = None
                continue

            # Otherwise, check if we do not have a rank yet
            if rank is None:
                continue

            # Otherwise, check if this is the start of a command
            if line[0] == '*':
                # Get the command name
                command_split = line[4:].split('** ')
                if len(command_split) == 1:
                    # Case: * **version**
                    current_command = command_split[0][:-2]
                else:
                    # Case: * **uninvite** "ID/IPID"
                    current_command = command_split[0]

                formatted_line = '/{}'.format(line[2:])
                formatted_line = formatted_line.replace('**', '')
                self.commandhelp[rank][current_command] = [formatted_line]
                continue

            # Otherwise, line is part of command description, so add it to its current command desc
            #     - Unlocks your area, provided the lock came as a result of /lock.
            # ... assuming we have a command
            if current_command:
                self.commandhelp[rank][current_command].append(line[4:])
                continue

            # Otherwise, we have a line that is a description of the rank
            # Do nothing about them
            continue # Not really needed, but made explicit
Esempio n. 12
0
 def load_characters(self):
     with Constants.fopen('config/characters.yaml', 'r', encoding='utf-8') as chars:
         self.char_list = Constants.yaml_load(chars)
     self.build_char_pages_ao1()
Esempio n. 13
0
    def load_config(self):
        with Constants.fopen('config/config.yaml', 'r', encoding='utf-8') as cfg:
            self.config = Constants.yaml_load(cfg)
            self.config['motd'] = self.config['motd'].replace('\\n', ' \n')
            self.all_passwords = list()
            # Mandatory passwords must be present in the configuration file. If they are not,
            # a server error will be raised.
            mandatory_passwords = ['modpass', 'cmpass', 'gmpass']
            for password in mandatory_passwords:
                if not (password not in self.config or not str(self.config[password])):
                    self.all_passwords.append(self.config[password])
                else:
                    err = (f'Password "{password}" is not defined in server/config.yaml. Please '
                           f'make sure it is set and try again.')
                    raise ServerError(err)

            # Daily (and guard) passwords are handled differently. They may optionally be left
            # blank or be not available. What this means is the server does not want a daily
            # password for that day (or a guard password)
            optional_passwords = ['guardpass'] + [f'gmpass{i}' for i in range(1, 8)]
            for password in optional_passwords:
                if not (password not in self.config or not str(self.config[password])):
                    self.all_passwords.append(self.config[password])
                else:
                    self.config[password] = None

        # Default values to fill in config.yaml if not present
        defaults_for_tags = {
            'utc_offset': 'local',
            'discord_link': None,
            'max_numdice': 20,
            'max_numfaces': 11037,
            'max_modifier_length': 12,
            'max_acceptable_term': 22074,
            'def_numdice': 1,
            'def_numfaces': 6,
            'def_modifier': '',
            'blackout_background': 'Blackout_HD',
            'default_area_description': 'No description.',
            'party_lights_timeout': 10,
            'show_ms2-prober': True,
            'showname_max_length': 30,
            'sneak_handicap': 5,
            'spectator_name': 'SPECTATOR',
            'music_change_floodguard': {'times_per_interval': 1,
                                        'interval_length': 0,
                                        'mute_length': 0}}

        for (tag, value) in defaults_for_tags.items():
            if tag not in self.config:
                self.config[tag] = value

        # Check that all passwords were generated are unique
        passwords = ['guardpass',
                     'modpass',
                     'cmpass',
                     'gmpass',
                     'gmpass1',
                     'gmpass2',
                     'gmpass3',
                     'gmpass4',
                     'gmpass5',
                     'gmpass6',
                     'gmpass7']

        for (i, password1) in enumerate(passwords):
            for (j, password2) in enumerate(passwords):
                if i != j and self.config[password1] == self.config[password2] != None:
                    info = ('Passwords "{}" and "{}" in server/config.yaml match. '
                            'Please change them so they are different and try again.'
                            .format(password1, password2))
                    raise ServerError(info)
Esempio n. 14
0
 def dump_ipids(self):
     with Constants.fopen('storage/ip_ids.json', 'w',
                          encoding='utf-8') as whole_list:
         json.dump(self.ipid_list, whole_list)
Esempio n. 15
0
 def dump_hdids(self):
     with Constants.fopen('storage/hd_ids.json', 'w') as whole_list:
         json.dump(self.hdid_list, whole_list)
Esempio n. 16
0
    def load_areas(self, area_list_file='config/areas.yaml'):
        """
        Load an area list.

        Parameters
        ----------
        area_list_file: str, optional
            Location of the area list to load. Defaults to 'config/areas.yaml'.

        Raises
        ------
        AreaError
            If any one of the following conditions are met:
            * An area has no 'area' or no 'background' tag.
            * An area uses the deprecated 'sound_proof' tag.
            * Two areas have the same name.
            * An area parameter was left deliberately blank as opposed to fully erased.
            * An area has a passage to an undefined area.

        FileNotFound
            If the area list could not be found.
        """

        self.area_names = set()
        current_area_id = 0
        temp_areas = list()
        temp_area_names = set()
        temp_reachable_area_names = set()

        # Check if valid area list file
        with Constants.fopen(area_list_file, 'r') as chars:
            areas = Constants.yaml_load(chars)

        def_param = {
            'afk_delay': 0,
            'afk_sendto': 0,
            'bglock': False,
            'bullet': True,
            'cbg_allowed': False,
            'change_reachability_allowed': True,
            'default_description':
            self.server.config['default_area_description'],
            'evidence_mod': 'FFA',
            'gm_iclock_allowed': True,
            'has_lights': True,
            'iniswap_allowed': False,
            'lobby_area': False,
            'locking_allowed': False,
            'private_area': False,
            'reachable_areas': '<ALL>',
            'restricted_chars': '',
            'rollp_allowed': True,
            'rp_getarea_allowed': True,
            'rp_getareas_allowed': True,
            'scream_range': '',
            'song_switch_allowed': False,
        }

        # Create the areas
        for item in areas:
            # Check required parameters
            if 'area' not in item:
                info = 'Area {} has no name.'.format(current_area_id)
                raise AreaError(info)
            if 'background' not in item:
                info = 'Area {} has no background.'.format(item['area'])
                raise AreaError(info)

            # Check unset optional parameters
            for param in def_param:
                if param not in item:
                    item[param] = def_param[param]

            # Prevent names that may be interpreted as a directory with . or ..
            # This prevents sending the client an entry to their music list which may be read as
            # including a relative directory
            if False:  # Constants.includes_relative_directories(item['area']):
                info = (
                    f'Area {item["area"]} could be interpreted as referencing the current or '
                    f'parent directories, so it is invalid. Please rename the area and try '
                    f'again.')
                raise AreaError(info)

            # Check use of backwards incompatible parameters
            if 'sound_proof' in item:
                info = (
                    'The sound_proof property was defined for area {}. '
                    'Support for sound_proof was removed in favor of scream_range. '
                    'Please replace the sound_proof tag with scream_range in '
                    'your area list and try again.'.format(item['area']))
                raise AreaError(info)

            # Avoid having areas with the same name
            if item['area'] in temp_area_names:
                info = (
                    'Two areas have the same name in area list: {}. '
                    'Please rename the duplicated areas and try again.'.format(
                        item['area']))
                raise AreaError(info)

            # Check if any of the items were interpreted as Python Nones (maybe due to empty lines)
            for parameter in item:
                if item[parameter] is None:
                    info = (
                        'Parameter {} is manually undefined for area {}. This can be the case '
                        'due to having an empty parameter line in your area list. '
                        'Please fix or remove the parameter from the area definition and try '
                        'again.'.format(parameter, item['area']))
                    raise AreaError(info)

            temp_areas.append(self.Area(current_area_id, self.server, item))
            temp_area_names.add(item['area'])
            temp_reachable_area_names |= temp_areas[-1].reachable_areas
            current_area_id += 1

        # Check if a reachable area is not an area name
        # Can only be done once all areas are created

        unrecognized_areas = temp_reachable_area_names - temp_area_names - {
            '<ALL>'
        }
        if unrecognized_areas != set():
            info = (
                'The following areas were defined as reachable areas of some areas in the '
                'area list file, but were not actually defined as areas: {}. Please rename the '
                'affected areas and try again.'.format(unrecognized_areas))
            raise AreaError(info)

        # Only once all areas have been created, actually set the corresponding values
        # Helps avoiding junk area lists if there was an error
        # But first, remove all zones

        backup_zones = self.server.zone_manager.get_zones()
        for (zone_id, zone) in backup_zones.items():
            self.server.zone_manager.delete_zone(zone_id)
            for client in zone.get_watchers():
                client.send_ooc('Your zone has been automatically deleted.')

        old_areas = self.areas
        self.areas = temp_areas
        self.area_names = temp_area_names

        # And cancel all existing day cycles
        for client in self.server.client_manager.clients:
            try:
                client.server.tasker.remove_task(client, ['as_day_cycle'])
            except KeyError:
                pass

        # And remove all global IC and global IC prefixes
        for client in self.server.client_manager.clients:
            if client.multi_ic:
                client.send_ooc(
                    'Due to an area list reload, your global IC was turned off. You '
                    'may turn it on again manually.')
                client.multi_ic = None
            if client.multi_ic_pre:
                client.send_ooc(
                    'Due to an area list reload, your global IC prefix was removed. '
                    'You may set it again manually.')
                client.multi_ic_pre = ''

        # If the default area ID is now past the number of available areas, reset it back to zero
        if self.server.default_area >= len(self.areas):
            self.server.default_area = 0

        for area in old_areas:
            # Decide whether the area still exists or not
            try:
                new_area = self.get_area_by_name(area.name)
                remains = True
            except AreaError:
                new_area = self.default_area()
                remains = False

            # Move existing clients to new corresponding area (or to default area if their previous
            # area no longer exists).
            for client in area.clients.copy():
                # Check if current char is available
                if new_area.is_char_available(client.char_id):
                    new_char_id = client.char_id
                else:
                    try:
                        new_char_id = new_area.get_rand_avail_char_id()
                    except AreaError:
                        new_char_id = -1

                if remains:
                    message = 'Area list reload. Moving you to the new {}.'
                else:
                    message = (
                        'Area list reload. Your previous area no longer exists. Moving you '
                        'to the server default area {}.')

                client.send_ooc(message.format(new_area.name))
                client.change_area(new_area,
                                   ignore_checks=True,
                                   change_to=new_char_id,
                                   ignore_notifications=True)

            # Move parties (independently)
            for party in area.parties.copy():
                party.area = new_area
                new_area.add_party(party)

        # Update the server's area list only once everything is successful
        self.server.old_area_list = self.server.area_list
        self.server.area_list = area_list_file
Esempio n. 17
0
 def load_backgrounds(self):
     with Constants.fopen('config/backgrounds.yaml', 'r', encoding='utf-8') as bgs:
         self.backgrounds = Constants.yaml_load(bgs)