Esempio n. 1
0
    async def upload(self, ctx):
        if not ctx.message.attachments:
            await ctx.send(
                f":no_entry_sign: Please include your custom rotation as an attachment!"
            )
            return
        attachment = ctx.message.attachments[0]
        if attachment.size > 1000000:
            await ctx.send(
                f":no_entry_sign: Invalid attachment!\n`File too big! Maximum is 1000000 bytes but received {str(attachment.size)} bytes`"
            )
            return
        if not attachment.filename.endswith(".json"):
            extension = "." + attachment.filename.split(".")[-1]
            await ctx.send(
                f":no_entry_sign: Invalid attachment!\n`Invalid file extension! Expected .json but received {extension}`"
            )
            return

        inst = self.bot.cache.instance(ctx.author.id, ctx.guild.id)

        content = str(await attachment.read(), 'utf-8')
        content = json.loads(content)

        inst.import_rotation(content=content)
        with open(Path(f'rotations/{str(inst.id)}.json'), 'w+') as f:
            f.write(json.dumps(content, indent=2))

        Instance(inst.id).set_uses_custom_rotation(1)
        game = Instance(inst.id).game.upper()
        if game == 'SQUAD': valid_maps = MAPS_SQUAD
        elif game == 'BTW': valid_maps = MAPS_BTW
        elif game == 'PS': valid_maps = MAPS_PS
        else: valid_maps = []

        embed = base_embed(inst.id,
                           title="Uploaded and enabled Custom Map Rotation",
                           color=discord.Color.green())
        embed.description = "`r!rotation upload` - Upload a new custom rotation\n`r!rotation enable` - Enable the custom rotation\n`r!rotation disable` - Disable custom rotation\n`r!rotation download` - Download your custom rotation"
        try:
            maps = sorted(
                set([str(entry) for entry in inst.map_rotation.get_entries()]))
            for m in maps:
                if m not in valid_maps:
                    maps[maps.index(m)] += " ⚠️"

        except:
            maps = ["Failed to fetch maps"]
        embed.add_field(name="Maps in rotation:", value="\n".join(maps))

        if " ⚠️" in "\n".join(maps):
            embed.add_field(
                name='⚠️ Warning ⚠️',
                value=
                "Some maps are not recognized and could be invalid. Please verify that the marked layers are correct."
            )

        await ctx.send(embed=embed)
Esempio n. 2
0
    async def instance_config(self, ctx, key: str = None, value=None):
        instance_id = self.bot.cache._get_selected_instance(ctx.author.id)
        instance = Instance(instance_id)
        if key: key = key.lower()

        if key == None or key not in instance.config.keys():
            embed = base_embed(instance.id, title='Config values')
            for key, value in instance.config.items():
                value = str(value) if str(value) else "NULL"
                embed.add_field(name=key, value=value)
            embed = add_empty_fields(embed)
            await ctx.send(embed=embed)

        elif value == None:
            embed = base_embed(
                instance.id,
                title='Config values',
                description=
                f"> **Current value:**\n> {key} = {instance.config[key]}")
            desc = CONFIG_DESC[key] if key in CONFIG_DESC.keys(
            ) else f"**{key}**\nNo description found."
            embed.description = embed.description + "\n\n" + desc
            await ctx.send(embed=embed)

        else:
            old_value = instance.config[key]
            try:
                value = type(old_value)(value)
            except:
                raise commands.BadArgument(
                    '%s should be %s, not %s' %
                    (value, type(old_value).__name__, type(value).__name__))
            else:

                if key == "guild_id":
                    guild = self.bot.get_guild(int(value))
                    if not guild:
                        raise commands.BadArgument(
                            'Unable to find a guild with ID %s' % value)
                    member = guild.get_member(ctx.author.id)
                    if not member:
                        raise commands.BadArgument(
                            'You haven\'t joined that guild yourself')
                    if not member.guild_permissions.administrator:
                        raise commands.BadArgument(
                            'You need to have administrator permissions in that guild'
                        )

                instance.config[key] = value
                instance.store_config()

                if not old_value: old_value = "None"
                embed = base_embed(instance.id, title='Updated config')
                embed.add_field(name="Old Value", value=str(old_value))
                embed.add_field(name="New value", value=str(value))
                await ctx.send(embed=embed)
Esempio n. 3
0
    async def disconnect_instance(self, ctx):
        instance_id = self.bot.cache._get_selected_instance(ctx.author.id)
        inst = Instance(instance_id)

        self.bot.cache.instances[instance_id] = None
        embed = base_embed(instance_id, title="Instance was shut down")
        await ctx.send(embed=embed)
Esempio n. 4
0
    async def logs(self, ctx, category: str = None):

        inst = self.bot.cache.instance(ctx.author, ctx.guild.id)
        await self._query(inst)

        if category != None: category = category.lower()
        if category in ["export", "file"]:
            inst_name = Instance(inst.id).name
            log_file = discord.File(fp=ServerLogs(inst.id).export(),
                                    filename=f"{inst_name}_log.txt")
            await ctx.send(file=log_file)

        else:
            logs = ServerLogs(inst.id).get_logs(category)

            if not logs:
                await ctx.send(
                    f"{config.get('error_message_emoji')} No logs could be found!"
                )
                return

            output = ""
            for log in logs[::-1]:
                message = format_log(log) + "\n"
                if len(message + output) + 12 > 2000: break
                else: output = message + output

            output = "```json\n" + output + "```"
            await ctx.send(output)
Esempio n. 5
0
    async def delete_instance(self, ctx):
        instance_id = self.bot.cache._get_selected_instance(ctx.author.id)
        inst = Instance(instance_id)

        embed = base_embed(
            inst.id,
            title="Are you sure you want to permanently delete this instance?",
            description=
            "Deleting an instance will break the connection, remove all permissions and clear all the logs. This can NOT be reverted.\n\nReact with 🗑️ to confirm and delete this instance."
        )
        try:
            msg = await ctx.author.send(embed=embed)
        except:
            msg = await ctx.send(embed=embed)

        await msg.add_reaction('🗑️')

        def check(reaction, user):
            return reaction.message.id == msg.id and str(
                reaction.emoji
            ) == "🗑️" and user.id == ctx.author.id and not user.bot

        try:
            await self.bot.wait_for('reaction_add', timeout=120.0, check=check)
        except:
            if isinstance(msg.channel, discord.TextChannel):
                await msg.clear_reactions()
            embed.description = "You took too long to respond. Execute the command again to retry."
            await msg.edit(embed=embed)
        else:
            if isinstance(msg.channel, discord.TextChannel):
                await msg.clear_reactions()
            embed = base_embed(inst.id, title="Instance deleted")
            self.bot.cache.delete_instance(instance_id)
            await msg.edit(embed=embed)
Esempio n. 6
0
    async def enable(self, ctx):
        inst = self.bot.cache.instance(ctx.author.id, ctx.guild.id)

        if inst.map_rotation:
            await ctx.send(
                ':no_entry_sign: Custom Map Rotation is already enabled!')
            return
        path = Path(f'rotations/{str(inst.id)}.json')
        if not os.path.exists(path):
            await ctx.send(
                ':no_entry_sign: Upload a custom rotation first using `r!rotation upload`!'
            )
            return

        inst.import_rotation(fp=path)
        Instance(inst.id).set_uses_custom_rotation(1)

        embed = base_embed(inst.id,
                           title="Enabled Custom Map Rotation",
                           color=discord.Color.green())
        embed.description = "`r!rotation upload` - Upload a new custom rotation\n`r!rotation enable` - Enable the custom rotation\n`r!rotation disable` - Disable custom rotation\n`r!rotation download` - Download your custom rotation"
        try:
            maps = sorted(
                set([str(entry) for entry in inst.map_rotation.get_entries()]))
        except:
            maps = ["Failed to fetch maps"]
        embed.add_field(name="Maps in rotation:", value="\n".join(maps))

        await ctx.send(embed=embed)
Esempio n. 7
0
    async def list_instances(self, ctx):
        instances = get_available_instances(ctx.author.id, ctx.guild.id)
        current_instance = self.bot.cache._get_selected_instance(ctx.author.id)
        try:
            current_instance = Instance(current_instance).name
        except:
            current_instance = "None"

        embed = discord.Embed(
            title=f"Available Instances ({str(len(instances))})",
            description=f'> Currently selected: {current_instance}')
        embed.set_author(name=str(ctx.author), icon_url=ctx.author.avatar_url)

        for i, (inst, perms) in enumerate(instances):
            try:
                self.bot.cache.instance(inst.id, by_inst_id=True)
            except:
                availability = "\🔴"
            else:
                availability = "\🟢"
            perms = ", ".join(
                [perm for perm, val in perms_to_dict(perms).items() if val])
            embed.add_field(name=f"{str(i+1)} | {inst.name} {availability}",
                            value=f"> **Perms:** {perms}")
        embed = add_empty_fields(embed)

        await ctx.send(embed=embed)
Esempio n. 8
0
def base_embed(instance,
               title: str = None,
               description: str = None,
               color=discord.Embed.Empty):
    if isinstance(instance, int):
        instance = Instance(instance)
    embed = EmbedMenu(title=title, description=description, color=color)
    embed.set_author(name=instance.name, icon_url=GAME_IMAGES[instance.game])
    return embed
Esempio n. 9
0
    async def connect_instance(self, ctx):
        instance_id = self.bot.cache._get_selected_instance(ctx.author.id)
        inst = Instance(instance_id)

        res = self.bot.cache._connect_instance(inst, return_exception=True)
        if isinstance(res, Exception):  # Instance could not be connected
            raise res
        else:  # Instance was successfully connected
            embed = base_embed(instance_id,
                               title="Instance was successfully (re)connected")
            await ctx.send(embed=embed)
Esempio n. 10
0
    async def disable(self, ctx):        
        inst = self.bot.cache.instance(ctx.author, ctx.guild.id)

        if inst.map_rotation == None:
            await ctx.send(':no_entry_sign: Custom Map Rotation is already disabled!')
            return
        
        inst.map_rotation = None
        Instance(inst.id).set_uses_custom_rotation(0)

        embed = base_embed(inst.id, title="Disabled Custom Map Rotation", color=discord.Color.red())
        embed.description = "`r!rotation upload` - Upload a new custom rotation\n`r!rotation enable` - Enable the custom rotation\n`r!rotation disable` - Disable custom rotation\n`r!rotation download` - Download your custom rotation"

        await ctx.send(embed=embed)
Esempio n. 11
0
    def map_changed(self, new_map):
        self._decrease_cooldown()
        self.cooldowns[str(new_map)] = 0
        if str(new_map) == str(self.next_map) or (
                self.next_map and not self.next_map.validate(len(
                    self.players))) or self.is_transitioning:
            self.next_map = self._get_next_map()
            if self.next_map:
                instance_details = Instance(self.id)
                if instance_details.game == 'squad':
                    self.rcon.set_next_layer(self.next_map)
                else:
                    self.rcon.set_next_map(self.next_map)

        return self.next_map
Esempio n. 12
0
    async def set_next_map(self, ctx, *, map_name: str):
        inst = self.bot.cache.instance(ctx.author, ctx.guild.id)
        instance_details = Instance(
            ctx.bot.cache._get_selected_instance(ctx.author, ctx.guild.id))

        # check current game for instance select - squad uses another command
        if instance_details.game == 'squad':
            res = inst.rcon.set_next_layer(map_name)
        else:
            res = inst.rcon.set_next_map(map_name)
        inst.next_map = Map(map_name)

        embed = base_embed(self.bot.cache.instance(ctx.author,
                                                   ctx.guild.id).id,
                           title="Queued the next map",
                           description=res)
        await ctx.send(embed=embed)
        ServerLogs(inst.id).add(
            'rcon',
            f'{ctx.author.name}#{ctx.author.discriminator} queued {map_name}')
Esempio n. 13
0
    async def skip_match(self, ctx, *, map_name: str = ""):
        inst = self.bot.cache.instance(ctx.author, ctx.guild.id)

        instance_details = Instance(
            ctx.bot.cache._get_selected_instance(ctx.author, ctx.guild.id))

        if map_name:
            # check current game for instance select - squad uses another command
            if instance_details.game == 'squad':
                res = inst.rcon.switch_to_layer(map_name)
            else:
                res = inst.rcon.switch_to_map(map_name)
        else:
            res = inst.rcon.end_match()

        embed = base_embed(self.bot.cache.instance(ctx.author,
                                                   ctx.guild.id).id,
                           title="Skipped the current match",
                           description=res)
        await ctx.send(embed=embed)
        ServerLogs(inst.id).add(
            'rcon',
            f'{ctx.author.name}#{ctx.author.discriminator} skipped the current match'
        )
Esempio n. 14
0
    async def check_server(self):
        for inst in self.bot.cache.instances.values():
            if inst:
                await self._query(inst)
                try:
                    max_id = self.last_seen_id[inst.id]
                except KeyError:
                    max_id = ServerLogs(inst.id)._get_max_log_id()
                    new_max_id = max_id
                    new_logs = []
                else:
                    new_max_id, new_logs = ServerLogs(
                        inst.id).get_logs_after(max_id)

                config = Instance(inst.id).config
                guild = self.bot.get_guild(config['guild_id'])
                if guild and new_logs:
                    # Note: Chat logs are handled alongside the triggers
                    channel_joins = guild.get_channel(
                        config['channel_log_joins'])
                    channel_match = guild.get_channel(
                        config['channel_log_match'])
                    channel_rcon = guild.get_channel(
                        config['channel_log_rcon'])

                    if channel_rcon:
                        default_embed = base_embed(inst.id)
                        logs = [
                            log for log in new_logs
                            if log['category'] == 'rcon'
                        ]
                        for log in logs:
                            embed = default_embed
                            embed.color = discord.Color.teal()
                            embed.title = log['message']
                            embed.set_footer(
                                text=
                                f"Recorded at {log['timestamp'].strftime('%a, %b %d, %Y %I:%M %p')}"
                            )
                            await channel_match.send(embed=embed)
                    if channel_joins:
                        default_embed = base_embed(inst.id)
                        logs = [
                            log for log in new_logs
                            if log['category'] == 'joins'
                        ]
                        if logs:
                            joins = [
                                log['message'] for log in logs
                                if log['message'].endswith(' connected')
                            ]
                            leaves = [
                                log['message'] for log in logs
                                if not log['message'].endswith(' connected')
                            ]

                            embed = default_embed
                            embed.set_footer(
                                text=
                                f"Recorded at {logs[-1]['timestamp'].strftime('%a, %b %d, %Y %I:%M %p')}"
                            )

                            if joins:
                                embed.color = discord.Color.dark_green()
                                embed.description = "\n".join(joins)
                                await channel_match.send(embed=embed)
                            if leaves:
                                embed.color = discord.Embed.Empty
                                embed.description = "\n".join(leaves)
                                await channel_match.send(embed=embed)
                    if channel_match:
                        default_embed = base_embed(inst.id)
                        logs = [
                            log for log in new_logs
                            if log['category'] == 'match'
                        ]
                        for log in logs:
                            embed = default_embed
                            embed.color = discord.Color.from_rgb(255, 255, 255)
                            embed.title = log['message']
                            embed.set_footer(
                                text=
                                f"Recorded at {log['timestamp'].strftime('%a, %b %d, %Y %I:%M %p')}"
                            )
                            await channel_match.send(embed=embed)

                self.last_seen_id[inst.id] = new_max_id
Esempio n. 15
0
    async def config_menu(self,
                          ctx,
                          config_title,
                          config_key,
                          option: str = None,
                          value=None):
        inst_id = self.bot.cache._get_selected_instance(
            ctx.author.id, ctx.channel.id)
        inst = Instance(inst_id)
        CONFIG_KEY = str(config_key)
        CONFIG_TITLE = str(config_title)

        if option:
            option = option.replace(CONFIG_KEY, "")
            key = CONFIG_KEY + option

        def update_value(option, value):
            option = option.replace(CONFIG_KEY, "")
            key = CONFIG_KEY + option
            if key not in inst.config.keys():
                raise KeyError("Config option %s does not exist" % key)
            old_value = inst.config[key]

            try:
                value = type(inst.config[key])(value)
            except ValueError:
                raise commands.BadArgument(
                    "Value should be %s, not %s" %
                    (type(inst.config[key]).__name__, type(value).__name__))

            inst.config[CONFIG_KEY + option] = value
            inst.store_config()

            return old_value, value

        if not option:
            embed = base_embed(
                inst,
                title=f"{CONFIG_TITLE} Configuration",
                description=
                f"To edit values, react with the emojis below or type `r!{ctx.command.name} <option> <value>`"
            )
            for k, v in inst.config.items():
                if k.startswith(CONFIG_KEY):
                    try:
                        option_info = CONFIGS[k]
                    except KeyError:
                        continue
                    value = inst.config[k] if inst.config[k] else "None"
                    embed.add_option(
                        option_info["emoji"],
                        title=option_info['name'],
                        description=
                        f"ID: {k.replace(CONFIG_KEY, '')}\nValue: `{value}`\n\n*{option_info['short_desc']}*"
                    )

            reaction = await embed.run(ctx)

            if reaction:
                (key, info) = [(k, v) for k, v in CONFIGS.items()
                               if v['emoji'] == str(reaction.emoji)][0]
                option = key.replace(CONFIG_KEY, "")
                embed = base_embed(
                    inst,
                    title=f"Editing value {key}... ({info['name']})",
                    description=
                    f"{get_name(ctx.author)}, what should the new value be? To cancel, type \"cancel\"."
                )
                msg = await ctx.send(embed=embed)

                def check(m):
                    return m.author == ctx.author and m.channel == ctx.channel

                try:
                    m = await self.bot.wait_for('message',
                                                timeout=120,
                                                check=check)
                except:
                    await msg.edit(embed=base_embed(
                        inst,
                        description=
                        f"{get_name(ctx.author)}, you took too long to respond."
                    ))
                else:

                    if m.content.lower() == "cancel":
                        embed.description = "You cancelled the action."
                        embed.color = discord.Color.dark_red()
                        await msg.edit(embed=embed)
                        return
                    value = m.content

                    old_value, value = update_value(option, value)

                    embed = base_embed(inst,
                                       title=f'Updated {CONFIGS[key]["name"]}')
                    embed.add_field(name="Old Value", value=str(old_value))
                    embed.add_field(name="New value", value=str(value))
                    await ctx.send(embed=embed)

        elif value == None:
            embed = base_embed(
                inst,
                title=f'Chat Alerts: {option}',
                description=f"> **Current value:**\n> {inst.config[key]}")
            desc = CONFIGS[key]['long_desc'] if key in CONFIGS.keys(
            ) and CONFIGS[key][
                'long_desc'] else f"**{key}**\nNo description found."
            embed.description = embed.description + "\n\n" + desc
            await ctx.send(embed=embed)

        else:
            old_value, value = update_value(option, value)

            embed = base_embed(inst, title=f'Updated {CONFIGS[key]["name"]}')
            embed.add_field(name="Old Value", value=str(old_value))
            embed.add_field(name="New value", value=str(value))
            await ctx.send(embed=embed)
Esempio n. 16
0
    async def guild_permissions(self,
                                ctx,
                                operation: str = '',
                                value: int = None):
        # List guild permissions
        if operation.lower() in ['', 'list', 'view', 'show']:
            instances = get_guild_instances(ctx.guild.id)

            if instances:
                embed = discord.Embed(title="Standard guild permissions")
                embed.set_author(icon_url=ctx.guild.icon_url,
                                 name=ctx.guild.name)

                for i, (instance, perms) in enumerate(instances):
                    perms = ", ".join([
                        perm for perm, val in perms_to_dict(perms).items()
                        if val
                    ])
                    embed.add_field(name=f"{str(i+1)} | {instance.name}",
                                    value=f"> **Perms:** {perms}")
                embed = add_empty_fields(embed)

            else:
                embed = discord.Embed(
                    title="Standard guild permissions",
                    description=
                    "There aren't any instances assigned to this guild just yet."
                )
                embed.set_author(icon_url=ctx.guild.icon_url,
                                 name=ctx.guild.name)

            await ctx.send(embed=embed)

        # Set guild permissions for the selected instance
        elif operation.lower() in ['set']:
            instance = Instance(
                self.bot.cache._get_selected_instance(ctx.author.id))

            # Update the guild permissions for the selected instance
            if value != None and int(value) >= 0 and int(value) <= 31:
                old_value = instance.default_perms
                instance.set_default_perms(value)

                embed = base_embed(
                    instance.id,
                    title=f"Changed guild permissions for {ctx.guild.name}")
                old_perms = ", ".join([
                    perm for perm, val in perms_to_dict(old_value).items()
                    if val
                ])
                new_perms = ", ".join([
                    perm for perm, val in perms_to_dict(value).items() if val
                ])
                embed.add_field(name="Old Perms", value=f"> {old_perms}")
                embed.add_field(name="New Perms", value=f"> {new_perms}")

                await ctx.send(embed=embed)

            # Error
            else:
                raise commands.BadArgument("Permissions value out of range")

        # Unknown operation
        else:
            raise commands.BadArgument(
                'Operation needs to be either "list" or "set", not "%s"' %
                operation)
Esempio n. 17
0
    async def permissions_group(self,
                                ctx,
                                user: discord.Member = None,
                                operation: str = "",
                                value: int = None):
        instance_id = self.bot.cache._get_selected_instance(ctx.author.id)
        instance = Instance(instance_id)

        # Limit command usage when missing permissions
        if not has_perms(ctx, instance=True):
            user = ctx.author
            operation = ""
            value = None

        # Default user to message author
        if not user:
            user = ctx.author

        # List all instances this user has access to here
        if operation.lower() == '':
            instances = get_available_instances(user.id, ctx.guild.id)

            if instances:
                embed = discord.Embed(title=f"Permissions in {ctx.guild.name}")
                embed.set_author(icon_url=user.avatar_url,
                                 name=f"{user.name}#{user.discriminator}")

                for i, (instance, perms) in enumerate(instances):
                    perms = ", ".join([
                        perm for perm, val in perms_to_dict(perms).items()
                        if val
                    ])
                    embed.add_field(name=f"{str(i+1)} | {instance.name}",
                                    value=f"> **Perms:** {perms}")
                embed = add_empty_fields(embed)

            else:
                embed = discord.Embed(
                    title=f"Permissions in {ctx.guild.name}",
                    description=
                    "You don't have access to any instances yet!\n\nInstances can be created by server owners, assuming they are an administrator of that guild.\n`r!instance create"
                )
                embed.set_author(icon_url=user.avatar_url,
                                 name=f"{user.name}#{user.discriminator}")

            await ctx.send(embed=embed)

        # List user permissions for this user
        elif operation.lower() in ['list', 'view', 'show']:
            perms = get_perms(user.id, -1, instance_id, is_dict=False)
            perms_dict = perms_to_dict(perms)
            perms_str = ", ".join(
                [perm for perm, val in perms_dict.items() if val])
            embed = base_embed(
                instance.id,
                title=
                f'Permission overwrites for {user.name}#{user.discriminator}',
                description=
                f"> Current value: {str(perms)}\n> Perms: {perms_str}")
            await ctx.send(embed=embed)

        # Set user permissions for the selected instance
        elif operation.lower() in ['set']:
            # Update the user permissions for the selected instance
            if value != None and int(value) >= 0 and int(value) <= 31:
                old_perms = ", ".join([
                    perm for perm, val in get_perms(user.id, -1,
                                                    instance_id).items() if val
                ])
                new_perms = ", ".join([
                    perm for perm, val in perms_to_dict(value).items() if val
                ])
                set_player_perms(user.id, instance_id, value)

                embed = base_embed(
                    instance.id,
                    title=
                    f"Changed permission overwrites for {user.name}#{user.discriminator}"
                )
                embed.add_field(name="Old Perms", value=f"> {old_perms}")
                embed.add_field(name="New Perms", value=f"> {new_perms}")

                await ctx.send(embed=embed)

            # Error
            else:
                raise commands.BadArgument("Permissions value out of range")

        # Reset user permissions for the selected instance
        elif operation.lower() in ['reset']:
            reset_player_perms(user.id, instance_id)
            embed = base_embed(
                instance.id,
                title=
                f"Removed permission overwrites for {user.name}#{user.discriminator}"
            )
            await ctx.send(embed=embed)

        # Unknown operation
        else:
            raise commands.BadArgument(
                'Operation needs to be either "list" or "set" or "reset", not "%s"'
                % operation)
Esempio n. 18
0
    async def ask_server_info(self, ctx, operation=0):
        try:
            await ctx.author.send(ctx.author.mention)
        except:
            await ctx.send(
                f":no_entry_sign: {ctx.author.mention}, please enable DMs")
            return

        if operation not in [0, 1]:
            operation = 0

        if operation == 1:
            inst = Instance(
                self.bot.cache._get_selected_instance(ctx.author.id,
                                                      ctx.guild.id))

        # Create embed
        def setup_embed(question):
            embed = discord.Embed(
                title=question,
                description=
                "Type your answer down below. You can change your answers later."
            )
            if operation == 0:
                embed = discord.Embed(
                    title=question,
                    description=
                    "Type your answer down below. You can change your answers later."
                )
                embed.set_author(name="Creating your instance...")
            elif operation == 1:
                embed = base_embed(
                    inst,
                    title=question,
                    description=
                    "Type your answer down below. You can change your answers later."
                )
                embed._author['name'] = f"Editing {inst.name}..."
            embed.set_footer(
                text=
                "This action will be canceled if the channel remains inactive for 2 minutes during its creation."
            )
            return embed

        async def ask_game():
            embed = setup_embed(
                "What game is your server for, Squad, PS or BTW?")
            msg = await ctx.author.send(embed=embed)

            def check(message):
                return message.channel == msg.channel and message.author == ctx.author

            answer = ""
            while answer not in ['squad', 'ps', 'btw']:
                answer = await self.bot.wait_for('message',
                                                 check=check,
                                                 timeout=120.0)
                answer = answer.content.lower()
                if answer not in ['squad', 'ps', 'btw']:
                    await ctx.author.send(
                        f'Send either "Squad", "PS" or "BTW", not "{answer}"')
            return answer

        async def ask_address_and_port():
            async def ask_address():
                embed = setup_embed("What is your server's IP address?")
                msg = await ctx.author.send(embed=embed)

                def check(message):
                    return message.channel == msg.channel and message.author == ctx.author

                answer = await self.bot.wait_for('message',
                                                 check=check,
                                                 timeout=120.0)
                return answer.content

            async def ask_port():
                embed = setup_embed("What is your server's RCON port?")
                msg = await ctx.author.send(embed=embed)

                def check(message):
                    return message.channel == msg.channel and message.author == ctx.author

                answer = await self.bot.wait_for('message',
                                                 check=check,
                                                 timeout=120.0)
                return answer.content

            address = ""
            port = 0
            while not address or not port:
                address = await ask_address()
                if len(address.split(":", 1)) == 2:
                    address, port = address.split(":", 1)
                else:
                    port = await ask_port()
                try:
                    port = int(port)
                except:
                    port = 0
                    await ctx.author.send(
                        "Invalid port! Please insert your address and port again."
                    )
            return address, port

        async def ask_password():
            embed = setup_embed("What is your server's RCON password?")
            embed.description += "\n\nNote: Passwords are stored by the bot, but will NEVER be shared."
            msg = await ctx.author.send(embed=embed)

            def check(message):
                return message.channel == msg.channel and message.author == ctx.author

            answer = await self.bot.wait_for('message',
                                             check=check,
                                             timeout=120.0)
            return answer.content

        async def ask_name():
            embed = setup_embed("What should your instance be called?")
            msg = await ctx.author.send(embed=embed)

            def check(message):
                return message.channel == msg.channel and message.author == ctx.author

            answer = await self.bot.wait_for('message',
                                             check=check,
                                             timeout=120.0)
            return answer.content

        async def ticket_confirmation(values):
            embed = discord.Embed(
                title="That was everything!",
                description=
                "If you need to change something, select one of the fields by reacting to this message. If all information is correct, react with ✅. To cancel, react with 🗑️.",
                color=discord.Color.gold())
            embed.add_field(name="1⃣ Game", value=values[0], inline=True)
            embed.add_field(name="2⃣ Address:Port",
                            value=values[1] + ":" + str(values[2]),
                            inline=True)
            embed.add_field(name="3⃣ Password", value=values[3], inline=True)
            embed.add_field(name="4⃣ Instance Name",
                            value=values[4],
                            inline=False)
            msg = await ctx.author.send(embed=embed)

            for emoji in ["✅", "1⃣", "2⃣", "3⃣", "4⃣", "🗑️"]:
                await msg.add_reaction(emoji)

            def check(reaction, user):
                return reaction.message.channel == msg.channel and user == ctx.author

            answer = await self.bot.wait_for('reaction_add',
                                             check=check,
                                             timeout=300.0)
            await msg.delete()

            return str(answer[0].emoji)

        try:
            game = await ask_game()
            address, port = await ask_address_and_port()
            password = await ask_password()
            name = await ask_name()
            values = [game, address, port, password, name]

            confirmation = ""
            while confirmation != "✅":
                confirmation = await ticket_confirmation(values)
                if confirmation == "1⃣": game = await ask_game()
                elif confirmation == "2⃣":
                    address, port = await ask_address_and_port()
                elif confirmation == "3⃣":
                    password = await ask_password()
                elif confirmation == "4⃣":
                    name = await ask_name()
                elif confirmation == "🗑️":
                    if operation == 0:
                        await ctx.author.send("Instance creation cancelled.")
                    elif operation == 0:
                        await ctx.author.send("Instance editing cancelled.")
                    return
                values = [game, address, port, password, name]

                if confirmation == "✅":
                    msg = await ctx.author.send(
                        "Trying to establish a connection...")
                    try:
                        if operation == 0:
                            inst = add_instance(game=values[0],
                                                address=values[1],
                                                port=values[2],
                                                password=values[3],
                                                name=values[4],
                                                owner_id=ctx.author.id)
                        elif operation == 1:
                            inst = edit_instance(inst.id,
                                                 game=values[0],
                                                 address=values[1],
                                                 port=values[2],
                                                 password=values[3],
                                                 name=values[4])
                    except RconAuthError as e:
                        await ctx.author.send(
                            f"Unable to connect to the server: {str(e)}")
                        await asyncio.sleep(3)
                        confirmation = ""
                    except commands.BadArgument as e:
                        await ctx.author.send(
                            f"An instance using this address already exists!")
                        await asyncio.sleep(3)
                        confirmation = ""
                    else:
                        await msg.edit(content="Connection established!")
                        self.bot.cache._connect_instance(inst)
                        embed = base_embed(
                            inst.id,
                            description=
                            "You can now customize your instance further by setting permissions and changing config options. See `r!inst help` for more information.",
                            color=discord.Color.green())
                        if operation == 0: embed.title = "Instance created!"
                        elif operation == 1: embed.title = "Instance edited!"
                        await ctx.author.send(embed=embed)

        except asyncio.TimeoutError:
            await ctx.author.send(
                "You took too long to respond. Execute the command again to retry."
            )
            return
Esempio n. 19
0
    async def check_server(self):
        try:
            for inst in self.bot.cache.instances.values():
                if inst:
                    try:
                        await self._query(inst)
                        try:
                            max_id = self.last_seen_id[inst.id]
                        except KeyError:
                            max_id = ServerLogs(inst.id)._get_max_log_id()
                            new_max_id = max_id
                            new_logs = []
                        else:
                            new_max_id, new_logs = ServerLogs(
                                inst.id).get_logs_after(max_id)

                        config = Instance(inst.id).config
                        guild = self.bot.get_guild(config['guild_id'])
                        if guild and new_logs:
                            # Note: Chat logs are handled alongside the triggers
                            channel_joins = guild.get_channel(
                                config['channel_log_joins'])
                            channel_match = guild.get_channel(
                                config['channel_log_match'])
                            channel_rcon = guild.get_channel(
                                config['channel_log_rcon'])
                            channel_teamkills = guild.get_channel(
                                config['channel_log_teamkills'])

                            if channel_rcon:
                                default_embed = base_embed(inst.id)
                                logs = [
                                    log for log in new_logs
                                    if log['category'] == 'rcon'
                                ]
                                for log in logs:
                                    embed = default_embed
                                    embed.color = discord.Color.teal()
                                    embed.title = log['message']
                                    embed.set_footer(
                                        text=
                                        f"Recorded at {log['timestamp'].strftime('%a, %b %d, %Y %I:%M %p')}"
                                    )
                                    await channel_rcon.send(embed=embed)
                            if channel_joins:
                                default_embed = base_embed(inst.id)
                                logs = [
                                    log for log in new_logs
                                    if log['category'] == 'joins'
                                ]
                                if logs:
                                    joins = [
                                        log['message'] for log in logs if
                                        log['message'].endswith(' connected')
                                    ]
                                    leaves = [
                                        log['message'] for log in logs
                                        if not log['message'].endswith(
                                            ' connected')
                                    ]

                                    embed = default_embed
                                    embed.set_footer(
                                        text=
                                        f"Recorded at {logs[-1]['timestamp'].strftime('%a, %b %d, %Y %I:%M %p')}"
                                    )

                                    if joins:
                                        embed.color = discord.Color.dark_green(
                                        )
                                        embed.description = "\n".join(joins)
                                        await channel_joins.send(embed=embed)
                                    if leaves:
                                        embed.color = discord.Embed.Empty
                                        embed.description = "\n".join(leaves)
                                        await channel_joins.send(embed=embed)
                            if channel_match:
                                default_embed = base_embed(inst.id)
                                logs = [
                                    log for log in new_logs
                                    if log['category'] == 'match'
                                ]
                                for log in logs:
                                    embed = default_embed
                                    embed.color = discord.Color.from_rgb(
                                        255, 255, 255)
                                    embed.title = log['message']
                                    embed.set_footer(
                                        text=
                                        f"Recorded at {log['timestamp'].strftime('%a, %b %d, %Y %I:%M %p')}"
                                    )
                                    await channel_match.send(embed=embed)
                            if channel_teamkills:
                                default_embed = base_embed(inst.id)
                                logs = [
                                    log for log in new_logs
                                    if log['category'] == 'teamkill'
                                ]
                                if logs:
                                    embed = default_embed
                                    embed.set_footer(
                                        text=
                                        f"Recorded at {logs[-1]['timestamp'].strftime('%a, %b %d, %Y %I:%M %p')}"
                                    )

                                    embed.color = discord.Color.dark_red()
                                    embed.description = "\n".join(
                                        [log['message'] for log in logs])
                                    await channel_teamkills.send(embed=embed)

                        self.last_seen_id[inst.id] = new_max_id
                    except Exception as e:
                        logging.exception(
                            'Inst %s: Unhandled exception whilst updating: %s: %s',
                            inst.id, e.__class__.__name__, e)
                        if isinstance(e, (RconAuthError, ConnectionLost)) and (
                                datetime.now() -
                                timedelta(minutes=30)) > inst.last_updated:
                            self.bot.cache.instances[inst.id] = None
        except Exception as e:
            logging.critical(
                'Unhandled exception in instance update cycle: %s: %s',
                e.__class__.__name__, e)
Esempio n. 20
0
    async def _query(self, inst):
        # Execute a command to receive new chat packets.
        # We can use this opportunity to update the cache,
        # though we don't want to overdo this.
        if (datetime.now() - inst.last_updated
            ).total_seconds() > SECONDS_BETWEEN_CACHE_REFRESH:
            inst.update()
        else:
            inst.rcon.exec_command("a")

        # Grab all the new chat messages
        new_chat_messages = inst.rcon.get_player_chat()
        inst.rcon.clear_player_chat()

        # Parse incoming messages
        # '[ChatAll] [SteamID:12345678901234567] [FP] Clan Member 1 : Hello world!'
        # '[ChatAdmin] ASQKillDeathRuleset : Player S.T.A.L.K.E.R%s Team Killed Player NUKE'
        # '[SteamID:76561198129591637] (WTH) Dylan has possessed admin camera.'
        for message in new_chat_messages:
            raw_data = {}
            if message.startswith('[ChatAdmin] ASQKillDeathRuleset'):
                # The message is a logged teamkill
                p1_name, p2_name = re.search(
                    r'\[ChatAdmin\] ASQKillDeathRuleset : Player (.*)%s Team Killed Player (.*)',
                    message).groups()
                p1 = inst.get_player(p1_name)
                p2 = inst.get_player(p2_name)
                p1_output = f"{p1_name} ({p1.steam_id})" if p1 else p1_name
                p2_output = f"{p2_name} ({p2.steam_id})" if p2 else p2_name

                message = f"{p1_output} team killed {p2_output}"
                ServerLogs(inst.id).add("teamkill", message)
                continue
            else:
                try:
                    raw_data['channel'], raw_data['steam_id'], raw_data[
                        'name'], raw_data['message'] = re.search(
                            r'\[(.+)\] \[SteamID:(\d{17})\] (.*) : (.*)',
                            message).groups()
                except:
                    continue

            player = inst.get_player(int(raw_data['steam_id']))
            if player:
                faction = inst.team1.faction_short if player.team_id == 1 else inst.team2.faction_short
                squad = player.squad_id

                if raw_data['channel'] == "ChatAdmin":
                    continue

                if raw_data['channel'] == "ChatAll":
                    channel = "All"
                elif raw_data['channel'] == "ChatTeam":
                    channel = faction
                elif raw_data['channel'] == "ChatSquad":
                    channel = f"{faction}/Squad{str(squad)}"
                else:
                    channel = raw_data['channel']
            else:
                channel = "Unknown"

            name = raw_data['name']
            text = raw_data['message']

            message = f"[{channel}] {name}: {text}"
            ServerLogs(inst.id).add("chat", message)

            # Do stuff with new messages
            instance = Instance(inst.id)
            config = instance.config
            guild = self.bot.get_guild(config["guild_id"])

            if guild:

                # Trigger words
                trigger_channel = guild.get_channel(
                    config["chat_trigger_channel_id"])
                trigger_words = config["chat_trigger_words"].split(",")
                if trigger_channel and trigger_words:
                    trigger_mentions = config["chat_trigger_mentions"]
                    for word in trigger_words:
                        word = word.strip().lower()
                        if word in text.lower():

                            try:
                                last_attempt = self.trigger_cooldowns[
                                    player.steam_id]
                                cooldown = int(
                                    config['chat_trigger_cooldown'] -
                                    (datetime.now() -
                                     last_attempt).total_seconds())
                            except KeyError:
                                cooldown = -1
                            if cooldown == 0:
                                # The player sent multiple admin reports in the same period.
                                # Let's not ping the admins twice, because why would we?
                                trigger_mentions = ""

                            if cooldown > 0:
                                # The player tried sending admin reports too fast and is still in cooldown
                                inst.rcon.warn(
                                    player.steam_id,
                                    f"You're sending reports too fast! Please wait {str(cooldown)}s and try again."
                                )
                                description = f"{name}: {discord.utils.escape_markdown(text)}"
                                embed = base_embed(inst.id,
                                                   description=description)
                                embed.set_footer(
                                    text=
                                    f"Cooldown was active for this player ({str(cooldown)}s). He was asked to try again."
                                )
                                await trigger_channel.send(embed=embed)

                            elif len(text.split(
                            )) < 3 and config["chat_trigger_require_reason"]:
                                # The player didn't include a reason, which is required
                                inst.rcon.warn(
                                    player.steam_id,
                                    f"Please include a reason in your '{word}' report and try again."
                                )
                                description = f"{name}: {discord.utils.escape_markdown(text)}"
                                embed = base_embed(inst.id,
                                                   description=description)
                                embed.set_footer(
                                    text=
                                    "No reason was included in this report. The player was asked to try again."
                                )
                                await trigger_channel.send(embed=embed)

                            else:
                                self.trigger_cooldowns[
                                    player.steam_id] = datetime.now()
                                description = f"{name}: {discord.utils.escape_markdown(text)}"
                                embed = base_embed(inst.id,
                                                   title="New Report",
                                                   description=description,
                                                   color=discord.Color.red())
                                if player:
                                    embed.set_footer(
                                        text=
                                        "Team ID: %s • Squad ID: %s • Player ID: %s"
                                        % (player.team_id, player.squad_id,
                                           player.player_id))
                                await trigger_channel.send(trigger_mentions,
                                                           embed=embed)
                                if config['chat_trigger_confirmation']:
                                    inst.rcon.warn(
                                        player.steam_id,
                                        config['chat_trigger_confirmation'])

                            break

                # Auto log
                chat_channel = guild.get_channel(config["channel_log_chat"])
                if chat_channel:
                    embed = base_embed(instance)
                    title = "{: <20} {}".format("[" + channel + "]", name)
                    embed.add_field(name=title,
                                    value=discord.utils.escape_markdown(text))

                    embed.set_footer(
                        text=
                        f"Recorded at {datetime.now().strftime('%a, %b %d, %Y %I:%M %p')}"
                    )

                    if not player or channel == "Unknown":
                        embed.color = discord.Embed.Empty
                    elif raw_data['channel'] == "ChatAll":
                        embed.color = discord.Color.dark_gray()
                    elif raw_data['channel'] == "ChatTeam":
                        if player.team_id == 1:
                            embed.color = discord.Color.from_rgb(66, 194, 245)
                        else:
                            embed.color = discord.Color.from_rgb(240, 36, 53)
                    elif raw_data['channel'] == "ChatSquad":
                        if player.team_id == 1:
                            embed.color = discord.Color.from_rgb(0, 100, 255)
                        else:
                            embed.color = discord.Color.from_rgb(201, 4, 24)

                    await chat_channel.send(embed=embed)