ms7m / dispike Goto Github PK
View Code? Open in Web Editor NEWAn independent, simple to use, powerful framework for creating interaction-based Discord bots. Powered by FastAPI
Home Page: https://dispike.ms7m.me/
License: MIT License
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
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=[...]
)
]
)
]
)
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.
Assistance in translating and improving documentation no matter how small is always appreciated!
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!
Some modules/classes are located in odd places. This is just a tracker to document changes to import locations.
dispike.register
to dispike.creating
.allow_mentions
file from dispike.models
to dispike.creating
components
from dispike.helper
to dispike.creating
dispike.models
to dispike.incoming
incoming
in dispike.incoming
to incoming_interactions
Application commands are commands that an application can register to Discord. They provide users a first-class way of interacting directly with your application that feels deeply integrated into Discord.
Relevant Documentation:
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
),
)
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)
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.
Developers can now set min_value
and max_value
constraints on numeric slash command option types (INTEGER
and NUMBER
).
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"
)
)
)
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?
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
I believe background tasks were sloppily put together, this issue is just keep me on track to make sure this is not lost.
What The command I want to add to my bot goes somewhat like this:
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?
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
}
Add support for Deferred messages.
You can now restrict CHANNEL
slash command options to specific channel types.
You can now attach files to ephemeral messages. There's also a new ephemeral
boolean property on attachment objects.
Can I use this lib with working django website that runs with nginx and gunicorn?
Workaround: add an optional argument. Should be fixed in the next update.
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
]
This library doesn't offer any handling for rate limiting and raises an DiscordAPIError
.
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.
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(...))
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(...)
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. Settingdefault_permission
tofalse
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
.
Slash commands now support a NUMBER
option type (10). This type lets you accept floating-point numbers as input.
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.
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)
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,
)
],
)
],
),
/options authenticated_role
'data': {
'id': '',
'name': 'options',
'options': [{
'name': 'authenticated_role',
'type': 1
}
],
'type': 1
}
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.
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)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.