Skip to content

Commit

Permalink
Update for new API rollout
Browse files Browse the repository at this point in the history
Optimise authorization header definition.

Add uploading attachments (and regular content/embeds) through multipart/form-data requests.

Add automatic rate limit handling.

Remove deprecated interaction response types.

Raise exception when trying to delete an ephemeral response.

Add optional content to followup messages.

Add ephemeral option to followup messages.
  • Loading branch information
viral32111 committed Mar 25, 2021
1 parent ab0394f commit 6221255
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 24 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
testing/
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
description = "A wrapper for the Discord Slash Commands API, to be used with discord.py.",
keywords = "discord slashcommands api wrapper library module development viral32111",

version = "1.0.0",
version = "1.1.0",
license = "AGPL-3.0-only",
url = "https://github.com/viral32111/slashcommands",

Expand Down
70 changes: 47 additions & 23 deletions slashcommands/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import asyncio, functools, enum
import asyncio, functools, enum, json
import requests, deepdiff

_API_BASE_URL = "https://discord.com/api/v8/"

# discord.com/developers/docs/interactions/slash-commands#interaction-response-interactionresponsetype
_INTERACTION_RESPONSE_MESSAGE_HIDDEN = 3 # DEPRECATED (will be replaced once command source and response are one single message)
_INTERACTION_RESPONSE_MESSAGE = 4
_INTERACTION_RESPONSE_DEFER = 5

Expand All @@ -16,17 +15,30 @@
_eventLoop = None
_allowedMentions = None

async def _request( endpoint, method = "GET", data = None ):
async def _request( endpoint, method = "GET", data = None, files = None ):
global _applicationToken, _eventLoop

if data:
response = await _eventLoop.run_in_executor( None, functools.partial( requests.request, method, _API_BASE_URL + endpoint, json = data, headers = {
"Authorization": "Bot " + _applicationToken
} ) )
else:
response = await _eventLoop.run_in_executor( None, functools.partial( requests.request, method, _API_BASE_URL + endpoint, headers = {
"Authorization": "Bot " + _applicationToken
} ) )
authorizationHeader = { "Authorization": "Bot " + _applicationToken }

responseCode = 429
while responseCode == 429:
if files and data:
files = { f"file{ num }": ( file.filename, file.fp ) for num, file in enumerate( files ) }
files[ "payload_json" ] = ( None, json.dumps( data ), "application/json" )
response = await _eventLoop.run_in_executor( None, functools.partial( requests.request, method, _API_BASE_URL + endpoint, files = files, headers = authorizationHeader ) )
elif files and not data:
files = { f"file{ num }": ( file.filename, file.fp ) for num, file in enumerate( files ) }
response = await _eventLoop.run_in_executor( None, functools.partial( requests.request, method, _API_BASE_URL + endpoint, files = files, headers = authorizationHeader ) )
elif not files and data:
response = await _eventLoop.run_in_executor( None, functools.partial( requests.request, method, _API_BASE_URL + endpoint, json = data, headers = authorizationHeader ) )
else:
response = await _eventLoop.run_in_executor( None, functools.partial( requests.request, method, _API_BASE_URL + endpoint, headers = authorizationHeader ) )

responseCode = response.status_code

if responseCode == 429:
retryAfter = response.json()[ "retry_after" ] + 1 # add 1 second to be safe
await asyncio.sleep( retryAfter )

response.raise_for_status()

Expand Down Expand Up @@ -170,6 +182,10 @@ async def respond( self, *arguments, **optional ):
if self.__hasResponded:
raise Exception( "Cannot send another original interaction response, use interaction.followup() instead." )

# this will work once discord implements attachments in interation responses
if optional.get( "files", None ):
raise Exception( "Cannot send files in original interaction response (yet), only available for interaction.followup()." )

discordEmbeds = optional.get( "embeds", None )
jsonDiscordEmbeds = [ embed.to_dict() for embed in discordEmbeds ] if discordEmbeds else None

Expand All @@ -182,19 +198,19 @@ async def respond( self, *arguments, **optional ):
jsonAllowedMentions = None

await _request( "interactions/" + str( self.id ) + "/" + self.__token + "/callback", method = "POST", data = {
"type": ( _INTERACTION_RESPONSE_MESSAGE_HIDDEN if optional.get( "hidden", False ) else _INTERACTION_RESPONSE_MESSAGE ),
"type": _INTERACTION_RESPONSE_MESSAGE,
"data": {
"tts": optional.get( "tts", False ),
"content": arguments[ 0 ] if len( arguments ) > 0 else None,
"embeds": jsonDiscordEmbeds,
"allowed_mentions": jsonAllowedMentions,
"flags": ( 64 if optional.get( "hidden", False ) else 0 )
}
} )
}, files = optional.get( "files", None ) )

self.__hasResponded = True

return interaction.original( self.__token )
return interaction.original( self.__token, optional.get( "hidden", False ) )

async def think( self, **optional ):
if self.__hasDeferred:
Expand All @@ -203,14 +219,13 @@ async def think( self, **optional ):
response = await _request( "interactions/" + str( self.id ) + "/" + self.__token + "/callback", method = "POST", data = {
"type": _INTERACTION_RESPONSE_DEFER,
"data": {
"content": "Thinking...", # I think this is needed until the new API changes are rolled out to every client
"flags": ( 64 if optional.get( "hidden", False ) else 0 )
}
} )

self.__hasDeferred = True

return interaction.original( self.__token )
return interaction.original( self.__token, optional.get( "hidden", False ) )

class data:
def __init__( self, data ):
Expand All @@ -235,8 +250,9 @@ def __init__( self, option ):
self.arguments = { option.name: option.value for option in self.options }

class original:
def __init__( self, token ):
def __init__( self, token, isHidden ):
self.__interactionToken = token
self.__isHidden = isHidden

async def edit( self, *arguments, **optional ):
discordEmbeds = optional.get( "embeds", None )
Expand All @@ -257,9 +273,12 @@ async def edit( self, *arguments, **optional ):
} )

async def delete( self ):
if self.__isHidden:
raise Exception( "Cannot delete an ephemeral response!" )

await _request( "webhooks/" + str( _applicationID ) + "/" + self.__interactionToken + "/messages/@original", method = "DELETE" )

async def followup( self, content, **optional ):
async def followup( self, *arguments, **optional ):
discordEmbeds = optional.get( "embeds", None )
jsonDiscordEmbeds = [ embed.to_dict() for embed in discordEmbeds ] if discordEmbeds else None

Expand All @@ -272,18 +291,20 @@ async def followup( self, content, **optional ):
jsonAllowedMentions = None

response = await _request( "webhooks/" + str( _applicationID ) + "/" + self.__interactionToken, method = "POST", data = {
"content": content,
"content": arguments[ 0 ] if len( arguments ) > 0 else None,
"embeds": jsonDiscordEmbeds,
"allowed_mentions": jsonAllowedMentions
} )
"allowed_mentions": jsonAllowedMentions,
"flags": ( 64 if optional.get( "hidden", False ) else 0 )
}, files = optional.get( "files", None ) )

# in the future, create a msg class for all the data returned in the response
return interaction.followup( self.__interactionToken, int( response[ "id" ] ) )
return interaction.followup( self.__interactionToken, int( response[ "id" ] ), optional.get( "hidden", False ) )

class followup:
def __init__( self, token, id ):
def __init__( self, token, id, isHidden ):
self.__interactionToken = token
self.__messageID = id
self.__isHidden = isHidden

async def edit( self, *arguments, **optional ):
discordEmbeds = optional.get( "embeds", None )
Expand All @@ -304,6 +325,9 @@ async def edit( self, *arguments, **optional ):
} )

async def delete( self ):
if self.__isHidden:
raise Exception( "Cannot delete an ephemeral response!" )

await _request( "webhooks/" + str( _applicationID ) + "/" + self.__interactionToken + "/messages/" + str( self.__messageID ), method = "DELETE" )

async def _ready( payload ):
Expand Down

0 comments on commit 6221255

Please sign in to comment.