Skip to content
This repository was archived by the owner on Mar 8, 2022. It is now read-only.

Commit bcb9da3

Browse files
committed
merged v5 into main
1 parent c82a797 commit bcb9da3

File tree

15 files changed

+576
-316
lines changed

15 files changed

+576
-316
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,4 @@ dmypy.json
133133
.vs/
134134

135135
internal/
136-
test.py
136+
test*.py

README.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
## Introduction
2121

22+
2223
This is a [discord.py](https://github.com/Rapptz/discord.py) ui extension made by [404kuso](https://github.com/404kuso) and [RedstoneZockt](https://github.com/RedstoneZockt)
2324
for using discord's newest ui features like buttons, slash commands and context commands (we got dpy2 supported if you want to keep using our libary)
2425

@@ -28,7 +29,6 @@ for using discord's newest ui features like buttons, slash commands and context
2829

2930

3031
### Windows
31-
3232
```cmd
3333
py -m pip install discord-ui
3434
```
@@ -175,6 +175,30 @@ You can find more (and better) examples [here](https://github.com/discord-py-ui/
175175

176176

177177
# Changelog
178+
179+
- <details>
180+
<summary>5.0.0</summary>
181+
182+
## **Fixed**
183+
- Roles not being parsed correctly
184+
185+
## **Changed**
186+
- default_permission
187+
> default_permission can now be of type `discord.Permissions` but the api doesn't support that yet.
188+
- slash http
189+
> some code changes to slash-http features
190+
191+
## **Added**
192+
- ChoiceGeneratorContext
193+
> Context class for choice generation
194+
- SlashOption
195+
> `choice_generator` keyword and `autocomplete` keyword.
196+
> `autocomplete` is not needed if you pass choice_generator
197+
- File sending
198+
> You are now able to send hidden files
199+
200+
</details>
201+
178202
- <details>
179203
<summary>4.3.8</summary>
180204

discord_ui/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
from .slash.tools import ParseMethod
3838
from .tools import components_to_dict
3939
from .slash.tools import create_choice
40-
from .receive import Interaction, InteractionType, Message, WebhookMessage, PressedButton, SelectedMenu, ComponentContext, SlashedCommand, SlashedSubCommand, EphemeralMessage, EphemeralResponseMessage
40+
from .receive import Interaction, InteractionType, Message, WebhookMessage, PressedButton, SelectedMenu, ComponentContext, SlashedCommand, SlashedSubCommand, EphemeralMessage, EphemeralResponseMessage, ChoiceGeneratorContext
4141
from .slash import ext
4242
from .listener import Listener
4343

@@ -46,4 +46,7 @@
4646

4747

4848
__title__ = "discord-ui"
49-
__version__ = "4.3.8"
49+
__version__ = "5.0.0a"
50+
51+
if __version__.endswith("a"):
52+
print("Warning: This version is a preview version and can contain some issues!")

discord_ui/client.py

Lines changed: 133 additions & 99 deletions
Large diffs are not rendered by default.

discord_ui/cogs.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -351,8 +351,9 @@ def slash_cog(name=None, description=None, options=[], guild_ids=None, default_p
351351
Choices for string and int types for the user to pick from; default MISSING
352352
guild_ids: List[:class:`str` | :class:`int`], optional
353353
A list of guild ids where the command is available; default MISSING
354-
default_permission: :class:`bool`, optional
355-
Whether the command can be used by everyone or not
354+
default_permission: :class:`bool` | :class:`discord.Permissions`, optional
355+
Permissions that a user needs to have in order to execute the command, default ``True``.
356+
If a bool was passed, it will indicate whether all users can use the command (``True``) or not (``False``)
356357
guild_permissions: Dict[``guild_id``: :class:`~SlashPermission`]
357358
The permissions for the command in guilds
358359
Format: ``{"guild_id": SlashPermission}``
@@ -417,8 +418,9 @@ def subslash_cog(base_names, name=None, description=None, options=[], guild_ids=
417418
Choices for string and int types for the user to pick from; default MISSING
418419
guild_ids: List[:class:`str` | :class:`int`], optional
419420
A list of guild ids where the command is available; default MISSING
420-
default_permission: :class:`bool`, optional
421-
Whether the command can be used by everyone or not
421+
default_permission: :class:`bool` | :class:`discord.Permissions`, optional
422+
Permissions that a user needs to have in order to execute the command, default ``True``.
423+
If a bool was passed, it will indicate whether all users can use the command (``True``) or not (``False``)
422424
guild_permissions: Dict[``guild_id``: :class:`~SlashPermission`]
423425
The permissions for the command in guilds
424426
Format: ``{"guild_id": SlashPermission}``
@@ -484,8 +486,9 @@ def context_cog(type: Literal["user", 2, "message", 3], name=None, guild_ids=Non
484486
The name of the command; default MISSING
485487
guild_ids: List[:class:`str` | :class:`int`]
486488
A list of guilds where the command can be used
487-
default_permission: :class:`bool`, optional
488-
Whether the command can be used by everyone; default True
489+
default_permission: :class:`bool` | :class:`discord.Permissions`, optional
490+
Permissions that a user needs to have in order to execute the command, default ``True``.
491+
If a bool was passed, it will indicate whether all users can use the command (``True``) or not (``False``)
489492
guild_permissions: Dict[:class:`SlashPermission`], optional
490493
Special permissions for guilds; default MISSING
491494

discord_ui/receive.py

Lines changed: 65 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1+
from .slash.http import ModifiedSlashState
12
from .errors import InvalidEvent, OutOfValidRange, WrongType
23
from .slash.errors import AlreadyDeferred, EphemeralDeletion
3-
from .slash.types import ContextCommand, OptionType, SlashCommand, SlashOption, SlashPermission, SlashSubcommand
4-
from .tools import MISSING, setup_logger, _none
4+
from .tools import MISSING, setup_logger, _none, get, _default
5+
from .slash.types import ContextCommand, SlashCommand, SlashOption, SlashPermission, SlashSubcommand
56
from .http import BetterRoute, jsonifyMessage, send_files
6-
from .components import ActionRow, Button, Component, LinkButton, SelectMenu, SelectOption, UseableComponent, make_component
7+
from .components import ActionRow, Button, LinkButton, SelectMenu, SelectOption, UseableComponent, make_component
78

89
import discord
910
from discord.ext.commands import Bot
1011
from discord.errors import HTTPException
1112
from discord.state import ConnectionState
1213

13-
from typing import List, Union, Dict
14+
from typing import Any, List, Union, Dict
1415
try:
1516
from typing import Literal
1617
except ImportError:
@@ -20,13 +21,14 @@
2021

2122

2223
class InteractionType:
23-
PING = Ping = 1
24-
APPLICATION_COMMAND = Command = 2
25-
MESSAGE_COMPONENT = Component = 3
24+
PING = Ping = 1
25+
APPLICATION_COMMAND = Command = 2
26+
MESSAGE_COMPONENT = Component = 3
27+
APPLICATION_COMMAND_AUTOCOMPLETE = Autocomplete = 4
2628

2729
class Interaction():
2830
def __init__(self, state, data, user=None, message=None) -> None:
29-
self._state: ConnectionState = state
31+
self._state: ModifiedSlashState = state
3032

3133
self.deferred: bool = False
3234
self.responded: bool = False
@@ -88,13 +90,12 @@ async def defer(self, hidden=False):
8890
logging.error(AlreadyDeferred())
8991
return
9092

91-
body = {"type": 5}
92-
93+
payload = None
9394
if hidden is True:
94-
body["data"] = {"flags": 64}
95+
payload = {"flags": 64}
9596
self._deferred_hidden = True
9697

97-
await self._state.http.request(BetterRoute("POST", f'/interactions/{self.id}/{self.token}/callback'), json=body)
98+
await self._state.slash_http.respond_to(self.id, self.token, 5, payload)
9899
self.deferred = True
99100

100101
async def respond(self, content=MISSING, *, tts=False, embed=MISSING, embeds=MISSING, file=MISSING, files=MISSING, nonce=MISSING,
@@ -141,10 +142,7 @@ async def respond(self, content=MISSING, *, tts=False, embed=MISSING, embeds=MIS
141142
"""
142143
if ninja_mode is True or all(y in [MISSING, False] for x, y in locals().items() if x not in ["self"]):
143144
try:
144-
route = BetterRoute("POST", f'/interactions/{self.id}/{self.token}/callback')
145-
r = await self._state.http.request(route, json={
146-
"type": 6
147-
})
145+
await self._state.slash_http.respond_to(self.id, self.token, 6)
148146
return
149147
except HTTPException as x:
150148
if "value must be one of (4, 5)" in str(x).lower():
@@ -169,49 +167,34 @@ async def respond(self, content=MISSING, *, tts=False, embed=MISSING, embeds=MIS
169167

170168

171169
r = None
172-
if not _none(delete_after) and hide_message is True:
170+
if delete_after is not MISSING and hide_message is True:
173171
raise EphemeralDeletion()
174172

175173
if hide_message:
176174
payload["flags"] = 64
177-
178-
if (file is not MISSING or files is not MISSING) and self.deferred is False:
179-
await self.defer(hidden=hide_message)
180175

181-
if self.deferred is False and hide_message is False:
182-
route = BetterRoute("POST", f'/interactions/{self.id}/{self.token}/callback')
183-
r = await self._state.http.request(route, json={
184-
"type": 4,
185-
"data": payload
186-
})
187-
else:
188-
if self.deferred is False and hide_message is True:
189-
await self.defer(hide_message)
176+
if self.deferred:
190177
route = BetterRoute("PATCH", f'/webhooks/{self.application_id}/{self.token}/messages/@original')
191178
if file is not MISSING or files is not MISSING:
192-
r = await send_files(route=route, files=[file] if files is MISSING else files, payload=payload, http=self._state.http)
179+
await send_files(route=route, files=[file] if files is MISSING else files, payload=payload, http=self._state.http)
193180
else:
194-
r = await self._state.http.request(route, json=payload)
181+
await self._state.http.request(route, json=payload)
182+
else:
183+
await self._state.slash_http.respond_to(self.id, self.token, 4, payload, files=[file] if file is not MISSING else _default(None, files))
195184
self.responded = True
196185

186+
r = await self._state.http.request(BetterRoute("GET", f"/webhooks/{self.application_id}/{self.token}/messages/@original"))
197187
if hide_message is True:
198-
msg = EphemeralMessage(state=self._state, channel=self._state.get_channel(int(r["channel_id"])), data=r, application_id=self.application_id, token=self.token)
199-
if listener is not MISSING:
200-
listener._start(msg)
201-
return msg
202-
203-
204-
if not hide_message:
205-
responseMSG = await self._state.http.request(BetterRoute("GET", f"/webhooks/{self.application_id}/{self.token}/messages/@original"))
206-
msg = await getMessage(self._state, data=responseMSG, response=False)
207-
if not _none(delete_after):
208-
await msg.delete(delete_after)
209-
if listener is not MISSING:
210-
listener._start(msg)
211-
return msg
212-
188+
msg = EphemeralMessage(state=self._state, channel=self.channel, data=r, application_id=self.application_id, token=self.token)
189+
else:
190+
msg = await getMessage(self._state, data=r, response=False)
191+
if listener is not MISSING:
192+
listener._start(msg)
193+
if not _none(delete_after):
194+
await msg.delete(delete_after)
195+
return msg
213196
async def send(self, content=None, *, tts=False, embed=MISSING, embeds=MISSING, file=MISSING, files=MISSING, nonce=MISSING,
214-
allowed_mentions=MISSING, mention_author=MISSING, components=MISSING, listener=MISSING, hidden=False) -> Union['Message', 'EphemeralMessage']:
197+
allowed_mentions=MISSING, mention_author=MISSING, components=MISSING, delete_after=MISSING, listener=MISSING, hidden=False) -> Union['Message', 'EphemeralMessage']:
215198
"""
216199
Sends a message to the interaction using a webhook
217200
@@ -237,6 +220,8 @@ async def send(self, content=None, *, tts=False, embed=MISSING, embeds=MISSING,
237220
Whether the author should be mentioned
238221
components: List[:class:`~Button` | :class:`~LinkButton` | :class:`~SelectMenu`]
239222
A list of message components to be included
223+
delete_after: :class:`float`
224+
After how many seconds the message should be deleted, only works for non-hiddend messages; default MISSING
240225
listener: :class:`Listener`
241226
A component-listener for this message
242227
hidden: :class:`bool`
@@ -250,7 +235,7 @@ async def send(self, content=None, *, tts=False, embed=MISSING, embeds=MISSING,
250235
:type: :class:`~Message` | :class:`EphemeralMessage`
251236
"""
252237
if self.responded is False:
253-
return await self.respond(content=content, tts=tts, embed=embed, embeds=embeds, file=file, files=files, nonce=nonce, allowed_mentions=allowed_mentions, mention_author=mention_author, components=components, listener=listener, hidden=hidden)
238+
return await self.respond(content=content, tts=tts, embed=embed, embeds=embeds, file=file, files=files, nonce=nonce, allowed_mentions=allowed_mentions, mention_author=mention_author, components=components, delete_after=delete_after, listener=listener, hidden=hidden)
254239

255240
if components is MISSING and listener is not MISSING:
256241
components = listener.to_components()
@@ -260,25 +245,48 @@ async def send(self, content=None, *, tts=False, embed=MISSING, embeds=MISSING,
260245
payload["flags"] = 64
261246

262247
route = BetterRoute("POST", f'/webhooks/{self.application_id}/{self.token}')
263-
264248
if file is not MISSING or files is not MISSING:
265249
r = await send_files(route=route, files=[file] if files is MISSING else files, payload=payload, http=self._state.http)
266250
else:
267251
r = await self._state.http.request(route, json=payload)
268252

269253
if hidden is True:
270-
msg = EphemeralMessage(state=self._state, channel=self._state.get_channel(r["channel_id"]), data=r, application_id=self.application_id, token=self.token)
271-
if listener is not MISSING:
272-
listener._start(msg)
273-
msg = await getMessage(self._state, r, response=False)
274-
254+
msg = EphemeralMessage(state=self._state, channel=self._state.get_channel(int(r["channel_id"])), data=r, application_id=self.application_id, token=self.token)
255+
else:
256+
msg = await getMessage(self._state, r, response=False)
257+
if delete_after is not MISSING:
258+
await msg.delete(delete_after)
275259
if listener is not MISSING:
276260
listener._start(msg)
261+
print(msg)
277262
return msg
278263
def _handle_auto_defer(self, auto_defer):
279264
self.deferred = auto_defer[0]
280265
self._deferred_hidden = auto_defer[1]
281266

267+
class ChoiceGeneratorContext(Interaction):
268+
"""A class for information that could be needed for generating choices for autocomopletion"""
269+
def __init__(self, command, state, data, options, user=None) -> None:
270+
Interaction.__init__(self, state, data, user=user)
271+
self.focused_option: dict = options[get(options, check=lambda x: options[x].get("focused", False))]
272+
"""The option for which the choices should be generated"""
273+
self.value_query: Union[str, int] = self.focused_option["value"]
274+
"""The current 'query' for the value"""
275+
self.selected_options: Dict[str, Any] = {options[x]["name"]: options[x]["value"] for x in options}
276+
"""All the options that were already selected"""
277+
self.command: Union[SlashedCommand, SlashedCommand, SlashedContext] = command
278+
"""The slash command for which the choices should be generated"""
279+
280+
async def defer(self, *args, **kwargs):
281+
"""Cannot defer this type of interaction"""
282+
raise NotImplementedError()
283+
async def respond(self, *args, **kwargs):
284+
"""Cannot rerspond to this type of interaction"""
285+
raise NotImplementedError()
286+
async def send(self, *args, **kwargs):
287+
"""Cannot send followup message to this type of interaction"""
288+
raise NotImplementedError()
289+
282290
class ComponentContext(Interaction, UseableComponent):
283291
"""A received component"""
284292
def __init__(self, state, data, user, message) -> None:
@@ -370,6 +378,8 @@ async def getMessage(state: ConnectionState, data, response=True):
370378
The User which pressed the button
371379
response: :class:`bool`
372380
Whether the Message returned should be of type `ResponseMessage` or `Message`
381+
channel: :class:`discord.Messageable`
382+
An alternative channel that will be used when no channel was found
373383
374384
Returns
375385
-------
@@ -486,7 +496,7 @@ async def edit(self, content=MISSING, *, embed=MISSING, embeds=MISSING, attachme
486496
data = await self._state.http.edit_message(self.channel.id, self.id, **payload)
487497
self._update(data)
488498

489-
if not _none(delete_after):
499+
if delete_after is not MISSING:
490500
await self.delete(delay=delete_after)
491501

492502
async def disable_action_row(self, row, disable = True):

discord_ui/slash/ext.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,9 @@ def guild_change(guild_id, *, name=None, description=None, default_permission=Tr
157157
The new name; default None
158158
description: :class:`str`, optional
159159
The new description; default None
160-
default_permission: :class:`bool`, optional
161-
Whether this command can be used by default; default True
160+
default_permission: :class:`bool` | :class:`discord.Permissions`, optional
161+
Permissions that a user needs to have in order to execute the command, default ``True``.
162+
If a bool was passed, it will indicate whether all users can use the command (``True``) or not (``False``)
162163
163164
"""
164165
def wraper(callback):

0 commit comments

Comments
 (0)