def add_poll(self, value):
     test = time.strftime('%y-%m-%d %H%M-%S')
     if not ([item for item in self.poll_list if item[0] == value]):
         if len(self.poll_list) < self.server.config['poll_slots']:
             self.poll_list.append([value, test])
             tmp = time.strftime('%y-%m-%d %H:%M:%S')
             newfile = {
                 'name': value,
                 'polldetail': None,
                 'multivote': False,
                 'choices': ["Yes", "No"],
                 'votes': {"yes": 0, "no": 0},
                 'created': tmp,
                 'log': [],
             }
             with open('storage/poll/{} \'{}\'.yaml'.format(test, value), 'w') as file:
                 yaml.dump(newfile, file, default_flow_style=False)
                 logger.log_serverpoll('Poll \'{}\' added successfully.'.format(value))
         else:
             logger.log_serverpoll('Failed to add poll. Reason: The poll queue is full.')
             raise ServerError('The Poll Queue is full!')
     else:
         logger.log_serverpoll('Failed to add poll. Reason: This poll already exists.')
         raise ServerError('This poll already exists.')
     self.write_poll_list()
Beispiel #2
0
    def prepare_music_list(self, c=None, specific_music_list=None):
        """
        If `specific_music_list` is not None, return a client-ready version of that music list.
        Else, if `c` is a client with a custom chosen music list, return their latest music list.
        Otherwise, return a client-ready version of the server music list.

        Parameters
        ----------
        c: ClientManager.Client
            Client whose current music list if it exists will be considered if `specific_music_list`
            is None
        specific_music_list: list of dictionaries with key sets {'category', 'songs'}
            Music list to use if given

        Returns
        -------
        list of str
            Music list ready to be sent to clients
        """

        # If not provided a specific music list to overwrite
        if specific_music_list is None:
            specific_music_list = self.music_list  # Default value
            # But just in case, check if this came as a request of a client who had a
            # previous music list preference
            if c and c.music_list is not None:
                specific_music_list = c.music_list

        prepared_music_list = list()
        try:
            for item in specific_music_list:
                prepared_music_list.append(item['category'])
                for song in item['songs']:
                    if 'length' not in song:
                        name, length = song['name'], -1
                    else:
                        name, length = song['name'], song['length']

                    # Check that length is a number, and if not, abort.
                    if not isinstance(length, (int, float)):
                        msg = (
                            "The music list expected a numerical length for track '{}', but "
                            "found it had length '{}'.").format(
                                song['name'], song['length'])
                        raise ServerError.MusicInvalidError(msg)

                    prepared_music_list.append(name)

        except KeyError as err:
            msg = (
                "The music list expected key '{}' for item {}, but could not find it."
                .format(err.args[0], item))
            raise ServerError.MusicInvalid(msg)
        except TypeError:
            msg = (
                "The music list expected songs to be listed for item {}, but could not find any."
                .format(item))
            raise ServerError.MusicInvalid(msg)

        return prepared_music_list
Beispiel #3
0
    def build_music_pages_ao1(self):
        self.music_pages_ao1 = []
        index = 0
        # add areas first
        for area in self.area_manager.areas:
            self.music_pages_ao1.append('{}#{}'.format(index, area.name))
            index += 1
        # then add music
        try:
            for item in self.music_list:
                self.music_pages_ao1.append('{}#{}'.format(index, item['category']))
                index += 1
                for song in item['songs']:
                    self.music_pages_ao1.append('{}#{}'.format(index, song['name']))
                    index += 1
        except KeyError as err:
            msg = ("The music list expected key '{}' for item {}, but could not find it."
                   .format(err.args[0], item))
            raise ServerError.MusicInvalid(msg)
        except TypeError:
            msg = ("The music list expected songs to be listed for item {}, but could not find any."
                   .format(item))
            raise ServerError.MusicInvalid(msg)

        self.music_pages_ao1 = [self.music_pages_ao1[x:x + 10] for x in range(0, len(self.music_pages_ao1), 10)]
Beispiel #4
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)
Beispiel #5
0
 def fopen(file, *args, **kwargs):
     try:
         f = open(file, *args, **kwargs)
         return f
     except FileNotFoundError:
         info = 'File not found: {}'.format(file)
         raise ServerError(info, code="FileNotFound")
     except OSError as ex:
         raise ServerError(str(ex))
Beispiel #6
0
    async def as_afk_kick(self, client, args):
        afk_delay, afk_sendto = args
        try:
            delay = int(afk_delay)*60 # afk_delay is in minutes, so convert to seconds
        except (TypeError, ValueError):
            info = ('The area file contains an invalid AFK kick delay for area {}: {}'.
                    format(client.area.id, afk_delay))
            raise ServerError(info)

        if delay <= 0: # Assumes 0-minute delay means that AFK kicking is disabled
            return

        try:
            await asyncio.sleep(delay)
        except asyncio.CancelledError:
            raise
        else:
            try:
                area = client.server.area_manager.get_area_by_id(int(afk_sendto))
            except Exception:
                info = ('The area file contains an invalid AFK kick destination area for area {}: '
                        '{}'.format(client.area.id, afk_sendto))
                raise ServerError(info)
            if client.area.id == afk_sendto: # Don't try and kick back to same area
                return
            if client.char_id < 0: # Assumes spectators are exempted from AFK kicks
                return
            if client.is_staff(): # Assumes staff are exempted from AFK kicks
                return

            try:
                original_area = client.area
                original_name = client.displayname
                client.change_area(area, override_passages=True, override_effects=True,
                                   ignore_bleeding=True)
            except Exception:
                pass # Server raised an error trying to perform the AFK kick, ignore AFK kick
            else:
                client.send_ooc('You were kicked from area {} to area {} for being inactive for '
                                '{} minutes.'.format(original_area.id, afk_sendto, afk_delay))

                if client.area.is_locked or client.area.is_modlocked:
                    try: # Try and remove the IPID from the area's invite list
                        client.area.invite_list.pop(client.ipid)
                    except KeyError:
                        pass # Would only happen if they joined the locked area through mod powers

                if client.party:
                    p = client.party
                    client.party.remove_member(client)
                    client.send_ooc('You were also kicked off from your party.')
                    for c in p.get_members():
                        c.send_ooc('{} was AFK kicked from your party.'.format(original_name))
Beispiel #7
0
 def remove_ban(self, ip):
     try:
         try:
             int(ip)
         except ValueError:
             ipaddress.ip_address(ip)
             ip = self.server.get_ipid(ip)
     except ValueError:
         raise ServerError('Argument must be an IP address or IPID.')
     if ip in self.bans:
         self.bans.remove(ip)
     else:
         raise ServerError('User is already not banned.')
     self.write_banlist()
Beispiel #8
0
    async def as_afk_kick(self, client, args):
        afk_delay, afk_sendto = args
        try:
            delay = int(
                afk_delay
            ) * 60  # afk_delay is in minutes, so convert to seconds
        except (TypeError, ValueError):
            raise ServerError(
                'The area file contains an invalid AFK kick delay for area {}: {}'
                .format(client.area.id, afk_delay))

        if delay <= 0:  # Assumes 0-minute delay means that AFK kicking is disabled
            return

        try:
            await asyncio.sleep(delay)
        except asyncio.CancelledError:
            raise
        else:
            try:
                area = client.server.area_manager.get_area_by_id(
                    int(afk_sendto))
            except:
                raise ServerError(
                    'The area file contains an invalid AFK kick destination area for area {}: {}'
                    .format(client.area.id, afk_sendto))

            if client.area.id == afk_sendto:  # Don't try and kick back to same area
                return
            if client.char_id < 0:  # Assumes spectators are exempted from AFK kicks
                return
            if client.is_staff():  # Assumes staff are exempted from AFK kicks
                return

            try:
                original_area = client.area
                client.change_area(area,
                                   override_passages=True,
                                   override_effects=True,
                                   ignore_bleeding=True)
            except:
                pass  # Server raised an error trying to perform the AFK kick, ignore AFK kick
            else:
                client.send_host_message(
                    "You were kicked from area {} to area {} for being inactive for {} minutes."
                    .format(original_area.id, afk_sendto, afk_delay))

                if client.area.is_locked or client.area.is_modlocked:
                    client.area.invite_list.pop(client.ipid)
 def add_ban(self, ip):
     try:
         try:
             int(ip)
         except ValueError:
             ipaddress.ip_address(ip)
             ip = self.server.get_ipid(ip)
     except ValueError:
         raise ServerError(
             'Argument must be an IP address or 10-digit number.')
     if ip not in self.bans:
         self.bans.append(ip)
     else:
         raise ServerError('User is already banned.')
     self.write_banlist()
Beispiel #10
0
 def remove_ban(self, ip):
     try:
         try:
             int(ip)
         except ValueError:
             ipaddress.ip_address(ip)
             ip = self.server.get_ipid(ip)
     except ValueError:
         raise ServerError(
             'Argument must be an IP address or 10-digit number.')
     if ip in self.bans:
         self.bans.remove(ip)
     else:
         raise ServerError('This IPID is not banned.')
     self.write_banlist()
 def get_votelist(self, value):
     try:
         if [i for i in self.poll_list if i[0] == "{}".format(value)]:
             poll_selected = [i[1] for i in self.poll_list if i[0] == "{}".format(value)]
             output = ('{} \'{}\''.format("".join(poll_selected), value))
             stream = open('storage/poll/{}.yaml'.format(output), 'r')
             stream2 = yaml.load(stream)
             log = stream2['log']
             return log
         else:
             return None
     except FileNotFoundError:
         raise ServerError('The specified poll has no file associated with it.')
     except IndexError:
         raise ServerError('The poll list is currently empty.')
 def remove_ban(self, ipid):
     ipid = str(ipid)
     if self.is_banned(ipid):
         del self.bans[ipid]
     else:
         raise ServerError('This IPID is not banned.')
     self.write_banlist()
Beispiel #13
0
 def remove_poll_choice(self, client, value, remove):
     try:
         if [i for i in self.poll_list if i[0] == "{}".format(value)]:
             poll_selected = [
                 i[1] for i in self.poll_list if i[0] == "{}".format(value)
             ]
             output = ('{} \'{}\''.format("".join(poll_selected), value))
             stream = open('storage/poll/{}.yaml'.format(output), 'r')
             stream2 = yaml.load(stream)
             choices = stream2['choices']
             if not remove in choices:
                 client.send_host_message('Item is not a choice.')
                 return
             stream2['choices'] = [x for x in choices if not x == remove]
             stream2['votes'].pop(remove.lower())
             with open('storage/poll/{}.yaml'.format(output),
                       'w') as votelist_file:
                 yaml.dump(stream2, votelist_file, default_flow_style=False)
             return stream2['choices']
         else:
             return None
     except FileNotFoundError:
         raise ServerError(
             'The specified poll has no file associated with it.')
     except IndexError:
         return
Beispiel #14
0
 def get_char_id_by_name(self, name):
     if name == self.config['spectator_name']:
         return -1
     for i, ch in enumerate(self.char_list):
         if ch.lower() == name.lower():
             return i
     raise ServerError('Character not found.')
Beispiel #15
0
 def add_poll_choice(self, client, value, add):
     try:
         if [i for i in self.poll_list if i[0] == "{}".format(value)]:
             poll_selected = [
                 i[1] for i in self.poll_list if i[0] == "{}".format(value)
             ]
             output = ('{} \'{}\''.format("".join(poll_selected), value))
             stream = open('storage/poll/{}.yaml'.format(output), 'r')
             stream2 = yaml.load(stream)
             if add.lower() in [x.lower() for x in stream2['choices']]:
                 client.send_host_message('Item already a choice.')
                 return
             stream2['choices'].append(str(add))
             stream2['votes'][add.lower()] = 0
             with open('storage/poll/{}.yaml'.format(output),
                       'w') as votelist_file:
                 yaml.dump(stream2, votelist_file, default_flow_style=False)
             return stream2['choices']
         else:
             return None
     except FileNotFoundError:
         raise ServerError(
             'The specified poll has no file associated with it.')
     except IndexError:
         return
 def add_ban(self, ipid, reason):
     ipid = str(ipid)
     if not self.is_banned(ipid):
         self.bans[ipid] = {'Reason': reason}
     else:
         raise ServerError('This IPID is already banned.')
     self.write_banlist()
Beispiel #17
0
def ooc_cmd_playrandom(client, arg):
	"""
	Plays a random track.
	Usage: /playrandom
	"""
	if len(arg) > 0:
		raise ArgumentError('This command takes no arguments.')
	index = 0
	for item in client.server.music_list:
		for song in item['songs']:
			index += 1
	if index == 0:
		raise ServerError(
				'No music found.')
	else:
		music_set = set(range(index))
		trackid = random.choice(tuple(music_set))
		index = 1
		for item in client.server.music_list:
			for song in item['songs']:
				if index == trackid:
					client.area.play_music(song['name'], client.char_id, song['length'])
					client.area.add_music_playing(client, song['name'])
					database.log_room('play', client, client.area, message=song['name'])
					return
				else:
					index += 1
Beispiel #18
0
    def get_song_data(self, music, area):
        """
		Get information about a track, if exists.
		:param music: track name
		:returns: tuple (name, length or -1)
		:raises: ServerError if track not found
		"""
        for item in self.music_list:
            if item['category'] == music:
                return '~stop.mp3', 0, -1, False
            for song in item['songs']:
                if song['name'] == music:
                    try:
                        return song['name'], song['length'], song['mod']
                    except KeyError:
                        try:
                            return song['name'], song['length'], -1, False
                        except KeyError:
                            return song['name'], 0, -1, False
        if len(area.cmusic_list) != 0:
            for item in area.cmusic_list:
                if item['category'] == music:
                    return '~stop.mp3', 0, -1, True
                if len(item['songs']) != 0:
                    for song in item['songs']:
                        if song['name'] == music:
                            try:
                                return song['name'], song['length'], song[
                                    'mod'], True
                            except KeyError:
                                return song['name'], song['length'], -1, True
        raise ServerError('Music not found.')
Beispiel #19
0
 def add_ban(self, ip):
     try:
         x = len(ip)
     except AttributeError:
         raise ServerError('Argument must be an 12-digit number.')
     if x == 12:
         self.bans[ip] = True
         self.write_banlist()
Beispiel #20
0
def rolla_reload(area):
    try:
        import yaml
        with open('config/dice.yaml', 'r') as dice:
            area.ability_dice = yaml.safe_load(dice)
    except:
        raise ServerError(
            'There was an error parsing the ability dice configuration. Check your syntax.'
        )
 def remove_poll(self, value):
     if ([i for i in self.poll_list if i[0] == "{}".format(value)]):
         self.poll_list = [i for i in self.poll_list if i[0] != "{}".format(value)]
         logger.log_serverpoll('Poll \'{}\' removed.'.format(value))
     elif value == "all":
         self.poll_list = []
         logger.log_serverpoll('All polls removed.')
     else:
         logger.log_serverpoll('Poll removal failed. Reason: The specified poll does not exist.')
         raise ServerError('The specified poll does not exist.')
     self.write_poll_list()
Beispiel #22
0
    def get_char_id_by_name(self, name):
        """
        Get a character ID by the name of the character.
        :param name: name of character
        :returns: Character ID

        """
        for i, ch in enumerate(self.char_list):
            if ch.lower() == name.lower():
                return i
        raise ServerError('Character not found.')
Beispiel #23
0
 def get_song_data(self, music):
     for item in self.music_list:
         if item['category'] == music:
             return item['category'], -1
         for song in item['songs']:
             if song['name'] == music:
                 try:
                     return song['name'], song['length']
                 except KeyError:
                     return song['name'], -1
     raise ServerError('Music not found.')
Beispiel #24
0
 def yaml_load(file):
     try:
         return yaml.safe_load(file)
     except yaml.YAMLError as exc:
         # Extract the name of the yaml
         separator = max(file.name.rfind('\\'), file.name.rfind('/'))
         file_name = file.name[separator+1:]
         # Then raise the exception
         msg = ('File {} returned the following YAML error when loading: `{}`.'
                .format(file_name, exc))
         raise ServerError.YAMLInvalidError(msg)
Beispiel #25
0
    def validate_contents(self, contents, extra_parameters=None) -> List[str]:
        # Check characters contents is indeed a list of strings
        if not isinstance(contents, list):
            msg = (f'Expected the characters list to be a list, got a '
                   f'{type(contents).__name__}: {contents}.')
            raise ServerError.FileSyntaxError(msg)

        for (i, character) in enumerate(contents.copy()):
            if character is None:
                msg = (f'Expected all character names to be defined, but character {i} was not.')
                raise ServerError.FileSyntaxError(msg)
            if not isinstance(character, (str, float, int, bool, complex)):
                msg = (f'Expected all character names to be strings or numbers, but character '
                       f'{i}: {character} was not a string or number.')
                raise ServerError.FileSyntaxError(msg)

            # Otherwise, character i is valid. Cast it as string to deal with YAML doing
            # potential casting of its own
            contents[i] = str(character)

        return contents
Beispiel #26
0
 def remove_ban(self, client, ip):
     try:
         try:
             int(ip)
         except ValueError:
             ipaddress.ip_address(ip)
             ip = client.server.get_ipid(ip)
     except ValueError:
         if not len(ip) == 12:
             raise ServerError(
                 'Argument must be an IP address or 10-digit number.')
     del self.bans[ip]
     self.write_banlist()
Beispiel #27
0
def ooc_cmd_judgelog(client, arg):
    if not client.is_mod:
        raise ClientError('You must be authorized to do that.')
    if len(arg) != 0:
        raise ArgumentError('This command does not take any arguments.')
    jlog = client.area.judgelog
    if len(jlog) > 0:
        jlog_msg = '== Judge Log =='
        for x in jlog:
            jlog_msg += '\r\n{}'.format(x)
        client.send_host_message(jlog_msg)
    else:
        raise ServerError(
            'There have been no judge actions in this area since start of session.'
        )
Beispiel #28
0
    def reload(self):
        # Keep backups in case of failure
        backup = [
            self.char_list.copy(),
            self.music_list.copy(),
            self.backgrounds.copy()
        ]

        # Do a dummy YAML load to see if the files can be loaded and parsed at all first.
        reloaded_assets = [
            self.load_characters, self.load_backgrounds, self.load_music
        ]
        for reloaded_asset in reloaded_assets:
            try:
                reloaded_asset()
            except ServerError.YAMLInvalidError as exc:
                # The YAML exception already provides a full description. Just add the fact the
                # reload was undone to ease the person who ran the command's nerves.
                msg = (f'{exc} Reload was undone.')
                raise ServerError.YAMLInvalidError(msg)
            except ServerError.FileSyntaxError as exc:
                msg = f'{exc} Reload was undone.'
                raise ServerError(msg)

        # Only on success reload
        self.load_characters()
        self.load_backgrounds()

        try:
            self.load_music()
        except ServerError as exc:
            self.char_list, self.music_list, self.backgrounds = backup
            msg = (
                'The new music list returned the following error when loading: `{}`. Fix the '
                'error and try again. Reload was undone.'.format(exc))
            raise ServerError.FileSyntaxError(msg)
Beispiel #29
0
    def validate_contents(self, contents, extra_parameters=None) -> List[str]:
        # Check background contents is indeed a list of strings
        if not isinstance(contents, list):
            msg = (f'Expected the background list to be a list, got a '
                   f'{type(contents).__name__}: {contents}.')
            raise ServerError.FileSyntaxError(msg)

        for (i, background) in enumerate(contents.copy()):
            if background is None:
                msg = (f'Expected all background names to be defined, '
                       f'found background {i} was not.')
                raise ServerError.FileSyntaxError(msg)
            if not isinstance(background, (str, float, int, bool, complex)):
                msg = (
                    f'Expected all background names to be strings or numbers, '
                    f'found background {i}: {background} was not a string or number.'
                )
                raise ServerError.FileSyntaxError(msg)

            # Otherwise, background i is valid. Cast it as string to deal with YAML doing
            # potential casting of its own
            contents[i] = str(background)

        return contents
Beispiel #30
0
    def load_music(self,
                   music_list_file='config/music.yaml',
                   server_music_list=True):
        try:
            with open(music_list_file, 'r', encoding='utf-8') as music:
                music_list = yaml.safe_load(music)
        except FileNotFoundError:
            raise ServerError(
                'Could not find music list file {}'.format(music_list_file))

        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