async def hide(self, message: Message, location: str): config = State.get_config(message.guild.id) channel: TextChannel = message.channel connection = self.roleplay.get_connection(channel.name, location) if not connection: await channel.send( f'No connection from {channel.name} to {location}.', delete_after=60*60 ) return is_hidden = config['connections'][connection.name].get('h', False) if is_hidden: await channel.send( f'Connection from {channel.name} to {location} is already ' 'hidden.', delete_after=60*60 ) else: config['connections'][connection.name]['h'] = True State.save_config(message.guild.id, config) await self.bot.save_guild_config(message.guild, config) text = f'A connection between {channel.name} and {location} has ' \ f'been hidden.' await channel.send(text) await message.delete() await self.bot.get_chronicle(message.guild).log_announcement( channel, text )
async def move_reset(self, message, user: Optional[str]): move_timers = State.get_var(message.guild.id, 'move_timers') if not move_timers: move_timers = defaultdict(lambda: datetime.min) for player in message.mentions: move_timers[player.id] = datetime.min State.set_var(message.guild.id, 'move_timers', move_timers)
async def start_game(self, message: Message, roleplay: str, players: str): config = State.get_config(message.guild.id) if config and 'rp' in config and config['rp']: await message.channel.send( f'Cannot start roleplay, {config["rp"]} is already in progress.' ) return if roleplay not in self.bot.roleplays: await message.channel.send( f'Cannot start roleplay, unknown roleplay name {roleplay}' ) return loading_message = await message.channel.send(f'Setting up new game...') config = {'rp': roleplay, 'connections': {}} await self.bot.save_guild_config(message.guild, config) await self.bot.refresh_from_config(message.guild, config, True) await message.author.add_roles(State.get_admin_role(message.guild.id)) self.roleplay = self.bot.roleplays[roleplay] player_role = State.get_player_role(message.guild.id) for member in message.mentions: await member.add_roles(player_role) await self.move_force(message, self.roleplay.starting_room, None) await message.channel.send(f'The game begins.') await loading_message.delete() await message.delete()
async def toggle_key( self, message: Message, room1: str, room2: str, user: str ): connection = self.roleplay.get_connection(room1, room2) if not connection: await message.channel.send( f'No connection from {room1} to {room2}.' ) return config = State.get_config(message.guild.id) keys = config['connections'][connection.name]['k'] for member in message.mentions: if member.id not in keys: keys.append(member.id) await message.channel.send( f'{member.mention} can now lock and unlock the connection ' f'from {room1} to {room2}.' ) else: keys.remove(member.id) await message.channel.send( f'{member.mention} can no longer lock and unlock the ' f'connection from {room1} to {room2}.' ) State.save_config(message.guild.id, config) await self.bot.save_guild_config(message.guild, config) await message.delete()
async def on_message(self, message: Message): # Ignore all DMs if message.guild is None: return # Ignore all messages from this bot if message.author == self.user: return plugins = self.get_all_plugins(message.guild) # Do a special case for !help, since we need an overview of all plugins # to make it complete if message.clean_content == PluginCommand.PREFIX + 'help': help_message = '\n'.join( p.get_help(State.is_admin(message.author)) for p in plugins ) if not help_message: help_message = 'No commands are currently available.' else: help_message = \ 'The following commands are currently available:\n' \ + help_message await reply(message, help_message) return for plugin in plugins: if await plugin.process_message(message): break else: roleplay = self.get_roleplay_for_guild(message.guild.id) if roleplay and message.channel.name in roleplay.rooms: await self.get_chronicle(message.guild).log_player_message( message.author, message.channel, message.clean_content )
async def _lock_or_unlock( self, message: Message, location: str, lock: bool = True ): config = State.get_config(message.guild.id) channel: TextChannel = message.channel connection = self.roleplay.get_connection(channel.name, location) if not connection: await channel.send( f'No connection from {channel.name} to {location}.', delete_after=60*60 ) return connection_config = config['connections'][connection.name] if not State.is_admin(message.author) \ and message.author.id not in connection_config['k']: await channel.send( f'{message.author.mention} does not have the key to ' f'{location}.', delete_after=60*60 ) return locked = connection_config['l'] if locked and lock or not locked and not lock: await channel.send( f'Access to {location} is already ' f'{"locked" if lock else "unlocked"}.', delete_after=60*60 ) return self._update_connection( config["connections"], channel.name, location, locked=lock ) State.save_config(message.guild.id, config) await self.bot.save_guild_config(message.guild, config) await channel.send( f'{"Locked" if lock else "Unlocked"} access to {location}.' ) await message.delete() name = 'the GM' \ if State.is_admin(message.author) else message.author.display_name await self.bot.get_chronicle(message.guild).log_announcement( channel, f'Access from **{message.channel.name}** to **{location}** ' f'{"" if lock else "un"}locked by **{name}**' )
async def view_remove(self, message: Message, room: str) -> None: config = State.get_config(message.guild.id) if 'views' not in config: config['views'] = {} if room in config['views'] \ and message.channel.name in config['views'][room]: config['views'][room].remove(message.channel.name) await self.bot.save_guild_config(message.guild, config) await message.channel.send(f'Remote view to {room} removed.') else: await message.channel.send( f'No remote view to {room} set up in this channel.' )
async def view_add(self, message: Message, room: str) -> None: config = State.get_config(message.guild.id) if 'views' not in config: config['views'] = {} if room not in config['views']: config['views'][room] = [] if message.channel.name not in config['views'][room]: config['views'][room].append(message.channel.name) await self.bot.save_guild_config(message.guild, config) await message.channel.send(f'Remote view to {room} added.') else: await message.channel.send( f'There is already a remote view to {room} in this channel.' )
async def character(request: Request): try: guild_id = int(request.match_info['guild_id']) except (KeyError, ValueError): guild_id = None char = None if guild_id is not None: user_id = request.match_info['user_id'] character_id = request.match_info['character_id'] config = State.get_config(guild_id) characters_data = config.get('characters', {}) char = characters_data.get(user_id, {}).get('characters', {}).get(character_id) if char: move_timers = State.get_var(guild_id, 'move_timers') move_countdown_time = None if move_timers: move_countdown_time = move_timers.get(int(user_id)) char = { **char, "move_countdown_time": move_countdown_time.astimezone().isoformat() if move_countdown_time else "" } return {'character': char}
async def view_list(self, message: Message) -> None: config = State.get_config(message.guild.id) all_views = [] for user_id, views in config.get('views', {}).items(): user: Member = message.guild.get_member(user_id) if user and views: all_views.append( f'**{user.display_name}:** ' + ', '.join(views) ) if all_views: await message.channel.send( 'The following remote views are set up:\n' + '\n'.join(all_views) ) else: await message.channel.send('No remote views are set up.')
async def global_variables(request: Request): bot: RoleplayBot = request.app['bot'] roleplays = [] for guild in bot.guilds: if bot.get_roleplay_for_guild(guild.id): roleplays.append({'id': guild.id, 'name': guild.name}) try: guild_id = int(request.match_info['guild_id']) except (KeyError, ValueError): guild_id = None roleplay = None if guild_id is not None: config = State.get_config(guild_id) roleplay_data = bot.get_roleplay_for_guild(guild_id) if roleplay_data: guild = bot.get_guild(guild_id) roleplay = { 'id': guild_id, 'name': guild.name, 'description': roleplay_data.description, 'characters': [{ 'user_id': user_id, 'id': character_id, **character, } for user_id, user_characters in config.get( 'characters', {}).items() for character_id, character in user_characters['characters'].items()], 'commands': [ command for plugin in bot.get_all_plugins(guild) for command in sorted(plugin.commands.values(), key=lambda c: c.name) if command.enabled and not command.requires_admin and not command.hidden ] } return { 'base_url': bot_config['rpbot']['base_url'], 'roleplay': roleplay, 'roleplays': sorted(roleplays, key=itemgetter('name')), }
async def log( self, source_channel: Optional[str], message: str, extra_only: bool = False, ) -> None: config = State.get_config(self.guild.id) if not config: return views = config.get('views', {}) dest_channels = set() if extra_only else {self.channel_name} if source_channel in views: dest_channels.update(views[source_channel]) for channel in self.guild.text_channels: if channel.name in dest_channels: await channel.send(message[:MAX_MESSAGE_LENGTH]) if message[MAX_MESSAGE_LENGTH:]: await channel.send(message[MAX_MESSAGE_LENGTH:])
async def status(self, message: Message) -> None: statuses = [] for member in message.channel.members: if State.is_player(member): try: character = await self._get_active_character(member) except CommandException: continue if character['status']: status = f'**{character["name"]}:** {character["status"]}' else: status = f'**{character["name"]}**' statuses.append(status) for npc in self._get_npcs_in_room(message.guild, message.channel.name): statuses.append(f'**{npc["name"]}:** {npc["status"]}') if statuses: text = 'The following people are here:' for status in sorted(statuses): text += f'\n{status}' await reply(message, text) else: await reply(message, 'Nobody is here.')
async def end_game(self, message: Message): loading_message = await message.channel.send(f'Ending game...') config = {} guild: Guild = message.guild State.save_config(guild.id, config) State.save_plugins(guild.id, []) await self.bot.save_guild_config(guild, config) player_role = State.get_player_role(guild.id) observer_role = State.get_observer_role(guild.id) for player in player_role.members: await player.remove_roles(player_role) await player.add_roles(observer_role) await message.channel.send(f'The game ends.') await loading_message.delete() await message.delete() await self.bot.refresh_from_config(guild, config)
async def mark_observer(self, message: Message, user: str): await self._mark_with_role( message, State.get_observer_role(message.guild.id), 'an observer' )
def _get_config(guild: Guild) -> Dict[str, Any]: config = State.get_config(guild.id) if not config or not config.get('rp'): raise CommandException( 'No active roleplay, characters not available.') return config
def get_all_plugins(self, guild: Guild) -> List[Plugin]: plugins = [ BasePlugin(self, self.get_roleplay_for_guild(guild.id)) ] plugins += State.get_plugins(guild.id) return plugins
async def refresh_from_config( self, guild: Guild, config: Dict[str, Any], force_reset=False ): logging.info(f'Refreshing server state from config for {guild.name}') State.save_config(guild.id, config) if 'rp' not in config or not config['rp']: return modified_config = False roleplay = self.roleplays[config['rp']] if 'connections' not in config: config['connections'] = {} modified_config = True for connection in roleplay.connections: if connection.name not in config['connections']: config['connections'][connection.name] = { 'h': connection.hidden, 'l': connection.locked, 'k': [] } modified_config = True if modified_config: State.save_config(guild.id, config) await self.save_guild_config(guild, config) # Pick all the plugins required by the roleplay plugins = [] plugin_configs = roleplay.plugins if plugin_configs: for plugin_id, plugin_config \ in plugin_configs.items(): # noinspection PyArgumentList plugins.append( self.plugins[plugin_id]( self, roleplay, **plugin_config ) ) State.save_plugins(guild.id, plugins) # Ensure the server state matches gm_role = await self.create_or_update_role(guild, roleplay.roles['gm']) player_role = await self.create_or_update_role( guild, roleplay.roles['player'] ) observer_role = await self.create_or_update_role( guild, roleplay.roles['observer'] ) State.save_roles(guild.id, gm_role, player_role, observer_role) sections: Dict[str, CategoryChannel] = { c.name: c for c in guild.categories } for channel_id, room in roleplay.rooms.items(): source = guild if room.section: if room.section not in sections: sections[room.section] = await guild.create_category( room.section, ) section = sections[room.section] source = section channel: TextChannel = next( (c for c in source.text_channels if c.name == channel_id), None ) overwrites = { guild.default_role: PermissionOverwrite( read_messages=False ), self.user: PermissionOverwrite(read_messages=True), gm_role: PermissionOverwrite( read_messages=True, send_messages=True ), observer_role: PermissionOverwrite( read_messages=True, send_messages=False ) } if channel: if not force_reset: continue await channel.edit(topic=room.description) for target, overwrite in overwrites.items(): await channel.set_permissions(target, overwrite=overwrite) else: await source.create_text_channel( channel_id, topic=room.description, overwrites=overwrites )
async def reload(self, message: Message) -> None: self.bot.reload() await self.bot.refresh_from_config( message.guild, State.get_config(message.guild.id) ) await message.channel.send('Roleplay definition and plugins reloaded.')
def get_roleplay_for_guild(self, guild_id: int): config = State.get_config(guild_id) return self.roleplays[config['rp']] \ if config and 'rp' in config and config['rp'] else None
async def _save_config(self, guild: Guild) -> None: await self.bot.save_guild_config(guild, State.get_config(guild.id))
async def move_all(self, message: Message, room: str): for player in State.get_player_role(message.guild.id).members: await self._move_player(player, room)
async def move(self, message: Message, room: Optional[str]): connections = State.get_config(message.guild.id)['connections'] channel: TextChannel = message.channel destinations = self._list_destinations(connections, channel.name) if not room: destination_list = '\n'.join( destination + (' *(locked)*' if locked else '') for destination, locked in destinations ) await channel.send( ( 'The following destinations are available from here:\n' + destination_list ) if destination_list else 'No destinations are currently available.', delete_after=60*60 ) await message.delete() return for destination, locked in destinations: if destination == room: if locked: await channel.send( f'{room} is currently locked off.', delete_after=60*60 ) await message.delete() return break else: await channel.send( f'Cannot reach {room} from here.', delete_after=60*60 ) return move_timers = State.get_var(message.guild.id, 'move_timers') if not move_timers: move_timers = defaultdict(lambda: datetime.min) time_remaining = ( move_timers[message.author.id] - datetime.now() ).total_seconds() if time_remaining > 0: minutes = time_remaining // 60 await channel.send( 'Must wait ' + ( f'{int(minutes)} minutes' if minutes else f'{int(time_remaining)} seconds' ) + ' before moving again.', delete_after=60*60 ) return connection = self.roleplay.get_connection(channel.name, room) new_channel = await self._move_player( message.author, room, message.channel ) move_timers[message.author.id] = datetime.now() + timedelta( minutes=connection.timer ) State.set_var(message.guild.id, 'move_timers', move_timers) await channel.send(f'{message.author.mention} moves to {room}') await new_channel.send( f'{message.author.mention} moves in from {channel.name}' ) await message.delete()
async def mark_gm(self, message: Message, user: str): await self._mark_with_role( message, State.get_admin_role(message.guild.id), 'an admin' )