GithubHelp home page GithubHelp logo

ms7m / dispike Goto Github PK

View Code? Open in Web Editor NEW
38.0 38.0 5.0 2.1 MB

An independent, simple to use, powerful framework for creating interaction-based Discord bots. Powered by FastAPI

Home Page: https://dispike.ms7m.me/

License: MIT License

Python 99.93% Makefile 0.07%
discord discord-slash-commands fastapi python

dispike's Introduction

dispike's People

Contributors

fossabot avatar mrshmllow avatar ms7m avatar squidtoon99 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

dispike's Issues

Possible Error in Event Callback Name Generation for Subcommands

My understanding is that Dispike generates callback names for commands, this process is not handled by Discord. Assuming this is in fact the case, this Issue describes what I feel to be unintuitive results from this process. When nesting a single level subcommand without the use of subcommand groups, the first argument name is included in the callback name.

The following example produces a callback name of lorem.ipsum.dolor.

However, the command usage is /lorem ipsum dolor:VALUE sit:VALUE amet:VALUE. Shouldn't the callback name be lorem.ipsum?

DiscordCommand(
    name="lorem",
    description="...",
    options=[
        CommandOption(
            name="ipsum",
            description="...",
            type=CommandTypes.SUB_COMMAND,
            options=[
                CommandOption(
                    name="dolor",
                    description="..."
                    type=CommandTypes.STRING,
                    required=True,
                    choices=[...]
                ),
                CommandOption(
                    name="sit",
                    description="..."
                    type=CommandTypes.STRING,
                    required=True,
                    choices=[...]
                )
                CommandOption(
                    name="amet",
                    description="..."
                    type=CommandTypes.STRING,
                    required=True,
                    choices=[...]
                )
            ]
        )    
    ]
)

Make bot appear online

Is it possible to make a bot always (when FastAPI server is working) appear online? Or is this the limitation of using webhook-based bots?

I created an example bot and it responds to commands corerectly, but it appears as offline in members list.

Documentation Assistance

Assistance in translating and improving documentation no matter how small is always appreciated!

Scaling Dispike to Large, Organized Projects - Questions and Concerns

I'd like to preface this discussion with thanks, Dispike is an impressive project and I look forward to seeing its continued growth.

As Dispike nears its first stable release, I've begun to scaffold my first project that will be based upon it. It's here that I've run into concerns regarding how a "large-scale" Dispike project should be organized; however, I'm unsure whether this is a matter of library design or lack of understanding on my end.

As it stands, an ideal Dispike bot (as provided by the example) is structured as follows:

bot.py

bot = Dispike(...)

@bot.interaction.on("...")
async def CommandA(...):
    return DiscordResponse(...)

@bot.interaction.on("...")
async def CommandB(...):
    return DiscordResponse(...)

@bot.interaction.on("...")
async def CommandC(...):
    return DiscordResponse(...)

This is a clean and simple approach to interfacing with the library, but it quickly becomes problematic at scale and I'm unsure how to resolve it. Because the bot instance is used in the decorator, using classes (and accessing self for global variables) is not an option(?) As you could imagine, this is less than ideal when you have several commands or commands with large amounts of logic.

Discord.py provides a solution to this as follows:

.../commandGroupA.py

from discord.ext.commands import command

class ExampleBot:
    @command(name="commandA")
    async def CommandA(...):
        await ctx.send(...)

    @command(name="commandB")
    async def CommandB(...):
        await ctx.send(...)

    @command(name="commandC")
    async def CommandC(...):
        await ctx.send(...)

By importing command and using it in the decorator, we do not need to access the bot object. This allows for commands (interaction) to be defined in various files across the project without having to avoid circular imports and classes.

An example of what this could look like for Dispike is as follows:

bot.py

bot = Dispike(...)

bot.run(...)

.../commandGroupA.py

from dispike import interaction

class CommandGroupA:
    @interaction.on("commandA", eventType=EventTypes.COMMAND)
    async def CommandA(...):
        return DiscordResponse(...)

    @interaction.on("commandB", eventType=EventTypes.COMMAND)
    async def CommandB(...):
        return DiscordResponse(...)

    @interaction.on("commandC", eventType=EventTypes.COMMAND)
    async def CommandC(...):
        return DiscordResponse(...)

.../.../commandGroupB.py

from dispike import interaction

class CommandGroupB:
    @interaction.on("commandD", eventType=EventTypes.COMMAND)
    async def CommandD(...):
        return DiscordResponse(...)

    @interaction.on("commandE", eventType=EventTypes.COMMAND)
    async def CommandE(...):
        return DiscordResponse(...)

    @interaction.on("commandF", eventType=EventTypes.COMMAND)
    async def CommandF(...):
        return DiscordResponse(...)

I'd appreciate any input you may have on this @ms7m! It's not a groundbreaking issue by any means, but I'm hopeful that you'll have a solution that I'm overlooking that may not even require changes to the library.

Perhaps the example bot could be updated to demonstrate the best practices?

Thanks!

Reorganizing Modules/Imports

Some modules/classes are located in odd places. This is just a tracker to document changes to import locations.


  • Renamed dispike.register to dispike.creating.
  • Moved allow_mentions file from dispike.models to dispike.creating
  • Moved components from dispike.helper to dispike.creating
  • Renamed dispike.models to dispike.incoming
  • Renamed incoming in dispike.incoming to incoming_interactions

DeferredResponse ignores ActionRow and Empherical

When attempting to send a DeferredResponse, certain components of the new_message DiscordResponse are ignored. In my experience, both action_row and empherical do not function.

Example:

@interactions.on("example")
async def ExampleCommand(ctx: IncomingDiscordInteraction) -> DeferredResponse:
    """Example command demonstrating DeferredResponse ignoring action_row and empherical."""

    await bot.send_deferred_message(
        original_context=ctx,
        new_message=DiscordResponse(
            content="This part works",
            action_row=ActionRow(
                components=[
                    LinkButton(
                        label="This does not work",
                        url="https://github.com/ms7m/dispike",
                    )
                ]
            ),
            empherical=True
        ),
    )

DeferredResponse (DiscordResponse) containing an Embed fails

First of all, thank you for the library - this is an impressive project that I look forward to see shape into a stable release.

Using the DeferredResponse that was introduced in #39 will succeed as expected unless an Embed has been added to the DiscordResponse that is used for the new_message. I have verified that this Embed succeeds when used with a normal DiscordResponse, it only fails when used with a DeferredResponse.

Error:

ERROR    | dispike.main:send_deferred_message:366 - Unable to send deferred message with error: {"code": 50035, "errors": {"embeds": {"0": {"_errors": [{"code": "MODEL_TYPE_CONVERT", "message": "Only dictionaries may be used in a ModelType"}]}}}, "message": "Invalid Form Body"}

Minimal example to reproduce this error:

@bot.interaction.on("test")
async def Test(ctx: IncomingDiscordInteraction, **kwargs) -> DeferredResponse:
    """Test command to demonstrate DeferredResponse embed failure."""

    response: DiscordResponse = DiscordResponse()
    embed: Embed = Embed()

    embed.add_field(name="Field", value="Test")

    response.add_new_embed(embed)

    await bot.send_deferred_message(original_context=ctx, new_message=response)

Add users to roles using slash commands

I would like to create a bot that creates some slash commands, for example /roles add ROLE and /roles leave ROLE, that allow users to be added/removed to/from certain roles when they use the command.

This probably isn't possible using just interactions, but if I understand Discord docs correctly, it should be possible if we use a bot user/token:

In many cases, you may still need a bot user. If you need to receive gateway events, or need to interact with other parts of our API (like fetching a guild, or a channel, or updating permissions on a user), those actions are all still tied to having a bot token.

DiscordResponse does not validate or throw error upon insufficient response

Attempting to send a DiscordResponse that does not contain content (or embeds?) but does contain an action_row is not a valid response. However, dispike will not throw an error of any kind when this interaction fails, the only indication that it was not successful is the Discord UI.

Example:

@interactions.on("example")
async def ExampleCommand(ctx: IncomingDiscordInteraction) -> DiscordResponse:
    """Example command demonstrating DiscordResponse quietly failing."""

    return DiscordResponse(
        action_row=ActionRow(
            components=LinkButton(
                label="Example", url="https://github.com/ms7m/dispike"
            )
        )
    )

Make a file full of common pytest fixtures for easy access.

# TODO: Make a file full of common pytest fixtures for easy access.
respx.post(
f"https://discord.com/api/v8/interactions/{_created_content._interaction_id}/{_created_content._interaction_token}/callback"
).mock(return_value=Response(status_code=200))
await _created_content.async_send_callback(sample_discord_response)


This issue was generated by todo based on a TODO comment in b66db50 when #11 was merged. cc @ms7m.

Is 3.6+ really a caveat?

Any production environment worth it's salt should use up to date packages, especially if it's public facing. 3.6 loses security support at the end of this year, to my bad understanding isn't it just a given that it doesn't support old versions?

DiscordResponse with Embed and ActionRow is invalid

Building a DiscordResponse with only an Embed and ActionRow is throwing a validation error saying it needs an embed or content field.

DiscordResponse(embeds=[embed], action_row=action_row)

This bit of validation seems to be the issue:

        if self._action_row:
            if self.content == None or self.embeds == [] or self.content == "":
                raise InvalidDiscordResponse(
                    "If creating a response with an action row, content or an embed must be present!"
                )

It should probably be:

if (self.content == None or self.content == "") and self.embeds == []:

A work around I've found seems to be setting content = " " in the DiscordResponse

Refactoring Background Tasks

I believe background tasks were sloppily put together, this issue is just keep me on track to make sure this is not lost.

Followup messages after delay

What The command I want to add to my bot goes somewhat like this:

  1. The bot receives the the request to do whatever it has to do (so far so good)
  2. It now checks something on my remote server and chooses whether it will do action A or B
  3. Once it has chosen I want it to inform the user on its choice.
  4. Do action A or B
  5. Send a message that the action is done

Of course there will be a delay between steps 3 and 5, until step 4 is done. If I use followup messages it just waits until step 4 is done and then sends all messages at the same time.
Here is a sample code where I use time.sleep for testing:

@bot.interaction.on("ping")
async def ping(ctx: IncomingDiscordInteraction) -> DiscordResponse:
    response = DiscordResponse(content="pong")
    loop = asyncio.get_event_loop()
    loop.create_task(follow(ctx))

    return response


async def follow(ctx: IncomingDiscordInteraction):
    time.sleep(2)
    followup = FollowUpMessages(bot=bot, interaction=ctx)
    responseF = DiscordResponse(follow_up_message=False)
    responseF.content = "pong follow up"
    await followup.async_create_follow_up_message(responseF)

I'm pretty sure I am doing something wrong.I am very unfamiliar with asyncio and event loops. Any help with that?

Validation error in IncomingDiscordSlashInteraction

I found a validation error when the slash command was sent using DM.

Below are the validation error, handler, and discord requests.

Validation Error

2021-11-04T07:05:55.953681+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.9/site-packages/dispike/server.py", line 146, in handle_interactions
2021-11-04T07:05:55.953681+00:00 app[web.1]:     _parse_to_object = IncomingDiscordSlashInteraction(**_get_request_body)
2021-11-04T07:05:55.953681+00:00 app[web.1]:   File "pydantic/main.py", line 406, in pydantic.main.BaseModel.__init__
2021-11-04T07:05:55.953681+00:00 app[web.1]: pydantic.error_wrappers.ValidationError: 2 validation errors for IncomingDiscordSlashInteraction
2021-11-04T07:05:55.953682+00:00 app[web.1]: guild_id
2021-11-04T07:05:55.953682+00:00 app[web.1]:   field required (type=value_error.missing)
2021-11-04T07:05:55.953682+00:00 app[web.1]: member
2021-11-04T07:05:55.953682+00:00 app[web.1]:   field required (type=value_error.missing)

Handler

@bot.on("verb")
async def handle_verb_request(ctx: IncomingDiscordSlashInteraction) -> DiscordResponse:

Discord Request

{
  "application_id": "<cleaned>",
  "channel_id": "<cleaned>",
  "data": {
    "id": "<cleaned>",
    "name": "verb",
    "type": 1
  },
  "id": "<cleaned>",
  "token": "<cleaned>",
  "type": 2,
  "user": {
    "avatar": "<cleaned>",
    "discriminator": "<cleaned>",
    "id": "<cleaned>",
    "public_flags": 0,
    "username": "<cleaned>"
  },
  "version": 1
}

Django support

Can I use this lib with working django website that runs with nginx and gunicorn?

Support for bulk update commands (Global/Guild)

Small improvement for slash commands has been released today: you can now bulk update commands on the global or guild level.

PUT /applications/{application.id}/commands
PUT /applications/{application.id}/guilds/{guild.id}/commands

[
  {
    "id": "12345678910", // id is optional, but required if you want to rename a command without its id changing
    "name": "command-name",
    "description": "command description",
    "options": [
      ...options
    ]
  },
  ...commands
]

Rate Limiting

This library doesn't offer any handling for rate limiting and raises an DiscordAPIError.

Responding to Interactions does not require a Bot Token

According to the Discord Slash Commands Documentation...

Slash Commands and Interactions bring something entirely new to the table: the ability to interact with an application without needing a bot user in the guild. As you read through this documentation, you'll see that bot tokens are only referenced as a helpful alternative to doing a client credentials auth flow. Slash Commands do not depend on a bot user in the guild; responding to interactions does not require a bot token.

In many cases, you may still need a bot user. If you need to receive gateway events, or need to interact with other parts of our API (like fetching a guild, or a channel, or updating permissions on a user), those actions are all still tied to having a bot token. However, if you don't need any of those things, you never have to add a bot user to your application at all.

It would seem that the bot_token argument for initializing a Dispike object should not be required. Dropping this requirement would mean one less step to beginning a project using Dispike and would keep things as efficient as possible.

Of course, it is possible that I'm underestimating the scope in which Dispike interacts with the API. In that case, this Issue is invalid.

Add add_new_action_row method to DiscordResponse

This is just a quality-of-life enhancement request.

DiscordResponse currently has an add_new_embed method that allows for a new Embed to be appended to the embeds property. Ideally, this API could be reimplemented for the ActionRow object and action_rows property.

Example

res: DiscordResponse = DiscordResponse(...)

res.add_new_action_row(ActionRow(...))

Callback Event Detection Fails for Subcommands Containing Hyphen

When a subcommand that contains a hyphen in its name is registered in an EventCollection, Dispike will receive the event and subsequently discard it regardless of whether or not the event is handled by an interactions.on() decorator.

Somewhat related to #56 and #46.

Logs

... | DEBUG    | dispike.server:handle_interactions:89 - incoming event name: lorem.ipsum-dolor
... | DEBUG    | dispike.server:handle_interactions:91 - discarding event not existing.
...\dispike\server.py:92: UserWarning: Event lorem.ipsum-dolor does not exist or does not have a callback.  warnings.warn(

Schema Example

DiscordCommand(
    name="lorem",
    description="..."
    options=[
        CommandOption(
            name="ipsum-dolor",
            description="...",
            type=CommandTypes.SUB_COMMAND,
            options=[
                CommandOption(
                    name="sit",
                    description="...",
                    type=CommandTypes.STRING,
                    required=True,
                    options=[...]
            ]
    ]

Handler Example

@interactions.on("lorem.ipsum-dolor")
async def LoremIpsumDolor(...) -> DiscordResponse:
    return DiscordResponse(...)

Support default_permission for DiscordCommand

You can also set a default_permission on your commands if you want them to be disabled by default when your app is added to a new guild. Setting default_permission to false will disallow anyone in a guild from using the command--even Administrators and guild owners--unless a specific overwrite is configured. It will also disable the command from being usable in DMs.

It would appear that this boolean value should be set on the DiscordCommand model after options.

Validation error in SubcommandIncomingDiscordOptionList

I think the options field in SubcommandIncomingDiscordOptionList needs be tagged as Optional.

I am unsure if this will break any existing features. The only reference to that class I could find was in determine_event_information which currently supports the field being optional.

Below is the validation error, command schema, and discord request.

Validation error
  File "/.venv/lib/python3.8/site-packages/dispike/server.py", line 146, in handle_interactions
	_parse_to_object = IncomingDiscordSlashInteraction(**_get_request_body)
                       │                                 └ {'application_id': '<cleaned>', 'channel_id': '<cleaned>', 'data': {'id': '<cleaned>', 'name': 'op...
                       └ <class 'dispike.incoming.incoming_interactions.IncomingDiscordSlashInteraction'>

  File "pydantic/main.py", line 406, in pydantic.main.BaseModel.__init__

pydantic.error_wrappers.ValidationError: 2 validation errors for IncomingDiscordSlashInteraction
data -> options -> 0 -> value
  field required (type=value_error.missing)
data -> options -> 0 -> options
  field required (type=value_error.missing)
Command schema
DiscordCommand(
	name="options",
	description=(
		"Various options for the server owner to play with. "
		"These apply to **your** server only."
	),
	options=[
		CommandOption(
			name="authenticated_role",
			description="The role to give to authenticated users.",
			type=OptionTypes.SUB_COMMAND,
			options=[
				CommandOption(
					name="set",
					description="Value to change this option to.",
					type=OptionTypes.STRING,
					required=False,
				)
			],
		)
	],
),
Discord command and request

/options authenticated_role

'data': {
	'id': '',
	'name': 'options',
	'options': [{
			'name': 'authenticated_role',
			'type': 1
		}
	],
	'type': 1
}

Depreciate show_user_input on DiscordResponse

The show_user_input argument of the DiscordResponse class is no longer accepted by the Interactions API, it is now strictly set to true regardless of what the request specifies. This results in a warning being logged during normal Dispike operation, however, it should be noted that this is not a breaking change to the API as the library remains functional outside of the fact that user input can no longer be hidden.

2021-06-05 19:00:31.180 | WARNING  | dispike.response:__init__:66 - show_user_input is longer supported by discord.

Using commands without having an Avatar throws an Error

If an User without an Avatar tries to use a /command it throws an error because the avatar is None.
The error Message is:

Project_Path\venv\lib\site-packages\dispike\server.py", line 42, in handle_interactions
    _parse_to_object = IncomingDiscordInteraction(**_get_request_body)
  File "pydantic\main.py", line 391, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for IncomingDiscordInteraction
member -> user -> avatar
  none is not an allowed value (type=type_error.none.not_allowed)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.