Merge branch 'iDevision:2.0' into 2.0
This commit is contained in:
@@ -53,6 +53,7 @@ from .context import Context
|
||||
from . import errors
|
||||
from .help import HelpCommand, DefaultHelpCommand
|
||||
from .cog import Cog
|
||||
from discord.utils import raise_expected_coro
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import importlib.machinery
|
||||
@@ -453,14 +454,59 @@ class BotBase(GroupMixin):
|
||||
elif self.owner_ids:
|
||||
return user.id in self.owner_ids
|
||||
else:
|
||||
# Populate the used fields, then retry the check. This is only done at-most once in the bot lifetime.
|
||||
await self.populate_owners()
|
||||
return await self.is_owner(user)
|
||||
|
||||
app = await self.application_info() # type: ignore
|
||||
if app.team:
|
||||
self.owner_ids = ids = {m.id for m in app.team.members}
|
||||
return user.id in ids
|
||||
async def try_owners(self) -> List[discord.User]:
|
||||
"""|coro|
|
||||
|
||||
Returns a list of :class:`~discord.User` representing the owners of the bot.
|
||||
It uses the :attr:`owner_id` and :attr:`owner_ids`, if set.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
The function also checks if the application is team-owned if
|
||||
:attr:`owner_ids` is not set.
|
||||
|
||||
Returns
|
||||
--------
|
||||
List[:class:`~discord.User`]
|
||||
List of owners of the bot.
|
||||
"""
|
||||
if self.owner_id:
|
||||
owner = await self.try_user(self.owner_id)
|
||||
|
||||
if owner:
|
||||
return [owner]
|
||||
else:
|
||||
self.owner_id = owner_id = app.owner.id
|
||||
return user.id == owner_id
|
||||
return []
|
||||
|
||||
elif self.owner_ids:
|
||||
owners = []
|
||||
|
||||
for owner_id in self.owner_ids:
|
||||
owner = await self.try_user(owner_id)
|
||||
if owner:
|
||||
owners.append(owner)
|
||||
|
||||
return owners
|
||||
else:
|
||||
# We didn't have owners cached yet, cache them and retry.
|
||||
await self.populate_owners()
|
||||
return await self.try_owners()
|
||||
|
||||
async def populate_owners(self):
|
||||
"""|coro|
|
||||
|
||||
Populate the :attr:`owner_id` and :attr:`owner_ids` through the use of :meth:`~.Bot.application_info`.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
app = await self.application_info() # type: ignore
|
||||
if app.team:
|
||||
self.owner_ids = {m.id for m in app.team.members}
|
||||
else:
|
||||
self.owner_id = app.owner.id
|
||||
|
||||
def before_invoke(self, coro: CFT) -> CFT:
|
||||
"""A decorator that registers a coroutine as a pre-invoke hook.
|
||||
@@ -488,11 +534,9 @@ class BotBase(GroupMixin):
|
||||
TypeError
|
||||
The coroutine passed is not actually a coroutine.
|
||||
"""
|
||||
if not asyncio.iscoroutinefunction(coro):
|
||||
raise TypeError('The pre-invoke hook must be a coroutine.')
|
||||
|
||||
self._before_invoke = coro
|
||||
return coro
|
||||
return raise_expected_coro(
|
||||
coro, 'The pre-invoke hook must be a coroutine.'
|
||||
)
|
||||
|
||||
def after_invoke(self, coro: CFT) -> CFT:
|
||||
r"""A decorator that registers a coroutine as a post-invoke hook.
|
||||
@@ -521,11 +565,10 @@ class BotBase(GroupMixin):
|
||||
TypeError
|
||||
The coroutine passed is not actually a coroutine.
|
||||
"""
|
||||
if not asyncio.iscoroutinefunction(coro):
|
||||
raise TypeError('The post-invoke hook must be a coroutine.')
|
||||
return raise_expected_coro(
|
||||
coro, 'The post-invoke hook must be a coroutine.'
|
||||
)
|
||||
|
||||
self._after_invoke = coro
|
||||
return coro
|
||||
|
||||
# listener registration
|
||||
|
||||
@@ -1266,7 +1309,7 @@ class Bot(BotBase, discord.Client):
|
||||
when passing an empty string, it should always be last as no prefix
|
||||
after it will be matched.
|
||||
case_insensitive: :class:`bool`
|
||||
Whether the commands should be case insensitive. Defaults to ``False``. This
|
||||
Whether the commands should be case insensitive. Defaults to ``True``. This
|
||||
attribute does not carry over to groups. You must set it to every group if
|
||||
you require group commands to be case insensitive as well.
|
||||
description: :class:`str`
|
||||
|
||||
@@ -21,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
@@ -32,6 +33,7 @@ import discord.abc
|
||||
import discord.utils
|
||||
|
||||
from discord.message import Message
|
||||
from discord import Permissions
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import ParamSpec
|
||||
@@ -62,10 +64,7 @@ T = TypeVar('T')
|
||||
BotT = TypeVar('BotT', bound="Union[Bot, AutoShardedBot]")
|
||||
CogT = TypeVar('CogT', bound="Cog")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
P = ParamSpec('P')
|
||||
else:
|
||||
P = TypeVar('P')
|
||||
P = ParamSpec('P') if TYPE_CHECKING else TypeVar('P')
|
||||
|
||||
|
||||
class Context(discord.abc.Messageable, Generic[BotT]):
|
||||
@@ -318,6 +317,13 @@ class Context(discord.abc.Messageable, Generic[BotT]):
|
||||
g = self.guild
|
||||
return g.voice_client if g else None
|
||||
|
||||
def author_permissions(self) -> Permissions:
|
||||
"""Returns the author permissions in the given channel.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
return self.channel.permissions_for(self.author)
|
||||
|
||||
async def send_help(self, *args: Any) -> Any:
|
||||
"""send_help(entity=<bot>)
|
||||
|
||||
|
||||
@@ -356,15 +356,15 @@ class PartialMessageConverter(Converter[discord.PartialMessage]):
|
||||
|
||||
@staticmethod
|
||||
def _resolve_channel(ctx, guild_id, channel_id) -> Optional[PartialMessageableChannel]:
|
||||
if guild_id is not None:
|
||||
guild = ctx.bot.get_guild(guild_id)
|
||||
if guild is not None and channel_id is not None:
|
||||
return guild._resolve_channel(channel_id) # type: ignore
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
if guild_id is None:
|
||||
return ctx.bot.get_channel(channel_id) if channel_id else ctx.channel
|
||||
|
||||
guild = ctx.bot.get_guild(guild_id)
|
||||
if guild is not None and channel_id is not None:
|
||||
return guild._resolve_channel(channel_id) # type: ignore
|
||||
else:
|
||||
return None
|
||||
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.PartialMessage:
|
||||
guild_id, message_id, channel_id = self._get_id_matches(ctx, argument)
|
||||
channel = self._resolve_channel(ctx, guild_id, channel_id)
|
||||
@@ -757,8 +757,8 @@ class GuildConverter(IDConverter[discord.Guild]):
|
||||
if result is None:
|
||||
result = discord.utils.get(ctx.bot.guilds, name=argument)
|
||||
|
||||
if result is None:
|
||||
raise GuildNotFound(argument)
|
||||
if result is None:
|
||||
raise GuildNotFound(argument)
|
||||
return result
|
||||
|
||||
|
||||
@@ -942,8 +942,7 @@ class clean_content(Converter[str]):
|
||||
def repl(match: re.Match) -> str:
|
||||
type = match[1]
|
||||
id = int(match[2])
|
||||
transformed = transforms[type](id)
|
||||
return transformed
|
||||
return transforms[type](id)
|
||||
|
||||
result = re.sub(r'<(@[!&]?|#)([0-9]{15,20})>', repl, argument)
|
||||
if self.escape_markdown:
|
||||
|
||||
@@ -1264,10 +1264,10 @@ class GroupMixin(Generic[CogT]):
|
||||
A mapping of command name to :class:`.Command`
|
||||
objects.
|
||||
case_insensitive: :class:`bool`
|
||||
Whether the commands should be case insensitive. Defaults to ``False``.
|
||||
Whether the commands should be case insensitive. Defaults to ``True``.
|
||||
"""
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
case_insensitive = kwargs.get('case_insensitive', False)
|
||||
case_insensitive = kwargs.get('case_insensitive', True)
|
||||
self.all_commands: Dict[str, Command[CogT, Any, Any]] = _CaseInsensitiveDict() if case_insensitive else {}
|
||||
self.case_insensitive: bool = case_insensitive
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -82,9 +82,7 @@ class StringView:
|
||||
def skip_string(self, string):
|
||||
strlen = len(string)
|
||||
if self.buffer[self.index:self.index + strlen] == string:
|
||||
self.previous = self.index
|
||||
self.index += strlen
|
||||
return True
|
||||
return self._return_index(strlen, True)
|
||||
return False
|
||||
|
||||
def read_rest(self):
|
||||
@@ -95,9 +93,7 @@ class StringView:
|
||||
|
||||
def read(self, n):
|
||||
result = self.buffer[self.index:self.index + n]
|
||||
self.previous = self.index
|
||||
self.index += n
|
||||
return result
|
||||
return self._return_index(n, result)
|
||||
|
||||
def get(self):
|
||||
try:
|
||||
@@ -105,9 +101,12 @@ class StringView:
|
||||
except IndexError:
|
||||
result = None
|
||||
|
||||
return self._return_index(1, result)
|
||||
|
||||
def _return_index(self, arg0, arg1):
|
||||
self.previous = self.index
|
||||
self.index += 1
|
||||
return result
|
||||
self.index += arg0
|
||||
return arg1
|
||||
|
||||
def get_word(self):
|
||||
pos = 0
|
||||
|
||||
@@ -46,7 +46,9 @@ import traceback
|
||||
|
||||
from collections.abc import Sequence
|
||||
from discord.backoff import ExponentialBackoff
|
||||
from discord.utils import MISSING
|
||||
from discord.utils import MISSING, raise_expected_coro
|
||||
|
||||
|
||||
|
||||
__all__ = (
|
||||
'loop',
|
||||
@@ -488,11 +490,7 @@ class Loop(Generic[LF]):
|
||||
The function was not a coroutine.
|
||||
"""
|
||||
|
||||
if not inspect.iscoroutinefunction(coro):
|
||||
raise TypeError(f'Expected coroutine function, received {coro.__class__.__name__!r}.')
|
||||
|
||||
self._before_loop = coro
|
||||
return coro
|
||||
return raise_expected_coro(coro, f'Expected coroutine function, received {coro.__class__.__name__!r}.')
|
||||
|
||||
def after_loop(self, coro: FT) -> FT:
|
||||
"""A decorator that register a coroutine to be called after the loop finished running.
|
||||
@@ -516,11 +514,7 @@ class Loop(Generic[LF]):
|
||||
The function was not a coroutine.
|
||||
"""
|
||||
|
||||
if not inspect.iscoroutinefunction(coro):
|
||||
raise TypeError(f'Expected coroutine function, received {coro.__class__.__name__!r}.')
|
||||
|
||||
self._after_loop = coro
|
||||
return coro
|
||||
return raise_expected_coro(coro, f'Expected coroutine function, received {coro.__class__.__name__!r}.')
|
||||
|
||||
def error(self, coro: ET) -> ET:
|
||||
"""A decorator that registers a coroutine to be called if the task encounters an unhandled exception.
|
||||
@@ -542,11 +536,7 @@ class Loop(Generic[LF]):
|
||||
TypeError
|
||||
The function was not a coroutine.
|
||||
"""
|
||||
if not inspect.iscoroutinefunction(coro):
|
||||
raise TypeError(f'Expected coroutine function, received {coro.__class__.__name__!r}.')
|
||||
|
||||
self._error = coro # type: ignore
|
||||
return coro
|
||||
return raise_expected_coro(coro, f'Expected coroutine function, received {coro.__class__.__name__!r}.')
|
||||
|
||||
def _get_next_sleep_time(self) -> datetime.datetime:
|
||||
if self._sleep is not MISSING:
|
||||
@@ -614,8 +604,7 @@ class Loop(Generic[LF]):
|
||||
)
|
||||
ret.append(t if t.tzinfo is not None else t.replace(tzinfo=utc))
|
||||
|
||||
ret = sorted(set(ret)) # de-dupe and sort times
|
||||
return ret
|
||||
return sorted(set(ret))
|
||||
|
||||
def change_interval(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user