Ejemplo n.º 1
0
async def update_template_field_delete(request: Request):
    """
    The route that handles the updating of the template metadata.
    """

    # Make sure the user is logged in
    session = await aiohttp_session.get_session(request)
    if not session.get("user_id"):
        return json_response({"error": "Not logged in."}, status=401)

    # Try and read the POST data from the user
    try:
        data = await request.json()
        field_id = data['field_id']
    except ValueError:
        return json_response({"error": "Couldn't validate the given POST data."}, status=400)
    except Exception:
        return json_response({"error": "Could not read POST data."}, status=400)

    # Now we want to check things with the database
    async with request.app['database']() as db:

        # Make sure the template exists
        field_rows = await db("""SELECT * FROM field WHERE field_id=$1""", field_id)
        if not field_rows:
            return json_response({"error": "Field does not exist."}, status=400)
        field = localutils.Field(**field_rows[0])
        template = await localutils.Template.fetch_template_by_id(db, field.template_id, fetch_fields=False)
        if not template:
            return json_response({"error": "Template does not exist."}, status=400)

        # Make sure the user is editing a template that they have permission to edit
        try:
            guild = await request.app['bots']['bot'].fetch_guild(template.guild_id)
        except discord.HTTPException:
            return json_response({"error": "Bot not in guild."}, status=401)
        try:
            member = await guild.fetch_member(session.get("user_id"))
        except discord.HTTPException:
            return json_response({"error": "User not in guild."}, status=401)
        if not member.guild_permissions.manage_guild:
            return json_response({"error": "Member cannot manage guild."}, status=401)

        # Update the template
        updated_rows = await db(
            """UPDATE field SET deleted=true WHERE field_id=$1""",
            field_id,
        )

    # Return
    return json_response({"error": ""}, status=200)
Ejemplo n.º 2
0
    async def create_new_field(
            self,
            ctx: utils.Context,
            template: utils.Template,
            index: int,
            image_set: bool = False,
            prompt_for_creation: bool = True,
            delete_messages: bool = False) -> typing.Optional[utils.Field]:
        """Talk a user through creating a new field for their template"""

        # Here are some things we can use later
        message_check = lambda m: m.author == ctx.author and m.channel == ctx.channel
        okay_reaction_check = lambda r, u: str(
            r.emoji) in prompt_emoji and u.id == ctx.author.id
        prompt_emoji = [self.TICK_EMOJI, self.CROSS_EMOJI]
        messages_to_delete = []

        # Ask if they want a new field
        if prompt_for_creation:
            field_message = await ctx.send(
                "Do you want to make a new field for your profile?",
                embed=template.build_embed())
            messages_to_delete.append(field_message)
            for e in prompt_emoji:
                try:
                    await field_message.add_reaction(e)
                except discord.Forbidden:
                    try:
                        await field_message.delete()
                    except discord.NotFound:
                        pass
                    await ctx.send(
                        "I tried to add a reaction to my message, but I was unable to. Please update my permissions for this channel and try again."
                    )
                    return None

            # Here's us waiting for the "do you want to make a new field" reaction
            try:
                reaction, _ = await self.bot.wait_for(
                    'reaction_add', check=okay_reaction_check, timeout=120)
            except asyncio.TimeoutError:
                try:
                    await ctx.send(
                        "Creating a new field has timed out. The profile is being created with the fields currently added."
                    )
                except (discord.Forbidden, discord.NotFound):
                    pass
                return None

            # See if they don't wanna continue
            if str(reaction.emoji) == self.CROSS_EMOJI:
                return None
            await field_message.edit(content=field_message.content, embed=None)

        # Get a name for the new field
        v = await ctx.send(
            "What name should this field have? This is the name shown on the embed, so it should be something like 'Name', 'Age', 'Gender', etc."
        )
        messages_to_delete.append(v)
        while True:
            try:
                field_name_message = await self.bot.wait_for(
                    'message', check=message_check, timeout=120)
                messages_to_delete.append(field_name_message)
            except asyncio.TimeoutError:
                try:
                    await ctx.send(
                        "Creating a new field has timed out. The profile is being created with the fields currently added."
                    )
                except (discord.Forbidden, discord.NotFound):
                    pass
                return None

            # Check if if name is too long
            if 256 >= len(field_name_message.content) >= 1:
                break
            else:
                v = await ctx.send(
                    "The maximum length of a field name is 256 characters. Please provide another name."
                )
                messages_to_delete.append(v)
        field_name = field_name_message.content

        # Get a prompt for the field
        v = await ctx.send(
            "What message should I send when I'm asking people to fill out this field? This should be a question or prompt, eg 'What is your name/age/gender/etc'."
        )
        messages_to_delete.append(v)
        while True:
            try:
                field_prompt_message = await self.bot.wait_for(
                    'message', check=message_check, timeout=120)
                messages_to_delete.append(field_prompt_message)
            except asyncio.TimeoutError:
                try:
                    await ctx.send(
                        "Creating a new field has timed out. The profile is being created with the fields currently added."
                    )
                except (discord.Forbidden, discord.NotFound):
                    pass
                return None

            if len(field_prompt_message.content) >= 1:
                break
            else:
                v = await ctx.send(
                    "You need to actually give text for the prompt :/")
                messages_to_delete.append(v)
        field_prompt = field_prompt_message.content
        prompt_is_command = bool(
            utils.CommandProcessor.COMMAND_REGEX.search(field_prompt))

        # If it's a command, then we don't need to deal with this
        if not prompt_is_command:

            # Get field optional
            prompt_message = await ctx.send("Is this field optional?")
            messages_to_delete.append(prompt_message)
            for e in prompt_emoji:
                await prompt_message.add_reaction(e)
            try:
                field_optional_reaction, _ = await self.bot.wait_for(
                    'reaction_add', check=okay_reaction_check, timeout=120)
                field_optional_emoji = str(field_optional_reaction.emoji)
            except asyncio.TimeoutError:
                field_optional_emoji = self.CROSS_EMOJI
            field_optional = field_optional_emoji == self.TICK_EMOJI

            # Get timeout
            v = await ctx.send(
                "How many seconds should I wait for people to fill out this field (I recommend 120 - that's 2 minutes)? The minimum is 30, and the maximum is 600."
            )
            messages_to_delete.append(v)
            while True:
                try:
                    field_timeout_message = await self.bot.wait_for(
                        'message', check=message_check, timeout=120)
                    messages_to_delete.append(field_timeout_message)
                except asyncio.TimeoutError:
                    await ctx.send(
                        "Creating a new field has timed out. The profile is being created with the fields currently added."
                    )
                    return None
                try:
                    timeout = int(field_timeout_message.content)
                    if timeout < 30:
                        raise ValueError()
                    break
                except ValueError:
                    v = await ctx.send(
                        "I couldn't convert your message into a valid number - the minimum is 30 seconds. Please try again."
                    )
                    messages_to_delete.append(v)
            field_timeout = min([timeout, 600])

            # Ask for field type
            if image_set:
                text = f"What type is this field? Will you be getting numbers ({self.NUMBERS_EMOJI}), or any text ({self.LETTERS_EMOJI})?"
            else:
                text = f"What type is this field? Will you be getting numbers ({self.NUMBERS_EMOJI}), any text ({self.LETTERS_EMOJI}), or an image ({self.PICTURE_EMOJI})?"
            field_type_message = await ctx.send(text)
            messages_to_delete.append(field_type_message)

            # Add reactions
            await field_type_message.add_reaction(self.NUMBERS_EMOJI)
            await field_type_message.add_reaction(self.LETTERS_EMOJI)
            if not image_set:
                await field_type_message.add_reaction(self.PICTURE_EMOJI)

            # See what they said
            field_type_emoji = [
                self.NUMBERS_EMOJI, self.LETTERS_EMOJI, self.PICTURE_EMOJI
            ]  # self.TICK_EMOJI
            field_type_check = lambda r, u: str(
                r.emoji) in field_type_emoji and u == ctx.author
            try:
                reaction, _ = await self.bot.wait_for('reaction_add',
                                                      check=field_type_check,
                                                      timeout=120)
                emoji = str(reaction.emoji)
            except asyncio.TimeoutError:
                try:
                    await ctx.send(
                        "Picking a field type has timed out - defaulting to text."
                    )
                except (discord.Forbidden, discord.NotFound):
                    pass
                emoji = self.LETTERS_EMOJI

            # Change that emoji into a datatype
            field_type = {
                self.NUMBERS_EMOJI: utils.NumberField,
                self.LETTERS_EMOJI: utils.TextField,
                self.PICTURE_EMOJI: utils.ImageField,
            }[emoji]
            if isinstance(field_type, utils.ImageField) and image_set:
                raise Exception(
                    "You shouldn't be able to set two image fields.")

        # Set some defaults for the field stuff
        else:
            field_optional = False
            field_timeout = 15
            field_type = utils.TextField

        # Make the field object
        field = utils.Field(
            field_id=uuid.uuid4(),
            name=field_name,
            index=index,
            prompt=field_prompt,
            timeout=field_timeout,
            field_type=field_type,
            template_id=template.template_id,
            optional=field_optional,
            deleted=False,
        )

        # See if we need to delete things
        if delete_messages:
            await ctx.channel.purge(
                check=lambda m: m.id in [i.id for i in messages_to_delete],
                bulk=ctx.channel.permissions_for(ctx.guild.me).manage_messages)

        # And we done
        return field
Ejemplo n.º 3
0
    async def create_new_field(self,
                               ctx: utils.Context,
                               profile_id: uuid.UUID,
                               index: int,
                               image_set: bool = False) -> utils.Field:
        """Lets a user create a new field in their profile"""

        # Ask if they want a new field
        field_message = await ctx.send(
            "Do you want to make a new field for your profile?")
        try:
            await field_message.add_reaction(self.TICK_EMOJI)
            await field_message.add_reaction(self.CROSS_EMOJI)
        except discord.Forbidden:
            try:
                await field_message.delete()
            except discord.NotFound:
                pass
            await ctx.send(
                "I tried to add a reaction to my message, but I was unable to. Please update my permissions for this channel and try again."
            )
            return None

        # Here are some checks we can use later
        message_check = lambda m: m.author == ctx.author and m.channel == ctx.channel
        okay_reaction_check = lambda r, u: str(r.emoji) in [
            self.TICK_EMOJI, self.CROSS_EMOJI
        ] and u.id == ctx.author.id

        # Here's us waiting for the "do you want to make a new field" reaction
        try:
            reaction, _ = await self.bot.wait_for('reaction_add',
                                                  check=okay_reaction_check,
                                                  timeout=120)
        except asyncio.TimeoutError:
            try:
                await ctx.send(
                    "Creating a new field has timed out. The profile is being created with the fields currently added."
                )
            except (discord.Forbidden, discord.NotFound):
                pass
            return None

        # See if they don't wanna continue
        if str(reaction.emoji) == self.CROSS_EMOJI:
            return None

        # Get a name for the new field
        await ctx.send(
            "What name should this field have (eg: `Name`, `Age`, `Image`, etc - this is what shows on the embed)?"
        )
        while True:
            try:
                field_name_message = await self.bot.wait_for(
                    'message', check=message_check, timeout=120)
            except asyncio.TimeoutError:
                try:
                    await ctx.send(
                        "Creating a new field has timed out. The profile is being created with the fields currently added."
                    )
                except (discord.Forbidden, discord.NotFound):
                    pass
                return None

            # Check if if name is too long
            if 256 >= len(field_name_message.content) >= 1:
                break
            else:
                await ctx.send(
                    "The maximum length of a field name is 256 characters. Please provide another name."
                )
        field_name = field_name_message.content

        # Get a prompt for the field
        await ctx.send(
            f"What message should I send when I'm asking people to fill out this field (eg: `What is your {field_name.lower()}?`)?"
        )
        while True:
            try:
                field_prompt_message = await self.bot.wait_for(
                    'message', check=message_check, timeout=120)
            except asyncio.TimeoutError:
                try:
                    await ctx.send(
                        "Creating a new field has timed out. The profile is being created with the fields currently added."
                    )
                except (discord.Forbidden, discord.NotFound):
                    pass
                return None

            if len(field_prompt_message.content) >= 1:
                break
            else:
                await ctx.send(
                    "You need to actually give text for the prompt :/")
        field_prompt = field_prompt_message.content

        # Get field optional
        prompt_message = await ctx.send(f"Is this field optional?")
        await prompt_message.add_reaction(self.TICK_EMOJI)
        await prompt_message.add_reaction(self.CROSS_EMOJI)
        try:
            field_optional_reaction, _ = await self.bot.wait_for(
                'reaction_add', check=okay_reaction_check, timeout=120)
            field_optional_emoji = str(field_optional_reaction.emoji)
        except asyncio.TimeoutError:
            field_optional_emoji = self.CROSS_EMOJI
        field_optional = field_optional_emoji == self.TICK_EMOJI

        # Get timeout
        await ctx.send(
            "How many seconds should I wait for people to fill out this field (I recommend 120 - that's 2 minutes)? The minimum is 30, and the maximum is 600."
        )
        while True:
            try:
                field_timeout_message = await self.bot.wait_for(
                    'message', check=message_check, timeout=120)
            except asyncio.TimeoutError:
                await ctx.send(
                    "Creating a new field has timed out. The profile is being created with the fields currently added."
                )
                return None
            try:
                timeout = int(field_timeout_message.content)
                if timeout < 30:
                    raise ValueError()
                break
            except ValueError:
                await ctx.send(
                    "I couldn't convert your message into a number - the minimum is 30 seconds. Please try again."
                )
        field_timeout = min([timeout, 600])

        # Ask for field type
        if image_set:
            text = f"What TYPE is this field? Will you be getting numbers ({self.NUMBERS_EMOJI}), or text ({self.LETTERS_EMOJI})?"
        else:
            text = f"What TYPE is this field? Will you be getting numbers ({self.NUMBERS_EMOJI}), text ({self.LETTERS_EMOJI}), or an image ({self.PICTURE_EMOJI})?"
        field_type_message = await ctx.send(text)

        # Add reactions
        await field_type_message.add_reaction(self.NUMBERS_EMOJI)
        await field_type_message.add_reaction(self.LETTERS_EMOJI)
        if not image_set:
            await field_type_message.add_reaction(self.PICTURE_EMOJI)

        # See what they said
        valid_emoji = [
            self.NUMBERS_EMOJI, self.LETTERS_EMOJI, self.PICTURE_EMOJI
        ]  # self.TICK_EMOJI
        field_type_check = lambda r, u: str(
            r.emoji) in valid_emoji and u == ctx.author
        try:
            reaction, _ = await self.bot.wait_for('reaction_add',
                                                  check=field_type_check,
                                                  timeout=120)
            emoji = str(reaction.emoji)
        except asyncio.TimeoutError:
            try:
                await ctx.send(
                    "Picking a field type has timed out - defaulting to text.")
            except (discord.Forbidden, discord.NotFound):
                pass
            emoji = self.LETTERS_EMOJI

        # Change that emoji into a datatype
        field_type = {
            self.NUMBERS_EMOJI:
            utils.NumberField,
            self.LETTERS_EMOJI:
            utils.TextField,
            # self.TICK_EMOJI: utils.BooleanField,  # TODO
            self.PICTURE_EMOJI:
            utils.ImageField,
        }.get(emoji, Exception("Shouldn't be reached."))()
        if isinstance(field_type, utils.ImageField) and image_set:
            raise Exception("You shouldn't be able to set two image fields.")

        # Make the field object
        return utils.Field(
            field_id=uuid.uuid4(),
            name=field_name,
            index=index,
            prompt=field_prompt,
            timeout=field_timeout,
            field_type=field_type,
            profile_id=profile_id,
            optional=field_optional,
            deleted=False,
        )
Ejemplo n.º 4
0
    async def create_new_field(
            self,
            ctx: utils.Context,
            template: localutils.Template,
            index: int,
            image_set: bool = False,
            prompt_for_creation: bool = True,
            delete_messages: bool = False
    ) -> typing.Optional[localutils.Field]:
        """
        Talk a user through creating a new field for their template.
        """

        # Here are some things we can use later
        def message_check(message):
            return all([
                message.author.id == ctx.author.id,
                message.channel.id == ctx.channel.id,
            ])

        messages_to_delete = []

        # Ask if they want a new field
        if prompt_for_creation:
            field_message = await ctx.send(
                "Do you want to make a new field for your profile?",
                embed=template.build_embed(self.bot),
                components=utils.MessageComponents.boolean_buttons(),
            )
            messages_to_delete.append(field_message)

            # Here's us waiting for the "do you want to make a new field" reaction
            try:
                payload = await field_message.wait_for_button_click(
                    check=lambda p: p.user.id == ctx.author.id, timeout=120)
                await payload.ack()
            except asyncio.TimeoutError:
                try:
                    await ctx.send(
                        "Creating a new field has timed out. The profile is being created with the fields currently added."
                    )
                except (discord.Forbidden, discord.NotFound):
                    pass
                return None

            # See if they don't wanna continue
            if payload.component.custom_id == "NO":
                return None
            await field_message.edit(content=field_message.content, embed=None)

        # Get a name for the new field
        field_name = await self.get_field_name(ctx, messages_to_delete)
        if field_name is None:
            return

        # Get a prompt for the field
        v = await ctx.send((
            "What message should I send when I'm asking people to fill out this field? "
            "This should be a question or prompt, eg 'What is your name/age/gender/etc'."
        ))
        messages_to_delete.append(v)
        while True:
            try:
                field_prompt_message = await self.bot.wait_for(
                    'message', check=message_check, timeout=120)
                messages_to_delete.append(field_prompt_message)
            except asyncio.TimeoutError:
                try:
                    await ctx.send(
                        "Creating a new field has timed out. The profile is being "
                        "created with the fields currently added.")
                except (discord.Forbidden, discord.NotFound):
                    pass
                return None

            if len(field_prompt_message.content) >= 1:
                break
            else:
                v = await ctx.send(
                    "You need to actually give text for the prompt :/")
                messages_to_delete.append(v)
        field_prompt = field_prompt_message.content
        prompt_is_command = bool(
            localutils.CommandProcessor.COMMAND_REGEX.search(field_prompt))

        # If it's a command, then we don't need to deal with this
        if not prompt_is_command:

            # Get field optional
            prompt_message = await ctx.send(
                "Is this field optional?",
                components=utils.MessageComponents.boolean_buttons(),
            )
            messages_to_delete.append(prompt_message)
            try:
                payload = await prompt_message.wait_for_button_click(
                    check=lambda p: p.user.id == ctx.author.id, timeout=120)
                await payload.ack()
                field_optional_emoji = payload.component.custom_id
            except asyncio.TimeoutError:
                field_optional_emoji = "NO"
            field_optional = field_optional_emoji == "YES"
            self.bot.loop.create_task(
                payload.message.edit(components=utils.MessageComponents.
                                     boolean_buttons().disable_components(), ))

            # Get timeout
            v = await ctx.send((
                "How many seconds should I wait for people to fill out this field (I recommend 120 - "
                "that's 2 minutes)? The minimum is 30, and the maximum is 600."
            ))
            messages_to_delete.append(v)
            while True:
                try:
                    field_timeout_message = await self.bot.wait_for(
                        'message', check=message_check, timeout=120)
                    messages_to_delete.append(field_timeout_message)
                except asyncio.TimeoutError:
                    await ctx.send(
                        "Creating a new field has timed out. The profile is being created with the fields currently added."
                    )
                    return None
                try:
                    timeout = int(field_timeout_message.content)
                    if timeout < 30:
                        raise ValueError()
                    break
                except ValueError:
                    v = await ctx.send(
                        "I couldn't convert your message into a valid number - the minimum is 30 seconds. Please try again."
                    )
                    messages_to_delete.append(v)
            field_timeout = min([timeout, 600])

            # Ask for a field type
            action_row = utils.ActionRow()
            action_row.add_component(utils.Button("Text", custom_id="TEXT"))
            action_row.add_component(
                utils.Button("Numbers", custom_id="NUMBERS"))
            if not image_set:
                action_row.add_component(
                    utils.Button("Image", custom_id="IMAGE"))
            components = utils.MessageComponents(action_row)
            field_type_message = await ctx.send("What type is this field?",
                                                components=components)
            messages_to_delete.append(field_type_message)

            # See what they said
            try:
                payload = await field_type_message.wait_for_button_click(
                    check=lambda p: p.user.id == ctx.author.id, timeout=120)
                await payload.ack()
                key = payload.component.custom_id
            except asyncio.TimeoutError:
                try:
                    await ctx.send(
                        "Picking a field type has timed out - defaulting to text."
                    )
                except (discord.Forbidden, discord.NotFound):
                    pass
                key = "TEXT"

            # Change that emoji into a datatype
            field_type = {
                "NUMBERS": localutils.NumberField,
                "TEXT": localutils.TextField,
                "IMAGE": localutils.ImageField,
            }[key]
            if isinstance(field_type, localutils.ImageField) and image_set:
                raise Exception(
                    "You shouldn't be able to set two image fields.")

        # Set some defaults for the field stuff
        else:
            field_optional = False
            field_timeout = 15
            field_type = localutils.TextField

        # Make the field object
        field = localutils.Field(
            field_id=uuid.uuid4(),
            name=field_name,
            index=index,
            prompt=field_prompt,
            timeout=field_timeout,
            field_type=field_type,
            template_id=template.template_id,
            optional=field_optional,
            deleted=False,
        )

        # See if we need to delete things
        if delete_messages:
            self.purge_message_list(ctx.channel, messages_to_delete)

        # And we done
        return field
Ejemplo n.º 5
0
async def update_template_field(request: Request):
    """
    The route that handles the updating of the template metadata.
    """

    # Make sure the user is logged in
    session = await aiohttp_session.get_session(request)
    if not session.get("user_id"):
        return json_response({"error": "Not logged in."}, status=401)

    # Try and read the POST data from the user
    required_fields = {"template_id", "name", "prompt", "timeout", "type", "optional"}
    field_converters = {"timeout": int, "optional": bool}
    try:
        data = await request.json()
        data = {i: o.strip() or None for i, o in data.items()}
        for f in required_fields:
            data.setdefault(f, None)
            if f in field_converters:
                data[f] = field_converters[f](data[f])
    except ValueError:
        return json_response({"error": "Couldn't validate the given POST data."}, status=400)
    except Exception:
        return json_response({"error": "Could not read POST data."}, status=400)

    # Now we want to check things with the database
    async with request.app['database']() as db:

        # Make sure the template exists
        field = None
        if data.get('field_id'):
            field_rows = await db("""SELECT * FROM field WHERE field_id=$1""", data['field_id'])
            if not field_rows:
                return json_response({"error": "Field does not exist."}, status=400)
            field = localutils.Field(**field_rows[0])
        template = await localutils.Template.fetch_template_by_id(db, field.template_id if field else data['template_id'], fetch_fields=False)
        if not template:
            return json_response({"error": "Template does not exist."}, status=400)

        # Make sure the user is editing a template that they have permission to edit
        try:
            guild = await request.app['bots']['bot'].fetch_guild(template.guild_id)
        except discord.HTTPException:
            return json_response({"error": "Bot not in guild."}, status=401)
        try:
            member = await guild.fetch_member(session.get("user_id"))
        except discord.HTTPException:
            return json_response({"error": "User not in guild."}, status=401)
        if not member.guild_permissions.manage_guild:
            return json_response({"error": "Member cannot manage guild."}, status=401)

        # Update the template
        if field:
            updated_rows = await db(
                """UPDATE field SET name=$2, prompt=$3, timeout=$4, field_type=$5,
                optional=$6 WHERE field_id=$1 RETURNING *""",
                data['field_id'], data['name'], data['prompt'], data['timeout'],
                data['type'], data['optional'],
            )
        else:
            updated_rows = await db(
                """INSERT INTO field (field_id, name, prompt, timeout, field_type, optional,
                template_id, index) VALUES (GEN_RANDOM_UUID(), $1, $2, $3, $4, $5, $6,
                COALESCE((SELECT MAX(index) FROM field WHERE template_id=$6) + 1, 0))
                RETURNING *""",
                data['name'], data['prompt'], data['timeout'],
                data['type'], data['optional'], template.template_id,
            )

    # Return
    ret_data = dict(updated_rows[0])
    ret_data['field_id'] = str(ret_data['field_id'])
    ret_data['template_id'] = str(ret_data['template_id'])
    return json_response({"error": "", "data": ret_data}, status=200)