Most slash command support completed, needs some debugging (and reindent)

This commit is contained in:
Gnome 2021-08-30 16:14:44 +01:00
parent 45d498c1b7
commit a19e43675f
20 changed files with 238 additions and 45 deletions

View File

@ -329,7 +329,7 @@ class Client:
If this is not passed via ``__init__`` then this is retrieved If this is not passed via ``__init__`` then this is retrieved
through the gateway when an event contains the data. Usually through the gateway when an event contains the data. Usually
after :func:`~discord.on_connect` is called. after :func:`~discord.on_connect` is called.
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
return self._connection.application_id return self._connection.application_id
@ -687,7 +687,7 @@ class Client:
self._connection._activity = value.to_dict() # type: ignore self._connection._activity = value.to_dict() # type: ignore
else: else:
raise TypeError('activity must derive from BaseActivity.') raise TypeError('activity must derive from BaseActivity.')
@property @property
def status(self): def status(self):
""":class:`.Status`: """:class:`.Status`:
@ -758,7 +758,7 @@ class Client:
This is useful if you have a channel_id but don't want to do an API call This is useful if you have a channel_id but don't want to do an API call
to send messages to it. to send messages to it.
.. versionadded:: 2.0 .. versionadded:: 2.0
Parameters Parameters
@ -1604,7 +1604,7 @@ class Client:
This method should be used for when a view is comprised of components This method should be used for when a view is comprised of components
that last longer than the lifecycle of the program. that last longer than the lifecycle of the program.
.. versionadded:: 2.0 .. versionadded:: 2.0
Parameters Parameters
@ -1636,7 +1636,7 @@ class Client:
@property @property
def persistent_views(self) -> Sequence[View]: def persistent_views(self) -> Sequence[View]:
"""Sequence[:class:`.View`]: A sequence of persistent views added to the client. """Sequence[:class:`.View`]: A sequence of persistent views added to the client.
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
return self._connection.persistent_views return self._connection.persistent_views

View File

@ -366,7 +366,7 @@ class Embed:
self._footer['icon_url'] = str(icon_url) self._footer['icon_url'] = str(icon_url)
return self return self
def remove_footer(self: E) -> E: def remove_footer(self: E) -> E:
"""Clears embed's footer information. """Clears embed's footer information.
@ -381,7 +381,7 @@ class Embed:
pass pass
return self return self
@property @property
def image(self) -> _EmbedMediaProxy: def image(self) -> _EmbedMediaProxy:
"""Returns an ``EmbedProxy`` denoting the image contents. """Returns an ``EmbedProxy`` denoting the image contents.

View File

@ -28,17 +28,23 @@ from __future__ import annotations
import asyncio import asyncio
import collections import collections
import collections.abc import collections.abc
import inspect import inspect
import importlib.util import importlib.util
import sys import sys
import traceback import traceback
import types import types
from typing import Any, Callable, Mapping, List, Dict, TYPE_CHECKING, Optional, TypeVar, Type, Union from typing import Any, Callable, cast, Mapping, List, Dict, TYPE_CHECKING, Optional, TypeVar, Type, Union
import discord import discord
from discord.types.interactions import (
ApplicationCommandInteractionData,
_ApplicationCommandInteractionDataOptionString
)
from .core import GroupMixin from .core import GroupMixin
from .view import StringView from .converter import Greedy
from .view import StringView, supported_quotes
from .context import Context from .context import Context
from . import errors from . import errors
from .help import HelpCommand, DefaultHelpCommand from .help import HelpCommand, DefaultHelpCommand
@ -66,6 +72,13 @@ T = TypeVar('T')
CFT = TypeVar('CFT', bound='CoroFunc') CFT = TypeVar('CFT', bound='CoroFunc')
CXT = TypeVar('CXT', bound='Context') CXT = TypeVar('CXT', bound='Context')
class _FakeSlashMessage(discord.PartialMessage):
activity = application = edited_at = reference = webhook_id = None
attachments = components = reactions = stickers = []
author: Union[discord.User, discord.Member]
tts = False
def when_mentioned(bot: Union[Bot, AutoShardedBot], msg: Message) -> List[str]: def when_mentioned(bot: Union[Bot, AutoShardedBot], msg: Message) -> List[str]:
"""A callable that implements a command prefix equivalent to being mentioned. """A callable that implements a command prefix equivalent to being mentioned.
@ -120,9 +133,17 @@ class _DefaultRepr:
_default = _DefaultRepr() _default = _DefaultRepr()
class BotBase(GroupMixin): class BotBase(GroupMixin):
def __init__(self, command_prefix, help_command=_default, description=None, **options): def __init__(self,
command_prefix,
help_command=_default,
description=None,
message_commands: bool = True,
slash_commands: bool = False, **options
):
super().__init__(**options) super().__init__(**options)
self.command_prefix = command_prefix self.command_prefix = command_prefix
self.slash_commands = slash_commands
self.message_commands = message_commands
self.extra_events: Dict[str, List[CoroFunc]] = {} self.extra_events: Dict[str, List[CoroFunc]] = {}
self.__cogs: Dict[str, Cog] = {} self.__cogs: Dict[str, Cog] = {}
self.__extensions: Dict[str, types.ModuleType] = {} self.__extensions: Dict[str, types.ModuleType] = {}
@ -142,11 +163,17 @@ class BotBase(GroupMixin):
if self.owner_ids and not isinstance(self.owner_ids, collections.abc.Collection): if self.owner_ids and not isinstance(self.owner_ids, collections.abc.Collection):
raise TypeError(f'owner_ids must be a collection not {self.owner_ids.__class__!r}') raise TypeError(f'owner_ids must be a collection not {self.owner_ids.__class__!r}')
if not (message_commands or slash_commands):
raise TypeError("Both message_commands and slash_commands are disabled.")
elif slash_commands:
self.slash_command_guild = options['slash_command_guild']
if help_command is _default: if help_command is _default:
self.help_command = DefaultHelpCommand() self.help_command = DefaultHelpCommand()
else: else:
self.help_command = help_command self.help_command = help_command
# internal helpers # internal helpers
def dispatch(self, event_name: str, *args: Any, **kwargs: Any) -> None: def dispatch(self, event_name: str, *args: Any, **kwargs: Any) -> None:
@ -1031,7 +1058,91 @@ class BotBase(GroupMixin):
await self.invoke(ctx) await self.invoke(ctx)
async def on_message(self, message): async def on_message(self, message):
await self.process_commands(message) if self.message_commands:
await self.process_commands(message)
async def on_interaction(self, interaction: discord.Interaction):
if not self.slash_commands or interaction.type != discord.InteractionType.application_command:
return
assert interaction.user is not None
interaction.data = cast(ApplicationCommandInteractionData, interaction.data)
# Ensure the interaction channel is usable
channel = interaction.channel
if channel is None or isinstance(channel, discord.PartialMessageable):
if interaction.guild is None:
channel = await interaction.user.create_dm()
elif interaction.channel_id is not None:
channel = await interaction.guild.fetch_channel(interaction.channel_id)
else:
return # cannot do anything without stable channel
# Fetch out subcommands from the options
command_name = interaction.data['name']
command_options = interaction.data.get('options') or []
for option in command_options:
if option['type'] in {1, 2}:
command_name = option['name']
command_options = option.get('options') or []
command_name += f'{command_name} '
command = self.get_command(command_name)
if command is None:
raise errors.CommandNotFound(f'Command "{command_name}" is not found')
message: discord.Message = _FakeSlashMessage(id=interaction.id, channel=channel) # type: ignore
message.author = interaction.user
# Fetch a valid prefix, so process_commands can function
prefix = await self.get_prefix(message)
if isinstance(prefix, list):
prefix = prefix[0]
# Add arguments to fake message content, in the right order
message.content = f'{prefix}{command_name} '
for name, param in command.clean_params.items():
option = next((o for o in command_options if o['name'] == name), None) # type: ignore
print(name, param, option)
if option is None:
if not command._is_typing_optional(param.annotation):
raise errors.MissingRequiredArgument(param)
elif (
option["type"] == 3
and " " in option["value"] # type: ignore
and param.kind != param.KEYWORD_ONLY
and not isinstance(param.annotation, Greedy)
):
# String with space in without "consume rest"
option = cast(_ApplicationCommandInteractionDataOptionString, option)
# we need to quote this string otherwise we may spill into
# other parameters and cause all kinds of trouble, as many
# quotes are supported and some may be in the option, we
# loop through all supported quotes and if neither open or
# close are in the string, we add them
quoted = False
string = option['value']
for open, close in supported_quotes.items():
if not (open in string or close in string):
message.content += f"{open}{string}{close} "
quoted = True
break
# all supported quotes are in the message and we cannot add any
# safely, very unlikely but still got to be covered
if not quoted:
raise errors.UnexpectedQuoteError(string)
else:
message.content += f'{option.get("value", "")} '
ctx = await self.get_context(message)
ctx.interaction = interaction
await self.invoke(ctx)
class Bot(BotBase, discord.Client): class Bot(BotBase, discord.Client):
"""Represents a discord bot. """Represents a discord bot.
@ -1103,7 +1214,20 @@ class Bot(BotBase, discord.Client):
.. versionadded:: 1.7 .. versionadded:: 1.7
""" """
pass # Needs to be moved to somewhere else, preferably BotBase
async def login(self, token: str) -> None:
await super().login(token=token)
await self._ready_commands()
async def _ready_commands(self):
if not self.slash_commands:
return
application = self.application_id or (await self.application_info()).id
commands = [scmd for cmd in self.commands if (scmd := cmd.to_application_command()) is not None]
await self.http.bulk_upsert_guild_commands(application, self.slash_command_guild, payload=commands)
class AutoShardedBot(BotBase, discord.AutoShardedClient): class AutoShardedBot(BotBase, discord.AutoShardedClient):
"""This is similar to :class:`.Bot` except that it is inherited from """This is similar to :class:`.Bot` except that it is inherited from

View File

@ -41,6 +41,7 @@ if TYPE_CHECKING:
from discord.member import Member from discord.member import Member
from discord.state import ConnectionState from discord.state import ConnectionState
from discord.user import ClientUser, User from discord.user import ClientUser, User
from discord.interactions import Interaction
from discord.voice_client import VoiceProtocol from discord.voice_client import VoiceProtocol
from .bot import Bot, AutoShardedBot from .bot import Bot, AutoShardedBot
@ -121,6 +122,7 @@ class Context(discord.abc.Messageable, Generic[BotT]):
A boolean that indicates if the command failed to be parsed, checked, A boolean that indicates if the command failed to be parsed, checked,
or invoked. or invoked.
""" """
interaction: Optional[Interaction] = None
def __init__(self, def __init__(self,
*, *,

View File

@ -23,6 +23,7 @@ DEALINGS IN THE SOFTWARE.
""" """
from __future__ import annotations from __future__ import annotations
from typing import ( from typing import (
Any, Any,
Callable, Callable,
@ -44,6 +45,7 @@ import asyncio
import functools import functools
import inspect import inspect
import datetime import datetime
from operator import itemgetter
import discord import discord
@ -59,6 +61,7 @@ if TYPE_CHECKING:
from typing_extensions import Concatenate, ParamSpec, TypeGuard from typing_extensions import Concatenate, ParamSpec, TypeGuard
from discord.message import Message from discord.message import Message
from discord.types.interactions import EditApplicationCommand
from ._types import ( from ._types import (
Coro, Coro,
@ -106,6 +109,16 @@ ContextT = TypeVar('ContextT', bound='Context')
GroupT = TypeVar('GroupT', bound='Group') GroupT = TypeVar('GroupT', bound='Group')
HookT = TypeVar('HookT', bound='Hook') HookT = TypeVar('HookT', bound='Hook')
ErrorT = TypeVar('ErrorT', bound='Error') ErrorT = TypeVar('ErrorT', bound='Error')
application_option_type_lookup = {
str: 3,
bool: 5,
int: 4,
(discord.Member, discord.User): 6, # Preferably discord.abc.User, but 'Protocols with non-method members don't support issubclass()'
(discord.abc.GuildChannel, discord.DMChannel): 7,
discord.Role: 8,
discord.Object: 9,
float: 10
}
if TYPE_CHECKING: if TYPE_CHECKING:
P = ParamSpec('P') P = ParamSpec('P')
@ -269,8 +282,8 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
which calls converters. If ``False`` then cooldown processing is done which calls converters. If ``False`` then cooldown processing is done
first and then the converters are called second. Defaults to ``False``. first and then the converters are called second. Defaults to ``False``.
extras: :class:`dict` extras: :class:`dict`
A dict of user provided extras to attach to the Command. A dict of user provided extras to attach to the Command.
.. note:: .. note::
This object may be copied by the library. This object may be copied by the library.
@ -309,6 +322,8 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
self.callback = func self.callback = func
self.enabled: bool = kwargs.get('enabled', True) self.enabled: bool = kwargs.get('enabled', True)
self.slash_command: Optional[bool] = kwargs.get("slash_command", None)
self.normal_command: Optional[bool] = kwargs.get("normal_command", None)
help_doc = kwargs.get('help') help_doc = kwargs.get('help')
if help_doc is not None: if help_doc is not None:
@ -344,7 +359,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
cooldown = func.__commands_cooldown__ cooldown = func.__commands_cooldown__
except AttributeError: except AttributeError:
cooldown = kwargs.get('cooldown') cooldown = kwargs.get('cooldown')
if cooldown is None: if cooldown is None:
buckets = CooldownMapping(cooldown, BucketType.default) buckets = CooldownMapping(cooldown, BucketType.default)
elif isinstance(cooldown, CooldownMapping): elif isinstance(cooldown, CooldownMapping):
@ -1098,7 +1113,13 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
A boolean indicating if the command can be invoked. A boolean indicating if the command can be invoked.
""" """
if not self.enabled: if not self.enabled or (
ctx.interaction is not None
and self.slash_command is False
) or (
ctx.interaction is None
and self.normal_command is False
):
raise DisabledCommand(f'{self.name} command is disabled') raise DisabledCommand(f'{self.name} command is disabled')
original = ctx.command original = ctx.command
@ -1125,6 +1146,54 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
finally: finally:
ctx.command = original ctx.command = original
def to_application_command(self) -> Optional[EditApplicationCommand]:
if self.slash_command is False:
return
payload = {
"name": self.name,
"description": self.short_doc or "no description",
"options": []
}
option_descriptions = self.extras.get("option_descriptions", {})
for name, param in self.clean_params.items():
annotation: Type[Any] = param.annotation if param.annotation is not param.empty else str
origin = getattr(param.annotation, "__origin__", None)
if origin is None and isinstance(annotation, Greedy):
annotation = annotation.converter
origin = Greedy
option: Dict[str, Any] = {
"name": name,
"required": not self._is_typing_optional(annotation),
"description": option_descriptions.get(name, "no description"),
}
if not option["required"] and origin is not None and len(annotation.__args__) == 2:
# Unpack Optional[T] (Union[T, None]) into just T
annotation, origin = annotation.__args__[0], None
if origin is None:
option["type"] = next(
(num for t, num in application_option_type_lookup.items()
if issubclass(annotation, t)), str
)
elif origin is Literal and len(origin.__args__) <= 25: # type: ignore
option["choices"] = [{
"name": literal_value,
"value": literal_value
} for literal_value in origin.__args__] # type: ignore
else:
option["type"] = 3 # STRING
payload["options"].append(option)
# Now we have all options, make sure required is before optional.
payload["options"] = sorted(payload["options"], key=itemgetter("required"), reverse=True)
return payload # type: ignore
class GroupMixin(Generic[CogT]): class GroupMixin(Generic[CogT]):
"""A mixin that implements common functionality for classes that behave """A mixin that implements common functionality for classes that behave
similar to :class:`.Group` and are allowed to register commands. similar to :class:`.Group` and are allowed to register commands.

View File

@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE.
from .errors import UnexpectedQuoteError, InvalidEndOfQuotedStringError, ExpectedClosingQuoteError from .errors import UnexpectedQuoteError, InvalidEndOfQuotedStringError, ExpectedClosingQuoteError
# map from opening quotes to closing quotes # map from opening quotes to closing quotes
_quotes = { supported_quotes = {
'"': '"', '"': '"',
"": "", "": "",
"": "", "": "",
@ -44,7 +44,7 @@ _quotes = {
"": "", "": "",
"": "", "": "",
} }
_all_quotes = set(_quotes.keys()) | set(_quotes.values()) _all_quotes = set(supported_quotes.keys()) | set(supported_quotes.values())
class StringView: class StringView:
def __init__(self, buffer): def __init__(self, buffer):
@ -129,7 +129,7 @@ class StringView:
if current is None: if current is None:
return None return None
close_quote = _quotes.get(current) close_quote = supported_quotes.get(current)
is_quoted = bool(close_quote) is_quoted = bool(close_quote)
if is_quoted: if is_quoted:
result = [] result = []

View File

@ -58,11 +58,11 @@ if TYPE_CHECKING:
from aiohttp import ClientSession from aiohttp import ClientSession
from .embeds import Embed from .embeds import Embed
from .ui.view import View from .ui.view import View
from .channel import VoiceChannel, StageChannel, TextChannel, CategoryChannel, StoreChannel, PartialMessageable from .channel import TextChannel, CategoryChannel, StoreChannel, PartialMessageable
from .threads import Thread from .threads import Thread
InteractionChannel = Union[ InteractionChannel = Union[
VoiceChannel, StageChannel, TextChannel, CategoryChannel, StoreChannel, Thread, PartialMessageable TextChannel, CategoryChannel, StoreChannel, Thread, PartialMessageable
] ]
MISSING: Any = utils.MISSING MISSING: Any = utils.MISSING
@ -179,7 +179,7 @@ class Interaction:
type = ChannelType.text if self.guild_id is not None else ChannelType.private type = ChannelType.text if self.guild_id is not None else ChannelType.private
return PartialMessageable(state=self._state, id=self.channel_id, type=type) return PartialMessageable(state=self._state, id=self.channel_id, type=type)
return None return None
return channel return channel # type: ignore
@property @property
def permissions(self) -> Permissions: def permissions(self) -> Permissions:

View File

@ -428,7 +428,7 @@ class Decoder(_OpusStruct):
@overload @overload
def decode(self, data: bytes, *, fec: bool) -> bytes: def decode(self, data: bytes, *, fec: bool) -> bytes:
... ...
@overload @overload
def decode(self, data: Literal[None], *, fec: Literal[False]) -> bytes: def decode(self, data: Literal[None], *, fec: Literal[False]) -> bytes:
... ...

View File

@ -310,7 +310,7 @@ class Template:
@property @property
def url(self) -> str: def url(self) -> str:
""":class:`str`: The template url. """:class:`str`: The template url.
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
return f'https://discord.new/{self.code}' return f'https://discord.new/{self.code}'

View File

@ -273,7 +273,7 @@ class Thread(Messageable, Hashable):
if parent is None: if parent is None:
raise ClientException('Parent channel not found') raise ClientException('Parent channel not found')
return parent.category return parent.category
@property @property
def category_id(self) -> Optional[int]: def category_id(self) -> Optional[int]:
"""The category channel ID the parent channel belongs to, if applicable. """The category channel ID the parent channel belongs to, if applicable.

View File

@ -229,8 +229,7 @@ class _EditApplicationCommandOptional(TypedDict, total=False):
description: str description: str
options: Optional[List[ApplicationCommandOption]] options: Optional[List[ApplicationCommandOption]]
type: ApplicationCommandType type: ApplicationCommandType
default_permission: bool
class EditApplicationCommand(_EditApplicationCommandOptional): class EditApplicationCommand(_EditApplicationCommandOptional):
name: str name: str
default_permission: bool

View File

@ -66,7 +66,7 @@ if TYPE_CHECKING:
VoiceServerUpdate as VoiceServerUpdatePayload, VoiceServerUpdate as VoiceServerUpdatePayload,
SupportedModes, SupportedModes,
) )
has_nacl: bool has_nacl: bool

View File

@ -355,7 +355,7 @@ texinfo_documents = [
#texinfo_no_detailmenu = False #texinfo_no_detailmenu = False
def setup(app): def setup(app):
if app.config.language == 'ja': if app.config.language == 'ja':
app.config.intersphinx_mapping['py'] = ('https://docs.python.org/ja/3', None) app.config.intersphinx_mapping['py'] = ('https://docs.python.org/ja/3', None)
app.config.html_context['discord_invite'] = 'https://discord.gg/nXzj3dg' app.config.html_context['discord_invite'] = 'https://discord.gg/nXzj3dg'
app.config.resource_links['discord'] = 'https://discord.gg/nXzj3dg' app.config.resource_links['discord'] = 'https://discord.gg/nXzj3dg'

View File

@ -52,4 +52,3 @@ def setup(app):
app.add_node(details, html=(visit_details_node, depart_details_node)) app.add_node(details, html=(visit_details_node, depart_details_node))
app.add_node(summary, html=(visit_summary_node, depart_summary_node)) app.add_node(summary, html=(visit_summary_node, depart_summary_node))
app.add_directive('details', DetailsDirective) app.add_directive('details', DetailsDirective)

View File

@ -5,7 +5,7 @@ from sphinx.util import logging as sphinx_logging
class NitpickFileIgnorer(logging.Filter): class NitpickFileIgnorer(logging.Filter):
def __init__(self, app: Sphinx) -> None: def __init__(self, app: Sphinx) -> None:
self.app = app self.app = app
super().__init__() super().__init__()

View File

@ -78,7 +78,7 @@ class ChannelOrMemberConverter(commands.Converter):
async def notify(ctx: commands.Context, target: ChannelOrMemberConverter): async def notify(ctx: commands.Context, target: ChannelOrMemberConverter):
# This command signature utilises the custom converter written above # This command signature utilises the custom converter written above
# What will happen during command invocation is that the `target` above will be passed to # What will happen during command invocation is that the `target` above will be passed to
# the `argument` parameter of the `ChannelOrMemberConverter.convert` method and # the `argument` parameter of the `ChannelOrMemberConverter.convert` method and
# the conversion will go through the process defined there. # the conversion will go through the process defined there.
await target.send(f'Hello, {target.name}!') await target.send(f'Hello, {target.name}!')

View File

@ -27,7 +27,7 @@ class MyBot(commands.Bot):
# subclass to the super() method, which tells the bot to # subclass to the super() method, which tells the bot to
# use the new MyContext class # use the new MyContext class
return await super().get_context(message, cls=cls) return await super().get_context(message, cls=cls)
bot = MyBot(command_prefix='!') bot = MyBot(command_prefix='!')
@ -43,7 +43,7 @@ async def guess(ctx, number: int):
await ctx.tick(number == value) await ctx.tick(number == value)
# IMPORTANT: You shouldn't hard code your token # IMPORTANT: You shouldn't hard code your token
# these are very important, and leaking them can # these are very important, and leaking them can
# let people do very malicious things with your # let people do very malicious things with your
# bot. Try to use a file or something to keep # bot. Try to use a file or something to keep
# them private, and don't commit it to GitHub # them private, and don't commit it to GitHub

View File

@ -5,7 +5,7 @@ from discord.ext import commands
bot = commands.Bot(command_prefix=commands.when_mentioned, description="Nothing to see here!") bot = commands.Bot(command_prefix=commands.when_mentioned, description="Nothing to see here!")
# the `hidden` keyword argument hides it from the help command. # the `hidden` keyword argument hides it from the help command.
@bot.group(hidden=True) @bot.group(hidden=True)
async def secret(ctx: commands.Context): async def secret(ctx: commands.Context):
"""What is this "secret" you speak of?""" """What is this "secret" you speak of?"""
@ -13,7 +13,7 @@ async def secret(ctx: commands.Context):
await ctx.send('Shh!', delete_after=5) await ctx.send('Shh!', delete_after=5)
def create_overwrites(ctx, *objects): def create_overwrites(ctx, *objects):
"""This is just a helper function that creates the overwrites for the """This is just a helper function that creates the overwrites for the
voice/text channels. voice/text channels.
A `discord.PermissionOverwrite` allows you to determine the permissions A `discord.PermissionOverwrite` allows you to determine the permissions
@ -45,10 +45,10 @@ def create_overwrites(ctx, *objects):
@secret.command() @secret.command()
@commands.guild_only() @commands.guild_only()
async def text(ctx: commands.Context, name: str, *objects: typing.Union[discord.Role, discord.Member]): async def text(ctx: commands.Context, name: str, *objects: typing.Union[discord.Role, discord.Member]):
"""This makes a text channel with a specified name """This makes a text channel with a specified name
that is only visible to roles or members that are specified. that is only visible to roles or members that are specified.
""" """
overwrites = create_overwrites(ctx, *objects) overwrites = create_overwrites(ctx, *objects)
await ctx.guild.create_text_channel( await ctx.guild.create_text_channel(

View File

@ -24,7 +24,7 @@ class Dropdown(discord.ui.Select):
async def callback(self, interaction: discord.Interaction): async def callback(self, interaction: discord.Interaction):
# Use the interaction object to send a response message containing # Use the interaction object to send a response message containing
# the user's favourite colour or choice. The self object refers to the # the user's favourite colour or choice. The self object refers to the
# Select object, and the values attribute gets a list of the user's # Select object, and the values attribute gets a list of the user's
# selected options. We only want the first one. # selected options. We only want the first one.
await interaction.response.send_message(f'Your favourite colour is {self.values[0]}') await interaction.response.send_message(f'Your favourite colour is {self.values[0]}')
@ -44,8 +44,8 @@ class Bot(commands.Bot):
async def on_ready(self): async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})') print(f'Logged in as {self.user} (ID: {self.user.id})')
print('------') print('------')
bot = Bot() bot = Bot()

View File

@ -3,7 +3,7 @@ import re
requirements = [] requirements = []
with open('requirements.txt') as f: with open('requirements.txt') as f:
requirements = f.read().splitlines() requirements = f.read().splitlines()
version = '' version = ''
with open('discord/__init__.py') as f: with open('discord/__init__.py') as f: