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)
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)
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()
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
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()
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)
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
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)
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
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)
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
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()
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)
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)
def dump_hdids(self): with Constants.fopen('storage/hd_ids.json', 'w') as whole_list: json.dump(self.hdid_list, whole_list)
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
def load_backgrounds(self): with Constants.fopen('config/backgrounds.yaml', 'r', encoding='utf-8') as bgs: self.backgrounds = Constants.yaml_load(bgs)