fix conflicts
This commit is contained in:
commit
917649b1b2
39
.github/ISSUE_TEMPLATE/bug_report.md
vendored
39
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,39 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug Report
|
|
||||||
about: Report broken or incorrect behaviour
|
|
||||||
labels: bug
|
|
||||||
---
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
<!-- A summary of your bug report -->
|
|
||||||
|
|
||||||
## Reproduction Steps
|
|
||||||
|
|
||||||
<!-- What you did to make it happen. Ideally there should be a short code snippet in this section to help reproduce the bug. -->
|
|
||||||
|
|
||||||
## Expected Results
|
|
||||||
|
|
||||||
<!-- What you expected to happen -->
|
|
||||||
|
|
||||||
## Actual Results
|
|
||||||
|
|
||||||
<!-- What actually happened. If there is a traceback, please show the entire thing. -->
|
|
||||||
|
|
||||||
## Intents
|
|
||||||
|
|
||||||
<!-- What intents are you using for your bot? This is the `discord.Intents` class you pass to the client -->
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
|
|
||||||
<!-- Put an x inside [ ] to check it, like so: [x] -->
|
|
||||||
|
|
||||||
- [ ] I have searched the open issues for duplicates.
|
|
||||||
- [ ] I have shown the entire traceback, if possible.
|
|
||||||
- [ ] I have removed my token from display, if visible.
|
|
||||||
- [ ] I have provided the intents that my bot is using.
|
|
||||||
|
|
||||||
## System Information
|
|
||||||
|
|
||||||
<!-- Run `python -m discord -v` and paste this information below. -->
|
|
||||||
<!-- This command is available in v1.1.0 or higher. If you are unable to run this command, paste basic info (ie. Python version, library version, and your operating system -->
|
|
75
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
75
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Report broken or incorrect behaviour
|
||||||
|
labels: unconfirmed bug
|
||||||
|
issue_body: true
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: >
|
||||||
|
Thanks for taking the time to fill out a bug.
|
||||||
|
If you want real-time support, consider joining our Discord at https://discord.gg/r3sSKJJ instead.
|
||||||
|
|
||||||
|
Please note that this form is for bugs only!
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Summary
|
||||||
|
description: A simple summary of your bug report
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Reproduction Steps
|
||||||
|
description: >
|
||||||
|
What you did to make it happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Minimal Reproducible Code
|
||||||
|
description: >
|
||||||
|
A short snippet of code that showcases the bug.
|
||||||
|
render: python
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Expected Results
|
||||||
|
description: >
|
||||||
|
What did you expect to happen?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Actual Results
|
||||||
|
description: >
|
||||||
|
What actually happened?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Intents
|
||||||
|
description: >
|
||||||
|
What intents are you using for your bot?
|
||||||
|
This is the `discord.Intents` class you pass to the client.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: System Information
|
||||||
|
description: >
|
||||||
|
Run `python -m discord -v` and paste this information below.
|
||||||
|
|
||||||
|
This command required v1.1.0 or higher of the library. If this errors out then show some basic
|
||||||
|
information involving your system such as operating system and Python version.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Checklist
|
||||||
|
description: >
|
||||||
|
Let's make sure you've properly done due dilligence when reporting this issue!
|
||||||
|
options:
|
||||||
|
- label: I have searched the open issues for duplicates.
|
||||||
|
required: true
|
||||||
|
- label: I have shown the entire traceback, if possible.
|
||||||
|
required: true
|
||||||
|
- label: I have removed my token from display, if visible.
|
||||||
|
required: true
|
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -4,5 +4,5 @@ contact_links:
|
|||||||
about: Ask questions and discuss with other users of the library.
|
about: Ask questions and discuss with other users of the library.
|
||||||
url: https://github.com/Rapptz/discord.py/discussions
|
url: https://github.com/Rapptz/discord.py/discussions
|
||||||
- name: Discord Server
|
- name: Discord Server
|
||||||
about: Use our official Discord server to ask help and questions as well.
|
about: Use our official Discord server to ask for help and questions as well.
|
||||||
url: https://discord.gg/r3sSKJJ
|
url: https://discord.gg/r3sSKJJ
|
||||||
|
27
.github/ISSUE_TEMPLATE/feature_request.md
vendored
27
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature Request
|
|
||||||
about: Suggest a feature for this library
|
|
||||||
labels: feature request
|
|
||||||
---
|
|
||||||
|
|
||||||
## The Problem
|
|
||||||
|
|
||||||
<!--
|
|
||||||
What problem is your feature trying to solve? What becomes easier or possible when this feature is implemented?
|
|
||||||
-->
|
|
||||||
|
|
||||||
## The Ideal Solution
|
|
||||||
|
|
||||||
<!--
|
|
||||||
What is your ideal solution to the problem? What would you like this feature to do?
|
|
||||||
-->
|
|
||||||
|
|
||||||
## The Current Solution
|
|
||||||
|
|
||||||
<!--
|
|
||||||
What is the current solution to the problem, if any?
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
<!-- A short summary of your feature request. -->
|
|
46
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
46
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Suggest a feature for this library
|
||||||
|
labels: feature request
|
||||||
|
issue_body: true
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Summary
|
||||||
|
description: >
|
||||||
|
A short summary of what your feature request is.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
multiple: false
|
||||||
|
label: What is the feature request for?
|
||||||
|
options:
|
||||||
|
- The core library
|
||||||
|
- discord.ext.commands
|
||||||
|
- discord.ext.tasks
|
||||||
|
- The documentation
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: The Problem
|
||||||
|
description: >
|
||||||
|
What problem is your feature trying to solve?
|
||||||
|
What becomes easier or possible when this feature is implemented?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: The Ideal Solution
|
||||||
|
description: >
|
||||||
|
What is your ideal solution to the problem?
|
||||||
|
What would you like this feature to do?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: The Current Solution
|
||||||
|
description: >
|
||||||
|
What is the current solution to the problem, if any?
|
||||||
|
validations:
|
||||||
|
required: false
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
@ -6,7 +6,7 @@ Discord API Wrapper
|
|||||||
|
|
||||||
A basic wrapper for the Discord API.
|
A basic wrapper for the Discord API.
|
||||||
|
|
||||||
:copyright: (c) 2015-2020 Rapptz
|
:copyright: (c) 2015-present Rapptz
|
||||||
:license: MIT, see LICENSE for more details.
|
:license: MIT, see LICENSE for more details.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -14,8 +14,8 @@ A basic wrapper for the Discord API.
|
|||||||
__title__ = 'discord'
|
__title__ = 'discord'
|
||||||
__author__ = 'Rapptz'
|
__author__ = 'Rapptz'
|
||||||
__license__ = 'MIT'
|
__license__ = 'MIT'
|
||||||
__copyright__ = 'Copyright 2015-2020 Rapptz'
|
__copyright__ = 'Copyright 2015-present Rapptz'
|
||||||
__version__ = '1.6.0.7'
|
__version__ = '1.7.1.7'
|
||||||
|
|
||||||
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
|
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
|
||||||
|
|
||||||
@ -62,6 +62,6 @@ from .sticker import Sticker
|
|||||||
|
|
||||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro enhanced releaselevel serial')
|
VersionInfo = namedtuple('VersionInfo', 'major minor micro enhanced releaselevel serial')
|
||||||
|
|
||||||
version_info = VersionInfo(major=1, minor=6, micro=0, enhanced=7, releaselevel='alpha', serial=0)
|
version_info = VersionInfo(major=1, minor=7, micro=1, enhanced=7, releaselevel='final', serial=0)
|
||||||
|
|
||||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -250,8 +250,9 @@ def newcog(parser, args):
|
|||||||
name = args.class_name
|
name = args.class_name
|
||||||
else:
|
else:
|
||||||
name = str(directory.stem)
|
name = str(directory.stem)
|
||||||
if '-' in name:
|
if '-' in name or '_' in name:
|
||||||
name = name.replace('-', ' ').title().replace(' ', '')
|
translation = str.maketrans('-_', ' ')
|
||||||
|
name = name.translate(translation).title().replace(' ', '')
|
||||||
else:
|
else:
|
||||||
name = name.title()
|
name = name.title()
|
||||||
|
|
||||||
@ -300,4 +301,5 @@ def main():
|
|||||||
parser, args = parse_args()
|
parser, args = parse_args()
|
||||||
args.func(parser, args)
|
args.func(parser, args)
|
||||||
|
|
||||||
main()
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
149
discord/abc.py
149
discord/abc.py
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -33,6 +33,7 @@ from .iterators import HistoryIterator
|
|||||||
from .context_managers import Typing
|
from .context_managers import Typing
|
||||||
from .enums import ChannelType
|
from .enums import ChannelType
|
||||||
from .errors import InvalidArgument, ClientException
|
from .errors import InvalidArgument, ClientException
|
||||||
|
from .mentions import AllowedMentions
|
||||||
from .permissions import PermissionOverwrite, Permissions
|
from .permissions import PermissionOverwrite, Permissions
|
||||||
from .role import Role
|
from .role import Role
|
||||||
from .invite import Invite
|
from .invite import Invite
|
||||||
@ -168,15 +169,15 @@ class _Overwrites:
|
|||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.id = kwargs.pop('id')
|
self.id = kwargs.pop('id')
|
||||||
self.allow = kwargs.pop('allow', 0)
|
self.allow = int(kwargs.pop('allow_new', 0))
|
||||||
self.deny = kwargs.pop('deny', 0)
|
self.deny = int(kwargs.pop('deny_new', 0))
|
||||||
self.type = sys.intern(kwargs.pop('type'))
|
self.type = sys.intern(kwargs.pop('type'))
|
||||||
|
|
||||||
def _asdict(self):
|
def _asdict(self):
|
||||||
return {
|
return {
|
||||||
'id': self.id,
|
'id': self.id,
|
||||||
'allow': self.allow,
|
'allow': str(self.allow),
|
||||||
'deny': self.deny,
|
'deny': str(self.deny),
|
||||||
'type': self.type,
|
'type': self.type,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,6 +189,7 @@ class GuildChannel:
|
|||||||
- :class:`~discord.TextChannel`
|
- :class:`~discord.TextChannel`
|
||||||
- :class:`~discord.VoiceChannel`
|
- :class:`~discord.VoiceChannel`
|
||||||
- :class:`~discord.CategoryChannel`
|
- :class:`~discord.CategoryChannel`
|
||||||
|
- :class:`~discord.StageChannel`
|
||||||
|
|
||||||
This ABC must also implement :class:`~discord.abc.Snowflake`.
|
This ABC must also implement :class:`~discord.abc.Snowflake`.
|
||||||
|
|
||||||
@ -259,6 +261,13 @@ class GuildChannel:
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
rtc_region = options.pop('rtc_region')
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
options['rtc_region'] = None if rtc_region is None else str(rtc_region)
|
||||||
|
|
||||||
lock_permissions = options.pop('sync_permissions', False)
|
lock_permissions = options.pop('sync_permissions', False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -710,6 +719,127 @@ class GuildChannel:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def move(self, **kwargs):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
A rich interface to help move a channel relative to other channels.
|
||||||
|
|
||||||
|
If exact position movement is required, :meth:`edit` should be used instead.
|
||||||
|
|
||||||
|
You must have the :attr:`~discord.Permissions.manage_channels` permission to
|
||||||
|
do this.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Voice channels will always be sorted below text channels.
|
||||||
|
This is a Discord limitation.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
beginning: :class:`bool`
|
||||||
|
Whether to move the channel to the beginning of the
|
||||||
|
channel list (or category if given).
|
||||||
|
This is mutually exclusive with ``end``, ``before``, and ``after``.
|
||||||
|
end: :class:`bool`
|
||||||
|
Whether to move the channel to the end of the
|
||||||
|
channel list (or category if given).
|
||||||
|
This is mutually exclusive with ``beginning``, ``before``, and ``after``.
|
||||||
|
before: :class:`~discord.abc.Snowflake`
|
||||||
|
The channel that should be before our current channel.
|
||||||
|
This is mutually exclusive with ``beginning``, ``end``, and ``after``.
|
||||||
|
after: :class:`~discord.abc.Snowflake`
|
||||||
|
The channel that should be after our current channel.
|
||||||
|
This is mutually exclusive with ``beginning``, ``end``, and ``before``.
|
||||||
|
offset: :class:`int`
|
||||||
|
The number of channels to offset the move by. For example,
|
||||||
|
an offset of ``2`` with ``beginning=True`` would move
|
||||||
|
it 2 after the beginning. A positive number moves it below
|
||||||
|
while a negative number moves it above. Note that this
|
||||||
|
number is relative and computed after the ``beginning``,
|
||||||
|
``end``, ``before``, and ``after`` parameters.
|
||||||
|
category: Optional[:class:`~discord.abc.Snowflake`]
|
||||||
|
The category to move this channel under.
|
||||||
|
If ``None`` is given then it moves it out of the category.
|
||||||
|
This parameter is ignored if moving a category channel.
|
||||||
|
sync_permissions: :class:`bool`
|
||||||
|
Whether to sync the permissions with the category (if given).
|
||||||
|
reason: :class:`str`
|
||||||
|
The reason for the move.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
InvalidArgument
|
||||||
|
An invalid position was given or a bad mix of arguments were passed.
|
||||||
|
Forbidden
|
||||||
|
You do not have permissions to move the channel.
|
||||||
|
HTTPException
|
||||||
|
Moving the channel failed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not kwargs:
|
||||||
|
return
|
||||||
|
|
||||||
|
beginning, end = kwargs.get('beginning'), kwargs.get('end')
|
||||||
|
before, after = kwargs.get('before'), kwargs.get('after')
|
||||||
|
offset = kwargs.get('offset', 0)
|
||||||
|
if sum(bool(a) for a in (beginning, end, before, after)) > 1:
|
||||||
|
raise InvalidArgument('Only one of [before, after, end, beginning] can be used.')
|
||||||
|
|
||||||
|
bucket = self._sorting_bucket
|
||||||
|
parent_id = kwargs.get('category', ...)
|
||||||
|
if parent_id not in (..., None):
|
||||||
|
parent_id = parent_id.id
|
||||||
|
channels = [
|
||||||
|
ch
|
||||||
|
for ch in self.guild.channels
|
||||||
|
if ch._sorting_bucket == bucket
|
||||||
|
and ch.category_id == parent_id
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
channels = [
|
||||||
|
ch
|
||||||
|
for ch in self.guild.channels
|
||||||
|
if ch._sorting_bucket == bucket
|
||||||
|
and ch.category_id == self.category_id
|
||||||
|
]
|
||||||
|
|
||||||
|
channels.sort(key=lambda c: (c.position, c.id))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Try to remove ourselves from the channel list
|
||||||
|
channels.remove(self)
|
||||||
|
except ValueError:
|
||||||
|
# If we're not there then it's probably due to not being in the category
|
||||||
|
pass
|
||||||
|
|
||||||
|
index = None
|
||||||
|
if beginning:
|
||||||
|
index = 0
|
||||||
|
elif end:
|
||||||
|
index = len(channels)
|
||||||
|
elif before:
|
||||||
|
index = next((i for i, c in enumerate(channels) if c.id == before.id), None)
|
||||||
|
elif after:
|
||||||
|
index = next((i + 1 for i, c in enumerate(channels) if c.id == after.id), None)
|
||||||
|
|
||||||
|
if index is None:
|
||||||
|
raise InvalidArgument('Could not resolve appropriate move position')
|
||||||
|
|
||||||
|
channels.insert(max((index + offset), 0), self)
|
||||||
|
payload = []
|
||||||
|
lock_permissions = kwargs.get('sync_permissions', False)
|
||||||
|
reason = kwargs.get('reason')
|
||||||
|
for index, channel in enumerate(channels):
|
||||||
|
d = { 'id': channel.id, 'position': index }
|
||||||
|
if parent_id is not ... and channel.id == self.id:
|
||||||
|
d.update(parent_id=parent_id, lock_permissions=lock_permissions)
|
||||||
|
payload.append(d)
|
||||||
|
|
||||||
|
await self._state.http.bulk_channel_update(self.guild.id, payload, reason=reason)
|
||||||
|
|
||||||
|
|
||||||
async def create_invite(self, *, reason=None, **fields):
|
async def create_invite(self, *, reason=None, **fields):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@ -904,7 +1034,7 @@ class Messageable(metaclass=abc.ABCMeta):
|
|||||||
allowed_mentions = state.allowed_mentions and state.allowed_mentions.to_dict()
|
allowed_mentions = state.allowed_mentions and state.allowed_mentions.to_dict()
|
||||||
|
|
||||||
if mention_author is not None:
|
if mention_author is not None:
|
||||||
allowed_mentions = allowed_mentions or {}
|
allowed_mentions = allowed_mentions or AllowedMentions().to_dict()
|
||||||
allowed_mentions['replied_user'] = bool(mention_author)
|
allowed_mentions['replied_user'] = bool(mention_author)
|
||||||
|
|
||||||
if reference is not None:
|
if reference is not None:
|
||||||
@ -1146,9 +1276,6 @@ class Connectable(metaclass=abc.ABCMeta):
|
|||||||
A voice client that is fully connected to the voice server.
|
A voice client that is fully connected to the voice server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not issubclass(cls, VoiceProtocol):
|
|
||||||
raise TypeError('Type must meet VoiceProtocol abstract base class.')
|
|
||||||
|
|
||||||
key_id, _ = self._get_voice_client_key()
|
key_id, _ = self._get_voice_client_key()
|
||||||
state = self._state
|
state = self._state
|
||||||
|
|
||||||
@ -1157,6 +1284,10 @@ class Connectable(metaclass=abc.ABCMeta):
|
|||||||
|
|
||||||
client = state._get_client()
|
client = state._get_client()
|
||||||
voice = cls(client, self)
|
voice = cls(client, self)
|
||||||
|
|
||||||
|
if not isinstance(voice, VoiceProtocol):
|
||||||
|
raise TypeError('Type must meet VoiceProtocol abstract base class.')
|
||||||
|
|
||||||
state._add_voice_client(key_id, voice)
|
state._add_voice_client(key_id, voice)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -693,8 +693,14 @@ class CustomActivity(BaseActivity):
|
|||||||
|
|
||||||
if emoji is None:
|
if emoji is None:
|
||||||
self.emoji = emoji
|
self.emoji = emoji
|
||||||
else:
|
elif isinstance(emoji, dict):
|
||||||
self.emoji = PartialEmoji.from_dict(emoji)
|
self.emoji = PartialEmoji.from_dict(emoji)
|
||||||
|
elif isinstance(emoji, str):
|
||||||
|
self.emoji = PartialEmoji(name=emoji)
|
||||||
|
elif isinstance(emoji, PartialEmoji):
|
||||||
|
self.emoji = emoji
|
||||||
|
else:
|
||||||
|
raise TypeError('Expected str, PartialEmoji, or None, received {0!r} instead.'.format(type(emoji)))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self):
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -61,30 +61,32 @@ class AppInfo:
|
|||||||
A list of RPC origin URLs, if RPC is enabled.
|
A list of RPC origin URLs, if RPC is enabled.
|
||||||
summary: :class:`str`
|
summary: :class:`str`
|
||||||
If this application is a game sold on Discord,
|
If this application is a game sold on Discord,
|
||||||
this field will be the summary field for the store page of its primary SKU
|
this field will be the summary field for the store page of its primary SKU.
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
|
|
||||||
verify_key: :class:`str`
|
verify_key: :class:`str`
|
||||||
The base64 encoded key for the GameSDK's GetTicket
|
The hex encoded key for verification in interactions and the
|
||||||
|
GameSDK's `GetTicket <https://discord.com/developers/docs/game-sdk/applications#getticket>`_.
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
|
|
||||||
guild_id: Optional[:class:`int`]
|
guild_id: Optional[:class:`int`]
|
||||||
If this application is a game sold on Discord,
|
If this application is a game sold on Discord,
|
||||||
this field will be the guild to which it has been linked
|
this field will be the guild to which it has been linked to.
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
|
|
||||||
primary_sku_id: Optional[:class:`int`]
|
primary_sku_id: Optional[:class:`int`]
|
||||||
If this application is a game sold on Discord,
|
If this application is a game sold on Discord,
|
||||||
this field will be the id of the "Game SKU" that is created, if exists
|
this field will be the id of the "Game SKU" that is created,
|
||||||
|
if it exists.
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
|
|
||||||
slug: Optional[:class:`str`]
|
slug: Optional[:class:`str`]
|
||||||
If this application is a game sold on Discord,
|
If this application is a game sold on Discord,
|
||||||
this field will be the URL slug that links to the store page
|
this field will be the URL slug that links to the store page.
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -165,7 +165,7 @@ class Asset:
|
|||||||
format = 'gif' if emoji.animated else static_format
|
format = 'gif' if emoji.animated else static_format
|
||||||
|
|
||||||
return cls(state, '/emojis/{0.id}.{1}'.format(emoji, format))
|
return cls(state, '/emojis/{0.id}.{1}'.format(emoji, format))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.BASE + self._url if self._url is not None else ''
|
return self.BASE + self._url if self._url is not None else ''
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -29,6 +29,7 @@ from .object import Object
|
|||||||
from .permissions import PermissionOverwrite, Permissions
|
from .permissions import PermissionOverwrite, Permissions
|
||||||
from .colour import Colour
|
from .colour import Colour
|
||||||
from .invite import Invite
|
from .invite import Invite
|
||||||
|
from .mixins import Hashable
|
||||||
|
|
||||||
def _transform_verification_level(entry, data):
|
def _transform_verification_level(entry, data):
|
||||||
return enums.try_enum(enums.VerificationLevel, data)
|
return enums.try_enum(enums.VerificationLevel, data)
|
||||||
@ -51,8 +52,7 @@ def _transform_snowflake(entry, data):
|
|||||||
def _transform_channel(entry, data):
|
def _transform_channel(entry, data):
|
||||||
if data is None:
|
if data is None:
|
||||||
return None
|
return None
|
||||||
channel = entry.guild.get_channel(int(data)) or Object(id=data)
|
return entry.guild.get_channel(int(data)) or Object(id=data)
|
||||||
return channel
|
|
||||||
|
|
||||||
def _transform_owner_id(entry, data):
|
def _transform_owner_id(entry, data):
|
||||||
if data is None:
|
if data is None:
|
||||||
@ -187,11 +187,28 @@ class AuditLogChanges:
|
|||||||
|
|
||||||
setattr(second, 'roles', data)
|
setattr(second, 'roles', data)
|
||||||
|
|
||||||
class AuditLogEntry:
|
class AuditLogEntry(Hashable):
|
||||||
r"""Represents an Audit Log entry.
|
r"""Represents an Audit Log entry.
|
||||||
|
|
||||||
You retrieve these via :meth:`Guild.audit_logs`.
|
You retrieve these via :meth:`Guild.audit_logs`.
|
||||||
|
|
||||||
|
.. container:: operations
|
||||||
|
|
||||||
|
.. describe:: x == y
|
||||||
|
|
||||||
|
Checks if two entries are equal.
|
||||||
|
|
||||||
|
.. describe:: x != y
|
||||||
|
|
||||||
|
Checks if two entries are not equal.
|
||||||
|
|
||||||
|
.. describe:: hash(x)
|
||||||
|
|
||||||
|
Returns the entry's hash.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
Audit log entries are now comparable and hashable.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
action: :class:`AuditLogAction`
|
action: :class:`AuditLogAction`
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -36,6 +36,8 @@ class CallMessage:
|
|||||||
This is only received in cases where the message type is equivalent to
|
This is only received in cases where the message type is equivalent to
|
||||||
:attr:`MessageType.call`.
|
:attr:`MessageType.call`.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
ended_timestamp: Optional[:class:`datetime.datetime`]
|
ended_timestamp: Optional[:class:`datetime.datetime`]
|
||||||
@ -53,12 +55,18 @@ class CallMessage:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def call_ended(self):
|
def call_ended(self):
|
||||||
""":class:`bool`: Indicates if the call has ended."""
|
""":class:`bool`: Indicates if the call has ended.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
"""
|
||||||
return self.ended_timestamp is not None
|
return self.ended_timestamp is not None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def channel(self):
|
def channel(self):
|
||||||
r""":class:`GroupChannel`\: The private channel associated with this message."""
|
r""":class:`GroupChannel`\: The private channel associated with this message.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
"""
|
||||||
return self.message.channel
|
return self.message.channel
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -68,6 +76,8 @@ class CallMessage:
|
|||||||
If the call has not ended then the current duration will
|
If the call has not ended then the current duration will
|
||||||
be returned.
|
be returned.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
---------
|
---------
|
||||||
:class:`datetime.timedelta`
|
:class:`datetime.timedelta`
|
||||||
@ -83,6 +93,8 @@ class GroupCall:
|
|||||||
|
|
||||||
This is accompanied with a :class:`CallMessage` denoting the information.
|
This is accompanied with a :class:`CallMessage` denoting the information.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
call: :class:`CallMessage`
|
call: :class:`CallMessage`
|
||||||
@ -122,7 +134,10 @@ class GroupCall:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def connected(self):
|
def connected(self):
|
||||||
"""List[:class:`User`]: A property that returns all users that are currently in this call."""
|
"""List[:class:`User`]: A property that returns all users that are currently in this call.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
"""
|
||||||
ret = [u for u in self.channel.recipients if self.voice_state_for(u) is not None]
|
ret = [u for u in self.channel.recipients if self.voice_state_for(u) is not None]
|
||||||
me = self.channel.me
|
me = self.channel.me
|
||||||
if self.voice_state_for(me) is not None:
|
if self.voice_state_for(me) is not None:
|
||||||
@ -132,15 +147,21 @@ class GroupCall:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def channel(self):
|
def channel(self):
|
||||||
r""":class:`GroupChannel`\: Returns the channel the group call is in."""
|
r""":class:`GroupChannel`\: Returns the channel the group call is in.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
"""
|
||||||
return self.call.channel
|
return self.call.channel
|
||||||
|
|
||||||
|
@utils.deprecated()
|
||||||
def voice_state_for(self, user):
|
def voice_state_for(self, user):
|
||||||
"""Retrieves the :class:`VoiceState` for a specified :class:`User`.
|
"""Retrieves the :class:`VoiceState` for a specified :class:`User`.
|
||||||
|
|
||||||
If the :class:`User` has no voice state then this function returns
|
If the :class:`User` has no voice state then this function returns
|
||||||
``None``.
|
``None``.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
------------
|
------------
|
||||||
user: :class:`User`
|
user: :class:`User`
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -29,7 +29,7 @@ import asyncio
|
|||||||
|
|
||||||
import discord.abc
|
import discord.abc
|
||||||
from .permissions import Permissions
|
from .permissions import Permissions
|
||||||
from .enums import ChannelType, try_enum
|
from .enums import ChannelType, try_enum, VoiceRegion
|
||||||
from .mixins import Hashable
|
from .mixins import Hashable
|
||||||
from . import utils
|
from . import utils
|
||||||
from .asset import Asset
|
from .asset import Asset
|
||||||
@ -38,6 +38,7 @@ from .errors import ClientException, NoMoreItems, InvalidArgument
|
|||||||
__all__ = (
|
__all__ = (
|
||||||
'TextChannel',
|
'TextChannel',
|
||||||
'VoiceChannel',
|
'VoiceChannel',
|
||||||
|
'StageChannel',
|
||||||
'DMChannel',
|
'DMChannel',
|
||||||
'CategoryChannel',
|
'CategoryChannel',
|
||||||
'StoreChannel',
|
'StoreChannel',
|
||||||
@ -148,6 +149,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
|||||||
.. versionadded:: 1.5.0.2"""
|
.. versionadded:: 1.5.0.2"""
|
||||||
return self.permissions_for(self.guild.me).send_messages
|
return self.permissions_for(self.guild.me).send_messages
|
||||||
|
|
||||||
|
@utils.copy_doc(discord.abc.GuildChannel.permissions_for)
|
||||||
def permissions_for(self, member):
|
def permissions_for(self, member):
|
||||||
base = super().permissions_for(member)
|
base = super().permissions_for(member)
|
||||||
|
|
||||||
@ -156,8 +158,6 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
|||||||
base.value &= ~denied.value
|
base.value &= ~denied.value
|
||||||
return base
|
return base
|
||||||
|
|
||||||
permissions_for.__doc__ = discord.abc.GuildChannel.permissions_for.__doc__
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def members(self):
|
def members(self):
|
||||||
"""List[:class:`Member`]: Returns all members that can see this channel."""
|
"""List[:class:`Member`]: Returns all members that can see this channel."""
|
||||||
@ -247,6 +247,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
|||||||
"""
|
"""
|
||||||
await self._edit(options, reason=reason)
|
await self._edit(options, reason=reason)
|
||||||
|
|
||||||
|
@utils.copy_doc(discord.abc.GuildChannel.clone)
|
||||||
async def clone(self, *, name=None, reason=None):
|
async def clone(self, *, name=None, reason=None):
|
||||||
return await self._clone_impl({
|
return await self._clone_impl({
|
||||||
'topic': self.topic,
|
'topic': self.topic,
|
||||||
@ -254,8 +255,6 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
|||||||
'rate_limit_per_user': self.slowmode_delay
|
'rate_limit_per_user': self.slowmode_delay
|
||||||
}, name=name, reason=reason)
|
}, name=name, reason=reason)
|
||||||
|
|
||||||
clone.__doc__ = discord.abc.GuildChannel.clone.__doc__
|
|
||||||
|
|
||||||
async def delete_messages(self, messages):
|
async def delete_messages(self, messages):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@ -354,7 +353,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
|||||||
bulk: :class:`bool`
|
bulk: :class:`bool`
|
||||||
If ``True``, use bulk delete. Setting this to ``False`` is useful for mass-deleting
|
If ``True``, use bulk delete. Setting this to ``False`` is useful for mass-deleting
|
||||||
a bot's own messages without :attr:`Permissions.manage_messages`. When ``True``, will
|
a bot's own messages without :attr:`Permissions.manage_messages`. When ``True``, will
|
||||||
fall back to single delete if current account is a user bot, or if messages are
|
fall back to single delete if current account is a user bot (now deprecated), or if messages are
|
||||||
older than two weeks.
|
older than two weeks.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
@ -546,7 +545,80 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
|||||||
from .message import PartialMessage
|
from .message import PartialMessage
|
||||||
return PartialMessage(channel=self, id=message_id)
|
return PartialMessage(channel=self, id=message_id)
|
||||||
|
|
||||||
class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
|
class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
|
||||||
|
__slots__ = ('name', 'id', 'guild', 'bitrate', 'user_limit',
|
||||||
|
'_state', 'position', '_overwrites', 'category_id',
|
||||||
|
'rtc_region')
|
||||||
|
|
||||||
|
def __init__(self, *, state, guild, data):
|
||||||
|
self._state = state
|
||||||
|
self.id = int(data['id'])
|
||||||
|
self._update(guild, data)
|
||||||
|
|
||||||
|
def _get_voice_client_key(self):
|
||||||
|
return self.guild.id, 'guild_id'
|
||||||
|
|
||||||
|
def _get_voice_state_pair(self):
|
||||||
|
return self.guild.id, self.id
|
||||||
|
|
||||||
|
def _update(self, guild, data):
|
||||||
|
self.guild = guild
|
||||||
|
self.name = data['name']
|
||||||
|
self.rtc_region = data.get('rtc_region')
|
||||||
|
if self.rtc_region:
|
||||||
|
self.rtc_region = try_enum(VoiceRegion, self.rtc_region)
|
||||||
|
self.category_id = utils._get_as_snowflake(data, 'parent_id')
|
||||||
|
self.position = data['position']
|
||||||
|
self.bitrate = data.get('bitrate')
|
||||||
|
self.user_limit = data.get('user_limit')
|
||||||
|
self._fill_overwrites(data)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _sorting_bucket(self):
|
||||||
|
return ChannelType.voice.value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def members(self):
|
||||||
|
"""List[:class:`Member`]: Returns all members that are currently inside this voice channel."""
|
||||||
|
ret = []
|
||||||
|
for user_id, state in self.guild._voice_states.items():
|
||||||
|
if state.channel and state.channel.id == self.id:
|
||||||
|
member = self.guild.get_member(user_id)
|
||||||
|
if member is not None:
|
||||||
|
ret.append(member)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
@property
|
||||||
|
def voice_states(self):
|
||||||
|
"""Returns a mapping of member IDs who have voice states in this channel.
|
||||||
|
|
||||||
|
.. versionadded:: 1.3
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This function is intentionally low level to replace :attr:`members`
|
||||||
|
when the member cache is unavailable.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
Mapping[:class:`int`, :class:`VoiceState`]
|
||||||
|
The mapping of member ID to a voice state.
|
||||||
|
"""
|
||||||
|
return {key: value for key, value in self.guild._voice_states.items() if value.channel.id == self.id}
|
||||||
|
|
||||||
|
@utils.copy_doc(discord.abc.GuildChannel.permissions_for)
|
||||||
|
def permissions_for(self, member):
|
||||||
|
base = super().permissions_for(member)
|
||||||
|
|
||||||
|
# voice channels cannot be edited by people who can't connect to them
|
||||||
|
# It also implicitly denies all other voice perms
|
||||||
|
if not base.connect:
|
||||||
|
denied = Permissions.voice()
|
||||||
|
denied.update(manage_channels=True, manage_roles=True)
|
||||||
|
base.value &= ~denied.value
|
||||||
|
return base
|
||||||
|
|
||||||
|
class VoiceChannel(VocalGuildChannel):
|
||||||
"""Represents a Discord guild voice channel.
|
"""Represents a Discord guild voice channel.
|
||||||
|
|
||||||
.. container:: operations
|
.. container:: operations
|
||||||
@ -584,20 +656,20 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
|
|||||||
The channel's preferred audio bitrate in bits per second.
|
The channel's preferred audio bitrate in bits per second.
|
||||||
user_limit: :class:`int`
|
user_limit: :class:`int`
|
||||||
The channel's limit for number of members that can be in a voice channel.
|
The channel's limit for number of members that can be in a voice channel.
|
||||||
|
rtc_region: Optional[:class:`VoiceRegion`]
|
||||||
|
The region for the voice channel's voice communication.
|
||||||
|
A value of ``None`` indicates automatic voice region detection.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('name', 'id', 'guild', 'bitrate', 'user_limit',
|
__slots__ = ()
|
||||||
'_state', 'position', '_overwrites', 'category_id')
|
|
||||||
|
|
||||||
def __init__(self, *, state, guild, data):
|
|
||||||
self._state = state
|
|
||||||
self.id = int(data['id'])
|
|
||||||
self._update(guild, data)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
attrs = [
|
attrs = [
|
||||||
('id', self.id),
|
('id', self.id),
|
||||||
('name', self.name),
|
('name', self.name),
|
||||||
|
('rtc_region', self.rtc_region),
|
||||||
('position', self.position),
|
('position', self.position),
|
||||||
('bitrate', self.bitrate),
|
('bitrate', self.bitrate),
|
||||||
('user_limit', self.user_limit),
|
('user_limit', self.user_limit),
|
||||||
@ -605,80 +677,18 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
|
|||||||
]
|
]
|
||||||
return '<%s %s>' % (self.__class__.__name__, ' '.join('%s=%r' % t for t in attrs))
|
return '<%s %s>' % (self.__class__.__name__, ' '.join('%s=%r' % t for t in attrs))
|
||||||
|
|
||||||
def _get_voice_client_key(self):
|
|
||||||
return self.guild.id, 'guild_id'
|
|
||||||
|
|
||||||
def _get_voice_state_pair(self):
|
|
||||||
return self.guild.id, self.id
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self):
|
||||||
""":class:`ChannelType`: The channel's Discord type."""
|
""":class:`ChannelType`: The channel's Discord type."""
|
||||||
return ChannelType.voice
|
return ChannelType.voice
|
||||||
|
|
||||||
def _update(self, guild, data):
|
@utils.copy_doc(discord.abc.GuildChannel.clone)
|
||||||
self.guild = guild
|
|
||||||
self.name = data['name']
|
|
||||||
self.category_id = utils._get_as_snowflake(data, 'parent_id')
|
|
||||||
self.position = data['position']
|
|
||||||
self.bitrate = data.get('bitrate')
|
|
||||||
self.user_limit = data.get('user_limit')
|
|
||||||
self._fill_overwrites(data)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _sorting_bucket(self):
|
|
||||||
return ChannelType.voice.value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def members(self):
|
|
||||||
"""List[:class:`Member`]: Returns all members that are currently inside this voice channel."""
|
|
||||||
ret = []
|
|
||||||
for user_id, state in self.guild._voice_states.items():
|
|
||||||
if state.channel.id == self.id:
|
|
||||||
member = self.guild.get_member(user_id)
|
|
||||||
if member is not None:
|
|
||||||
ret.append(member)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
@property
|
|
||||||
def voice_states(self):
|
|
||||||
"""Returns a mapping of member IDs who have voice states in this channel.
|
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
This function is intentionally low level to replace :attr:`members`
|
|
||||||
when the member cache is unavailable.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
--------
|
|
||||||
Mapping[:class:`int`, :class:`VoiceState`]
|
|
||||||
The mapping of member ID to a voice state.
|
|
||||||
"""
|
|
||||||
return {key: value for key, value in self.guild._voice_states.items() if value.channel.id == self.id}
|
|
||||||
|
|
||||||
def permissions_for(self, member):
|
|
||||||
base = super().permissions_for(member)
|
|
||||||
|
|
||||||
# voice channels cannot be edited by people who can't connect to them
|
|
||||||
# It also implicitly denies all other voice perms
|
|
||||||
if not base.connect:
|
|
||||||
denied = Permissions.voice()
|
|
||||||
denied.update(manage_channels=True, manage_roles=True)
|
|
||||||
base.value &= ~denied.value
|
|
||||||
return base
|
|
||||||
|
|
||||||
permissions_for.__doc__ = discord.abc.GuildChannel.permissions_for.__doc__
|
|
||||||
|
|
||||||
async def clone(self, *, name=None, reason=None):
|
async def clone(self, *, name=None, reason=None):
|
||||||
return await self._clone_impl({
|
return await self._clone_impl({
|
||||||
'bitrate': self.bitrate,
|
'bitrate': self.bitrate,
|
||||||
'user_limit': self.user_limit
|
'user_limit': self.user_limit
|
||||||
}, name=name, reason=reason)
|
}, name=name, reason=reason)
|
||||||
|
|
||||||
clone.__doc__ = discord.abc.GuildChannel.clone.__doc__
|
|
||||||
|
|
||||||
async def edit(self, *, reason=None, **options):
|
async def edit(self, *, reason=None, **options):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@ -711,6 +721,135 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
|
|||||||
overwrites: :class:`dict`
|
overwrites: :class:`dict`
|
||||||
A :class:`dict` of target (either a role or a member) to
|
A :class:`dict` of target (either a role or a member) to
|
||||||
:class:`PermissionOverwrite` to apply to the channel.
|
:class:`PermissionOverwrite` to apply to the channel.
|
||||||
|
rtc_region: Optional[:class:`VoiceRegion`]
|
||||||
|
The new region for the voice channel's voice communication.
|
||||||
|
A value of ``None`` indicates automatic voice region detection.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
InvalidArgument
|
||||||
|
If the permission overwrite information is not in proper form.
|
||||||
|
Forbidden
|
||||||
|
You do not have permissions to edit the channel.
|
||||||
|
HTTPException
|
||||||
|
Editing the channel failed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
await self._edit(options, reason=reason)
|
||||||
|
|
||||||
|
class StageChannel(VocalGuildChannel):
|
||||||
|
"""Represents a Discord guild stage channel.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
.. container:: operations
|
||||||
|
|
||||||
|
.. describe:: x == y
|
||||||
|
|
||||||
|
Checks if two channels are equal.
|
||||||
|
|
||||||
|
.. describe:: x != y
|
||||||
|
|
||||||
|
Checks if two channels are not equal.
|
||||||
|
|
||||||
|
.. describe:: hash(x)
|
||||||
|
|
||||||
|
Returns the channel's hash.
|
||||||
|
|
||||||
|
.. describe:: str(x)
|
||||||
|
|
||||||
|
Returns the channel's name.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
-----------
|
||||||
|
name: :class:`str`
|
||||||
|
The channel name.
|
||||||
|
guild: :class:`Guild`
|
||||||
|
The guild the channel belongs to.
|
||||||
|
id: :class:`int`
|
||||||
|
The channel ID.
|
||||||
|
topic: Optional[:class:`str`]
|
||||||
|
The channel's topic. ``None`` if it isn't set.
|
||||||
|
category_id: Optional[:class:`int`]
|
||||||
|
The category channel ID this channel belongs to, if applicable.
|
||||||
|
position: :class:`int`
|
||||||
|
The position in the channel list. This is a number that starts at 0. e.g. the
|
||||||
|
top channel is position 0.
|
||||||
|
bitrate: :class:`int`
|
||||||
|
The channel's preferred audio bitrate in bits per second.
|
||||||
|
user_limit: :class:`int`
|
||||||
|
The channel's limit for number of members that can be in a stage channel.
|
||||||
|
rtc_region: Optional[:class:`VoiceRegion`]
|
||||||
|
The region for the stage channel's voice communication.
|
||||||
|
A value of ``None`` indicates automatic voice region detection.
|
||||||
|
"""
|
||||||
|
__slots__ = ('topic',)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
attrs = [
|
||||||
|
('id', self.id),
|
||||||
|
('name', self.name),
|
||||||
|
('topic', self.topic),
|
||||||
|
('rtc_region', self.rtc_region),
|
||||||
|
('position', self.position),
|
||||||
|
('bitrate', self.bitrate),
|
||||||
|
('user_limit', self.user_limit),
|
||||||
|
('category_id', self.category_id)
|
||||||
|
]
|
||||||
|
return '<%s %s>' % (self.__class__.__name__, ' '.join('%s=%r' % t for t in attrs))
|
||||||
|
|
||||||
|
def _update(self, guild, data):
|
||||||
|
super()._update(guild, data)
|
||||||
|
self.topic = data.get('topic')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def requesting_to_speak(self):
|
||||||
|
"""List[:class:`Member`]: A list of members who are requesting to speak in the stage channel."""
|
||||||
|
return [member for member in self.members if member.voice.requested_to_speak_at is not None]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self):
|
||||||
|
""":class:`ChannelType`: The channel's Discord type."""
|
||||||
|
return ChannelType.stage_voice
|
||||||
|
|
||||||
|
@utils.copy_doc(discord.abc.GuildChannel.clone)
|
||||||
|
async def clone(self, *, name=None, reason=None):
|
||||||
|
return await self._clone_impl({
|
||||||
|
'topic': self.topic,
|
||||||
|
}, name=name, reason=reason)
|
||||||
|
|
||||||
|
async def edit(self, *, reason=None, **options):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Edits the channel.
|
||||||
|
|
||||||
|
You must have the :attr:`~Permissions.manage_channels` permission to
|
||||||
|
use this.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
name: :class:`str`
|
||||||
|
The new channel's name.
|
||||||
|
topic: :class:`str`
|
||||||
|
The new channel's topic.
|
||||||
|
position: :class:`int`
|
||||||
|
The new channel's position.
|
||||||
|
sync_permissions: :class:`bool`
|
||||||
|
Whether to sync permissions with the channel's new or pre-existing
|
||||||
|
category. Defaults to ``False``.
|
||||||
|
category: Optional[:class:`CategoryChannel`]
|
||||||
|
The new category for this channel. Can be ``None`` to remove the
|
||||||
|
category.
|
||||||
|
reason: Optional[:class:`str`]
|
||||||
|
The reason for editing this channel. Shows up on the audit log.
|
||||||
|
overwrites: :class:`dict`
|
||||||
|
A :class:`dict` of target (either a role or a member) to
|
||||||
|
:class:`PermissionOverwrite` to apply to the channel.
|
||||||
|
rtc_region: Optional[:class:`VoiceRegion`]
|
||||||
|
The new region for the stage channel's voice communication.
|
||||||
|
A value of ``None`` indicates automatic voice region detection.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
@ -791,13 +930,12 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
|
|||||||
""":class:`bool`: Checks if the category is NSFW."""
|
""":class:`bool`: Checks if the category is NSFW."""
|
||||||
return self.nsfw
|
return self.nsfw
|
||||||
|
|
||||||
|
@utils.copy_doc(discord.abc.GuildChannel.clone)
|
||||||
async def clone(self, *, name=None, reason=None):
|
async def clone(self, *, name=None, reason=None):
|
||||||
return await self._clone_impl({
|
return await self._clone_impl({
|
||||||
'nsfw': self.nsfw
|
'nsfw': self.nsfw
|
||||||
}, name=name, reason=reason)
|
}, name=name, reason=reason)
|
||||||
|
|
||||||
clone.__doc__ = discord.abc.GuildChannel.clone.__doc__
|
|
||||||
|
|
||||||
async def edit(self, *, reason=None, **options):
|
async def edit(self, *, reason=None, **options):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@ -835,6 +973,11 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
|
|||||||
|
|
||||||
await self._edit(options=options, reason=reason)
|
await self._edit(options=options, reason=reason)
|
||||||
|
|
||||||
|
@utils.copy_doc(discord.abc.GuildChannel.move)
|
||||||
|
async def move(self, **kwargs):
|
||||||
|
kwargs.pop('category', None)
|
||||||
|
await super().move(**kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def channels(self):
|
def channels(self):
|
||||||
"""List[:class:`abc.GuildChannel`]: Returns the channels that are under this category.
|
"""List[:class:`abc.GuildChannel`]: Returns the channels that are under this category.
|
||||||
@ -866,6 +1009,18 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
|
|||||||
ret.sort(key=lambda c: (c.position, c.id))
|
ret.sort(key=lambda c: (c.position, c.id))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stage_channels(self):
|
||||||
|
"""List[:class:`StageChannel`]: Returns the voice channels that are under this category.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
"""
|
||||||
|
ret = [c for c in self.guild.channels
|
||||||
|
if c.category_id == self.id
|
||||||
|
and isinstance(c, StageChannel)]
|
||||||
|
ret.sort(key=lambda c: (c.position, c.id))
|
||||||
|
return ret
|
||||||
|
|
||||||
async def create_text_channel(self, name, *, overwrites=None, reason=None, **options):
|
async def create_text_channel(self, name, *, overwrites=None, reason=None, **options):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@ -890,6 +1045,20 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
|
|||||||
"""
|
"""
|
||||||
return await self.guild.create_voice_channel(name, overwrites=overwrites, category=self, reason=reason, **options)
|
return await self.guild.create_voice_channel(name, overwrites=overwrites, category=self, reason=reason, **options)
|
||||||
|
|
||||||
|
async def create_stage_channel(self, name, *, overwrites=None, reason=None, **options):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
A shortcut method to :meth:`Guild.create_stage_channel` to create a :class:`StageChannel` in the category.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:class:`StageChannel`
|
||||||
|
The channel that was just created.
|
||||||
|
"""
|
||||||
|
return await self.guild.create_stage_channel(name, overwrites=overwrites, category=self, reason=reason, **options)
|
||||||
|
|
||||||
class StoreChannel(discord.abc.GuildChannel, Hashable):
|
class StoreChannel(discord.abc.GuildChannel, Hashable):
|
||||||
"""Represents a Discord guild store channel.
|
"""Represents a Discord guild store channel.
|
||||||
|
|
||||||
@ -953,6 +1122,7 @@ class StoreChannel(discord.abc.GuildChannel, Hashable):
|
|||||||
""":class:`ChannelType`: The channel's Discord type."""
|
""":class:`ChannelType`: The channel's Discord type."""
|
||||||
return ChannelType.store
|
return ChannelType.store
|
||||||
|
|
||||||
|
@utils.copy_doc(discord.abc.GuildChannel.permissions_for)
|
||||||
def permissions_for(self, member):
|
def permissions_for(self, member):
|
||||||
base = super().permissions_for(member)
|
base = super().permissions_for(member)
|
||||||
|
|
||||||
@ -961,19 +1131,16 @@ class StoreChannel(discord.abc.GuildChannel, Hashable):
|
|||||||
base.value &= ~denied.value
|
base.value &= ~denied.value
|
||||||
return base
|
return base
|
||||||
|
|
||||||
permissions_for.__doc__ = discord.abc.GuildChannel.permissions_for.__doc__
|
|
||||||
|
|
||||||
def is_nsfw(self):
|
def is_nsfw(self):
|
||||||
""":class:`bool`: Checks if the channel is NSFW."""
|
""":class:`bool`: Checks if the channel is NSFW."""
|
||||||
return self.nsfw
|
return self.nsfw
|
||||||
|
|
||||||
|
@utils.copy_doc(discord.abc.GuildChannel.clone)
|
||||||
async def clone(self, *, name=None, reason=None):
|
async def clone(self, *, name=None, reason=None):
|
||||||
return await self._clone_impl({
|
return await self._clone_impl({
|
||||||
'nsfw': self.nsfw
|
'nsfw': self.nsfw
|
||||||
}, name=name, reason=reason)
|
}, name=name, reason=reason)
|
||||||
|
|
||||||
clone.__doc__ = discord.abc.GuildChannel.clone.__doc__
|
|
||||||
|
|
||||||
async def edit(self, *, reason=None, **options):
|
async def edit(self, *, reason=None, **options):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@ -1255,8 +1422,8 @@ class GroupChannel(discord.abc.Messageable, Hashable):
|
|||||||
|
|
||||||
This returns all the Text related permissions set to ``True`` except:
|
This returns all the Text related permissions set to ``True`` except:
|
||||||
|
|
||||||
- send_tts_messages: You cannot send TTS messages in a DM.
|
- :attr:`~Permissions.send_tts_messages`: You cannot send TTS messages in a DM.
|
||||||
- manage_messages: You cannot delete others messages in a DM.
|
- :attr:`~Permissions.manage_messages`: You cannot delete others messages in a DM.
|
||||||
|
|
||||||
This also checks the kick_members permission if the user is the owner.
|
This also checks the kick_members permission if the user is the owner.
|
||||||
|
|
||||||
@ -1281,6 +1448,7 @@ class GroupChannel(discord.abc.Messageable, Hashable):
|
|||||||
|
|
||||||
return base
|
return base
|
||||||
|
|
||||||
|
@utils.deprecated()
|
||||||
async def add_recipients(self, *recipients):
|
async def add_recipients(self, *recipients):
|
||||||
r"""|coro|
|
r"""|coro|
|
||||||
|
|
||||||
@ -1291,6 +1459,8 @@ class GroupChannel(discord.abc.Messageable, Hashable):
|
|||||||
add a recipient to the group, you must have a relationship
|
add a recipient to the group, you must have a relationship
|
||||||
with the user of type :attr:`RelationshipType.friend`.
|
with the user of type :attr:`RelationshipType.friend`.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
\*recipients: :class:`User`
|
\*recipients: :class:`User`
|
||||||
@ -1308,11 +1478,14 @@ class GroupChannel(discord.abc.Messageable, Hashable):
|
|||||||
for recipient in recipients:
|
for recipient in recipients:
|
||||||
await req(self.id, recipient.id)
|
await req(self.id, recipient.id)
|
||||||
|
|
||||||
|
@utils.deprecated()
|
||||||
async def remove_recipients(self, *recipients):
|
async def remove_recipients(self, *recipients):
|
||||||
r"""|coro|
|
r"""|coro|
|
||||||
|
|
||||||
Removes recipients from this group.
|
Removes recipients from this group.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
\*recipients: :class:`User`
|
\*recipients: :class:`User`
|
||||||
@ -1330,11 +1503,14 @@ class GroupChannel(discord.abc.Messageable, Hashable):
|
|||||||
for recipient in recipients:
|
for recipient in recipients:
|
||||||
await req(self.id, recipient.id)
|
await req(self.id, recipient.id)
|
||||||
|
|
||||||
|
@utils.deprecated()
|
||||||
async def edit(self, **fields):
|
async def edit(self, **fields):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Edits the group.
|
Edits the group.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
name: Optional[:class:`str`]
|
name: Optional[:class:`str`]
|
||||||
@ -1392,5 +1568,7 @@ def _channel_factory(channel_type):
|
|||||||
return TextChannel, value
|
return TextChannel, value
|
||||||
elif value is ChannelType.store:
|
elif value is ChannelType.store:
|
||||||
return StoreChannel, value
|
return StoreChannel, value
|
||||||
|
elif value is ChannelType.stage_voice:
|
||||||
|
return StageChannel, value
|
||||||
else:
|
else:
|
||||||
return None, value
|
return None, value
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -576,6 +576,8 @@ class Client:
|
|||||||
Keyword argument that specifies if the account logging on is a bot
|
Keyword argument that specifies if the account logging on is a bot
|
||||||
token or not.
|
token or not.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
:exc:`.LoginFailure`
|
:exc:`.LoginFailure`
|
||||||
@ -590,10 +592,13 @@ class Client:
|
|||||||
await self.http.static_login(token.strip(), bot=bot)
|
await self.http.static_login(token.strip(), bot=bot)
|
||||||
self._connection.is_bot = bot
|
self._connection.is_bot = bot
|
||||||
|
|
||||||
|
@utils.deprecated('Client.close')
|
||||||
async def logout(self):
|
async def logout(self):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Logs out of Discord and closes all connections.
|
Logs out of Discord and closes all connections.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@ -754,7 +759,7 @@ class Client:
|
|||||||
try:
|
try:
|
||||||
loop.run_until_complete(start(*args, **kwargs))
|
loop.run_until_complete(start(*args, **kwargs))
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
loop.run_until_complete(logout())
|
loop.run_until_complete(close())
|
||||||
# cancel all tasks lingering
|
# cancel all tasks lingering
|
||||||
finally:
|
finally:
|
||||||
loop.close()
|
loop.close()
|
||||||
@ -833,16 +838,14 @@ class Client:
|
|||||||
|
|
||||||
@allowed_mentions.setter
|
@allowed_mentions.setter
|
||||||
def allowed_mentions(self, value):
|
def allowed_mentions(self, value):
|
||||||
if value is None:
|
if value is None or isinstance(value, AllowedMentions):
|
||||||
self._connection.allowed_mentions = value
|
|
||||||
elif isinstance(value, AllowedMentions):
|
|
||||||
self._connection.allowed_mentions = value
|
self._connection.allowed_mentions = value
|
||||||
else:
|
else:
|
||||||
raise TypeError('allowed_mentions must be AllowedMentions not {0.__class__!r}'.format(value))
|
raise TypeError('allowed_mentions must be AllowedMentions not {0.__class__!r}'.format(value))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def intents(self):
|
def intents(self):
|
||||||
""":class:`Intents`: The intents configured for this connection.
|
""":class:`~discord.Intents`: The intents configured for this connection.
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
.. versionadded:: 1.5
|
||||||
"""
|
"""
|
||||||
@ -1154,9 +1157,7 @@ class Client:
|
|||||||
# Guild stuff
|
# Guild stuff
|
||||||
|
|
||||||
def fetch_guilds(self, *, limit=100, before=None, after=None):
|
def fetch_guilds(self, *, limit=100, before=None, after=None):
|
||||||
"""|coro|
|
"""Retrieves an :class:`.AsyncIterator` that enables receiving your guilds.
|
||||||
|
|
||||||
Retrieves an :class:`.AsyncIterator` that enables receiving your guilds.
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@ -1306,15 +1307,13 @@ class Client:
|
|||||||
if icon is not None:
|
if icon is not None:
|
||||||
icon = utils._bytes_to_base64_data(icon)
|
icon = utils._bytes_to_base64_data(icon)
|
||||||
|
|
||||||
if region is None:
|
region = region or VoiceRegion.us_west
|
||||||
region = VoiceRegion.us_west.value
|
region_value = region.value
|
||||||
else:
|
|
||||||
region = region.value
|
|
||||||
|
|
||||||
if code:
|
if code:
|
||||||
data = await self.http.create_from_template(code, name, region, icon)
|
data = await self.http.create_from_template(code, name, region_value, icon)
|
||||||
else:
|
else:
|
||||||
data = await self.http.create_guild(name, region, icon)
|
data = await self.http.create_guild(name, region_value, icon)
|
||||||
return Guild(data=data, state=self._connection)
|
return Guild(data=data, state=self._connection)
|
||||||
|
|
||||||
# Invite management
|
# Invite management
|
||||||
@ -1480,7 +1479,7 @@ class Client:
|
|||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This method is an API call. For general usage, consider :meth:`get_user` instead.
|
This method is an API call. If you have :attr:`Intents.members` and member cache enabled, consider :meth:`get_user` instead.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
@ -1502,11 +1501,14 @@ class Client:
|
|||||||
data = await self.http.get_user(user_id)
|
data = await self.http.get_user(user_id)
|
||||||
return User(state=self._connection, data=data)
|
return User(state=self._connection, data=data)
|
||||||
|
|
||||||
|
@utils.deprecated()
|
||||||
async def fetch_user_profile(self, user_id):
|
async def fetch_user_profile(self, user_id):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Gets an arbitrary user's profile.
|
Gets an arbitrary user's profile.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This can only be used by non-bot accounts.
|
This can only be used by non-bot accounts.
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -119,18 +119,32 @@ class Colour:
|
|||||||
rgb = colorsys.hsv_to_rgb(h, s, v)
|
rgb = colorsys.hsv_to_rgb(h, s, v)
|
||||||
return cls.from_rgb(*(int(x * 255) for x in rgb))
|
return cls.from_rgb(*(int(x * 255) for x in rgb))
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def random(cls):
|
|
||||||
"""A factory method that returns a :class:`Colour` with a random value.
|
|
||||||
|
|
||||||
.. versionadded:: 1.6"""
|
|
||||||
return cls(random.randint(0x000000,0xffffff))
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default(cls):
|
def default(cls):
|
||||||
"""A factory method that returns a :class:`Colour` with a value of ``0``."""
|
"""A factory method that returns a :class:`Colour` with a value of ``0``."""
|
||||||
return cls(0)
|
return cls(0)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def random(cls, *, seed=None):
|
||||||
|
"""A factory method that returns a :class:`Colour` with a random hue.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The random algorithm works by choosing a colour with a random hue but
|
||||||
|
with maxed out saturation and value.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
seed: Optional[Union[:class:`int`, :class:`str`, :class:`float`, :class:`bytes`, :class:`bytearray`]]
|
||||||
|
The seed to initialize the RNG with. If ``None`` is passed the default RNG is used.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
"""
|
||||||
|
rand = random if seed is None else random.Random(seed)
|
||||||
|
return cls.from_hsv(rand.random(), 1, 1)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def teal(cls):
|
def teal(cls):
|
||||||
"""A factory method that returns a :class:`Colour` with a value of ``0x1abc9c``."""
|
"""A factory method that returns a :class:`Colour` with a value of ``0x1abc9c``."""
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -82,6 +82,8 @@ class Embed:
|
|||||||
type: :class:`str`
|
type: :class:`str`
|
||||||
The type of embed. Usually "rich".
|
The type of embed. Usually "rich".
|
||||||
This can be set during initialisation.
|
This can be set during initialisation.
|
||||||
|
Possible strings for embed types can be found on discord's
|
||||||
|
`api docs <https://discord.com/developers/docs/resources/channel#embed-object-embed-types>`_
|
||||||
description: :class:`str`
|
description: :class:`str`
|
||||||
The description of the embed.
|
The description of the embed.
|
||||||
This can be set during initialisation.
|
This can be set during initialisation.
|
||||||
@ -120,6 +122,15 @@ class Embed:
|
|||||||
self.url = kwargs.get('url', EmptyEmbed)
|
self.url = kwargs.get('url', EmptyEmbed)
|
||||||
self.description = kwargs.get('description', EmptyEmbed)
|
self.description = kwargs.get('description', EmptyEmbed)
|
||||||
|
|
||||||
|
if self.title is not EmptyEmbed:
|
||||||
|
self.title = str(self.title)
|
||||||
|
|
||||||
|
if self.description is not EmptyEmbed:
|
||||||
|
self.description = str(self.description)
|
||||||
|
|
||||||
|
if self.url is not EmptyEmbed:
|
||||||
|
self.url = str(self.url)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
timestamp = kwargs['timestamp']
|
timestamp = kwargs['timestamp']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -153,6 +164,15 @@ class Embed:
|
|||||||
self.description = data.get('description', EmptyEmbed)
|
self.description = data.get('description', EmptyEmbed)
|
||||||
self.url = data.get('url', EmptyEmbed)
|
self.url = data.get('url', EmptyEmbed)
|
||||||
|
|
||||||
|
if self.title is not EmptyEmbed:
|
||||||
|
self.title = str(self.title)
|
||||||
|
|
||||||
|
if self.description is not EmptyEmbed:
|
||||||
|
self.description = str(self.description)
|
||||||
|
|
||||||
|
if self.url is not EmptyEmbed:
|
||||||
|
self.url = str(self.url)
|
||||||
|
|
||||||
# try to fill in the more rich fields
|
# try to fill in the more rich fields
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -422,7 +442,7 @@ class Embed:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def fields(self):
|
def fields(self):
|
||||||
"""Union[List[:class:`EmbedProxy`], :attr:`Empty`]: Returns a :class:`list` of ``EmbedProxy`` denoting the field contents.
|
"""List[Union[``EmbedProxy``, :attr:`Empty`]]: Returns a :class:`list` of ``EmbedProxy`` denoting the field contents.
|
||||||
|
|
||||||
See :meth:`add_field` for possible values you can access.
|
See :meth:`add_field` for possible values you can access.
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -158,24 +158,30 @@ class ChannelType(Enum):
|
|||||||
category = 4
|
category = 4
|
||||||
news = 5
|
news = 5
|
||||||
store = 6
|
store = 6
|
||||||
|
stage_voice = 13
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
class MessageType(Enum):
|
class MessageType(Enum):
|
||||||
default = 0
|
default = 0
|
||||||
recipient_add = 1
|
recipient_add = 1
|
||||||
recipient_remove = 2
|
recipient_remove = 2
|
||||||
call = 3
|
call = 3
|
||||||
channel_name_change = 4
|
channel_name_change = 4
|
||||||
channel_icon_change = 5
|
channel_icon_change = 5
|
||||||
pins_add = 6
|
pins_add = 6
|
||||||
new_member = 7
|
new_member = 7
|
||||||
premium_guild_subscription = 8
|
premium_guild_subscription = 8
|
||||||
premium_guild_tier_1 = 9
|
premium_guild_tier_1 = 9
|
||||||
premium_guild_tier_2 = 10
|
premium_guild_tier_2 = 10
|
||||||
premium_guild_tier_3 = 11
|
premium_guild_tier_3 = 11
|
||||||
channel_follow_add = 12
|
channel_follow_add = 12
|
||||||
|
guild_stream = 13
|
||||||
|
guild_discovery_disqualified = 14
|
||||||
|
guild_discovery_requalified = 15
|
||||||
|
guild_discovery_grace_period_initial_warning = 16
|
||||||
|
guild_discovery_grace_period_final_warning = 17
|
||||||
|
|
||||||
class VoiceRegion(Enum):
|
class VoiceRegion(Enum):
|
||||||
us_west = 'us-west'
|
us_west = 'us-west'
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -104,7 +104,7 @@ class HTTPException(DiscordException):
|
|||||||
|
|
||||||
fmt = '{0.status} {0.reason} (error code: {1})'
|
fmt = '{0.status} {0.reason} (error code: {1})'
|
||||||
if len(self.text):
|
if len(self.text):
|
||||||
fmt = fmt + ': {2}'
|
fmt += ': {2}'
|
||||||
|
|
||||||
super().__init__(fmt.format(self.response, self.code, self.text))
|
super().__init__(fmt.format(self.response, self.code, self.text))
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ discord.ext.commands
|
|||||||
|
|
||||||
An extension module to facilitate creation of bot commands.
|
An extension module to facilitate creation of bot commands.
|
||||||
|
|
||||||
:copyright: (c) 2015-2020 Rapptz
|
:copyright: (c) 2015-present Rapptz
|
||||||
:license: MIT, see LICENSE for more details.
|
:license: MIT, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -110,6 +110,7 @@ class BotBase(GroupMixin):
|
|||||||
self.description = inspect.cleandoc(description) if description else ''
|
self.description = inspect.cleandoc(description) if description else ''
|
||||||
self.owner_id = options.get('owner_id')
|
self.owner_id = options.get('owner_id')
|
||||||
self.owner_ids = options.get('owner_ids', set())
|
self.owner_ids = options.get('owner_ids', set())
|
||||||
|
self.strip_after_prefix = options.get('strip_after_prefix', False)
|
||||||
|
|
||||||
if self.owner_id and self.owner_ids:
|
if self.owner_id and self.owner_ids:
|
||||||
raise TypeError('Both owner_id and owner_ids are set.')
|
raise TypeError('Both owner_id and owner_ids are set.')
|
||||||
@ -190,9 +191,8 @@ class BotBase(GroupMixin):
|
|||||||
return
|
return
|
||||||
|
|
||||||
cog = context.cog
|
cog = context.cog
|
||||||
if cog:
|
if cog and Cog._get_overridden_method(cog.cog_command_error) is not None:
|
||||||
if Cog._get_overridden_method(cog.cog_command_error) is not None:
|
return
|
||||||
return
|
|
||||||
|
|
||||||
print('Ignoring exception in command {}:'.format(context.command), file=sys.stderr)
|
print('Ignoring exception in command {}:'.format(context.command), file=sys.stderr)
|
||||||
traceback.print_exception(type(exception), exception, exception.__traceback__, file=sys.stderr)
|
traceback.print_exception(type(exception), exception, exception.__traceback__, file=sys.stderr)
|
||||||
@ -656,7 +656,13 @@ class BotBase(GroupMixin):
|
|||||||
else:
|
else:
|
||||||
self.__extensions[key] = lib
|
self.__extensions[key] = lib
|
||||||
|
|
||||||
def load_extension(self, name):
|
def _resolve_name(self, name, package):
|
||||||
|
try:
|
||||||
|
return importlib.util.resolve_name(name, package)
|
||||||
|
except ImportError:
|
||||||
|
raise errors.ExtensionNotFound(name)
|
||||||
|
|
||||||
|
def load_extension(self, name, *, package=None):
|
||||||
"""Loads an extension.
|
"""Loads an extension.
|
||||||
|
|
||||||
An extension is a python module that contains commands, cogs, or
|
An extension is a python module that contains commands, cogs, or
|
||||||
@ -672,11 +678,19 @@ class BotBase(GroupMixin):
|
|||||||
The extension name to load. It must be dot separated like
|
The extension name to load. It must be dot separated like
|
||||||
regular Python imports if accessing a sub-module. e.g.
|
regular Python imports if accessing a sub-module. e.g.
|
||||||
``foo.test`` if you want to import ``foo/test.py``.
|
``foo.test`` if you want to import ``foo/test.py``.
|
||||||
|
package: Optional[:class:`str`]
|
||||||
|
The package name to resolve relative imports with.
|
||||||
|
This is required when loading an extension using a relative path, e.g ``.foo.test``.
|
||||||
|
Defaults to ``None``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
--------
|
--------
|
||||||
ExtensionNotFound
|
ExtensionNotFound
|
||||||
The extension could not be imported.
|
The extension could not be imported.
|
||||||
|
This is also raised if the name of the extension could not
|
||||||
|
be resolved using the provided ``package`` parameter.
|
||||||
ExtensionAlreadyLoaded
|
ExtensionAlreadyLoaded
|
||||||
The extension is already loaded.
|
The extension is already loaded.
|
||||||
NoEntryPointError
|
NoEntryPointError
|
||||||
@ -685,6 +699,7 @@ class BotBase(GroupMixin):
|
|||||||
The extension or its setup function had an execution error.
|
The extension or its setup function had an execution error.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
name = self._resolve_name(name, package)
|
||||||
if name in self.__extensions:
|
if name in self.__extensions:
|
||||||
raise errors.ExtensionAlreadyLoaded(name)
|
raise errors.ExtensionAlreadyLoaded(name)
|
||||||
|
|
||||||
@ -694,7 +709,7 @@ class BotBase(GroupMixin):
|
|||||||
|
|
||||||
self._load_from_module_spec(spec, name)
|
self._load_from_module_spec(spec, name)
|
||||||
|
|
||||||
def unload_extension(self, name):
|
def unload_extension(self, name, *, package=None):
|
||||||
"""Unloads an extension.
|
"""Unloads an extension.
|
||||||
|
|
||||||
When the extension is unloaded, all commands, listeners, and cogs are
|
When the extension is unloaded, all commands, listeners, and cogs are
|
||||||
@ -711,13 +726,23 @@ class BotBase(GroupMixin):
|
|||||||
The extension name to unload. It must be dot separated like
|
The extension name to unload. It must be dot separated like
|
||||||
regular Python imports if accessing a sub-module. e.g.
|
regular Python imports if accessing a sub-module. e.g.
|
||||||
``foo.test`` if you want to import ``foo/test.py``.
|
``foo.test`` if you want to import ``foo/test.py``.
|
||||||
|
package: Optional[:class:`str`]
|
||||||
|
The package name to resolve relative imports with.
|
||||||
|
This is required when unloading an extension using a relative path, e.g ``.foo.test``.
|
||||||
|
Defaults to ``None``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
|
ExtensionNotFound
|
||||||
|
The name of the extension could not
|
||||||
|
be resolved using the provided ``package`` parameter.
|
||||||
ExtensionNotLoaded
|
ExtensionNotLoaded
|
||||||
The extension was not loaded.
|
The extension was not loaded.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
name = self._resolve_name(name, package)
|
||||||
lib = self.__extensions.get(name)
|
lib = self.__extensions.get(name)
|
||||||
if lib is None:
|
if lib is None:
|
||||||
raise errors.ExtensionNotLoaded(name)
|
raise errors.ExtensionNotLoaded(name)
|
||||||
@ -725,7 +750,7 @@ class BotBase(GroupMixin):
|
|||||||
self._remove_module_references(lib.__name__)
|
self._remove_module_references(lib.__name__)
|
||||||
self._call_module_finalizers(lib, name)
|
self._call_module_finalizers(lib, name)
|
||||||
|
|
||||||
def reload_extension(self, name):
|
def reload_extension(self, name, *, package=None):
|
||||||
"""Atomically reloads an extension.
|
"""Atomically reloads an extension.
|
||||||
|
|
||||||
This replaces the extension with the same extension, only refreshed. This is
|
This replaces the extension with the same extension, only refreshed. This is
|
||||||
@ -739,6 +764,12 @@ class BotBase(GroupMixin):
|
|||||||
The extension name to reload. It must be dot separated like
|
The extension name to reload. It must be dot separated like
|
||||||
regular Python imports if accessing a sub-module. e.g.
|
regular Python imports if accessing a sub-module. e.g.
|
||||||
``foo.test`` if you want to import ``foo/test.py``.
|
``foo.test`` if you want to import ``foo/test.py``.
|
||||||
|
package: Optional[:class:`str`]
|
||||||
|
The package name to resolve relative imports with.
|
||||||
|
This is required when reloading an extension using a relative path, e.g ``.foo.test``.
|
||||||
|
Defaults to ``None``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
@ -746,12 +777,15 @@ class BotBase(GroupMixin):
|
|||||||
The extension was not loaded.
|
The extension was not loaded.
|
||||||
ExtensionNotFound
|
ExtensionNotFound
|
||||||
The extension could not be imported.
|
The extension could not be imported.
|
||||||
|
This is also raised if the name of the extension could not
|
||||||
|
be resolved using the provided ``package`` parameter.
|
||||||
NoEntryPointError
|
NoEntryPointError
|
||||||
The extension does not have a setup function.
|
The extension does not have a setup function.
|
||||||
ExtensionFailed
|
ExtensionFailed
|
||||||
The extension setup function had an execution error.
|
The extension setup function had an execution error.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
name = self._resolve_name(name, package)
|
||||||
lib = self.__extensions.get(name)
|
lib = self.__extensions.get(name)
|
||||||
if lib is None:
|
if lib is None:
|
||||||
raise errors.ExtensionNotLoaded(name)
|
raise errors.ExtensionNotLoaded(name)
|
||||||
@ -768,7 +802,7 @@ class BotBase(GroupMixin):
|
|||||||
self._remove_module_references(lib.__name__)
|
self._remove_module_references(lib.__name__)
|
||||||
self._call_module_finalizers(lib, name)
|
self._call_module_finalizers(lib, name)
|
||||||
self.load_extension(name)
|
self.load_extension(name)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
# if the load failed, the remnants should have been
|
# if the load failed, the remnants should have been
|
||||||
# cleaned from the load_extension function call
|
# cleaned from the load_extension function call
|
||||||
# so let's load it from our old compiled library.
|
# so let's load it from our old compiled library.
|
||||||
@ -920,6 +954,9 @@ class BotBase(GroupMixin):
|
|||||||
# Getting here shouldn't happen
|
# Getting here shouldn't happen
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
if self.strip_after_prefix:
|
||||||
|
view.skip_ws()
|
||||||
|
|
||||||
invoker = view.get_word()
|
invoker = view.get_word()
|
||||||
ctx.invoked_with = invoker
|
ctx.invoked_with = invoker
|
||||||
ctx.prefix = invoked_prefix
|
ctx.prefix = invoked_prefix
|
||||||
@ -1054,6 +1091,12 @@ class Bot(BotBase, discord.Client):
|
|||||||
for the collection. You cannot set both ``owner_id`` and ``owner_ids``.
|
for the collection. You cannot set both ``owner_id`` and ``owner_ids``.
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
|
strip_after_prefix: :class:`bool`
|
||||||
|
Whether to strip whitespace characters after encountering the command
|
||||||
|
prefix. This allows for ``! hello`` and ``!hello`` to both work if
|
||||||
|
the ``command_prefix`` is set to ``!``. Defaults to ``False``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -101,7 +101,7 @@ class CogMeta(type):
|
|||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
name, bases, attrs = args
|
name, bases, attrs = args
|
||||||
attrs['__cog_name__'] = kwargs.pop('name', name)
|
attrs['__cog_name__'] = kwargs.pop('name', name)
|
||||||
attrs['__cog_settings__'] = command_attrs = kwargs.pop('command_attrs', {})
|
attrs['__cog_settings__'] = kwargs.pop('command_attrs', {})
|
||||||
|
|
||||||
aliases = kwargs.pop('aliases', [])
|
aliases = kwargs.pop('aliases', [])
|
||||||
if not isinstance(aliases, list):
|
if not isinstance(aliases, list):
|
||||||
@ -136,7 +136,7 @@ class CogMeta(type):
|
|||||||
commands[elem] = value
|
commands[elem] = value
|
||||||
elif inspect.iscoroutinefunction(value):
|
elif inspect.iscoroutinefunction(value):
|
||||||
try:
|
try:
|
||||||
is_listener = getattr(value, '__cog_listener__')
|
getattr(value, '__cog_listener__')
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
@ -202,7 +202,7 @@ class Cog(metaclass=CogMeta):
|
|||||||
parent = lookup[parent.qualified_name]
|
parent = lookup[parent.qualified_name]
|
||||||
|
|
||||||
# Update our parent's reference to our self
|
# Update our parent's reference to our self
|
||||||
removed = parent.remove_command(command.name)
|
parent.remove_command(command.name)
|
||||||
parent.add_command(command)
|
parent.add_command(command)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
@ -306,6 +306,13 @@ class Cog(metaclass=CogMeta):
|
|||||||
return func
|
return func
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
def has_error_handler(self):
|
||||||
|
""":class:`bool`: Checks whether the cog has an error handler.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
"""
|
||||||
|
return not hasattr(self.cog_command_error.__func__, '__cog_special_method__')
|
||||||
|
|
||||||
@_cog_special_method
|
@_cog_special_method
|
||||||
def cog_unload(self):
|
def cog_unload(self):
|
||||||
"""A special method that is called when the cog gets removed.
|
"""A special method that is called when the cog gets removed.
|
||||||
@ -411,7 +418,8 @@ class Cog(metaclass=CogMeta):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
# undo our additions
|
# undo our additions
|
||||||
for to_undo in self.__cog_commands__[:index]:
|
for to_undo in self.__cog_commands__[:index]:
|
||||||
bot.remove_command(to_undo.name)
|
if to_undo.parent is None:
|
||||||
|
bot.remove_command(to_undo.name)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
# check if we're overriding the default
|
# check if we're overriding the default
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -58,6 +58,14 @@ class Context(discord.abc.Messageable):
|
|||||||
invoked_with: :class:`str`
|
invoked_with: :class:`str`
|
||||||
The command name that triggered this invocation. Useful for finding out
|
The command name that triggered this invocation. Useful for finding out
|
||||||
which alias called the command.
|
which alias called the command.
|
||||||
|
invoked_parents: List[:class:`str`]
|
||||||
|
The command names of the parents that triggered this invocation. Useful for
|
||||||
|
finding out which aliases called the command.
|
||||||
|
|
||||||
|
For example in commands ``?a b c test``, the invoked parents are ``['a', 'b', 'c']``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
invoked_subcommand: :class:`Command`
|
invoked_subcommand: :class:`Command`
|
||||||
The subcommand that was invoked.
|
The subcommand that was invoked.
|
||||||
If no valid subcommand was invoked then this is equal to ``None``.
|
If no valid subcommand was invoked then this is equal to ``None``.
|
||||||
@ -80,6 +88,7 @@ class Context(discord.abc.Messageable):
|
|||||||
self.command = attrs.pop('command', None)
|
self.command = attrs.pop('command', None)
|
||||||
self.view = attrs.pop('view', None)
|
self.view = attrs.pop('view', None)
|
||||||
self.invoked_with = attrs.pop('invoked_with', None)
|
self.invoked_with = attrs.pop('invoked_with', None)
|
||||||
|
self.invoked_parents = attrs.pop('invoked_parents', [])
|
||||||
self.invoked_subcommand = attrs.pop('invoked_subcommand', None)
|
self.invoked_subcommand = attrs.pop('invoked_subcommand', None)
|
||||||
self.subcommand_passed = attrs.pop('subcommand_passed', None)
|
self.subcommand_passed = attrs.pop('subcommand_passed', None)
|
||||||
self.command_failed = attrs.pop('command_failed', False)
|
self.command_failed = attrs.pop('command_failed', False)
|
||||||
@ -184,13 +193,15 @@ class Context(discord.abc.Messageable):
|
|||||||
index, previous = view.index, view.previous
|
index, previous = view.index, view.previous
|
||||||
invoked_with = self.invoked_with
|
invoked_with = self.invoked_with
|
||||||
invoked_subcommand = self.invoked_subcommand
|
invoked_subcommand = self.invoked_subcommand
|
||||||
|
invoked_parents = self.invoked_parents
|
||||||
subcommand_passed = self.subcommand_passed
|
subcommand_passed = self.subcommand_passed
|
||||||
|
|
||||||
if restart:
|
if restart:
|
||||||
to_call = cmd.root_parent or cmd
|
to_call = cmd.root_parent or cmd
|
||||||
view.index = len(self.prefix)
|
view.index = len(self.prefix)
|
||||||
view.previous = 0
|
view.previous = 0
|
||||||
view.get_word() # advance to get the root command
|
self.invoked_parents = []
|
||||||
|
self.invoked_with = view.get_word() # advance to get the root command
|
||||||
else:
|
else:
|
||||||
to_call = cmd
|
to_call = cmd
|
||||||
|
|
||||||
@ -202,6 +213,7 @@ class Context(discord.abc.Messageable):
|
|||||||
view.previous = previous
|
view.previous = previous
|
||||||
self.invoked_with = invoked_with
|
self.invoked_with = invoked_with
|
||||||
self.invoked_subcommand = invoked_subcommand
|
self.invoked_subcommand = invoked_subcommand
|
||||||
|
self.invoked_parents = invoked_parents
|
||||||
self.subcommand_passed = subcommand_passed
|
self.subcommand_passed = subcommand_passed
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -214,7 +226,7 @@ class Context(discord.abc.Messageable):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def cog(self):
|
def cog(self):
|
||||||
""":class:`.Cog`: Returns the cog associated with this context's command. None if it does not exist."""
|
"""Optional[:class:`.Cog`]: Returns the cog associated with this context's command. None if it does not exist."""
|
||||||
|
|
||||||
if self.command is None:
|
if self.command is None:
|
||||||
return None
|
return None
|
||||||
@ -227,8 +239,8 @@ class Context(discord.abc.Messageable):
|
|||||||
|
|
||||||
@discord.utils.cached_property
|
@discord.utils.cached_property
|
||||||
def channel(self):
|
def channel(self):
|
||||||
""":class:`.TextChannel`:
|
"""Union[:class:`.abc.Messageable`]: Returns the channel associated with this context's command.
|
||||||
Returns the channel associated with this context's command. Shorthand for :attr:`.Message.channel`.
|
Shorthand for :attr:`.Message.channel`.
|
||||||
"""
|
"""
|
||||||
return self.message.channel
|
return self.message.channel
|
||||||
|
|
||||||
@ -311,7 +323,7 @@ class Context(discord.abc.Messageable):
|
|||||||
entity = bot.get_cog(entity) or bot.get_command(entity)
|
entity = bot.get_cog(entity) or bot.get_command(entity)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
qualified_name = entity.qualified_name
|
entity.qualified_name
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# if we're here then it's not a cog, group, or command.
|
# if we're here then it's not a cog, group, or command.
|
||||||
return None
|
return None
|
||||||
@ -333,7 +345,6 @@ class Context(discord.abc.Messageable):
|
|||||||
except CommandError as e:
|
except CommandError as e:
|
||||||
await cmd.on_help_command_error(self, e)
|
await cmd.on_help_command_error(self, e)
|
||||||
|
|
||||||
|
@discord.utils.copy_doc(discord.Message.reply)
|
||||||
async def reply(self, content=None, **kwargs):
|
async def reply(self, content=None, **kwargs):
|
||||||
return await self.message.reply(content, **kwargs)
|
return await self.message.reply(content, **kwargs)
|
||||||
|
|
||||||
reply.__doc__ = discord.Message.reply.__doc__
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -37,17 +37,21 @@ __all__ = (
|
|||||||
'MemberConverter',
|
'MemberConverter',
|
||||||
'UserConverter',
|
'UserConverter',
|
||||||
'MessageConverter',
|
'MessageConverter',
|
||||||
|
'PartialMessageConverter',
|
||||||
'TextChannelConverter',
|
'TextChannelConverter',
|
||||||
'InviteConverter',
|
'InviteConverter',
|
||||||
|
'GuildConverter',
|
||||||
'RoleConverter',
|
'RoleConverter',
|
||||||
'GameConverter',
|
'GameConverter',
|
||||||
'ColourConverter',
|
'ColourConverter',
|
||||||
'ColorConverter',
|
'ColorConverter',
|
||||||
'VoiceChannelConverter',
|
'VoiceChannelConverter',
|
||||||
|
'StageChannelConverter',
|
||||||
'EmojiConverter',
|
'EmojiConverter',
|
||||||
'PartialEmojiConverter',
|
'PartialEmojiConverter',
|
||||||
'CategoryChannelConverter',
|
'CategoryChannelConverter',
|
||||||
'IDConverter',
|
'IDConverter',
|
||||||
|
'StoreChannelConverter',
|
||||||
'clean_content',
|
'clean_content',
|
||||||
'Greedy',
|
'Greedy',
|
||||||
)
|
)
|
||||||
@ -100,7 +104,7 @@ class Converter:
|
|||||||
|
|
||||||
class IDConverter(Converter):
|
class IDConverter(Converter):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._id_regex = re.compile(r'([0-9]{15,21})$')
|
self._id_regex = re.compile(r'([0-9]{15,20})$')
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def _get_id_match(self, argument):
|
def _get_id_match(self, argument):
|
||||||
@ -251,7 +255,38 @@ class UserConverter(IDConverter):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
class MessageConverter(Converter):
|
class PartialMessageConverter(Converter):
|
||||||
|
"""Converts to a :class:`discord.PartialMessage`.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
The creation strategy is as follows (in order):
|
||||||
|
|
||||||
|
1. By "{channel ID}-{message ID}" (retrieved by shift-clicking on "Copy ID")
|
||||||
|
2. By message ID (The message is assumed to be in the context channel.)
|
||||||
|
3. By message URL
|
||||||
|
"""
|
||||||
|
def _get_id_matches(self, argument):
|
||||||
|
id_regex = re.compile(r'(?:(?P<channel_id>[0-9]{15,20})-)?(?P<message_id>[0-9]{15,20})$')
|
||||||
|
link_regex = re.compile(
|
||||||
|
r'https?://(?:(ptb|canary|www)\.)?discord(?:app)?\.com/channels/'
|
||||||
|
r'(?:[0-9]{15,20}|@me)'
|
||||||
|
r'/(?P<channel_id>[0-9]{15,20})/(?P<message_id>[0-9]{15,20})/?$'
|
||||||
|
)
|
||||||
|
match = id_regex.match(argument) or link_regex.match(argument)
|
||||||
|
if not match:
|
||||||
|
raise MessageNotFound(argument)
|
||||||
|
channel_id = match.group("channel_id")
|
||||||
|
return int(match.group("message_id")), int(channel_id) if channel_id else None
|
||||||
|
|
||||||
|
async def convert(self, ctx, argument):
|
||||||
|
message_id, channel_id = self._get_id_matches(argument)
|
||||||
|
channel = ctx.bot.get_channel(channel_id) if channel_id else ctx.channel
|
||||||
|
if not channel:
|
||||||
|
raise ChannelNotFound(channel_id)
|
||||||
|
return discord.PartialMessage(channel=channel, id=message_id)
|
||||||
|
|
||||||
|
class MessageConverter(PartialMessageConverter):
|
||||||
"""Converts to a :class:`discord.Message`.
|
"""Converts to a :class:`discord.Message`.
|
||||||
|
|
||||||
.. versionadded:: 1.1
|
.. versionadded:: 1.1
|
||||||
@ -266,21 +301,11 @@ class MessageConverter(Converter):
|
|||||||
Raise :exc:`.ChannelNotFound`, :exc:`.MessageNotFound` or :exc:`.ChannelNotReadable` instead of generic :exc:`.BadArgument`
|
Raise :exc:`.ChannelNotFound`, :exc:`.MessageNotFound` or :exc:`.ChannelNotReadable` instead of generic :exc:`.BadArgument`
|
||||||
"""
|
"""
|
||||||
async def convert(self, ctx, argument):
|
async def convert(self, ctx, argument):
|
||||||
id_regex = re.compile(r'(?:(?P<channel_id>[0-9]{15,21})-)?(?P<message_id>[0-9]{15,21})$')
|
message_id, channel_id = self._get_id_matches(argument)
|
||||||
link_regex = re.compile(
|
|
||||||
r'https?://(?:(ptb|canary|www)\.)?discord(?:app)?\.com/channels/'
|
|
||||||
r'(?:[0-9]{15,21}|@me)'
|
|
||||||
r'/(?P<channel_id>[0-9]{15,21})/(?P<message_id>[0-9]{15,21})/?$'
|
|
||||||
)
|
|
||||||
match = id_regex.match(argument) or link_regex.match(argument)
|
|
||||||
if not match:
|
|
||||||
raise MessageNotFound(argument)
|
|
||||||
message_id = int(match.group("message_id"))
|
|
||||||
channel_id = match.group("channel_id")
|
|
||||||
message = ctx.bot._connection._get_message(message_id)
|
message = ctx.bot._connection._get_message(message_id)
|
||||||
if message:
|
if message:
|
||||||
return message
|
return message
|
||||||
channel = ctx.bot.get_channel(int(channel_id)) if channel_id else ctx.channel
|
channel = ctx.bot.get_channel(channel_id) if channel_id else ctx.channel
|
||||||
if not channel:
|
if not channel:
|
||||||
raise ChannelNotFound(channel_id)
|
raise ChannelNotFound(channel_id)
|
||||||
try:
|
try:
|
||||||
@ -373,6 +398,46 @@ class VoiceChannelConverter(IDConverter):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
class StageChannelConverter(IDConverter):
|
||||||
|
"""Converts to a :class:`~discord.StageChannel`.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
All lookups are via the local guild. If in a DM context, then the lookup
|
||||||
|
is done by the global cache.
|
||||||
|
|
||||||
|
The lookup strategy is as follows (in order):
|
||||||
|
|
||||||
|
1. Lookup by ID.
|
||||||
|
2. Lookup by mention.
|
||||||
|
3. Lookup by name
|
||||||
|
"""
|
||||||
|
async def convert(self, ctx, argument):
|
||||||
|
bot = ctx.bot
|
||||||
|
match = self._get_id_match(argument) or re.match(r'<#([0-9]+)>$', argument)
|
||||||
|
result = None
|
||||||
|
guild = ctx.guild
|
||||||
|
|
||||||
|
if match is None:
|
||||||
|
# not a mention
|
||||||
|
if guild:
|
||||||
|
result = discord.utils.get(guild.stage_channels, name=argument)
|
||||||
|
else:
|
||||||
|
def check(c):
|
||||||
|
return isinstance(c, discord.StageChannel) and c.name == argument
|
||||||
|
result = discord.utils.find(check, bot.get_all_channels())
|
||||||
|
else:
|
||||||
|
channel_id = int(match.group(1))
|
||||||
|
if guild:
|
||||||
|
result = guild.get_channel(channel_id)
|
||||||
|
else:
|
||||||
|
result = _get_from_guilds(bot, 'get_channel', channel_id)
|
||||||
|
|
||||||
|
if not isinstance(result, discord.StageChannel):
|
||||||
|
raise ChannelNotFound(argument)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
class CategoryChannelConverter(IDConverter):
|
class CategoryChannelConverter(IDConverter):
|
||||||
"""Converts to a :class:`~discord.CategoryChannel`.
|
"""Converts to a :class:`~discord.CategoryChannel`.
|
||||||
|
|
||||||
@ -415,6 +480,47 @@ class CategoryChannelConverter(IDConverter):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
class StoreChannelConverter(IDConverter):
|
||||||
|
"""Converts to a :class:`~discord.StoreChannel`.
|
||||||
|
|
||||||
|
All lookups are via the local guild. If in a DM context, then the lookup
|
||||||
|
is done by the global cache.
|
||||||
|
|
||||||
|
The lookup strategy is as follows (in order):
|
||||||
|
|
||||||
|
1. Lookup by ID.
|
||||||
|
2. Lookup by mention.
|
||||||
|
3. Lookup by name.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def convert(self, ctx, argument):
|
||||||
|
bot = ctx.bot
|
||||||
|
match = self._get_id_match(argument) or re.match(r'<#([0-9]+)>$', argument)
|
||||||
|
result = None
|
||||||
|
guild = ctx.guild
|
||||||
|
|
||||||
|
if match is None:
|
||||||
|
# not a mention
|
||||||
|
if guild:
|
||||||
|
result = discord.utils.get(guild.channels, name=argument)
|
||||||
|
else:
|
||||||
|
def check(c):
|
||||||
|
return isinstance(c, discord.StoreChannel) and c.name == argument
|
||||||
|
result = discord.utils.find(check, bot.get_all_channels())
|
||||||
|
else:
|
||||||
|
channel_id = int(match.group(1))
|
||||||
|
if guild:
|
||||||
|
result = guild.get_channel(channel_id)
|
||||||
|
else:
|
||||||
|
result = _get_from_guilds(bot, 'get_channel', channel_id)
|
||||||
|
|
||||||
|
if not isinstance(result, discord.StoreChannel):
|
||||||
|
raise ChannelNotFound(argument)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
class ColourConverter(Converter):
|
class ColourConverter(Converter):
|
||||||
"""Converts to a :class:`~discord.Colour`.
|
"""Converts to a :class:`~discord.Colour`.
|
||||||
|
|
||||||
@ -426,37 +532,84 @@ class ColourConverter(Converter):
|
|||||||
- ``0x<hex>``
|
- ``0x<hex>``
|
||||||
- ``#<hex>``
|
- ``#<hex>``
|
||||||
- ``0x#<hex>``
|
- ``0x#<hex>``
|
||||||
|
- ``rgb(<number>, <number>, <number>)``
|
||||||
- Any of the ``classmethod`` in :class:`Colour`
|
- Any of the ``classmethod`` in :class:`Colour`
|
||||||
|
|
||||||
- The ``_`` in the name can be optionally replaced with spaces.
|
- The ``_`` in the name can be optionally replaced with spaces.
|
||||||
|
|
||||||
|
Like CSS, ``<number>`` can be either 0-255 or 0-100% and ``<hex>`` can be
|
||||||
|
either a 6 digit hex number or a 3 digit hex shortcut (e.g. #fff).
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
.. versionchanged:: 1.5
|
||||||
Raise :exc:`.BadColourArgument` instead of generic :exc:`.BadArgument`
|
Raise :exc:`.BadColourArgument` instead of generic :exc:`.BadArgument`
|
||||||
"""
|
|
||||||
async def convert(self, ctx, argument):
|
|
||||||
arg = argument.replace('0x', '').lower()
|
|
||||||
|
|
||||||
if arg[0] == '#':
|
.. versionchanged:: 1.7
|
||||||
arg = arg[1:]
|
Added support for ``rgb`` function and 3-digit hex shortcuts
|
||||||
|
"""
|
||||||
|
|
||||||
|
RGB_REGEX = re.compile(r'rgb\s*\((?P<r>[0-9]{1,3}%?)\s*,\s*(?P<g>[0-9]{1,3}%?)\s*,\s*(?P<b>[0-9]{1,3}%?)\s*\)')
|
||||||
|
|
||||||
|
def parse_hex_number(self, argument):
|
||||||
|
arg = ''.join(i * 2 for i in argument) if len(argument) == 3 else argument
|
||||||
try:
|
try:
|
||||||
value = int(arg, base=16)
|
value = int(arg, base=16)
|
||||||
if not (0 <= value <= 0xFFFFFF):
|
if not (0 <= value <= 0xFFFFFF):
|
||||||
raise BadColourArgument(arg)
|
raise BadColourArgument(argument)
|
||||||
return discord.Colour(value=value)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
arg = arg.replace(' ', '_')
|
raise BadColourArgument(argument)
|
||||||
method = getattr(discord.Colour, arg, None)
|
else:
|
||||||
if arg.startswith('from_') or method is None or not inspect.ismethod(method):
|
return discord.Color(value=value)
|
||||||
raise BadColourArgument(arg)
|
|
||||||
return method()
|
def parse_rgb_number(self, argument, number):
|
||||||
|
if number[-1] == '%':
|
||||||
|
value = int(number[:-1])
|
||||||
|
if not (0 <= value <= 100):
|
||||||
|
raise BadColourArgument(argument)
|
||||||
|
return round(255 * (value / 100))
|
||||||
|
|
||||||
|
value = int(number)
|
||||||
|
if not (0 <= value <= 255):
|
||||||
|
raise BadColourArgument(argument)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def parse_rgb(self, argument, *, regex=RGB_REGEX):
|
||||||
|
match = regex.match(argument)
|
||||||
|
if match is None:
|
||||||
|
raise BadColourArgument(argument)
|
||||||
|
|
||||||
|
red = self.parse_rgb_number(argument, match.group('r'))
|
||||||
|
green = self.parse_rgb_number(argument, match.group('g'))
|
||||||
|
blue = self.parse_rgb_number(argument, match.group('b'))
|
||||||
|
return discord.Color.from_rgb(red, green, blue)
|
||||||
|
|
||||||
|
async def convert(self, ctx, argument):
|
||||||
|
if argument[0] == '#':
|
||||||
|
return self.parse_hex_number(argument[1:])
|
||||||
|
|
||||||
|
if argument[0:2] == '0x':
|
||||||
|
rest = argument[2:]
|
||||||
|
# Legacy backwards compatible syntax
|
||||||
|
if rest.startswith('#'):
|
||||||
|
return self.parse_hex_number(rest[1:])
|
||||||
|
return self.parse_hex_number(rest)
|
||||||
|
|
||||||
|
arg = argument.lower()
|
||||||
|
if arg[0:3] == 'rgb':
|
||||||
|
return self.parse_rgb(arg)
|
||||||
|
|
||||||
|
arg = arg.replace(' ', '_')
|
||||||
|
method = getattr(discord.Colour, arg, None)
|
||||||
|
if arg.startswith('from_') or method is None or not inspect.ismethod(method):
|
||||||
|
raise BadColourArgument(arg)
|
||||||
|
return method()
|
||||||
|
|
||||||
ColorConverter = ColourConverter
|
ColorConverter = ColourConverter
|
||||||
|
|
||||||
class RoleConverter(IDConverter):
|
class RoleConverter(IDConverter):
|
||||||
"""Converts to a :class:`~discord.Role`.
|
"""Converts to a :class:`~discord.Role`.
|
||||||
|
|
||||||
All lookups are via the local guild. If in a DM context, then the lookup
|
All lookups are via the local guild. If in a DM context, the converter raises
|
||||||
is done by the global cache.
|
:exc:`.NoPrivateMessage` exception.
|
||||||
|
|
||||||
The lookup strategy is as follows (in order):
|
The lookup strategy is as follows (in order):
|
||||||
|
|
||||||
@ -502,6 +655,32 @@ class InviteConverter(Converter):
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise BadInviteArgument() from exc
|
raise BadInviteArgument() from exc
|
||||||
|
|
||||||
|
class GuildConverter(IDConverter):
|
||||||
|
"""Converts to a :class:`~discord.Guild`.
|
||||||
|
|
||||||
|
The lookup strategy is as follows (in order):
|
||||||
|
|
||||||
|
1. Lookup by ID.
|
||||||
|
2. Lookup by name. (There is no disambiguation for Guilds with multiple matching names).
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def convert(self, ctx, argument):
|
||||||
|
match = self._get_id_match(argument)
|
||||||
|
result = None
|
||||||
|
|
||||||
|
if match is not None:
|
||||||
|
guild_id = int(match.group(1))
|
||||||
|
result = ctx.bot.get_guild(guild_id)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
result = discord.utils.get(ctx.bot.guilds, name=argument)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
raise GuildNotFound(argument)
|
||||||
|
return result
|
||||||
|
|
||||||
class EmojiConverter(IDConverter):
|
class EmojiConverter(IDConverter):
|
||||||
"""Converts to a :class:`~discord.Emoji`.
|
"""Converts to a :class:`~discord.Emoji`.
|
||||||
|
|
||||||
@ -580,11 +759,16 @@ class clean_content(Converter):
|
|||||||
Whether to use nicknames when transforming mentions.
|
Whether to use nicknames when transforming mentions.
|
||||||
escape_markdown: :class:`bool`
|
escape_markdown: :class:`bool`
|
||||||
Whether to also escape special markdown characters.
|
Whether to also escape special markdown characters.
|
||||||
|
remove_markdown: :class:`bool`
|
||||||
|
Whether to also remove special markdown characters. This option is not supported with ``escape_markdown``
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
"""
|
"""
|
||||||
def __init__(self, *, fix_channel_mentions=False, use_nicknames=True, escape_markdown=False):
|
def __init__(self, *, fix_channel_mentions=False, use_nicknames=True, escape_markdown=False, remove_markdown=False):
|
||||||
self.fix_channel_mentions = fix_channel_mentions
|
self.fix_channel_mentions = fix_channel_mentions
|
||||||
self.use_nicknames = use_nicknames
|
self.use_nicknames = use_nicknames
|
||||||
self.escape_markdown = escape_markdown
|
self.escape_markdown = escape_markdown
|
||||||
|
self.remove_markdown = remove_markdown
|
||||||
|
|
||||||
async def convert(self, ctx, argument):
|
async def convert(self, ctx, argument):
|
||||||
message = ctx.message
|
message = ctx.message
|
||||||
@ -635,6 +819,8 @@ class clean_content(Converter):
|
|||||||
|
|
||||||
if self.escape_markdown:
|
if self.escape_markdown:
|
||||||
result = discord.utils.escape_markdown(result)
|
result = discord.utils.escape_markdown(result)
|
||||||
|
elif self.remove_markdown:
|
||||||
|
result = discord.utils.remove_markdown(result)
|
||||||
|
|
||||||
# Completely ensure no mentions escape:
|
# Completely ensure no mentions escape:
|
||||||
return discord.utils.escape_mentions(result)
|
return discord.utils.escape_mentions(result)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -66,6 +66,9 @@ class BucketType(Enum):
|
|||||||
# recieving a DMChannel or GroupChannel which inherit from PrivateChannel and do
|
# recieving a DMChannel or GroupChannel which inherit from PrivateChannel and do
|
||||||
return (msg.channel if isinstance(msg.channel, PrivateChannel) else msg.author.top_role).id
|
return (msg.channel if isinstance(msg.channel, PrivateChannel) else msg.author.top_role).id
|
||||||
|
|
||||||
|
def __call__(self, msg):
|
||||||
|
return self.get_key(msg)
|
||||||
|
|
||||||
|
|
||||||
class Cooldown:
|
class Cooldown:
|
||||||
__slots__ = ('rate', 'per', 'type', '_window', '_tokens', '_last')
|
__slots__ = ('rate', 'per', 'type', '_window', '_tokens', '_last')
|
||||||
@ -78,8 +81,8 @@ class Cooldown:
|
|||||||
self._tokens = self.rate
|
self._tokens = self.rate
|
||||||
self._last = 0.0
|
self._last = 0.0
|
||||||
|
|
||||||
if not isinstance(self.type, BucketType):
|
if not callable(self.type):
|
||||||
raise TypeError('Cooldown type must be a BucketType')
|
raise TypeError('Cooldown type must be a BucketType or callable')
|
||||||
|
|
||||||
def get_tokens(self, current=None):
|
def get_tokens(self, current=None):
|
||||||
if not current:
|
if not current:
|
||||||
@ -151,7 +154,7 @@ class CooldownMapping:
|
|||||||
return cls(Cooldown(rate, per, type))
|
return cls(Cooldown(rate, per, type))
|
||||||
|
|
||||||
def _bucket_key(self, msg):
|
def _bucket_key(self, msg):
|
||||||
return self._cooldown.type.get_key(msg)
|
return self._cooldown.type(msg)
|
||||||
|
|
||||||
def _verify_cache_integrity(self, current=None):
|
def _verify_cache_integrity(self, current=None):
|
||||||
# we want to delete all cache objects that haven't been used
|
# we want to delete all cache objects that haven't been used
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -158,7 +158,7 @@ class Command(_BaseCommand):
|
|||||||
isn't one.
|
isn't one.
|
||||||
cog: Optional[:class:`Cog`]
|
cog: Optional[:class:`Cog`]
|
||||||
The cog that this command belongs to. ``None`` if there isn't one.
|
The cog that this command belongs to. ``None`` if there isn't one.
|
||||||
checks: List[Callable[..., :class:`bool`]]
|
checks: List[Callable[[:class:`.Context`], :class:`bool`]]
|
||||||
A list of predicates that verifies if the command could be executed
|
A list of predicates that verifies if the command could be executed
|
||||||
with the given :class:`.Context` as the sole parameter. If an exception
|
with the given :class:`.Context` as the sole parameter. If an exception
|
||||||
is necessary to be thrown to signal failure, then one inherited from
|
is necessary to be thrown to signal failure, then one inherited from
|
||||||
@ -523,7 +523,7 @@ class Command(_BaseCommand):
|
|||||||
# The greedy converter is simple -- it keeps going until it fails in which case,
|
# The greedy converter is simple -- it keeps going until it fails in which case,
|
||||||
# it undos the view ready for the next parameter to use instead
|
# it undos the view ready for the next parameter to use instead
|
||||||
if type(converter) is converters._Greedy:
|
if type(converter) is converters._Greedy:
|
||||||
if param.kind == param.POSITIONAL_OR_KEYWORD:
|
if param.kind == param.POSITIONAL_OR_KEYWORD or param.kind == param.POSITIONAL_ONLY:
|
||||||
return await self._transform_greedy_pos(ctx, param, required, converter.converter)
|
return await self._transform_greedy_pos(ctx, param, required, converter.converter)
|
||||||
elif param.kind == param.VAR_POSITIONAL:
|
elif param.kind == param.VAR_POSITIONAL:
|
||||||
return await self._transform_greedy_var_pos(ctx, param, converter.converter)
|
return await self._transform_greedy_var_pos(ctx, param, converter.converter)
|
||||||
@ -693,7 +693,7 @@ class Command(_BaseCommand):
|
|||||||
raise discord.ClientException(fmt.format(self))
|
raise discord.ClientException(fmt.format(self))
|
||||||
|
|
||||||
for name, param in iterator:
|
for name, param in iterator:
|
||||||
if param.kind == param.POSITIONAL_OR_KEYWORD:
|
if param.kind == param.POSITIONAL_OR_KEYWORD or param.kind == param.POSITIONAL_ONLY:
|
||||||
transformed = await self.transform(ctx, param)
|
transformed = await self.transform(ctx, param)
|
||||||
args.append(transformed)
|
args.append(transformed)
|
||||||
elif param.kind == param.KEYWORD_ONLY:
|
elif param.kind == param.KEYWORD_ONLY:
|
||||||
@ -715,9 +715,8 @@ class Command(_BaseCommand):
|
|||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
break
|
break
|
||||||
|
|
||||||
if not self.ignore_extra:
|
if not self.ignore_extra and not view.eof:
|
||||||
if not view.eof:
|
raise TooManyArguments('Too many arguments passed to ' + self.qualified_name)
|
||||||
raise TooManyArguments('Too many arguments passed to ' + self.qualified_name)
|
|
||||||
|
|
||||||
async def call_before_hooks(self, ctx):
|
async def call_before_hooks(self, ctx):
|
||||||
# now that we're done preparing we can call the pre-command hooks
|
# now that we're done preparing we can call the pre-command hooks
|
||||||
@ -904,6 +903,13 @@ class Command(_BaseCommand):
|
|||||||
self.on_error = coro
|
self.on_error = coro
|
||||||
return coro
|
return coro
|
||||||
|
|
||||||
|
def has_error_handler(self):
|
||||||
|
""":class:`bool`: Checks whether the command has an error handler registered.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
"""
|
||||||
|
return hasattr(self, 'on_error')
|
||||||
|
|
||||||
def before_invoke(self, coro):
|
def before_invoke(self, coro):
|
||||||
"""A decorator that registers a coroutine as a pre-invoke hook.
|
"""A decorator that registers a coroutine as a pre-invoke hook.
|
||||||
|
|
||||||
@ -1335,6 +1341,8 @@ class Group(GroupMixin, Command):
|
|||||||
injected = hooked_wrapped_callback(self, ctx, self.callback)
|
injected = hooked_wrapped_callback(self, ctx, self.callback)
|
||||||
await injected(*ctx.args, **ctx.kwargs)
|
await injected(*ctx.args, **ctx.kwargs)
|
||||||
|
|
||||||
|
ctx.invoked_parents.append(ctx.invoked_with)
|
||||||
|
|
||||||
if trigger and ctx.invoked_subcommand:
|
if trigger and ctx.invoked_subcommand:
|
||||||
ctx.invoked_with = trigger
|
ctx.invoked_with = trigger
|
||||||
await ctx.invoked_subcommand.invoke(ctx)
|
await ctx.invoked_subcommand.invoke(ctx)
|
||||||
@ -1373,6 +1381,8 @@ class Group(GroupMixin, Command):
|
|||||||
if call_hooks:
|
if call_hooks:
|
||||||
await self.call_after_hooks(ctx)
|
await self.call_after_hooks(ctx)
|
||||||
|
|
||||||
|
ctx.invoked_parents.append(ctx.invoked_with)
|
||||||
|
|
||||||
if trigger and ctx.invoked_subcommand:
|
if trigger and ctx.invoked_subcommand:
|
||||||
ctx.invoked_with = trigger
|
ctx.invoked_with = trigger
|
||||||
await ctx.invoked_subcommand.reinvoke(ctx, call_hooks=call_hooks)
|
await ctx.invoked_subcommand.reinvoke(ctx, call_hooks=call_hooks)
|
||||||
@ -1949,8 +1959,11 @@ def cooldown(rate, per, type=BucketType.default):
|
|||||||
The number of times a command can be used before triggering a cooldown.
|
The number of times a command can be used before triggering a cooldown.
|
||||||
per: :class:`float`
|
per: :class:`float`
|
||||||
The amount of seconds to wait for a cooldown when it's been triggered.
|
The amount of seconds to wait for a cooldown when it's been triggered.
|
||||||
type: :class:`.BucketType`
|
type: Union[:class:`.BucketType`, Callable[[:class:`.Message`], Any]]
|
||||||
The type of cooldown to have.
|
The type of cooldown to have. If callable, should return a key for the mapping.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
Callables are now supported for custom bucket types.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -45,6 +45,7 @@ __all__ = (
|
|||||||
'NotOwner',
|
'NotOwner',
|
||||||
'MessageNotFound',
|
'MessageNotFound',
|
||||||
'MemberNotFound',
|
'MemberNotFound',
|
||||||
|
'GuildNotFound',
|
||||||
'UserNotFound',
|
'UserNotFound',
|
||||||
'ChannelNotFound',
|
'ChannelNotFound',
|
||||||
'ChannelNotReadable',
|
'ChannelNotReadable',
|
||||||
@ -230,6 +231,22 @@ class MemberNotFound(BadArgument):
|
|||||||
self.argument = argument
|
self.argument = argument
|
||||||
super().__init__('Member "{}" not found.'.format(argument))
|
super().__init__('Member "{}" not found.'.format(argument))
|
||||||
|
|
||||||
|
class GuildNotFound(BadArgument):
|
||||||
|
"""Exception raised when the guild provided was not found in the bot's cache.
|
||||||
|
|
||||||
|
This inherits from :exc:`BadArgument`
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
-----------
|
||||||
|
argument: :class:`str`
|
||||||
|
The guild supplied by the called that was not found
|
||||||
|
"""
|
||||||
|
def __init__(self, argument):
|
||||||
|
self.argument = argument
|
||||||
|
super().__init__('Guild "{}" not found.'.format(argument))
|
||||||
|
|
||||||
class UserNotFound(BadArgument):
|
class UserNotFound(BadArgument):
|
||||||
"""Exception raised when the user provided was not found in the bot's
|
"""Exception raised when the user provided was not found in the bot's
|
||||||
cache.
|
cache.
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -79,18 +79,22 @@ class Paginator:
|
|||||||
The suffix appended at the end of every page. e.g. three backticks.
|
The suffix appended at the end of every page. e.g. three backticks.
|
||||||
max_size: :class:`int`
|
max_size: :class:`int`
|
||||||
The maximum amount of codepoints allowed in a page.
|
The maximum amount of codepoints allowed in a page.
|
||||||
|
linesep: :class:`str`
|
||||||
|
The character string inserted between lines. e.g. a newline character.
|
||||||
|
.. versionadded:: 1.7
|
||||||
"""
|
"""
|
||||||
def __init__(self, prefix='```', suffix='```', max_size=2000):
|
def __init__(self, prefix='```', suffix='```', max_size=2000, linesep='\n'):
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
self.suffix = suffix
|
self.suffix = suffix
|
||||||
self.max_size = max_size
|
self.max_size = max_size
|
||||||
|
self.linesep = linesep
|
||||||
self.clear()
|
self.clear()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""Clears the paginator to have no pages."""
|
"""Clears the paginator to have no pages."""
|
||||||
if self.prefix is not None:
|
if self.prefix is not None:
|
||||||
self._current_page = [self.prefix]
|
self._current_page = [self.prefix]
|
||||||
self._count = len(self.prefix) + 1 # prefix + newline
|
self._count = len(self.prefix) + self._linesep_len # prefix + newline
|
||||||
else:
|
else:
|
||||||
self._current_page = []
|
self._current_page = []
|
||||||
self._count = 0
|
self._count = 0
|
||||||
@ -104,6 +108,10 @@ class Paginator:
|
|||||||
def _suffix_len(self):
|
def _suffix_len(self):
|
||||||
return len(self.suffix) if self.suffix else 0
|
return len(self.suffix) if self.suffix else 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _linesep_len(self):
|
||||||
|
return len(self.linesep)
|
||||||
|
|
||||||
def add_line(self, line='', *, empty=False):
|
def add_line(self, line='', *, empty=False):
|
||||||
"""Adds a line to the current page.
|
"""Adds a line to the current page.
|
||||||
|
|
||||||
@ -122,29 +130,29 @@ class Paginator:
|
|||||||
RuntimeError
|
RuntimeError
|
||||||
The line was too big for the current :attr:`max_size`.
|
The line was too big for the current :attr:`max_size`.
|
||||||
"""
|
"""
|
||||||
max_page_size = self.max_size - self._prefix_len - self._suffix_len - 2
|
max_page_size = self.max_size - self._prefix_len - self._suffix_len - 2 * self._linesep_len
|
||||||
if len(line) > max_page_size:
|
if len(line) > max_page_size:
|
||||||
raise RuntimeError('Line exceeds maximum page size %s' % (max_page_size))
|
raise RuntimeError('Line exceeds maximum page size %s' % (max_page_size))
|
||||||
|
|
||||||
if self._count + len(line) + 1 > self.max_size - self._suffix_len:
|
if self._count + len(line) + self._linesep_len > self.max_size - self._suffix_len:
|
||||||
self.close_page()
|
self.close_page()
|
||||||
|
|
||||||
self._count += len(line) + 1
|
self._count += len(line) + self._linesep_len
|
||||||
self._current_page.append(line)
|
self._current_page.append(line)
|
||||||
|
|
||||||
if empty:
|
if empty:
|
||||||
self._current_page.append('')
|
self._current_page.append('')
|
||||||
self._count += 1
|
self._count += self._linesep_len
|
||||||
|
|
||||||
def close_page(self):
|
def close_page(self):
|
||||||
"""Prematurely terminate a page."""
|
"""Prematurely terminate a page."""
|
||||||
if self.suffix is not None:
|
if self.suffix is not None:
|
||||||
self._current_page.append(self.suffix)
|
self._current_page.append(self.suffix)
|
||||||
self._pages.append('\n'.join(self._current_page))
|
self._pages.append(self.linesep.join(self._current_page))
|
||||||
|
|
||||||
if self.prefix is not None:
|
if self.prefix is not None:
|
||||||
self._current_page = [self.prefix]
|
self._current_page = [self.prefix]
|
||||||
self._count = len(self.prefix) + 1 # prefix + newline
|
self._count = len(self.prefix) + self._linesep_len # prefix + linesep
|
||||||
else:
|
else:
|
||||||
self._current_page = []
|
self._current_page = []
|
||||||
self._count = 0
|
self._count = 0
|
||||||
@ -162,7 +170,7 @@ class Paginator:
|
|||||||
return self._pages
|
return self._pages
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
fmt = '<Paginator prefix: {0.prefix} suffix: {0.suffix} max_size: {0.max_size} count: {0._count}>'
|
fmt = '<Paginator prefix: {0.prefix!r} suffix: {0.suffix!r} linesep: {0.linesep!r} max_size: {0.max_size} count: {0._count}>'
|
||||||
return fmt.format(self)
|
return fmt.format(self)
|
||||||
|
|
||||||
def _not_overriden(f):
|
def _not_overriden(f):
|
||||||
@ -264,9 +272,13 @@ class HelpCommand:
|
|||||||
show_hidden: :class:`bool`
|
show_hidden: :class:`bool`
|
||||||
Specifies if hidden commands should be shown in the output.
|
Specifies if hidden commands should be shown in the output.
|
||||||
Defaults to ``False``.
|
Defaults to ``False``.
|
||||||
verify_checks: :class:`bool`
|
verify_checks: Optional[:class:`bool`]
|
||||||
Specifies if commands should have their :attr:`.Command.checks` called
|
Specifies if commands should have their :attr:`.Command.checks` called
|
||||||
and verified. Defaults to ``True``.
|
and verified. If ``True``, always calls :attr:`.Commands.checks`.
|
||||||
|
If ``None``, only calls :attr:`.Commands.checks` in a guild setting.
|
||||||
|
If ``False``, never calls :attr:`.Commands.checks`. Defaults to ``True``.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
command_attrs: :class:`dict`
|
command_attrs: :class:`dict`
|
||||||
A dictionary of options to pass in for the construction of the help command.
|
A dictionary of options to pass in for the construction of the help command.
|
||||||
This allows you to change the command behaviour without actually changing
|
This allows you to change the command behaviour without actually changing
|
||||||
@ -309,7 +321,7 @@ class HelpCommand:
|
|||||||
attrs.setdefault('name', 'help')
|
attrs.setdefault('name', 'help')
|
||||||
attrs.setdefault('help', 'Shows this message')
|
attrs.setdefault('help', 'Shows this message')
|
||||||
self.context = None
|
self.context = None
|
||||||
self._command_impl = None
|
self._command_impl = _HelpCommandImpl(self, **self.command_attrs)
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
obj = self.__class__(*self.__original_args__, **self.__original_kwargs__)
|
obj = self.__class__(*self.__original_args__, **self.__original_kwargs__)
|
||||||
@ -324,7 +336,6 @@ class HelpCommand:
|
|||||||
def _remove_from_bot(self, bot):
|
def _remove_from_bot(self, bot):
|
||||||
bot.remove_command(self._command_impl.name)
|
bot.remove_command(self._command_impl.name)
|
||||||
self._command_impl._eject_cog()
|
self._command_impl._eject_cog()
|
||||||
self._command_impl = None
|
|
||||||
|
|
||||||
def add_check(self, func):
|
def add_check(self, func):
|
||||||
"""
|
"""
|
||||||
@ -338,13 +349,7 @@ class HelpCommand:
|
|||||||
The function that will be used as a check.
|
The function that will be used as a check.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._command_impl is not None:
|
self._command_impl.add_check(func)
|
||||||
self._command_impl.add_check(func)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
self.command_attrs["checks"].append(func)
|
|
||||||
except KeyError:
|
|
||||||
self.command_attrs["checks"] = [func]
|
|
||||||
|
|
||||||
def remove_check(self, func):
|
def remove_check(self, func):
|
||||||
"""
|
"""
|
||||||
@ -361,13 +366,7 @@ class HelpCommand:
|
|||||||
The function to remove from the checks.
|
The function to remove from the checks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._command_impl is not None:
|
self._command_impl.remove_check(func)
|
||||||
self._command_impl.remove_check(func)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
self.command_attrs["checks"].remove(func)
|
|
||||||
except (KeyError, ValueError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_bot_mapping(self):
|
def get_bot_mapping(self):
|
||||||
"""Retrieves the bot mapping passed to :meth:`send_bot_help`."""
|
"""Retrieves the bot mapping passed to :meth:`send_bot_help`."""
|
||||||
@ -376,7 +375,7 @@ class HelpCommand:
|
|||||||
cog: cog.get_commands()
|
cog: cog.get_commands()
|
||||||
for cog in bot.cogs.values()
|
for cog in bot.cogs.values()
|
||||||
}
|
}
|
||||||
mapping[None] = [c for c in bot.all_commands.values() if c.cog is None]
|
mapping[None] = [c for c in bot.commands if c.cog is None]
|
||||||
return mapping
|
return mapping
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -425,15 +424,24 @@ class HelpCommand:
|
|||||||
The signature for the command.
|
The signature for the command.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
parent = command.full_parent_name
|
parent = command.parent
|
||||||
|
entries = []
|
||||||
|
while parent is not None:
|
||||||
|
if not parent.signature or parent.invoke_without_command:
|
||||||
|
entries.append(parent.name)
|
||||||
|
else:
|
||||||
|
entries.append(parent.name + ' ' + parent.signature)
|
||||||
|
parent = parent.parent
|
||||||
|
parent_sig = ' '.join(reversed(entries))
|
||||||
|
|
||||||
if len(command.aliases) > 0:
|
if len(command.aliases) > 0:
|
||||||
aliases = '|'.join(command.aliases)
|
aliases = '|'.join(command.aliases)
|
||||||
fmt = '[%s|%s]' % (command.name, aliases)
|
fmt = '[%s|%s]' % (command.name, aliases)
|
||||||
if parent:
|
if parent_sig:
|
||||||
fmt = parent + ' ' + fmt
|
fmt = parent_sig + ' ' + fmt
|
||||||
alias = fmt
|
alias = fmt
|
||||||
else:
|
else:
|
||||||
alias = command.name if not parent else parent + ' ' + command.name
|
alias = command.name if not parent_sig else parent_sig + ' ' + command.name
|
||||||
|
|
||||||
return '%s%s %s' % (self.clean_prefix, alias, command.signature)
|
return '%s%s %s' % (self.clean_prefix, alias, command.signature)
|
||||||
|
|
||||||
@ -560,11 +568,15 @@ class HelpCommand:
|
|||||||
|
|
||||||
iterator = commands if self.show_hidden else filter(lambda c: not c.hidden, commands)
|
iterator = commands if self.show_hidden else filter(lambda c: not c.hidden, commands)
|
||||||
|
|
||||||
if not self.verify_checks:
|
if self.verify_checks is False:
|
||||||
# if we do not need to verify the checks then we can just
|
# if we do not need to verify the checks then we can just
|
||||||
# run it straight through normally without using await.
|
# run it straight through normally without using await.
|
||||||
return sorted(iterator, key=key) if sort else list(iterator)
|
return sorted(iterator, key=key) if sort else list(iterator)
|
||||||
|
|
||||||
|
if self.verify_checks is None and not self.context.guild:
|
||||||
|
# if verify_checks is None and we're in a DM, don't verify
|
||||||
|
return sorted(iterator, key=key) if sort else list(iterator)
|
||||||
|
|
||||||
# if we're here then we need to check every command if it can run
|
# if we're here then we need to check every command if it can run
|
||||||
async def predicate(cmd):
|
async def predicate(cmd):
|
||||||
try:
|
try:
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -103,19 +103,19 @@ class Loop:
|
|||||||
now = datetime.datetime.now(datetime.timezone.utc)
|
now = datetime.datetime.now(datetime.timezone.utc)
|
||||||
if now > self._next_iteration:
|
if now > self._next_iteration:
|
||||||
self._next_iteration = now
|
self._next_iteration = now
|
||||||
except self._valid_exception as exc:
|
except self._valid_exception:
|
||||||
self._last_iteration_failed = True
|
self._last_iteration_failed = True
|
||||||
if not self.reconnect:
|
if not self.reconnect:
|
||||||
raise
|
raise
|
||||||
await asyncio.sleep(backoff.delay())
|
await asyncio.sleep(backoff.delay())
|
||||||
else:
|
else:
|
||||||
|
await sleep_until(self._next_iteration)
|
||||||
|
|
||||||
if self._stop_next_iteration:
|
if self._stop_next_iteration:
|
||||||
return
|
return
|
||||||
self._current_loop += 1
|
self._current_loop += 1
|
||||||
if self._current_loop == self.count:
|
if self._current_loop == self.count:
|
||||||
break
|
break
|
||||||
|
|
||||||
await sleep_until(self._next_iteration)
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
self._is_being_cancelled = True
|
self._is_being_cancelled = True
|
||||||
raise
|
raise
|
||||||
@ -154,14 +154,14 @@ class Loop:
|
|||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
"""
|
"""
|
||||||
if self._task is None and self._sleep:
|
if self._task is None:
|
||||||
return None
|
return None
|
||||||
elif self._task and self._task.done() or self._stop_next_iteration:
|
elif self._task and self._task.done() or self._stop_next_iteration:
|
||||||
return None
|
return None
|
||||||
return self._next_iteration
|
return self._next_iteration
|
||||||
|
|
||||||
async def __call__(self, *args, **kwargs):
|
async def __call__(self, *args, **kwargs):
|
||||||
"""|coro|
|
r"""|coro|
|
||||||
|
|
||||||
Calls the internal callback that the task holds.
|
Calls the internal callback that the task holds.
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -58,7 +58,7 @@ class File:
|
|||||||
Whether the attachment is a spoiler.
|
Whether the attachment is a spoiler.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('fp', 'filename', '_original_pos', '_owner', '_closer')
|
__slots__ = ('fp', 'filename', 'spoiler', '_original_pos', '_owner', '_closer')
|
||||||
|
|
||||||
def __init__(self, fp, filename=None, *, spoiler=False):
|
def __init__(self, fp, filename=None, *, spoiler=False):
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
@ -92,6 +92,8 @@ class File:
|
|||||||
if spoiler and self.filename is not None and not self.filename.startswith('SPOILER_'):
|
if spoiler and self.filename is not None and not self.filename.startswith('SPOILER_'):
|
||||||
self.filename = 'SPOILER_' + self.filename
|
self.filename = 'SPOILER_' + self.filename
|
||||||
|
|
||||||
|
self.spoiler = spoiler or (self.filename is not None and self.filename.startswith('SPOILER_'))
|
||||||
|
|
||||||
def reset(self, *, seek=True):
|
def reset(self, *, seek=True):
|
||||||
# The `seek` parameter is needed because
|
# The `seek` parameter is needed because
|
||||||
# the retry-loop is iterated over multiple times
|
# the retry-loop is iterated over multiple times
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -492,7 +492,7 @@ class Intents(BaseFlags):
|
|||||||
- :attr:`Member.nick`
|
- :attr:`Member.nick`
|
||||||
- :attr:`Member.premium_since`
|
- :attr:`Member.premium_since`
|
||||||
- :attr:`User.name`
|
- :attr:`User.name`
|
||||||
- :attr:`User.avatar` (:meth:`User.avatar_url` and :meth:`User.avatar_url_as`)
|
- :attr:`User.avatar` (:attr:`User.avatar_url` and :meth:`User.avatar_url_as`)
|
||||||
- :attr:`User.discriminator`
|
- :attr:`User.discriminator`
|
||||||
|
|
||||||
For more information go to the :ref:`member intent documentation <need_members_intent>`.
|
For more information go to the :ref:`member intent documentation <need_members_intent>`.
|
||||||
@ -954,4 +954,4 @@ class MemberCacheFlags(BaseFlags):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def _online_only(self):
|
def _online_only(self):
|
||||||
return self.value == 1
|
return self.value == 1
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -422,16 +422,11 @@ class DiscordWebSocket:
|
|||||||
if type(msg) is bytes:
|
if type(msg) is bytes:
|
||||||
self._buffer.extend(msg)
|
self._buffer.extend(msg)
|
||||||
|
|
||||||
if len(msg) >= 4:
|
if len(msg) < 4 or msg[-4:] != b'\x00\x00\xff\xff':
|
||||||
if msg[-4:] == b'\x00\x00\xff\xff':
|
|
||||||
msg = self._zlib.decompress(self._buffer)
|
|
||||||
msg = msg.decode('utf-8')
|
|
||||||
self._buffer = bytearray()
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
return
|
return
|
||||||
|
msg = self._zlib.decompress(self._buffer)
|
||||||
|
msg = msg.decode('utf-8')
|
||||||
|
self._buffer = bytearray()
|
||||||
msg = json.loads(msg)
|
msg = json.loads(msg)
|
||||||
|
|
||||||
log.debug('For Shard ID %s: WebSocket Event: %s', self.shard_id, msg)
|
log.debug('For Shard ID %s: WebSocket Event: %s', self.shard_id, msg)
|
||||||
@ -876,7 +871,7 @@ class DiscordVoiceWebSocket:
|
|||||||
def average_latency(self):
|
def average_latency(self):
|
||||||
""":class:`list`: Average of last 20 HEARTBEAT latencies."""
|
""":class:`list`: Average of last 20 HEARTBEAT latencies."""
|
||||||
heartbeat = self._keep_alive
|
heartbeat = self._keep_alive
|
||||||
if heartbeat is None:
|
if heartbeat is None or not heartbeat.recent_ack_latencies:
|
||||||
return float('inf')
|
return float('inf')
|
||||||
|
|
||||||
return sum(heartbeat.recent_ack_latencies) / len(heartbeat.recent_ack_latencies)
|
return sum(heartbeat.recent_ack_latencies) / len(heartbeat.recent_ack_latencies)
|
||||||
|
172
discord/guild.py
172
discord/guild.py
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -95,7 +95,7 @@ class Guild(Hashable):
|
|||||||
The guild owner's ID. Use :attr:`Guild.owner` instead.
|
The guild owner's ID. Use :attr:`Guild.owner` instead.
|
||||||
unavailable: :class:`bool`
|
unavailable: :class:`bool`
|
||||||
Indicates if the guild is unavailable. If this is ``True`` then the
|
Indicates if the guild is unavailable. If this is ``True`` then the
|
||||||
reliability of other attributes outside of :meth:`Guild.id` is slim and they might
|
reliability of other attributes outside of :attr:`Guild.id` is slim and they might
|
||||||
all be ``None``. It is best to not do anything with the guild if it is unavailable.
|
all be ``None``. It is best to not do anything with the guild if it is unavailable.
|
||||||
|
|
||||||
Check the :func:`on_guild_unavailable` and :func:`on_guild_available` events.
|
Check the :func:`on_guild_unavailable` and :func:`on_guild_available` events.
|
||||||
@ -205,7 +205,7 @@ class Guild(Hashable):
|
|||||||
self._members.pop(member.id, None)
|
self._members.pop(member.id, None)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name or ''
|
||||||
|
|
||||||
def __int__(self):
|
def __int__(self):
|
||||||
return self.id
|
return self.id
|
||||||
@ -374,6 +374,18 @@ class Guild(Hashable):
|
|||||||
r.sort(key=lambda c: (c.position, c.id))
|
r.sort(key=lambda c: (c.position, c.id))
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stage_channels(self):
|
||||||
|
"""List[:class:`StageChannel`]: A list of voice channels that belongs to this guild.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
This is sorted by the position and are in UI order from top to bottom.
|
||||||
|
"""
|
||||||
|
r = [ch for ch in self._channels.values() if isinstance(ch, StageChannel)]
|
||||||
|
r.sort(key=lambda c: (c.position, c.id))
|
||||||
|
return r
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def me(self):
|
def me(self):
|
||||||
""":class:`Member`: Similar to :attr:`Client.user` except an instance of :class:`Member`.
|
""":class:`Member`: Similar to :attr:`Client.user` except an instance of :class:`Member`.
|
||||||
@ -861,6 +873,13 @@ class Guild(Hashable):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
rtc_region = options.pop('rtc_region')
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
options['rtc_region'] = None if rtc_region is None else str(rtc_region)
|
||||||
|
|
||||||
parent_id = category.id if category else None
|
parent_id = category.id if category else None
|
||||||
return self._state.http.create_channel(self.id, channel_type.value, name=name, parent_id=parent_id,
|
return self._state.http.create_channel(self.id, channel_type.value, name=name, parent_id=parent_id,
|
||||||
permission_overwrites=perms, **options)
|
permission_overwrites=perms, **options)
|
||||||
@ -962,6 +981,11 @@ class Guild(Hashable):
|
|||||||
The channel's preferred audio bitrate in bits per second.
|
The channel's preferred audio bitrate in bits per second.
|
||||||
user_limit: :class:`int`
|
user_limit: :class:`int`
|
||||||
The channel's limit for number of members that can be in a voice channel.
|
The channel's limit for number of members that can be in a voice channel.
|
||||||
|
rtc_region: Optional[:class:`VoiceRegion`]
|
||||||
|
The region for the voice channel's voice communication.
|
||||||
|
A value of ``None`` indicates automatic voice region detection.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
@ -984,6 +1008,38 @@ class Guild(Hashable):
|
|||||||
self._channels[channel.id] = channel
|
self._channels[channel.id] = channel
|
||||||
return channel
|
return channel
|
||||||
|
|
||||||
|
async def create_stage_channel(self, name, *, topic=None, category=None, overwrites=None, reason=None, position=None):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
This is similar to :meth:`create_text_channel` except makes a :class:`StageChannel` instead.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``slowmode_delay`` and ``nsfw`` parameters are not supported in this function.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
Forbidden
|
||||||
|
You do not have the proper permissions to create this channel.
|
||||||
|
HTTPException
|
||||||
|
Creating the channel failed.
|
||||||
|
InvalidArgument
|
||||||
|
The permission overwrite information is not in proper form.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:class:`StageChannel`
|
||||||
|
The channel that was just created.
|
||||||
|
"""
|
||||||
|
data = await self._create_channel(name, overwrites, ChannelType.stage_voice, category, reason=reason, position=position, topic=topic)
|
||||||
|
channel = StageChannel(state=self._state, guild=self, data=data)
|
||||||
|
|
||||||
|
# temporarily add to the cache
|
||||||
|
self._channels[channel.id] = channel
|
||||||
|
return channel
|
||||||
|
|
||||||
async def create_category(self, name, *, overwrites=None, reason=None, position=None):
|
async def create_category(self, name, *, overwrites=None, reason=None, position=None):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@ -1069,8 +1125,8 @@ class Guild(Hashable):
|
|||||||
The new description of the guild. This is only available to guilds that
|
The new description of the guild. This is only available to guilds that
|
||||||
contain ``PUBLIC`` in :attr:`Guild.features`.
|
contain ``PUBLIC`` in :attr:`Guild.features`.
|
||||||
icon: :class:`bytes`
|
icon: :class:`bytes`
|
||||||
A :term:`py:bytes-like object` representing the icon. Only PNG/JPEG supported
|
A :term:`py:bytes-like object` representing the icon. Only PNG/JPEG is supported.
|
||||||
and GIF This is only available to guilds that contain ``ANIMATED_ICON`` in :attr:`Guild.features`.
|
GIF is only available to guilds that contain ``ANIMATED_ICON`` in :attr:`Guild.features`.
|
||||||
Could be ``None`` to denote removal of the icon.
|
Could be ``None`` to denote removal of the icon.
|
||||||
banner: :class:`bytes`
|
banner: :class:`bytes`
|
||||||
A :term:`py:bytes-like object` representing the banner.
|
A :term:`py:bytes-like object` representing the banner.
|
||||||
@ -1270,7 +1326,7 @@ class Guild(Hashable):
|
|||||||
def convert(d):
|
def convert(d):
|
||||||
factory, ch_type = _channel_factory(d['type'])
|
factory, ch_type = _channel_factory(d['type'])
|
||||||
if factory is None:
|
if factory is None:
|
||||||
raise InvalidData('Unknown channel type {type} for channel ID {id}.'.format_map(data))
|
raise InvalidData('Unknown channel type {type} for channel ID {id}.'.format_map(d))
|
||||||
|
|
||||||
channel = factory(guild=self, state=self._state, data=d)
|
channel = factory(guild=self, state=self._state, data=d)
|
||||||
return channel
|
return channel
|
||||||
@ -1278,9 +1334,7 @@ class Guild(Hashable):
|
|||||||
return [convert(d) for d in data]
|
return [convert(d) for d in data]
|
||||||
|
|
||||||
def fetch_members(self, *, limit=1000, after=None):
|
def fetch_members(self, *, limit=1000, after=None):
|
||||||
"""|coro|
|
"""Retrieves an :class:`.AsyncIterator` that enables receiving the guild's members. In order to use this,
|
||||||
|
|
||||||
Retrieves an :class:`.AsyncIterator` that enables receiving the guild's members. In order to use this,
|
|
||||||
:meth:`Intents.members` must be enabled.
|
:meth:`Intents.members` must be enabled.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@ -1370,11 +1424,11 @@ class Guild(Hashable):
|
|||||||
async def fetch_member(self, member_id):
|
async def fetch_member(self, member_id):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Retreives a :class:`Member` from a guild ID, and a member ID.
|
Retrieves a :class:`Member` from a guild ID, and a member ID.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This method is an API call. For general usage, consider :meth:`get_member` instead.
|
This method is an API call. If you have :attr:`Intents.members` and member cache enabled, consider :meth:`get_member` instead.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
@ -1399,9 +1453,7 @@ class Guild(Hashable):
|
|||||||
async def fetch_ban(self, user):
|
async def fetch_ban(self, user):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Retrieves the :class:`BanEntry` for a user, which is a namedtuple
|
Retrieves the :class:`BanEntry` for a user.
|
||||||
with a ``user`` and ``reason`` field. See :meth:`bans` for more
|
|
||||||
information.
|
|
||||||
|
|
||||||
You must have the :attr:`~Permissions.ban_members` permission
|
You must have the :attr:`~Permissions.ban_members` permission
|
||||||
to get this information.
|
to get this information.
|
||||||
@ -1422,8 +1474,8 @@ class Guild(Hashable):
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
BanEntry
|
:class:`BanEntry`
|
||||||
The BanEntry object for the specified user.
|
The :class:`BanEntry` object for the specified user.
|
||||||
"""
|
"""
|
||||||
data = await self._state.http.get_ban(user.id, self.id)
|
data = await self._state.http.get_ban(user.id, self.id)
|
||||||
return BanEntry(
|
return BanEntry(
|
||||||
@ -1434,12 +1486,7 @@ class Guild(Hashable):
|
|||||||
async def bans(self):
|
async def bans(self):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Retrieves all the users that are banned from the guild.
|
Retrieves all the users that are banned from the guild as a :class:`list` of :class:`BanEntry`.
|
||||||
|
|
||||||
This coroutine returns a :class:`list` of BanEntry objects, which is a
|
|
||||||
namedtuple with a ``user`` field to denote the :class:`User`
|
|
||||||
that got banned along with a ``reason`` field specifying
|
|
||||||
why the user was banned that could be set to ``None``.
|
|
||||||
|
|
||||||
You must have the :attr:`~Permissions.ban_members` permission
|
You must have the :attr:`~Permissions.ban_members` permission
|
||||||
to get this information.
|
to get this information.
|
||||||
@ -1453,8 +1500,8 @@ class Guild(Hashable):
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
--------
|
--------
|
||||||
List[BanEntry]
|
List[:class:`BanEntry`]
|
||||||
A list of BanEntry objects.
|
A list of :class:`BanEntry` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data = await self._state.http.get_bans(self.id)
|
data = await self._state.http.get_bans(self.id)
|
||||||
@ -1521,6 +1568,29 @@ class Guild(Hashable):
|
|||||||
data = await self._state.http.prune_members(self.id, days, compute_prune_count=compute_prune_count, roles=roles, reason=reason)
|
data = await self._state.http.prune_members(self.id, days, compute_prune_count=compute_prune_count, roles=roles, reason=reason)
|
||||||
return data['pruned']
|
return data['pruned']
|
||||||
|
|
||||||
|
async def templates(self):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Gets the list of templates from this guild.
|
||||||
|
|
||||||
|
Requires :attr:`~.Permissions.manage_guild` permissions.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You don't have permissions to get the templates.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
List[:class:`Template`]
|
||||||
|
The templates for this guild.
|
||||||
|
"""
|
||||||
|
from .template import Template
|
||||||
|
data = await self._state.http.guild_templates(self.id)
|
||||||
|
return [Template(data=d, state=self._state) for d in data]
|
||||||
|
|
||||||
async def webhooks(self):
|
async def webhooks(self):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@ -1543,7 +1613,7 @@ class Guild(Hashable):
|
|||||||
data = await self._state.http.guild_webhooks(self.id)
|
data = await self._state.http.guild_webhooks(self.id)
|
||||||
return [Webhook.from_state(d, state=self._state) for d in data]
|
return [Webhook.from_state(d, state=self._state) for d in data]
|
||||||
|
|
||||||
async def estimate_pruned_members(self, *, days):
|
async def estimate_pruned_members(self, *, days, roles=None):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Similar to :meth:`prune_members` except instead of actually
|
Similar to :meth:`prune_members` except instead of actually
|
||||||
@ -1554,6 +1624,11 @@ class Guild(Hashable):
|
|||||||
-----------
|
-----------
|
||||||
days: :class:`int`
|
days: :class:`int`
|
||||||
The number of days before counting as inactive.
|
The number of days before counting as inactive.
|
||||||
|
roles: Optional[List[:class:`abc.Snowflake`]]
|
||||||
|
A list of :class:`abc.Snowflake` that represent roles to include in the estimate. If a member
|
||||||
|
has a role that is not specified, they'll be excluded.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
@ -1573,7 +1648,10 @@ class Guild(Hashable):
|
|||||||
if not isinstance(days, int):
|
if not isinstance(days, int):
|
||||||
raise InvalidArgument('Expected int for ``days``, received {0.__class__.__name__} instead.'.format(days))
|
raise InvalidArgument('Expected int for ``days``, received {0.__class__.__name__} instead.'.format(days))
|
||||||
|
|
||||||
data = await self._state.http.estimate_pruned_members(self.id, days)
|
if roles:
|
||||||
|
roles = [str(role.id) for role in roles]
|
||||||
|
|
||||||
|
data = await self._state.http.estimate_pruned_members(self.id, days, roles)
|
||||||
return data['pruned']
|
return data['pruned']
|
||||||
|
|
||||||
async def invites(self):
|
async def invites(self):
|
||||||
@ -1607,6 +1685,36 @@ class Guild(Hashable):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
async def create_template(self, *, name, description=None):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Creates a template for the guild.
|
||||||
|
|
||||||
|
You must have the :attr:`~Permissions.manage_guild` permission to
|
||||||
|
do this.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
name: :class:`str`
|
||||||
|
The name of the template.
|
||||||
|
description: Optional[:class:`str`]
|
||||||
|
The description of the template.
|
||||||
|
"""
|
||||||
|
from .template import Template
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'name': name
|
||||||
|
}
|
||||||
|
|
||||||
|
if description:
|
||||||
|
payload['description'] = description
|
||||||
|
|
||||||
|
data = await self._state.http.create_template(self.id, payload)
|
||||||
|
|
||||||
|
return Template(state=self._state, data=data)
|
||||||
|
|
||||||
async def create_integration(self, *, type, id):
|
async def create_integration(self, *, type, id):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@ -1786,6 +1894,9 @@ class Guild(Hashable):
|
|||||||
You must have the :attr:`~Permissions.manage_roles` permission to
|
You must have the :attr:`~Permissions.manage_roles` permission to
|
||||||
do this.
|
do this.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.6
|
||||||
|
Can now pass ``int`` to ``colour`` keyword-only parameter.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
name: :class:`str`
|
name: :class:`str`
|
||||||
@ -2031,6 +2142,7 @@ class Guild(Hashable):
|
|||||||
payload['max_age'] = 0
|
payload['max_age'] = 0
|
||||||
return Invite(state=self._state, data=payload)
|
return Invite(state=self._state, data=payload)
|
||||||
|
|
||||||
|
@utils.deprecated()
|
||||||
def ack(self):
|
def ack(self):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@ -2038,6 +2150,8 @@ class Guild(Hashable):
|
|||||||
|
|
||||||
The user must not be a bot user.
|
The user must not be a bot user.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
HTTPException
|
HTTPException
|
||||||
@ -2162,7 +2276,8 @@ class Guild(Hashable):
|
|||||||
if not self._state._intents.members:
|
if not self._state._intents.members:
|
||||||
raise ClientException('Intents.members must be enabled to use this.')
|
raise ClientException('Intents.members must be enabled to use this.')
|
||||||
|
|
||||||
return await self._state.chunk_guild(self, cache=cache)
|
if not self._state.is_guild_evicted(self):
|
||||||
|
return await self._state.chunk_guild(self, cache=cache)
|
||||||
|
|
||||||
async def query_members(self, query=None, *, limit=5, user_ids=None, presences=False, cache=True):
|
async def query_members(self, query=None, *, limit=5, user_ids=None, presences=False, cache=True):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
@ -2224,6 +2339,9 @@ class Guild(Hashable):
|
|||||||
if user_ids is not None and query is not None:
|
if user_ids is not None and query is not None:
|
||||||
raise ValueError('Cannot pass both query and user_ids')
|
raise ValueError('Cannot pass both query and user_ids')
|
||||||
|
|
||||||
|
if user_ids is not None and not user_ids:
|
||||||
|
raise ValueError('user_ids must contain at least 1 value')
|
||||||
|
|
||||||
limit = min(100, limit or 5)
|
limit = min(100, limit or 5)
|
||||||
return await self._state.query_members(self, query=query, limit=limit, user_ids=user_ids, presences=presences, cache=cache)
|
return await self._state.query_members(self, query=query, limit=limit, user_ids=user_ids, presences=presences, cache=cache)
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -131,7 +131,7 @@ class HTTPClient:
|
|||||||
|
|
||||||
return await self.__session.ws_connect(url, **kwargs)
|
return await self.__session.ws_connect(url, **kwargs)
|
||||||
|
|
||||||
async def request(self, route, *, files=None, **kwargs):
|
async def request(self, route, *, files=None, form=None, **kwargs):
|
||||||
bucket = route.bucket
|
bucket = route.bucket
|
||||||
method = route.method
|
method = route.method
|
||||||
url = route.url
|
url = route.url
|
||||||
@ -181,6 +181,13 @@ class HTTPClient:
|
|||||||
if files:
|
if files:
|
||||||
for f in files:
|
for f in files:
|
||||||
f.reset(seek=tries)
|
f.reset(seek=tries)
|
||||||
|
|
||||||
|
if form:
|
||||||
|
form_data = aiohttp.FormData()
|
||||||
|
for params in form:
|
||||||
|
form_data.add_field(**params)
|
||||||
|
kwargs['data'] = form_data
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with self.__session.request(method, url, **kwargs) as r:
|
async with self.__session.request(method, url, **kwargs) as r:
|
||||||
log.debug('%s %s with %s has returned %s', method, url, kwargs.get('data'), r.status)
|
log.debug('%s %s with %s has returned %s', method, url, kwargs.get('data'), r.status)
|
||||||
@ -371,7 +378,7 @@ class HTTPClient:
|
|||||||
|
|
||||||
def send_files(self, channel_id, *, files, content=None, tts=False, embed=None, nonce=None, allowed_mentions=None, message_reference=None):
|
def send_files(self, channel_id, *, files, content=None, tts=False, embed=None, nonce=None, allowed_mentions=None, message_reference=None):
|
||||||
r = Route('POST', '/channels/{channel_id}/messages', channel_id=channel_id)
|
r = Route('POST', '/channels/{channel_id}/messages', channel_id=channel_id)
|
||||||
form = aiohttp.FormData()
|
form = []
|
||||||
|
|
||||||
payload = {'tts': tts}
|
payload = {'tts': tts}
|
||||||
if content:
|
if content:
|
||||||
@ -385,15 +392,25 @@ class HTTPClient:
|
|||||||
if message_reference:
|
if message_reference:
|
||||||
payload['message_reference'] = message_reference
|
payload['message_reference'] = message_reference
|
||||||
|
|
||||||
form.add_field('payload_json', utils.to_json(payload))
|
form.append({'name': 'payload_json', 'value': utils.to_json(payload)})
|
||||||
if len(files) == 1:
|
if len(files) == 1:
|
||||||
file = files[0]
|
file = files[0]
|
||||||
form.add_field('file', file.fp, filename=file.filename, content_type='application/octet-stream')
|
form.append({
|
||||||
|
'name': 'file',
|
||||||
|
'value': file.fp,
|
||||||
|
'filename': file.filename,
|
||||||
|
'content_type': 'application/octet-stream'
|
||||||
|
})
|
||||||
else:
|
else:
|
||||||
for index, file in enumerate(files):
|
for index, file in enumerate(files):
|
||||||
form.add_field('file%s' % index, file.fp, filename=file.filename, content_type='application/octet-stream')
|
form.append({
|
||||||
|
'name': 'file%s' % index,
|
||||||
|
'value': file.fp,
|
||||||
|
'filename': file.filename,
|
||||||
|
'content_type': 'application/octet-stream'
|
||||||
|
})
|
||||||
|
|
||||||
return self.request(r, data=form, files=files)
|
return self.request(r, form=form, files=files)
|
||||||
|
|
||||||
async def ack_message(self, channel_id, message_id):
|
async def ack_message(self, channel_id, message_id):
|
||||||
r = Route('POST', '/channels/{channel_id}/messages/{message_id}/ack', channel_id=channel_id, message_id=message_id)
|
r = Route('POST', '/channels/{channel_id}/messages/{message_id}/ack', channel_id=channel_id, message_id=message_id)
|
||||||
@ -557,6 +574,14 @@ class HTTPClient:
|
|||||||
}
|
}
|
||||||
return self.request(r, json=payload, reason=reason)
|
return self.request(r, json=payload, reason=reason)
|
||||||
|
|
||||||
|
def edit_my_voice_state(self, guild_id, payload):
|
||||||
|
r = Route('PATCH', '/guilds/{guild_id}/voice-states/@me', guild_id=guild_id)
|
||||||
|
return self.request(r, json=payload)
|
||||||
|
|
||||||
|
def edit_voice_state(self, guild_id, user_id, payload):
|
||||||
|
r = Route('PATCH', '/guilds/{guild_id}/voice-states/{user_id}', guild_id=guild_id, user_id=user_id)
|
||||||
|
return self.request(r, json=payload)
|
||||||
|
|
||||||
def edit_member(self, guild_id, user_id, *, reason=None, **fields):
|
def edit_member(self, guild_id, user_id, *, reason=None, **fields):
|
||||||
r = Route('PATCH', '/guilds/{guild_id}/members/{user_id}', guild_id=guild_id, user_id=user_id)
|
r = Route('PATCH', '/guilds/{guild_id}/members/{user_id}', guild_id=guild_id, user_id=user_id)
|
||||||
return self.request(r, json=fields, reason=reason)
|
return self.request(r, json=fields, reason=reason)
|
||||||
@ -567,11 +592,10 @@ class HTTPClient:
|
|||||||
r = Route('PATCH', '/channels/{channel_id}', channel_id=channel_id)
|
r = Route('PATCH', '/channels/{channel_id}', channel_id=channel_id)
|
||||||
valid_keys = ('name', 'parent_id', 'topic', 'bitrate', 'nsfw',
|
valid_keys = ('name', 'parent_id', 'topic', 'bitrate', 'nsfw',
|
||||||
'user_limit', 'position', 'permission_overwrites', 'rate_limit_per_user',
|
'user_limit', 'position', 'permission_overwrites', 'rate_limit_per_user',
|
||||||
'type')
|
'type', 'rtc_region')
|
||||||
payload = {
|
payload = {
|
||||||
k: v for k, v in options.items() if k in valid_keys
|
k: v for k, v in options.items() if k in valid_keys
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.request(r, reason=reason, json=payload)
|
return self.request(r, reason=reason, json=payload)
|
||||||
|
|
||||||
def bulk_channel_update(self, guild_id, data, *, reason=None):
|
def bulk_channel_update(self, guild_id, data, *, reason=None):
|
||||||
@ -584,7 +608,8 @@ class HTTPClient:
|
|||||||
}
|
}
|
||||||
|
|
||||||
valid_keys = ('name', 'parent_id', 'topic', 'bitrate', 'nsfw',
|
valid_keys = ('name', 'parent_id', 'topic', 'bitrate', 'nsfw',
|
||||||
'user_limit', 'position', 'permission_overwrites', 'rate_limit_per_user')
|
'user_limit', 'position', 'permission_overwrites', 'rate_limit_per_user',
|
||||||
|
'rtc_region')
|
||||||
payload.update({
|
payload.update({
|
||||||
k: v for k, v in options.items() if k in valid_keys and v is not None
|
k: v for k, v in options.items() if k in valid_keys and v is not None
|
||||||
})
|
})
|
||||||
@ -670,6 +695,28 @@ class HTTPClient:
|
|||||||
def get_template(self, code):
|
def get_template(self, code):
|
||||||
return self.request(Route('GET', '/guilds/templates/{code}', code=code))
|
return self.request(Route('GET', '/guilds/templates/{code}', code=code))
|
||||||
|
|
||||||
|
def guild_templates(self, guild_id):
|
||||||
|
return self.request(Route('GET', '/guilds/{guild_id}/templates', guild_id=guild_id))
|
||||||
|
|
||||||
|
def create_template(self, guild_id, payload):
|
||||||
|
return self.request(Route('POST', '/guilds/{guild_id}/templates', guild_id=guild_id), json=payload)
|
||||||
|
|
||||||
|
def sync_template(self, guild_id, code):
|
||||||
|
return self.request(Route('PUT', '/guilds/{guild_id}/templates/{code}', guild_id=guild_id, code=code))
|
||||||
|
|
||||||
|
def edit_template(self, guild_id, code, payload):
|
||||||
|
valid_keys = (
|
||||||
|
'name',
|
||||||
|
'description',
|
||||||
|
)
|
||||||
|
payload = {
|
||||||
|
k: v for k, v in payload.items() if k in valid_keys
|
||||||
|
}
|
||||||
|
return self.request(Route('PATCH', '/guilds/{guild_id}/templates/{code}', guild_id=guild_id, code=code), json=payload)
|
||||||
|
|
||||||
|
def delete_template(self, guild_id, code):
|
||||||
|
return self.request(Route('DELETE', '/guilds/{guild_id}/templates/{code}', guild_id=guild_id, code=code))
|
||||||
|
|
||||||
def create_from_template(self, code, name, region, icon):
|
def create_from_template(self, code, name, region, icon):
|
||||||
payload = {
|
payload = {
|
||||||
'name': name,
|
'name': name,
|
||||||
@ -717,10 +764,13 @@ class HTTPClient:
|
|||||||
|
|
||||||
return self.request(Route('POST', '/guilds/{guild_id}/prune', guild_id=guild_id), json=payload, reason=reason)
|
return self.request(Route('POST', '/guilds/{guild_id}/prune', guild_id=guild_id), json=payload, reason=reason)
|
||||||
|
|
||||||
def estimate_pruned_members(self, guild_id, days):
|
def estimate_pruned_members(self, guild_id, days, roles):
|
||||||
params = {
|
params = {
|
||||||
'days': days
|
'days': days
|
||||||
}
|
}
|
||||||
|
if roles:
|
||||||
|
params['include_roles'] = ', '.join(roles)
|
||||||
|
|
||||||
return self.request(Route('GET', '/guilds/{guild_id}/prune', guild_id=guild_id), params=params)
|
return self.request(Route('GET', '/guilds/{guild_id}/prune', guild_id=guild_id), params=params)
|
||||||
|
|
||||||
def get_all_custom_emojis(self, guild_id):
|
def get_all_custom_emojis(self, guild_id):
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -258,7 +258,8 @@ class Invite(Hashable):
|
|||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
max_age: :class:`int`
|
max_age: :class:`int`
|
||||||
How long the before the invite expires in seconds. A value of 0 indicates that it doesn't expire.
|
How long the before the invite expires in seconds.
|
||||||
|
A value of ``0`` indicates that it doesn't expire.
|
||||||
code: :class:`str`
|
code: :class:`str`
|
||||||
The URL fragment used for the invite.
|
The URL fragment used for the invite.
|
||||||
guild: Optional[Union[:class:`Guild`, :class:`Object`, :class:`PartialInviteGuild`]]
|
guild: Optional[Union[:class:`Guild`, :class:`Object`, :class:`PartialInviteGuild`]]
|
||||||
@ -274,6 +275,7 @@ class Invite(Hashable):
|
|||||||
How many times the invite has been used.
|
How many times the invite has been used.
|
||||||
max_uses: :class:`int`
|
max_uses: :class:`int`
|
||||||
How many times the invite can be used.
|
How many times the invite can be used.
|
||||||
|
A value of ``0`` indicates that it has unlimited uses.
|
||||||
inviter: :class:`User`
|
inviter: :class:`User`
|
||||||
The user who created the invite.
|
The user who created the invite.
|
||||||
approximate_member_count: Optional[:class:`int`]
|
approximate_member_count: Optional[:class:`int`]
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -101,7 +101,7 @@ class _ChunkedAsyncIterator(_AsyncIterator):
|
|||||||
def __init__(self, iterator, max_size):
|
def __init__(self, iterator, max_size):
|
||||||
self.iterator = iterator
|
self.iterator = iterator
|
||||||
self.max_size = max_size
|
self.max_size = max_size
|
||||||
|
|
||||||
async def next(self):
|
async def next(self):
|
||||||
ret = []
|
ret = []
|
||||||
n = 0
|
n = 0
|
||||||
@ -291,13 +291,10 @@ class HistoryIterator(_AsyncIterator):
|
|||||||
|
|
||||||
def _get_retrieve(self):
|
def _get_retrieve(self):
|
||||||
l = self.limit
|
l = self.limit
|
||||||
if l is None:
|
if l is None or l > 100:
|
||||||
r = 100
|
r = 100
|
||||||
elif l <= 100:
|
|
||||||
r = l
|
|
||||||
else:
|
else:
|
||||||
r = 100
|
r = l
|
||||||
|
|
||||||
self.retrieve = r
|
self.retrieve = r
|
||||||
return r > 0
|
return r > 0
|
||||||
|
|
||||||
@ -447,13 +444,10 @@ class AuditLogIterator(_AsyncIterator):
|
|||||||
|
|
||||||
def _get_retrieve(self):
|
def _get_retrieve(self):
|
||||||
l = self.limit
|
l = self.limit
|
||||||
if l is None:
|
if l is None or l > 100:
|
||||||
r = 100
|
r = 100
|
||||||
elif l <= 100:
|
|
||||||
r = l
|
|
||||||
else:
|
else:
|
||||||
r = 100
|
r = l
|
||||||
|
|
||||||
self.retrieve = r
|
self.retrieve = r
|
||||||
return r > 0
|
return r > 0
|
||||||
|
|
||||||
@ -547,13 +541,10 @@ class GuildIterator(_AsyncIterator):
|
|||||||
|
|
||||||
def _get_retrieve(self):
|
def _get_retrieve(self):
|
||||||
l = self.limit
|
l = self.limit
|
||||||
if l is None:
|
if l is None or l > 100:
|
||||||
r = 100
|
r = 100
|
||||||
elif l <= 100:
|
|
||||||
r = l
|
|
||||||
else:
|
else:
|
||||||
r = 100
|
r = l
|
||||||
|
|
||||||
self.retrieve = r
|
self.retrieve = r
|
||||||
return r > 0
|
return r > 0
|
||||||
|
|
||||||
@ -636,13 +627,10 @@ class MemberIterator(_AsyncIterator):
|
|||||||
|
|
||||||
def _get_retrieve(self):
|
def _get_retrieve(self):
|
||||||
l = self.limit
|
l = self.limit
|
||||||
if l is None:
|
if l is None or l > 1000:
|
||||||
r = 1000
|
r = 1000
|
||||||
elif l <= 1000:
|
|
||||||
r = l
|
|
||||||
else:
|
else:
|
||||||
r = 1000
|
r = l
|
||||||
|
|
||||||
self.retrieve = r
|
self.retrieve = r
|
||||||
return r > 0
|
return r > 0
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -24,6 +24,8 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import inspect
|
||||||
import itertools
|
import itertools
|
||||||
import sys
|
import sys
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
@ -31,6 +33,7 @@ from operator import attrgetter
|
|||||||
import discord.abc
|
import discord.abc
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
|
from .errors import ClientException
|
||||||
from .user import BaseUser, User
|
from .user import BaseUser, User
|
||||||
from .activity import create_activity
|
from .activity import create_activity
|
||||||
from .permissions import Permissions
|
from .permissions import Permissions
|
||||||
@ -58,15 +61,32 @@ class VoiceState:
|
|||||||
|
|
||||||
self_video: :class:`bool`
|
self_video: :class:`bool`
|
||||||
Indicates if the user is currently broadcasting video.
|
Indicates if the user is currently broadcasting video.
|
||||||
|
suppress: :class:`bool`
|
||||||
|
Indicates if the user is suppressed from speaking.
|
||||||
|
|
||||||
|
Only applies to stage channels.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
requested_to_speak_at: Optional[:class:`datetime.datetime`]
|
||||||
|
A datetime object that specifies the date and time in UTC that the member
|
||||||
|
requested to speak. It will be ``None`` if they are not requesting to speak
|
||||||
|
anymore or have been accepted to speak.
|
||||||
|
|
||||||
|
Only applicable to stage channels.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
afk: :class:`bool`
|
afk: :class:`bool`
|
||||||
Indicates if the user is currently in the AFK channel in the guild.
|
Indicates if the user is currently in the AFK channel in the guild.
|
||||||
channel: Optional[:class:`VoiceChannel`]
|
channel: Optional[Union[:class:`VoiceChannel`, :class:`StageChannel`]]
|
||||||
The voice channel that the user is currently connected to. ``None`` if the user
|
The voice channel that the user is currently connected to. ``None`` if the user
|
||||||
is not currently in a voice channel.
|
is not currently in a voice channel.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('session_id', 'deaf', 'mute', 'self_mute',
|
__slots__ = ('session_id', 'deaf', 'mute', 'self_mute',
|
||||||
'self_stream', 'self_video', 'self_deaf', 'afk', 'channel')
|
'self_stream', 'self_video', 'self_deaf', 'afk', 'channel',
|
||||||
|
'requested_to_speak_at', 'suppress')
|
||||||
|
|
||||||
def __init__(self, *, data, channel=None):
|
def __init__(self, *, data, channel=None):
|
||||||
self.session_id = data.get('session_id')
|
self.session_id = data.get('session_id')
|
||||||
@ -80,10 +100,20 @@ class VoiceState:
|
|||||||
self.afk = data.get('suppress', False)
|
self.afk = data.get('suppress', False)
|
||||||
self.mute = data.get('mute', False)
|
self.mute = data.get('mute', False)
|
||||||
self.deaf = data.get('deaf', False)
|
self.deaf = data.get('deaf', False)
|
||||||
|
self.suppress = data.get('suppress', False)
|
||||||
|
self.requested_to_speak_at = utils.parse_time(data.get('request_to_speak_timestamp'))
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<VoiceState self_mute={0.self_mute} self_deaf={0.self_deaf} self_stream={0.self_stream} channel={0.channel!r}>'.format(self)
|
attrs = [
|
||||||
|
('self_mute', self.self_mute),
|
||||||
|
('self_deaf', self.self_deaf),
|
||||||
|
('self_stream', self.self_stream),
|
||||||
|
('suppress', self.suppress),
|
||||||
|
('requested_to_speak_at', self.requested_to_speak_at),
|
||||||
|
('channel', self.channel)
|
||||||
|
]
|
||||||
|
return '<%s %s>' % (self.__class__.__name__, ' '.join('%s=%r' % t for t in attrs))
|
||||||
|
|
||||||
def flatten_user(cls):
|
def flatten_user(cls):
|
||||||
for attr, value in itertools.chain(BaseUser.__dict__.items(), User.__dict__.items()):
|
for attr, value in itertools.chain(BaseUser.__dict__.items(), User.__dict__.items()):
|
||||||
@ -106,14 +136,19 @@ def flatten_user(cls):
|
|||||||
# It probably breaks something in Sphinx.
|
# It probably breaks something in Sphinx.
|
||||||
# probably a member function by now
|
# probably a member function by now
|
||||||
def generate_function(x):
|
def generate_function(x):
|
||||||
def general(self, *args, **kwargs):
|
# We want sphinx to properly show coroutine functions as coroutines
|
||||||
return getattr(self._user, x)(*args, **kwargs)
|
if inspect.iscoroutinefunction(value):
|
||||||
|
async def general(self, *args, **kwargs):
|
||||||
|
return await getattr(self._user, x)(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
def general(self, *args, **kwargs):
|
||||||
|
return getattr(self._user, x)(*args, **kwargs)
|
||||||
|
|
||||||
general.__name__ = x
|
general.__name__ = x
|
||||||
return general
|
return general
|
||||||
|
|
||||||
func = generate_function(attr)
|
func = generate_function(attr)
|
||||||
func.__doc__ = value.__doc__
|
func = utils.copy_doc(value)(func)
|
||||||
setattr(cls, attr, func)
|
setattr(cls, attr, func)
|
||||||
|
|
||||||
return cls
|
return cls
|
||||||
@ -153,6 +188,13 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
If the member left and rejoined the guild, this will be the latest date. In certain cases, this can be ``None``.
|
If the member left and rejoined the guild, this will be the latest date. In certain cases, this can be ``None``.
|
||||||
activities: Tuple[Union[:class:`BaseActivity`, :class:`Spotify`]]
|
activities: Tuple[Union[:class:`BaseActivity`, :class:`Spotify`]]
|
||||||
The activities that the user is currently doing.
|
The activities that the user is currently doing.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Due to a Discord API limitation, a user's Spotify activity may not appear
|
||||||
|
if they are listening to a song with a title longer
|
||||||
|
than 128 characters. See :issue:`1738` for more information.
|
||||||
|
|
||||||
guild: :class:`Guild`
|
guild: :class:`Guild`
|
||||||
The guild that the member belongs to.
|
The guild that the member belongs to.
|
||||||
nick: Optional[:class:`str`]
|
nick: Optional[:class:`str`]
|
||||||
@ -293,12 +335,12 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
|
|
||||||
def _update_inner_user(self, user):
|
def _update_inner_user(self, user):
|
||||||
u = self._user
|
u = self._user
|
||||||
original = (u.name, u.avatar, u.discriminator)
|
original = (u.name, u.avatar, u.discriminator, u._public_flags)
|
||||||
# These keys seem to always be available
|
# These keys seem to always be available
|
||||||
modified = (user['username'], user['avatar'], user['discriminator'])
|
modified = (user['username'], user['avatar'], user['discriminator'], user.get('public_flags', 0))
|
||||||
if original != modified:
|
if original != modified:
|
||||||
to_return = User._copy(self._user)
|
to_return = User._copy(self._user)
|
||||||
u.name, u.avatar, u.discriminator = modified
|
u.name, u.avatar, u.discriminator, u._public_flags = modified
|
||||||
# Signal to dispatch on_user_update
|
# Signal to dispatch on_user_update
|
||||||
return to_return, u
|
return to_return, u
|
||||||
|
|
||||||
@ -401,13 +443,19 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
if they have a guild specific nickname then that
|
if they have a guild specific nickname then that
|
||||||
is returned instead.
|
is returned instead.
|
||||||
"""
|
"""
|
||||||
return self.nick if self.nick is not None else self.name
|
return self.nick or self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def activity(self):
|
def activity(self):
|
||||||
"""Union[:class:`BaseActivity`, :class:`Spotify`]: Returns the primary
|
"""Union[:class:`BaseActivity`, :class:`Spotify`]: Returns the primary
|
||||||
activity the user is currently doing. Could be ``None`` if no activity is being done.
|
activity the user is currently doing. Could be ``None`` if no activity is being done.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Due to a Discord API limitation, this may be ``None`` if
|
||||||
|
the user is listening to a song on Spotify with a title longer
|
||||||
|
than 128 characters. See :issue:`1738` for more information.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
A user may have multiple activities, these can be accessed under :attr:`activities`.
|
A user may have multiple activities, these can be accessed under :attr:`activities`.
|
||||||
@ -434,11 +482,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
if self._user.mentioned_in(message):
|
if self._user.mentioned_in(message):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
for role in message.role_mentions:
|
return any(self._roles.has(role.id) for role in message.role_mentions)
|
||||||
if self._roles.has(role.id):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def permissions_in(self, channel):
|
def permissions_in(self, channel):
|
||||||
"""An alias for :meth:`abc.GuildChannel.permissions_for`.
|
"""An alias for :meth:`abc.GuildChannel.permissions_for`.
|
||||||
@ -560,6 +604,11 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
Indicates if the member should be guild muted or un-muted.
|
Indicates if the member should be guild muted or un-muted.
|
||||||
deafen: :class:`bool`
|
deafen: :class:`bool`
|
||||||
Indicates if the member should be guild deafened or un-deafened.
|
Indicates if the member should be guild deafened or un-deafened.
|
||||||
|
suppress: :class:`bool`
|
||||||
|
Indicates if the member should be suppressed in stage channels.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
roles: Optional[List[:class:`Role`]]
|
roles: Optional[List[:class:`Role`]]
|
||||||
The member's new list of roles. This *replaces* the roles.
|
The member's new list of roles. This *replaces* the roles.
|
||||||
voice_channel: Optional[:class:`VoiceChannel`]
|
voice_channel: Optional[:class:`VoiceChannel`]
|
||||||
@ -577,6 +626,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
"""
|
"""
|
||||||
http = self._state.http
|
http = self._state.http
|
||||||
guild_id = self.guild.id
|
guild_id = self.guild.id
|
||||||
|
me = self._state.self_id == self.id
|
||||||
payload = {}
|
payload = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -585,8 +635,8 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
# nick not present so...
|
# nick not present so...
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
nick = nick if nick else ''
|
nick = nick or ''
|
||||||
if self._state.self_id == self.id:
|
if me:
|
||||||
await http.change_my_nickname(guild_id, nick, reason=reason)
|
await http.change_my_nickname(guild_id, nick, reason=reason)
|
||||||
else:
|
else:
|
||||||
payload['nick'] = nick
|
payload['nick'] = nick
|
||||||
@ -599,6 +649,23 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
if mute is not None:
|
if mute is not None:
|
||||||
payload['mute'] = mute
|
payload['mute'] = mute
|
||||||
|
|
||||||
|
suppress = fields.get('suppress')
|
||||||
|
if suppress is not None:
|
||||||
|
voice_state_payload = {
|
||||||
|
'channel_id': self.voice.channel.id,
|
||||||
|
'suppress': suppress,
|
||||||
|
}
|
||||||
|
|
||||||
|
if suppress or self.bot:
|
||||||
|
voice_state_payload['request_to_speak_timestamp'] = None
|
||||||
|
|
||||||
|
if me:
|
||||||
|
await http.edit_my_voice_state(guild_id, voice_state_payload)
|
||||||
|
else:
|
||||||
|
if not suppress:
|
||||||
|
voice_state_payload['request_to_speak_timestamp'] = datetime.datetime.utcnow().isoformat()
|
||||||
|
await http.edit_voice_state(guild_id, self.id, voice_state_payload)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
vc = fields['voice_channel']
|
vc = fields['voice_channel']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -613,10 +680,43 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
else:
|
else:
|
||||||
payload['roles'] = tuple(r.id for r in roles)
|
payload['roles'] = tuple(r.id for r in roles)
|
||||||
|
|
||||||
await http.edit_member(guild_id, self.id, reason=reason, **payload)
|
if payload:
|
||||||
|
await http.edit_member(guild_id, self.id, reason=reason, **payload)
|
||||||
|
|
||||||
# TODO: wait for WS event for modify-in-place behaviour
|
# TODO: wait for WS event for modify-in-place behaviour
|
||||||
|
|
||||||
|
async def request_to_speak(self):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Request to speak in the connected channel.
|
||||||
|
|
||||||
|
Only applies to stage channels.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Requesting members that are not the client is equivalent
|
||||||
|
to :attr:`.edit` providing ``suppress`` as ``False``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You do not have the proper permissions to the action requested.
|
||||||
|
HTTPException
|
||||||
|
The operation failed.
|
||||||
|
"""
|
||||||
|
payload = {
|
||||||
|
'channel_id': self.voice.channel.id,
|
||||||
|
'request_to_speak_timestamp': datetime.datetime.utcnow().isoformat(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if self._state.self_id != self.id:
|
||||||
|
payload['suppress'] = False
|
||||||
|
await self._state.http.edit_voice_state(self.guild.id, self.id, payload)
|
||||||
|
else:
|
||||||
|
await self._state.http.edit_my_voice_state(self.guild.id, payload)
|
||||||
|
|
||||||
async def move_to(self, channel, *, reason=None):
|
async def move_to(self, channel, *, reason=None):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -68,9 +68,30 @@ def convert_emoji_reaction(emoji):
|
|||||||
|
|
||||||
raise InvalidArgument('emoji argument must be str, Emoji, or Reaction not {.__class__.__name__}.'.format(emoji))
|
raise InvalidArgument('emoji argument must be str, Emoji, or Reaction not {.__class__.__name__}.'.format(emoji))
|
||||||
|
|
||||||
class Attachment:
|
class Attachment(Hashable):
|
||||||
"""Represents an attachment from Discord.
|
"""Represents an attachment from Discord.
|
||||||
|
|
||||||
|
.. container:: operations
|
||||||
|
|
||||||
|
.. describe:: str(x)
|
||||||
|
|
||||||
|
Returns the URL of the attachment.
|
||||||
|
|
||||||
|
.. describe:: x == y
|
||||||
|
|
||||||
|
Checks if the attachment is equal to another attachment.
|
||||||
|
|
||||||
|
.. describe:: x != y
|
||||||
|
|
||||||
|
Checks if the attachment is not equal to another attachment.
|
||||||
|
|
||||||
|
.. describe:: hash(x)
|
||||||
|
|
||||||
|
Returns the hash of the attachment.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
Attachment can now be casted to :class:`str` and is hashable.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
------------
|
------------
|
||||||
id: :class:`int`
|
id: :class:`int`
|
||||||
@ -90,9 +111,13 @@ class Attachment:
|
|||||||
The proxy URL. This is a cached version of the :attr:`~Attachment.url` in the
|
The proxy URL. This is a cached version of the :attr:`~Attachment.url` in the
|
||||||
case of images. When the message is deleted, this URL might be valid for a few
|
case of images. When the message is deleted, this URL might be valid for a few
|
||||||
minutes or not valid at all.
|
minutes or not valid at all.
|
||||||
|
content_type: Optional[:class:`str`]
|
||||||
|
The attachment's `media type <https://en.wikipedia.org/wiki/Media_type>`_
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('id', 'size', 'height', 'width', 'filename', 'url', 'proxy_url', '_http')
|
__slots__ = ('id', 'size', 'height', 'width', 'filename', 'url', 'proxy_url', '_http', 'content_type')
|
||||||
|
|
||||||
def __init__(self, *, data, state):
|
def __init__(self, *, data, state):
|
||||||
self.id = int(data['id'])
|
self.id = int(data['id'])
|
||||||
@ -103,6 +128,7 @@ class Attachment:
|
|||||||
self.url = data.get('url')
|
self.url = data.get('url')
|
||||||
self.proxy_url = data.get('proxy_url')
|
self.proxy_url = data.get('proxy_url')
|
||||||
self._http = state.http
|
self._http = state.http
|
||||||
|
self.content_type = data.get('content_type')
|
||||||
|
|
||||||
def is_spoiler(self):
|
def is_spoiler(self):
|
||||||
""":class:`bool`: Whether this attachment contains a spoiler."""
|
""":class:`bool`: Whether this attachment contains a spoiler."""
|
||||||
@ -114,6 +140,9 @@ class Attachment:
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Attachment id={0.id} filename={0.filename!r} url={0.url!r}>'.format(self)
|
return '<Attachment id={0.id} filename={0.filename!r} url={0.url!r}>'.format(self)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.url or ''
|
||||||
|
|
||||||
async def save(self, fp, *, seek_begin=True, use_cached=False):
|
async def save(self, fp, *, seek_begin=True, use_cached=False):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@ -282,6 +311,12 @@ class MessageReference:
|
|||||||
The channel id of the message referenced.
|
The channel id of the message referenced.
|
||||||
guild_id: Optional[:class:`int`]
|
guild_id: Optional[:class:`int`]
|
||||||
The guild id of the message referenced.
|
The guild id of the message referenced.
|
||||||
|
fail_if_not_exists: :class:`bool`
|
||||||
|
Whether replying to the referenced message should raise :class:`HTTPException`
|
||||||
|
if the message no longer exists or Discord could not fetch the message.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
resolved: Optional[Union[:class:`Message`, :class:`DeletedReferencedMessage`]]
|
resolved: Optional[Union[:class:`Message`, :class:`DeletedReferencedMessage`]]
|
||||||
The message that this reference resolved to. If this is ``None``
|
The message that this reference resolved to. If this is ``None``
|
||||||
then the original message was not fetched either due to the Discord API
|
then the original message was not fetched either due to the Discord API
|
||||||
@ -294,14 +329,15 @@ class MessageReference:
|
|||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('message_id', 'channel_id', 'guild_id', 'resolved', '_state')
|
__slots__ = ('message_id', 'channel_id', 'guild_id', 'fail_if_not_exists', 'resolved', '_state')
|
||||||
|
|
||||||
def __init__(self, *, message_id, channel_id, guild_id=None):
|
def __init__(self, *, message_id, channel_id, guild_id=None, fail_if_not_exists=True):
|
||||||
self._state = None
|
self._state = None
|
||||||
self.resolved = None
|
self.resolved = None
|
||||||
self.message_id = message_id
|
self.message_id = message_id
|
||||||
self.channel_id = channel_id
|
self.channel_id = channel_id
|
||||||
self.guild_id = guild_id
|
self.guild_id = guild_id
|
||||||
|
self.fail_if_not_exists = fail_if_not_exists
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def with_state(cls, state, data):
|
def with_state(cls, state, data):
|
||||||
@ -309,12 +345,13 @@ class MessageReference:
|
|||||||
self.message_id = utils._get_as_snowflake(data, 'message_id')
|
self.message_id = utils._get_as_snowflake(data, 'message_id')
|
||||||
self.channel_id = int(data.pop('channel_id'))
|
self.channel_id = int(data.pop('channel_id'))
|
||||||
self.guild_id = utils._get_as_snowflake(data, 'guild_id')
|
self.guild_id = utils._get_as_snowflake(data, 'guild_id')
|
||||||
|
self.fail_if_not_exists = data.get('fail_if_not_exists', True)
|
||||||
self._state = state
|
self._state = state
|
||||||
self.resolved = None
|
self.resolved = None
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_message(cls, message):
|
def from_message(cls, message, *, fail_if_not_exists=True):
|
||||||
"""Creates a :class:`MessageReference` from an existing :class:`~discord.Message`.
|
"""Creates a :class:`MessageReference` from an existing :class:`~discord.Message`.
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
@ -323,13 +360,18 @@ class MessageReference:
|
|||||||
----------
|
----------
|
||||||
message: :class:`~discord.Message`
|
message: :class:`~discord.Message`
|
||||||
The message to be converted into a reference.
|
The message to be converted into a reference.
|
||||||
|
fail_if_not_exists: :class:`bool`
|
||||||
|
Whether replying to the referenced message should raise :class:`HTTPException`
|
||||||
|
if the message no longer exists or Discord could not fetch the message.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
:class:`MessageReference`
|
:class:`MessageReference`
|
||||||
A reference to the message.
|
A reference to the message.
|
||||||
"""
|
"""
|
||||||
self = cls(message_id=message.id, channel_id=message.channel.id, guild_id=getattr(message.guild, 'id', None))
|
self = cls(message_id=message.id, channel_id=message.channel.id, guild_id=getattr(message.guild, 'id', None), fail_if_not_exists=fail_if_not_exists)
|
||||||
self._state = message._state
|
self._state = message._state
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@ -356,6 +398,15 @@ class MessageReference:
|
|||||||
"""Optional[:class:`~discord.Message`]: The cached message, if found in the internal message cache."""
|
"""Optional[:class:`~discord.Message`]: The cached message, if found in the internal message cache."""
|
||||||
return self._state._get_message(self.message_id)
|
return self._state._get_message(self.message_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def jump_url(self):
|
||||||
|
""":class:`str`: Returns a URL that allows the client to jump to the referenced message.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
"""
|
||||||
|
guild_id = self.guild_id if self.guild_id is not None else '@me'
|
||||||
|
return 'https://discord.com/channels/{0}/{1.channel_id}/{1.message_id}'.format(guild_id, self)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<MessageReference message_id={0.message_id!r} channel_id={0.channel_id!r} guild_id={0.guild_id!r}>'.format(self)
|
return '<MessageReference message_id={0.message_id!r} channel_id={0.channel_id!r} guild_id={0.guild_id!r}>'.format(self)
|
||||||
|
|
||||||
@ -364,6 +415,8 @@ class MessageReference:
|
|||||||
result['channel_id'] = self.channel_id
|
result['channel_id'] = self.channel_id
|
||||||
if self.guild_id is not None:
|
if self.guild_id is not None:
|
||||||
result['guild_id'] = self.guild_id
|
result['guild_id'] = self.guild_id
|
||||||
|
if self.fail_if_not_exists is not None:
|
||||||
|
result['fail_if_not_exists'] = self.fail_if_not_exists
|
||||||
return result
|
return result
|
||||||
|
|
||||||
to_message_reference_dict = to_dict
|
to_message_reference_dict = to_dict
|
||||||
@ -418,7 +471,7 @@ class Message(Hashable):
|
|||||||
The actual contents of the message.
|
The actual contents of the message.
|
||||||
nonce
|
nonce
|
||||||
The value used by the discord guild and the client to verify that the message is successfully sent.
|
The value used by the discord guild and the client to verify that the message is successfully sent.
|
||||||
This is typically non-important.
|
This is not stored long term within Discord's servers and is only used ephemerally.
|
||||||
embeds: List[:class:`Embed`]
|
embeds: List[:class:`Embed`]
|
||||||
A list of embeds the message has.
|
A list of embeds the message has.
|
||||||
channel: Union[:class:`abc.Messageable`]
|
channel: Union[:class:`abc.Messageable`]
|
||||||
@ -427,6 +480,9 @@ class Message(Hashable):
|
|||||||
call: Optional[:class:`CallMessage`]
|
call: Optional[:class:`CallMessage`]
|
||||||
The call that the message refers to. This is only applicable to messages of type
|
The call that the message refers to. This is only applicable to messages of type
|
||||||
:attr:`MessageType.call`.
|
:attr:`MessageType.call`.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
reference: Optional[:class:`~discord.MessageReference`]
|
reference: Optional[:class:`~discord.MessageReference`]
|
||||||
The message that this message references. This is only applicable to messages of
|
The message that this message references. This is only applicable to messages of
|
||||||
type :attr:`MessageType.pins_add`, crossposted messages created by a
|
type :attr:`MessageType.pins_add`, crossposted messages created by a
|
||||||
@ -516,6 +572,7 @@ class Message(Hashable):
|
|||||||
self.application = data.get('application')
|
self.application = data.get('application')
|
||||||
self.activity = data.get('activity')
|
self.activity = data.get('activity')
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
|
self.call = None
|
||||||
self._edited_timestamp = utils.parse_time(data['edited_timestamp'])
|
self._edited_timestamp = utils.parse_time(data['edited_timestamp'])
|
||||||
self.type = try_enum(MessageType, data['type'])
|
self.type = try_enum(MessageType, data['type'])
|
||||||
self.pinned = data['pinned']
|
self.pinned = data['pinned']
|
||||||
@ -798,9 +855,9 @@ class Message(Hashable):
|
|||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This *does not* escape markdown. If you want to escape
|
This *does not* affect markdown. If you want to escape
|
||||||
markdown then use :func:`utils.escape_markdown` along
|
or remove markdown then use :func:`utils.escape_markdown` or :func:`utils.remove_markdown`
|
||||||
with this function.
|
respectively, along with this function.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
transformations = {
|
transformations = {
|
||||||
@ -938,6 +995,21 @@ class Message(Hashable):
|
|||||||
if self.type is MessageType.channel_follow_add:
|
if self.type is MessageType.channel_follow_add:
|
||||||
return '{0.author.name} has added {0.content} to this channel'.format(self)
|
return '{0.author.name} has added {0.content} to this channel'.format(self)
|
||||||
|
|
||||||
|
if self.type is MessageType.guild_stream:
|
||||||
|
return '{0.author.name} is live! Now streaming {0.author.activity.name}'.format(self)
|
||||||
|
|
||||||
|
if self.type is MessageType.guild_discovery_disqualified:
|
||||||
|
return 'This server has been removed from Server Discovery because it no longer passes all the requirements. Check Server Settings for more details.'
|
||||||
|
|
||||||
|
if self.type is MessageType.guild_discovery_requalified:
|
||||||
|
return 'This server is eligible for Server Discovery again and has been automatically relisted!'
|
||||||
|
|
||||||
|
if self.type is MessageType.guild_discovery_grace_period_initial_warning:
|
||||||
|
return 'This server has failed Discovery activity requirements for 1 week. If this server fails for 4 weeks in a row, it will be automatically removed from Discovery.'
|
||||||
|
|
||||||
|
if self.type is MessageType.guild_discovery_grace_period_final_warning:
|
||||||
|
return 'This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery.'
|
||||||
|
|
||||||
async def delete(self, *, delay=None, silent=False):
|
async def delete(self, *, delay=None, silent=False):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@ -1272,6 +1344,7 @@ class Message(Hashable):
|
|||||||
"""
|
"""
|
||||||
await self._state.http.clear_reactions(self.channel.id, self.id)
|
await self._state.http.clear_reactions(self.channel.id, self.id)
|
||||||
|
|
||||||
|
@utils.deprecated()
|
||||||
async def ack(self):
|
async def ack(self):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@ -1279,6 +1352,8 @@ class Message(Hashable):
|
|||||||
|
|
||||||
The user must not be a bot user.
|
The user must not be a bot user.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
HTTPException
|
HTTPException
|
||||||
@ -1295,8 +1370,8 @@ class Message(Hashable):
|
|||||||
async def reply(self, content=None, **kwargs):
|
async def reply(self, content=None, **kwargs):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
A shortcut method to :meth:`abc.Messageable.send` to reply to the
|
A shortcut method to :meth:`.abc.Messageable.send` to reply to the
|
||||||
:class:`Message`.
|
:class:`.Message`.
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
@ -1312,24 +1387,32 @@ class Message(Hashable):
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
---------
|
---------
|
||||||
:class:`Message`
|
:class:`.Message`
|
||||||
The message that was sent.
|
The message that was sent.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await self.channel.send(content, reference=self, **kwargs)
|
return await self.channel.send(content, reference=self, **kwargs)
|
||||||
|
|
||||||
def to_reference(self):
|
def to_reference(self, *, fail_if_not_exists=True):
|
||||||
"""Creates a :class:`~discord.MessageReference` from the current message.
|
"""Creates a :class:`~discord.MessageReference` from the current message.
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
fail_if_not_exists: :class:`bool`
|
||||||
|
Whether replying using the message reference should raise :class:`HTTPException`
|
||||||
|
if the message no longer exists or Discord could not fetch the message.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
---------
|
---------
|
||||||
:class:`~discord.MessageReference`
|
:class:`~discord.MessageReference`
|
||||||
The reference to this message.
|
The reference to this message.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return MessageReference.from_message(self)
|
return MessageReference.from_message(self, fail_if_not_exists=fail_if_not_exists)
|
||||||
|
|
||||||
def to_message_reference_dict(self):
|
def to_message_reference_dict(self):
|
||||||
data = {
|
data = {
|
||||||
@ -1389,7 +1472,6 @@ class PartialMessage(Hashable):
|
|||||||
_exported_names = (
|
_exported_names = (
|
||||||
'jump_url',
|
'jump_url',
|
||||||
'delete',
|
'delete',
|
||||||
'edit',
|
|
||||||
'publish',
|
'publish',
|
||||||
'pin',
|
'pin',
|
||||||
'unpin',
|
'unpin',
|
||||||
@ -1454,3 +1536,102 @@ class PartialMessage(Hashable):
|
|||||||
|
|
||||||
data = await self._state.http.get_message(self.channel.id, self.id)
|
data = await self._state.http.get_message(self.channel.id, self.id)
|
||||||
return self._state.create_message(channel=self.channel, data=data)
|
return self._state.create_message(channel=self.channel, data=data)
|
||||||
|
|
||||||
|
async def edit(self, **fields):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Edits the message.
|
||||||
|
|
||||||
|
The content must be able to be transformed into a string via ``str(content)``.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
:class:`discord.Message` is returned instead of ``None`` if an edit took place.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
content: Optional[:class:`str`]
|
||||||
|
The new content to replace the message with.
|
||||||
|
Could be ``None`` to remove the content.
|
||||||
|
embed: Optional[:class:`Embed`]
|
||||||
|
The new embed to replace the original with.
|
||||||
|
Could be ``None`` to remove the embed.
|
||||||
|
suppress: :class:`bool`
|
||||||
|
Whether to suppress embeds for the message. This removes
|
||||||
|
all the embeds if set to ``True``. If set to ``False``
|
||||||
|
this brings the embeds back if they were suppressed.
|
||||||
|
Using this parameter requires :attr:`~.Permissions.manage_messages`.
|
||||||
|
delete_after: Optional[:class:`float`]
|
||||||
|
If provided, the number of seconds to wait in the background
|
||||||
|
before deleting the message we just edited. If the deletion fails,
|
||||||
|
then it is silently ignored.
|
||||||
|
allowed_mentions: Optional[:class:`~discord.AllowedMentions`]
|
||||||
|
Controls the mentions being processed in this message. If this is
|
||||||
|
passed, then the object is merged with :attr:`~discord.Client.allowed_mentions`.
|
||||||
|
The merging behaviour only overrides attributes that have been explicitly passed
|
||||||
|
to the object, otherwise it uses the attributes set in :attr:`~discord.Client.allowed_mentions`.
|
||||||
|
If no object is passed at all then the defaults given by :attr:`~discord.Client.allowed_mentions`
|
||||||
|
are used instead.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
NotFound
|
||||||
|
The message was not found.
|
||||||
|
HTTPException
|
||||||
|
Editing the message failed.
|
||||||
|
Forbidden
|
||||||
|
Tried to suppress a message without permissions or
|
||||||
|
edited a message's content or embed that isn't yours.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
Optional[:class:`Message`]
|
||||||
|
The message that was edited.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
content = fields['content']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if content is not None:
|
||||||
|
fields['content'] = str(content)
|
||||||
|
|
||||||
|
try:
|
||||||
|
embed = fields['embed']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if embed is not None:
|
||||||
|
fields['embed'] = embed.to_dict()
|
||||||
|
|
||||||
|
try:
|
||||||
|
suppress = fields.pop('suppress')
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
flags = MessageFlags._from_value(0)
|
||||||
|
flags.suppress_embeds = suppress
|
||||||
|
fields['flags'] = flags.value
|
||||||
|
|
||||||
|
delete_after = fields.pop('delete_after', None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
allowed_mentions = fields.pop('allowed_mentions')
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if allowed_mentions is not None:
|
||||||
|
if self._state.allowed_mentions is not None:
|
||||||
|
allowed_mentions = self._state.allowed_mentions.merge(allowed_mentions).to_dict()
|
||||||
|
else:
|
||||||
|
allowed_mentions = allowed_mentions.to_dict()
|
||||||
|
fields['allowed_mentions'] = allowed_mentions
|
||||||
|
|
||||||
|
if fields:
|
||||||
|
data = await self._state.http.edit_message(self.channel.id, self.id, **fields)
|
||||||
|
|
||||||
|
if delete_after is not None:
|
||||||
|
await self.delete(delay=delete_after)
|
||||||
|
|
||||||
|
if fields:
|
||||||
|
return self._state.create_message(channel=self.channel, data=data)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -276,17 +276,14 @@ class _OpusStruct:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_opus_version() -> str:
|
def get_opus_version() -> str:
|
||||||
if not is_loaded():
|
if not is_loaded() and not _load_default():
|
||||||
if not _load_default():
|
raise OpusNotLoaded()
|
||||||
raise OpusNotLoaded()
|
|
||||||
|
|
||||||
return _lib.opus_get_version_string().decode('utf-8')
|
return _lib.opus_get_version_string().decode('utf-8')
|
||||||
|
|
||||||
class Encoder(_OpusStruct):
|
class Encoder(_OpusStruct):
|
||||||
def __init__(self, application=APPLICATION_AUDIO):
|
def __init__(self, application=APPLICATION_AUDIO):
|
||||||
if not is_loaded():
|
_OpusStruct.get_opus_version()
|
||||||
if not _load_default():
|
|
||||||
raise OpusNotLoaded()
|
|
||||||
|
|
||||||
self.application = application
|
self.application = application
|
||||||
self._state = self._create_state()
|
self._state = self._create_state()
|
||||||
@ -342,9 +339,7 @@ class Encoder(_OpusStruct):
|
|||||||
|
|
||||||
class Decoder(_OpusStruct):
|
class Decoder(_OpusStruct):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if not is_loaded():
|
_OpusStruct.get_opus_version()
|
||||||
if not _load_default():
|
|
||||||
raise OpusNotLoaded()
|
|
||||||
|
|
||||||
self._state = self._create_state()
|
self._state = self._create_state()
|
||||||
|
|
||||||
@ -374,7 +369,7 @@ class Decoder(_OpusStruct):
|
|||||||
|
|
||||||
def _set_gain(self, adjustment):
|
def _set_gain(self, adjustment):
|
||||||
"""Configures decoder gain adjustment.
|
"""Configures decoder gain adjustment.
|
||||||
|
|
||||||
Scales the decoded output by a factor specified in Q8 dB units.
|
Scales the decoded output by a factor specified in Q8 dB units.
|
||||||
This has a maximum range of -32768 to 32767 inclusive, and returns
|
This has a maximum range of -32768 to 32767 inclusive, and returns
|
||||||
OPUS_BAD_ARG (-1) otherwise. The default is zero indicating no adjustment.
|
OPUS_BAD_ARG (-1) otherwise. The default is zero indicating no adjustment.
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -149,10 +149,43 @@ class PartialEmoji(_EmojiTag):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self):
|
def url(self):
|
||||||
""":class:`Asset`: Returns an asset of the emoji, if it is custom."""
|
""":class:`Asset`: Returns the asset of the emoji, if it is custom.
|
||||||
|
|
||||||
|
This is equivalent to calling :meth:`url_as` with
|
||||||
|
the default parameters (i.e. png/gif detection).
|
||||||
|
"""
|
||||||
|
return self.url_as(format=None)
|
||||||
|
|
||||||
|
def url_as(self, *, format=None, static_format="png"):
|
||||||
|
"""Returns an :class:`Asset` for the emoji's url, if it is custom.
|
||||||
|
|
||||||
|
The format must be one of 'webp', 'jpeg', 'jpg', 'png' or 'gif'.
|
||||||
|
'gif' is only valid for animated emojis.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
format: Optional[:class:`str`]
|
||||||
|
The format to attempt to convert the emojis to.
|
||||||
|
If the format is ``None``, then it is automatically
|
||||||
|
detected as either 'gif' or static_format, depending on whether the
|
||||||
|
emoji is animated or not.
|
||||||
|
static_format: Optional[:class:`str`]
|
||||||
|
Format to attempt to convert only non-animated emoji's to.
|
||||||
|
Defaults to 'png'
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
InvalidArgument
|
||||||
|
Bad image format passed to ``format`` or ``static_format``.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`Asset`
|
||||||
|
The resulting CDN asset.
|
||||||
|
"""
|
||||||
if self.is_unicode_emoji():
|
if self.is_unicode_emoji():
|
||||||
return Asset(self._state)
|
return Asset(self._state)
|
||||||
|
|
||||||
_format = 'gif' if self.animated else 'png'
|
return Asset._from_emoji(self._state, self, format=format, static_format=static_format)
|
||||||
url = "/emojis/{0.id}.{1}".format(self, _format)
|
|
||||||
return Asset(self._state, url)
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -140,8 +140,9 @@ class Permissions(BaseFlags):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def all(cls):
|
def all(cls):
|
||||||
"""A factory method that creates a :class:`Permissions` with all
|
"""A factory method that creates a :class:`Permissions` with all
|
||||||
permissions set to ``True``."""
|
permissions set to ``True``.
|
||||||
return cls(0b01111111111111111111111111111111)
|
"""
|
||||||
|
return cls(0b111111111111111111111111111111111)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def all_channel(cls):
|
def all_channel(cls):
|
||||||
@ -149,26 +150,53 @@ class Permissions(BaseFlags):
|
|||||||
``True`` and the guild-specific ones set to ``False``. The guild-specific
|
``True`` and the guild-specific ones set to ``False``. The guild-specific
|
||||||
permissions are currently:
|
permissions are currently:
|
||||||
|
|
||||||
- manage_guild
|
- :attr:`manage_emojis`
|
||||||
- kick_members
|
- :attr:`view_audit_log`
|
||||||
- ban_members
|
- :attr:`view_guild_insights`
|
||||||
- administrator
|
- :attr:`manage_guild`
|
||||||
- change_nickname
|
- :attr:`change_nickname`
|
||||||
- manage_nicknames
|
- :attr:`manage_nicknames`
|
||||||
|
- :attr:`kick_members`
|
||||||
|
- :attr:`ban_members`
|
||||||
|
- :attr:`administrator`
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
Added :attr:`stream`, :attr:`priority_speaker` and :attr:`use_slash_commands` permissions.
|
||||||
"""
|
"""
|
||||||
return cls(0b00110011111101111111110001010001)
|
return cls(0b10110011111101111111111101010001)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def general(cls):
|
def general(cls):
|
||||||
"""A factory method that creates a :class:`Permissions` with all
|
"""A factory method that creates a :class:`Permissions` with all
|
||||||
"General" permissions from the official Discord UI set to ``True``."""
|
"General" permissions from the official Discord UI set to ``True``.
|
||||||
return cls(0b01111100000010000000000010111111)
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
Permission :attr:`read_messages` is now included in the general permissions, but
|
||||||
|
permissions :attr:`administrator`, :attr:`create_instant_invite`, :attr:`kick_members`,
|
||||||
|
:attr:`ban_members`, :attr:`change_nickname` and :attr:`manage_nicknames` are
|
||||||
|
no longer part of the general permissions.
|
||||||
|
"""
|
||||||
|
return cls(0b01110000000010000000010010110000)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def membership(cls):
|
||||||
|
"""A factory method that creates a :class:`Permissions` with all
|
||||||
|
"Membership" permissions from the official Discord UI set to ``True``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
"""
|
||||||
|
return cls(0b00001100000000000000000000000111)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def text(cls):
|
def text(cls):
|
||||||
"""A factory method that creates a :class:`Permissions` with all
|
"""A factory method that creates a :class:`Permissions` with all
|
||||||
"Text" permissions from the official Discord UI set to ``True``."""
|
"Text" permissions from the official Discord UI set to ``True``.
|
||||||
return cls(0b00000000000001111111110001000000)
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
Permission :attr:`read_messages` is no longer part of the text permissions.
|
||||||
|
Added :attr:`use_slash_commands` permission.
|
||||||
|
"""
|
||||||
|
return cls(0b10000000000001111111100001000000)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def voice(cls):
|
def voice(cls):
|
||||||
@ -176,6 +204,32 @@ class Permissions(BaseFlags):
|
|||||||
"Voice" permissions from the official Discord UI set to ``True``."""
|
"Voice" permissions from the official Discord UI set to ``True``."""
|
||||||
return cls(0b00000011111100000000001100000000)
|
return cls(0b00000011111100000000001100000000)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def stage(cls):
|
||||||
|
"""A factory method that creates a :class:`Permissions` with all
|
||||||
|
"Stage Channel" permissions from the official Discord UI set to ``True``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
"""
|
||||||
|
return cls(1 << 32)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def stage_moderator(cls):
|
||||||
|
"""A factory method that creates a :class:`Permissions` with all
|
||||||
|
"Stage Moderator" permissions from the official Discord UI set to ``True``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
"""
|
||||||
|
return cls(0b100000001010000000000000000000000)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def advanced(cls):
|
||||||
|
"""A factory method that creates a :class:`Permissions` with all
|
||||||
|
"Advanced" permissions from the official Discord UI set to ``True``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
"""
|
||||||
|
return cls(1 << 3)
|
||||||
|
|
||||||
def update(self, **kwargs):
|
def update(self, **kwargs):
|
||||||
r"""Bulk updates this permission object.
|
r"""Bulk updates this permission object.
|
||||||
@ -405,9 +459,21 @@ class Permissions(BaseFlags):
|
|||||||
""":class:`bool`: Returns ``True`` if a user can create, edit, or delete emojis."""
|
""":class:`bool`: Returns ``True`` if a user can create, edit, or delete emojis."""
|
||||||
return 1 << 30
|
return 1 << 30
|
||||||
|
|
||||||
# 1 unused
|
@flag_value
|
||||||
|
def use_slash_commands(self):
|
||||||
|
""":class:`bool`: Returns ``True`` if a user can use slash commands.
|
||||||
|
|
||||||
# after these 32 bits, there's 21 more unused ones technically
|
.. versionadded:: 1.7
|
||||||
|
"""
|
||||||
|
return 1 << 31
|
||||||
|
|
||||||
|
@flag_value
|
||||||
|
def request_to_speak(self):
|
||||||
|
""":class:`bool`: Returns ``True`` if a user can request to speak in a stage channel.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
"""
|
||||||
|
return 1 << 32
|
||||||
|
|
||||||
def augment_from_permissions(cls):
|
def augment_from_permissions(cls):
|
||||||
cls.VALID_NAMES = set(Permissions.VALID_FLAGS)
|
cls.VALID_NAMES = set(Permissions.VALID_FLAGS)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -93,14 +93,19 @@ class RawMessageUpdateEvent(_RawReprMixin):
|
|||||||
The channel ID where the update took place.
|
The channel ID where the update took place.
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
|
guild_id: Optional[:class:`int`]
|
||||||
|
The guild ID where the message got updated, if applicable.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
data: :class:`dict`
|
data: :class:`dict`
|
||||||
The raw data given by the `gateway <https://discord.com/developers/docs/topics/gateway#message-update>`_
|
The raw data given by the `gateway <https://discord.com/developers/docs/topics/gateway#message-update>`_
|
||||||
cached_message: Optional[:class:`Message`]
|
cached_message: Optional[:class:`Message`]
|
||||||
The cached message, if found in the internal message cache.
|
The cached message, if found in the internal message cache. Represents the message before
|
||||||
|
it is modified by the data in :attr:`RawMessageUpdateEvent.data`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('message_id', 'channel_id', 'data', 'cached_message')
|
__slots__ = ('message_id', 'channel_id', 'guild_id', 'data', 'cached_message')
|
||||||
|
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.message_id = int(data['id'])
|
self.message_id = int(data['id'])
|
||||||
@ -108,6 +113,11 @@ class RawMessageUpdateEvent(_RawReprMixin):
|
|||||||
self.data = data
|
self.data = data
|
||||||
self.cached_message = None
|
self.cached_message = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.guild_id = int(data['guild_id'])
|
||||||
|
except KeyError:
|
||||||
|
self.guild_id = None
|
||||||
|
|
||||||
class RawReactionActionEvent(_RawReprMixin):
|
class RawReactionActionEvent(_RawReprMixin):
|
||||||
"""Represents the payload for a :func:`on_raw_reaction_add` or
|
"""Represents the payload for a :func:`on_raw_reaction_add` or
|
||||||
:func:`on_raw_reaction_remove` event.
|
:func:`on_raw_reaction_remove` event.
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -25,6 +25,7 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from .enums import RelationshipType, try_enum
|
from .enums import RelationshipType, try_enum
|
||||||
|
from . import utils
|
||||||
|
|
||||||
class Relationship:
|
class Relationship:
|
||||||
"""Represents a relationship in Discord.
|
"""Represents a relationship in Discord.
|
||||||
@ -32,6 +33,8 @@ class Relationship:
|
|||||||
A relationship is like a friendship, a person who is blocked, etc.
|
A relationship is like a friendship, a person who is blocked, etc.
|
||||||
Only non-bot accounts can have relationships.
|
Only non-bot accounts can have relationships.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
user: :class:`User`
|
user: :class:`User`
|
||||||
@ -50,11 +53,14 @@ class Relationship:
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Relationship user={0.user!r} type={0.type!r}>'.format(self)
|
return '<Relationship user={0.user!r} type={0.type!r}>'.format(self)
|
||||||
|
|
||||||
|
@utils.deprecated()
|
||||||
async def delete(self):
|
async def delete(self):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Deletes the relationship.
|
Deletes the relationship.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
HTTPException
|
HTTPException
|
||||||
@ -63,12 +69,15 @@ class Relationship:
|
|||||||
|
|
||||||
await self._state.http.remove_relationship(self.user.id)
|
await self._state.http.remove_relationship(self.user.id)
|
||||||
|
|
||||||
|
@utils.deprecated()
|
||||||
async def accept(self):
|
async def accept(self):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Accepts the relationship request. e.g. accepting a
|
Accepts the relationship request. e.g. accepting a
|
||||||
friend request.
|
friend request.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
HTTPException
|
HTTPException
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -191,7 +191,7 @@ class Role(Hashable):
|
|||||||
|
|
||||||
def _update(self, data):
|
def _update(self, data):
|
||||||
self.name = data['name']
|
self.name = data['name']
|
||||||
self._permissions = data.get('permissions', 0)
|
self._permissions = int(data.get('permissions_new', 0))
|
||||||
self.position = data.get('position', 0)
|
self.position = data.get('position', 0)
|
||||||
self._colour = data.get('color', 0)
|
self._colour = data.get('color', 0)
|
||||||
self.hoist = data.get('hoist', False)
|
self.hoist = data.get('hoist', False)
|
||||||
@ -343,7 +343,7 @@ class Role(Hashable):
|
|||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
'name': fields.get('name', self.name),
|
'name': fields.get('name', self.name),
|
||||||
'permissions': fields.get('permissions', self.permissions).value,
|
'permissions': str(fields.get('permissions', self.permissions).value),
|
||||||
'color': colour.value,
|
'color': colour.value,
|
||||||
'hoist': fields.get('hoist', self.hoist),
|
'hoist': fields.get('hoist', self.hoist),
|
||||||
'mentionable': fields.get('mentionable', self.mentionable)
|
'mentionable': fields.get('mentionable', self.mentionable)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -413,7 +413,7 @@ class AutoShardedClient(Client):
|
|||||||
|
|
||||||
self._connection.shard_count = self.shard_count
|
self._connection.shard_count = self.shard_count
|
||||||
|
|
||||||
shard_ids = self.shard_ids if self.shard_ids else range(self.shard_count)
|
shard_ids = self.shard_ids or range(self.shard_count)
|
||||||
self._connection.shard_ids = shard_ids
|
self._connection.shard_ids = shard_ids
|
||||||
|
|
||||||
for shard_id in shard_ids:
|
for shard_id in shard_ids:
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -827,6 +827,9 @@ class ConnectionState:
|
|||||||
|
|
||||||
return self._add_guild_from_data(data)
|
return self._add_guild_from_data(data)
|
||||||
|
|
||||||
|
def is_guild_evicted(self, guild) -> bool:
|
||||||
|
return guild.id not in self._guilds
|
||||||
|
|
||||||
async def chunk_guild(self, guild, *, wait=True, cache=None):
|
async def chunk_guild(self, guild, *, wait=True, cache=None):
|
||||||
cache = cache or self.member_cache_flags.joined
|
cache = cache or self.member_cache_flags.joined
|
||||||
request = self._chunk_requests.get(guild.id)
|
request = self._chunk_requests.get(guild.id)
|
||||||
@ -896,7 +899,7 @@ class ConnectionState:
|
|||||||
log.debug('GUILD_DELETE referencing an unknown guild ID: %s. Discarding.', data['id'])
|
log.debug('GUILD_DELETE referencing an unknown guild ID: %s. Discarding.', data['id'])
|
||||||
return
|
return
|
||||||
|
|
||||||
if data.get('unavailable', False) and guild is not None:
|
if data.get('unavailable', False):
|
||||||
# GUILD_DELETE with unavailable being True means that the
|
# GUILD_DELETE with unavailable being True means that the
|
||||||
# guild that was available is now currently unavailable
|
# guild that was available is now currently unavailable
|
||||||
guild.unavailable = True
|
guild.unavailable = True
|
||||||
@ -928,10 +931,9 @@ class ConnectionState:
|
|||||||
|
|
||||||
def parse_guild_ban_remove(self, data):
|
def parse_guild_ban_remove(self, data):
|
||||||
guild = self._get_guild(int(data['guild_id']))
|
guild = self._get_guild(int(data['guild_id']))
|
||||||
if guild is not None:
|
if guild is not None and 'user' in data:
|
||||||
if 'user' in data:
|
user = self.store_user(data['user'])
|
||||||
user = self.store_user(data['user'])
|
self.dispatch('member_unban', guild, user)
|
||||||
self.dispatch('member_unban', guild, user)
|
|
||||||
|
|
||||||
def parse_guild_role_create(self, data):
|
def parse_guild_role_create(self, data):
|
||||||
guild = self._get_guild(int(data['guild_id']))
|
guild = self._get_guild(int(data['guild_id']))
|
||||||
@ -1053,6 +1055,11 @@ class ConnectionState:
|
|||||||
member = channel.recipient
|
member = channel.recipient
|
||||||
elif isinstance(channel, TextChannel) and guild is not None:
|
elif isinstance(channel, TextChannel) and guild is not None:
|
||||||
member = guild.get_member(user_id)
|
member = guild.get_member(user_id)
|
||||||
|
if member is None:
|
||||||
|
member_data = data.get('member')
|
||||||
|
if member_data:
|
||||||
|
member = Member(data=member_data, state=self, guild=guild)
|
||||||
|
|
||||||
elif isinstance(channel, GroupChannel):
|
elif isinstance(channel, GroupChannel):
|
||||||
member = utils.find(lambda x: x.id == user_id, channel.recipients)
|
member = utils.find(lambda x: x.id == user_id, channel.recipients)
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -30,7 +30,7 @@ from .utils import snowflake_time
|
|||||||
from .enums import StickerType, try_enum
|
from .enums import StickerType, try_enum
|
||||||
|
|
||||||
class Sticker(Hashable):
|
class Sticker(Hashable):
|
||||||
"""Represents a sticker
|
"""Represents a sticker.
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
@ -38,34 +38,34 @@ class Sticker(Hashable):
|
|||||||
|
|
||||||
.. describe:: str(x)
|
.. describe:: str(x)
|
||||||
|
|
||||||
Returns the name of the sticker
|
Returns the name of the sticker.
|
||||||
|
|
||||||
.. describe:: x == y
|
.. describe:: x == y
|
||||||
|
|
||||||
Checks if the sticker is equal to another sticker
|
Checks if the sticker is equal to another sticker.
|
||||||
|
|
||||||
.. describe:: x != y
|
.. describe:: x != y
|
||||||
|
|
||||||
Checks if the sticker is not equal to another sticker
|
Checks if the sticker is not equal to another sticker.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
name: :class:`str`
|
name: :class:`str`
|
||||||
The sticker's name
|
The sticker's name.
|
||||||
id: :class:`int`
|
id: :class:`int`
|
||||||
The id of the sticker
|
The id of the sticker.
|
||||||
description: :class:`str`
|
description: :class:`str`
|
||||||
The description of the sticker
|
The description of the sticker.
|
||||||
pack_id: :class:`int`
|
pack_id: :class:`int`
|
||||||
The id of the sticker's pack
|
The id of the sticker's pack.
|
||||||
format: :class:`StickerType`
|
format: :class:`StickerType`
|
||||||
The format for the sticker's image
|
The format for the sticker's image.
|
||||||
image: :class:`str`
|
image: :class:`str`
|
||||||
The sticker's image
|
The sticker's image.
|
||||||
tags: List[:class:`str`]
|
tags: List[:class:`str`]
|
||||||
A list of tags for the sticker
|
A list of tags for the sticker.
|
||||||
preview_image: Optional[:class:`str`]
|
preview_image: Optional[:class:`str`]
|
||||||
The sticker's preview asset hash
|
The sticker's preview asset hash.
|
||||||
"""
|
"""
|
||||||
__slots__ = ('_state', 'id', 'name', 'description', 'pack_id', 'format', 'image', 'tags', 'preview_image')
|
__slots__ = ('_state', 'id', 'name', 'description', 'pack_id', 'format', 'image', 'tags', 'preview_image')
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ class Sticker(Hashable):
|
|||||||
"""Returns an :class:`Asset` for the sticker's image.
|
"""Returns an :class:`Asset` for the sticker's image.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
This will return ``None`` if the format is ``StickerType.lottie``
|
This will return ``None`` if the format is ``StickerType.lottie``.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -105,7 +105,9 @@ class Template:
|
|||||||
|
|
||||||
def __init__(self, *, state, data):
|
def __init__(self, *, state, data):
|
||||||
self._state = state
|
self._state = state
|
||||||
|
self._store(data)
|
||||||
|
|
||||||
|
def _store(self, data):
|
||||||
self.code = data['code']
|
self.code = data['code']
|
||||||
self.uses = data['usage_count']
|
self.uses = data['usage_count']
|
||||||
self.name = data['name']
|
self.name = data['name']
|
||||||
@ -117,11 +119,16 @@ class Template:
|
|||||||
self.updated_at = parse_time(data.get('updated_at'))
|
self.updated_at = parse_time(data.get('updated_at'))
|
||||||
|
|
||||||
id = _get_as_snowflake(data, 'source_guild_id')
|
id = _get_as_snowflake(data, 'source_guild_id')
|
||||||
source_serialised = data['serialized_source_guild']
|
|
||||||
source_serialised['id'] = id
|
|
||||||
state = _PartialTemplateState(state=self._state)
|
|
||||||
|
|
||||||
self.source_guild = Guild(data=source_serialised, state=state)
|
guild = self._state._get_guild(id)
|
||||||
|
|
||||||
|
if guild is None:
|
||||||
|
source_serialised = data['serialized_source_guild']
|
||||||
|
source_serialised['id'] = id
|
||||||
|
state = _PartialTemplateState(state=self._state)
|
||||||
|
guild = Guild(data=source_serialised, state=state)
|
||||||
|
|
||||||
|
self.source_guild = guild
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Template code={0.code!r} uses={0.uses} name={0.name!r}' \
|
return '<Template code={0.code!r} uses={0.uses} name={0.name!r}' \
|
||||||
@ -147,9 +154,9 @@ class Template:
|
|||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
:exc:`.HTTPException`
|
HTTPException
|
||||||
Guild creation failed.
|
Guild creation failed.
|
||||||
:exc:`.InvalidArgument`
|
InvalidArgument
|
||||||
Invalid icon image format given. Must be PNG or JPG.
|
Invalid icon image format given. Must be PNG or JPG.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
@ -161,10 +168,81 @@ class Template:
|
|||||||
if icon is not None:
|
if icon is not None:
|
||||||
icon = _bytes_to_base64_data(icon)
|
icon = _bytes_to_base64_data(icon)
|
||||||
|
|
||||||
if region is None:
|
region = region or VoiceRegion.us_west
|
||||||
region = VoiceRegion.us_west.value
|
region_value = region.value
|
||||||
else:
|
|
||||||
region = region.value
|
|
||||||
|
|
||||||
data = await self._state.http.create_from_template(self.code, name, region, icon)
|
data = await self._state.http.create_from_template(self.code, name, region_value, icon)
|
||||||
return Guild(data=data, state=self._state)
|
return Guild(data=data, state=self._state)
|
||||||
|
|
||||||
|
async def sync(self):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Sync the template to the guild's current state.
|
||||||
|
|
||||||
|
You must have the :attr:`~Permissions.manage_guild` permission in the
|
||||||
|
source guild to do this.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
HTTPException
|
||||||
|
Editing the template failed.
|
||||||
|
Forbidden
|
||||||
|
You don't have permissions to edit the template.
|
||||||
|
NotFound
|
||||||
|
This template does not exist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = await self._state.http.sync_template(self.source_guild.id, self.code)
|
||||||
|
self._store(data)
|
||||||
|
|
||||||
|
async def edit(self, **kwargs):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Edit the template metadata.
|
||||||
|
|
||||||
|
You must have the :attr:`~Permissions.manage_guild` permission in the
|
||||||
|
source guild to do this.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
name: Optional[:class:`str`]
|
||||||
|
The template's new name.
|
||||||
|
description: Optional[:class:`str`]
|
||||||
|
The template's description.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
HTTPException
|
||||||
|
Editing the template failed.
|
||||||
|
Forbidden
|
||||||
|
You don't have permissions to edit the template.
|
||||||
|
NotFound
|
||||||
|
This template does not exist.
|
||||||
|
"""
|
||||||
|
data = await self._state.http.edit_template(self.source_guild.id, self.code, kwargs)
|
||||||
|
self._store(data)
|
||||||
|
|
||||||
|
async def delete(self):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Delete the template.
|
||||||
|
|
||||||
|
You must have the :attr:`~Permissions.manage_guild` permission in the
|
||||||
|
source guild to do this.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
HTTPException
|
||||||
|
Editing the template failed.
|
||||||
|
Forbidden
|
||||||
|
You don't have permissions to edit the template.
|
||||||
|
NotFound
|
||||||
|
This template does not exist.
|
||||||
|
"""
|
||||||
|
await self._state.http.delete_template(self.source_guild.id, self.code)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -33,6 +33,7 @@ from .enums import DefaultAvatar, RelationshipType, UserFlags, HypeSquadHouse, P
|
|||||||
from .errors import ClientException
|
from .errors import ClientException
|
||||||
from .colour import Colour
|
from .colour import Colour
|
||||||
from .asset import Asset
|
from .asset import Asset
|
||||||
|
from .utils import deprecated
|
||||||
|
|
||||||
class Profile(namedtuple('Profile', 'flags user mutual_guilds connected_accounts premium_since')):
|
class Profile(namedtuple('Profile', 'flags user mutual_guilds connected_accounts premium_since')):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
@ -123,6 +124,7 @@ class BaseUser(_BaseUser):
|
|||||||
self.avatar = user.avatar
|
self.avatar = user.avatar
|
||||||
self.bot = user.bot
|
self.bot = user.bot
|
||||||
self._state = user._state
|
self._state = user._state
|
||||||
|
self._public_flags = user._public_flags
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@ -272,11 +274,7 @@ class BaseUser(_BaseUser):
|
|||||||
if message.mention_everyone:
|
if message.mention_everyone:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
for user in message.mentions:
|
return any(user.id == self.id for user in message.mentions)
|
||||||
if user.id == self.id:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
class ClientUser(BaseUser):
|
class ClientUser(BaseUser):
|
||||||
"""Represents your Discord user.
|
"""Represents your Discord user.
|
||||||
@ -317,17 +315,25 @@ class ClientUser(BaseUser):
|
|||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
|
|
||||||
verified: :class:`bool`
|
verified: :class:`bool`
|
||||||
Specifies if the user is a verified account.
|
Specifies if the user's email is verified.
|
||||||
email: Optional[:class:`str`]
|
email: Optional[:class:`str`]
|
||||||
The email the user used when registering.
|
The email the user used when registering.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
locale: Optional[:class:`str`]
|
locale: Optional[:class:`str`]
|
||||||
The IETF language tag used to identify the language the user is using.
|
The IETF language tag used to identify the language the user is using.
|
||||||
mfa_enabled: :class:`bool`
|
mfa_enabled: :class:`bool`
|
||||||
Specifies if the user has MFA turned on and working.
|
Specifies if the user has MFA turned on and working.
|
||||||
premium: :class:`bool`
|
premium: :class:`bool`
|
||||||
Specifies if the user is a premium user (e.g. has Discord Nitro).
|
Specifies if the user is a premium user (e.g. has Discord Nitro).
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
premium_type: Optional[:class:`PremiumType`]
|
premium_type: Optional[:class:`PremiumType`]
|
||||||
Specifies the type of premium a user has (e.g. Nitro or Nitro Classic). Could be None if the user is not premium.
|
Specifies the type of premium a user has (e.g. Nitro or Nitro Classic). Could be None if the user is not premium.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
"""
|
"""
|
||||||
__slots__ = BaseUser.__slots__ + \
|
__slots__ = BaseUser.__slots__ + \
|
||||||
('email', 'locale', '_flags', 'verified', 'mfa_enabled',
|
('email', 'locale', '_flags', 'verified', 'mfa_enabled',
|
||||||
@ -352,9 +358,12 @@ class ClientUser(BaseUser):
|
|||||||
self.premium = data.get('premium', False)
|
self.premium = data.get('premium', False)
|
||||||
self.premium_type = try_enum(PremiumType, data.get('premium_type', None))
|
self.premium_type = try_enum(PremiumType, data.get('premium_type', None))
|
||||||
|
|
||||||
|
@deprecated()
|
||||||
def get_relationship(self, user_id):
|
def get_relationship(self, user_id):
|
||||||
"""Retrieves the :class:`Relationship` if applicable.
|
"""Retrieves the :class:`Relationship` if applicable.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This can only be used by non-bot accounts.
|
This can only be used by non-bot accounts.
|
||||||
@ -375,6 +384,8 @@ class ClientUser(BaseUser):
|
|||||||
def relationships(self):
|
def relationships(self):
|
||||||
"""List[:class:`User`]: Returns all the relationships that the user has.
|
"""List[:class:`User`]: Returns all the relationships that the user has.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This can only be used by non-bot accounts.
|
This can only be used by non-bot accounts.
|
||||||
@ -385,6 +396,8 @@ class ClientUser(BaseUser):
|
|||||||
def friends(self):
|
def friends(self):
|
||||||
r"""List[:class:`User`]: Returns all the users that the user is friends with.
|
r"""List[:class:`User`]: Returns all the users that the user is friends with.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This can only be used by non-bot accounts.
|
This can only be used by non-bot accounts.
|
||||||
@ -395,6 +408,8 @@ class ClientUser(BaseUser):
|
|||||||
def blocked(self):
|
def blocked(self):
|
||||||
r"""List[:class:`User`]: Returns all the users that the user has blocked.
|
r"""List[:class:`User`]: Returns all the users that the user has blocked.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This can only be used by non-bot accounts.
|
This can only be used by non-bot accounts.
|
||||||
@ -409,6 +424,10 @@ class ClientUser(BaseUser):
|
|||||||
If a bot account is used then a password field is optional,
|
If a bot account is used then a password field is optional,
|
||||||
otherwise it is required.
|
otherwise it is required.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
The user account-only fields are deprecated.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
To upload an avatar, a :term:`py:bytes-like object` must be passed in that
|
To upload an avatar, a :term:`py:bytes-like object` must be passed in that
|
||||||
@ -500,6 +519,7 @@ class ClientUser(BaseUser):
|
|||||||
|
|
||||||
self._update(data)
|
self._update(data)
|
||||||
|
|
||||||
|
@deprecated()
|
||||||
async def create_group(self, *recipients):
|
async def create_group(self, *recipients):
|
||||||
r"""|coro|
|
r"""|coro|
|
||||||
|
|
||||||
@ -507,6 +527,8 @@ class ClientUser(BaseUser):
|
|||||||
provided. These recipients must be have a relationship
|
provided. These recipients must be have a relationship
|
||||||
of type :attr:`RelationshipType.friend`.
|
of type :attr:`RelationshipType.friend`.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This can only be used by non-bot accounts.
|
This can only be used by non-bot accounts.
|
||||||
@ -540,11 +562,14 @@ class ClientUser(BaseUser):
|
|||||||
data = await self._state.http.start_group(self.id, users)
|
data = await self._state.http.start_group(self.id, users)
|
||||||
return GroupChannel(me=self, data=data, state=self._state)
|
return GroupChannel(me=self, data=data, state=self._state)
|
||||||
|
|
||||||
|
@deprecated()
|
||||||
async def edit_settings(self, **kwargs):
|
async def edit_settings(self, **kwargs):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Edits the client user's settings.
|
Edits the client user's settings.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This can only be used by non-bot accounts.
|
This can only be used by non-bot accounts.
|
||||||
@ -706,8 +731,22 @@ class User(BaseUser, discord.abc.Messageable):
|
|||||||
"""
|
"""
|
||||||
return self._state._get_private_channel_by_user(self.id)
|
return self._state._get_private_channel_by_user(self.id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mutual_guilds(self):
|
||||||
|
"""List[:class:`Guild`]: The guilds that the user shares with the client.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This will only return mutual guilds within the client's internal cache.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
"""
|
||||||
|
return [guild for guild in self._state._guilds.values() if guild.get_member(self.id)]
|
||||||
|
|
||||||
async def create_dm(self):
|
async def create_dm(self):
|
||||||
"""Creates a :class:`DMChannel` with this user.
|
"""|coro|
|
||||||
|
|
||||||
|
Creates a :class:`DMChannel` with this user.
|
||||||
|
|
||||||
This should be rarely called, as this is done transparently for most
|
This should be rarely called, as this is done transparently for most
|
||||||
people.
|
people.
|
||||||
@ -729,17 +768,22 @@ class User(BaseUser, discord.abc.Messageable):
|
|||||||
def relationship(self):
|
def relationship(self):
|
||||||
"""Optional[:class:`Relationship`]: Returns the :class:`Relationship` with this user if applicable, ``None`` otherwise.
|
"""Optional[:class:`Relationship`]: Returns the :class:`Relationship` with this user if applicable, ``None`` otherwise.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This can only be used by non-bot accounts.
|
This can only be used by non-bot accounts.
|
||||||
"""
|
"""
|
||||||
return self._state.user.get_relationship(self.id)
|
return self._state.user.get_relationship(self.id)
|
||||||
|
|
||||||
|
@deprecated()
|
||||||
async def mutual_friends(self):
|
async def mutual_friends(self):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Gets all mutual friends of this user.
|
Gets all mutual friends of this user.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This can only be used by non-bot accounts.
|
This can only be used by non-bot accounts.
|
||||||
@ -760,9 +804,12 @@ class User(BaseUser, discord.abc.Messageable):
|
|||||||
mutuals = await state.http.get_mutual_friends(self.id)
|
mutuals = await state.http.get_mutual_friends(self.id)
|
||||||
return [User(state=state, data=friend) for friend in mutuals]
|
return [User(state=state, data=friend) for friend in mutuals]
|
||||||
|
|
||||||
|
@deprecated()
|
||||||
def is_friend(self):
|
def is_friend(self):
|
||||||
""":class:`bool`: Checks if the user is your friend.
|
""":class:`bool`: Checks if the user is your friend.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This can only be used by non-bot accounts.
|
This can only be used by non-bot accounts.
|
||||||
@ -772,9 +819,12 @@ class User(BaseUser, discord.abc.Messageable):
|
|||||||
return False
|
return False
|
||||||
return r.type is RelationshipType.friend
|
return r.type is RelationshipType.friend
|
||||||
|
|
||||||
|
@deprecated()
|
||||||
def is_blocked(self):
|
def is_blocked(self):
|
||||||
""":class:`bool`: Checks if the user is blocked.
|
""":class:`bool`: Checks if the user is blocked.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This can only be used by non-bot accounts.
|
This can only be used by non-bot accounts.
|
||||||
@ -784,11 +834,14 @@ class User(BaseUser, discord.abc.Messageable):
|
|||||||
return False
|
return False
|
||||||
return r.type is RelationshipType.blocked
|
return r.type is RelationshipType.blocked
|
||||||
|
|
||||||
|
@deprecated()
|
||||||
async def block(self):
|
async def block(self):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Blocks the user.
|
Blocks the user.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This can only be used by non-bot accounts.
|
This can only be used by non-bot accounts.
|
||||||
@ -803,11 +856,14 @@ class User(BaseUser, discord.abc.Messageable):
|
|||||||
|
|
||||||
await self._state.http.add_relationship(self.id, type=RelationshipType.blocked.value)
|
await self._state.http.add_relationship(self.id, type=RelationshipType.blocked.value)
|
||||||
|
|
||||||
|
@deprecated()
|
||||||
async def unblock(self):
|
async def unblock(self):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Unblocks the user.
|
Unblocks the user.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This can only be used by non-bot accounts.
|
This can only be used by non-bot accounts.
|
||||||
@ -821,11 +877,14 @@ class User(BaseUser, discord.abc.Messageable):
|
|||||||
"""
|
"""
|
||||||
await self._state.http.remove_relationship(self.id)
|
await self._state.http.remove_relationship(self.id)
|
||||||
|
|
||||||
|
@deprecated()
|
||||||
async def remove_friend(self):
|
async def remove_friend(self):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Removes the user as a friend.
|
Removes the user as a friend.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This can only be used by non-bot accounts.
|
This can only be used by non-bot accounts.
|
||||||
@ -839,11 +898,14 @@ class User(BaseUser, discord.abc.Messageable):
|
|||||||
"""
|
"""
|
||||||
await self._state.http.remove_relationship(self.id)
|
await self._state.http.remove_relationship(self.id)
|
||||||
|
|
||||||
|
@deprecated()
|
||||||
async def send_friend_request(self):
|
async def send_friend_request(self):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Sends the user a friend request.
|
Sends the user a friend request.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This can only be used by non-bot accounts.
|
This can only be used by non-bot accounts.
|
||||||
@ -857,11 +919,14 @@ class User(BaseUser, discord.abc.Messageable):
|
|||||||
"""
|
"""
|
||||||
await self._state.http.send_friend_request(username=self.name, discriminator=self.discriminator)
|
await self._state.http.send_friend_request(username=self.name, discriminator=self.discriminator)
|
||||||
|
|
||||||
|
@deprecated()
|
||||||
async def profile(self):
|
async def profile(self):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Gets the user's profile.
|
Gets the user's profile.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This can only be used by non-bot accounts.
|
This can only be used by non-bot accounts.
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -32,7 +32,7 @@ from base64 import b64encode
|
|||||||
from bisect import bisect_left
|
from bisect import bisect_left
|
||||||
import datetime
|
import datetime
|
||||||
import functools
|
import functools
|
||||||
from inspect import isawaitable as _isawaitable
|
from inspect import isawaitable as _isawaitable, signature as _signature
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
@ -110,6 +110,13 @@ def parse_time(timestamp):
|
|||||||
return datetime.datetime(*map(int, re.split(r'[^\d]', timestamp.replace('+00:00', ''))))
|
return datetime.datetime(*map(int, re.split(r'[^\d]', timestamp.replace('+00:00', ''))))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def copy_doc(original):
|
||||||
|
def decorator(overriden):
|
||||||
|
overriden.__doc__ = original.__doc__
|
||||||
|
overriden.__signature__ = _signature(original)
|
||||||
|
return overriden
|
||||||
|
return decorator
|
||||||
|
|
||||||
def deprecated(instead=None):
|
def deprecated(instead=None):
|
||||||
def actual_decorator(func):
|
def actual_decorator(func):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
@ -126,7 +133,7 @@ def deprecated(instead=None):
|
|||||||
return decorated
|
return decorated
|
||||||
return actual_decorator
|
return actual_decorator
|
||||||
|
|
||||||
def oauth_url(client_id, permissions=None, guild=None, redirect_uri=None):
|
def oauth_url(client_id, permissions=None, guild=None, redirect_uri=None, scopes=None):
|
||||||
"""A helper function that returns the OAuth2 URL for inviting the bot
|
"""A helper function that returns the OAuth2 URL for inviting the bot
|
||||||
into guilds.
|
into guilds.
|
||||||
|
|
||||||
@ -141,13 +148,18 @@ def oauth_url(client_id, permissions=None, guild=None, redirect_uri=None):
|
|||||||
The guild to pre-select in the authorization screen, if available.
|
The guild to pre-select in the authorization screen, if available.
|
||||||
redirect_uri: :class:`str`
|
redirect_uri: :class:`str`
|
||||||
An optional valid redirect URI.
|
An optional valid redirect URI.
|
||||||
|
scopes: Iterable[:class:`str`]
|
||||||
|
An optional valid list of scopes. Defaults to ``('bot',)``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
--------
|
--------
|
||||||
:class:`str`
|
:class:`str`
|
||||||
The OAuth2 URL for inviting the bot into guilds.
|
The OAuth2 URL for inviting the bot into guilds.
|
||||||
"""
|
"""
|
||||||
url = 'https://discord.com/oauth2/authorize?client_id={}&scope=bot'.format(client_id)
|
url = 'https://discord.com/oauth2/authorize?client_id={}'.format(client_id)
|
||||||
|
url = url + '&scope=' + '+'.join(scopes or ('bot',))
|
||||||
if permissions is not None:
|
if permissions is not None:
|
||||||
url = url + '&permissions=' + str(permissions.value)
|
url = url + '&permissions=' + str(permissions.value)
|
||||||
if guild is not None:
|
if guild is not None:
|
||||||
@ -419,11 +431,8 @@ def _string_width(string, *, _IS_ASCII=_IS_ASCII):
|
|||||||
return match.endpos
|
return match.endpos
|
||||||
|
|
||||||
UNICODE_WIDE_CHAR_TYPE = 'WFA'
|
UNICODE_WIDE_CHAR_TYPE = 'WFA'
|
||||||
width = 0
|
|
||||||
func = unicodedata.east_asian_width
|
func = unicodedata.east_asian_width
|
||||||
for char in string:
|
return sum(2 if func(char) in UNICODE_WIDE_CHAR_TYPE else 1 for char in string)
|
||||||
width += 2 if func(char) in UNICODE_WIDE_CHAR_TYPE else 1
|
|
||||||
return width
|
|
||||||
|
|
||||||
def resolve_invite(invite):
|
def resolve_invite(invite):
|
||||||
"""
|
"""
|
||||||
@ -482,6 +491,43 @@ _MARKDOWN_ESCAPE_COMMON = r'^>(?:>>)?\s|\[.+\]\(.+\)'
|
|||||||
|
|
||||||
_MARKDOWN_ESCAPE_REGEX = re.compile(r'(?P<markdown>%s|%s)' % (_MARKDOWN_ESCAPE_SUBREGEX, _MARKDOWN_ESCAPE_COMMON), re.MULTILINE)
|
_MARKDOWN_ESCAPE_REGEX = re.compile(r'(?P<markdown>%s|%s)' % (_MARKDOWN_ESCAPE_SUBREGEX, _MARKDOWN_ESCAPE_COMMON), re.MULTILINE)
|
||||||
|
|
||||||
|
_URL_REGEX = r'(?P<url><[^: >]+:\/[^ >]+>|(?:https?|steam):\/\/[^\s<]+[^<.,:;\"\'\]\s])'
|
||||||
|
|
||||||
|
_MARKDOWN_STOCK_REGEX = r'(?P<markdown>[_\\~|\*`]|%s)' % _MARKDOWN_ESCAPE_COMMON
|
||||||
|
|
||||||
|
def remove_markdown(text, *, ignore_links=True):
|
||||||
|
"""A helper function that removes markdown characters.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
This function is not markdown aware and may remove meaning from the original text. For example,
|
||||||
|
if the input contains ``10 * 5`` then it will be converted into ``10 5``.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
text: :class:`str`
|
||||||
|
The text to remove markdown from.
|
||||||
|
ignore_links: :class:`bool`
|
||||||
|
Whether to leave links alone when removing markdown. For example,
|
||||||
|
if a URL in the text contains characters such as ``_`` then it will
|
||||||
|
be left alone. Defaults to ``True``.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`str`
|
||||||
|
The text with the markdown special characters removed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def replacement(match):
|
||||||
|
groupdict = match.groupdict()
|
||||||
|
return groupdict.get('url', '')
|
||||||
|
|
||||||
|
regex = _MARKDOWN_STOCK_REGEX
|
||||||
|
if ignore_links:
|
||||||
|
regex = '(?:%s|%s)' % (_URL_REGEX, regex)
|
||||||
|
return re.sub(regex, replacement, text, 0, re.MULTILINE)
|
||||||
|
|
||||||
def escape_markdown(text, *, as_needed=False, ignore_links=True):
|
def escape_markdown(text, *, as_needed=False, ignore_links=True):
|
||||||
r"""A helper function that escapes Discord's markdown.
|
r"""A helper function that escapes Discord's markdown.
|
||||||
|
|
||||||
@ -508,7 +554,6 @@ def escape_markdown(text, *, as_needed=False, ignore_links=True):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if not as_needed:
|
if not as_needed:
|
||||||
url_regex = r'(?P<url><[^: >]+:\/[^ >]+>|(?:https?|steam):\/\/[^\s<]+[^<.,:;\"\'\]\s])'
|
|
||||||
def replacement(match):
|
def replacement(match):
|
||||||
groupdict = match.groupdict()
|
groupdict = match.groupdict()
|
||||||
is_url = groupdict.get('url')
|
is_url = groupdict.get('url')
|
||||||
@ -516,9 +561,9 @@ def escape_markdown(text, *, as_needed=False, ignore_links=True):
|
|||||||
return is_url
|
return is_url
|
||||||
return '\\' + groupdict['markdown']
|
return '\\' + groupdict['markdown']
|
||||||
|
|
||||||
regex = r'(?P<markdown>[_\\~|\*`]|%s)' % _MARKDOWN_ESCAPE_COMMON
|
regex = _MARKDOWN_STOCK_REGEX
|
||||||
if ignore_links:
|
if ignore_links:
|
||||||
regex = '(?:%s|%s)' % (url_regex, regex)
|
regex = '(?:%s|%s)' % (_URL_REGEX, regex)
|
||||||
return re.sub(regex, replacement, text, 0, re.MULTILINE)
|
return re.sub(regex, replacement, text, 0, re.MULTILINE)
|
||||||
else:
|
else:
|
||||||
text = re.sub(r'\\', r'\\\\', text)
|
text = re.sub(r'\\', r'\\\\', text)
|
||||||
@ -547,4 +592,4 @@ def escape_mentions(text):
|
|||||||
:class:`str`
|
:class:`str`
|
||||||
The text with the mentions removed.
|
The text with the mentions removed.
|
||||||
"""
|
"""
|
||||||
return re.sub(r'@(everyone|here|[!&]?[0-9]{17,21})', '@\u200b\\1', text)
|
return re.sub(r'@(everyone|here|[!&]?[0-9]{17,20})', '@\u200b\\1', text)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -70,7 +70,7 @@ class VoiceProtocol:
|
|||||||
|
|
||||||
These classes are passed to :meth:`abc.Connectable.connect`.
|
These classes are passed to :meth:`abc.Connectable.connect`.
|
||||||
|
|
||||||
.. _Lavalink: https://github.com/Frederikam/Lavalink
|
.. _Lavalink: https://github.com/freyacodes/Lavalink
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
------------
|
------------
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -203,13 +203,6 @@ class AsyncWebhookAdapter(WebhookAdapter):
|
|||||||
if reason:
|
if reason:
|
||||||
headers['X-Audit-Log-Reason'] = _uriquote(reason, safe='/ ')
|
headers['X-Audit-Log-Reason'] = _uriquote(reason, safe='/ ')
|
||||||
|
|
||||||
if multipart:
|
|
||||||
data = aiohttp.FormData()
|
|
||||||
for key, value in multipart.items():
|
|
||||||
if key.startswith('file'):
|
|
||||||
data.add_field(key, value[1], filename=value[0], content_type=value[2])
|
|
||||||
else:
|
|
||||||
data.add_field(key, value)
|
|
||||||
|
|
||||||
base_url = url.replace(self._request_url, '/') or '/'
|
base_url = url.replace(self._request_url, '/') or '/'
|
||||||
_id = self._webhook_id
|
_id = self._webhook_id
|
||||||
@ -217,6 +210,14 @@ class AsyncWebhookAdapter(WebhookAdapter):
|
|||||||
for file in files:
|
for file in files:
|
||||||
file.reset(seek=tries)
|
file.reset(seek=tries)
|
||||||
|
|
||||||
|
if multipart:
|
||||||
|
data = aiohttp.FormData()
|
||||||
|
for key, value in multipart.items():
|
||||||
|
if key.startswith('file'):
|
||||||
|
data.add_field(key, value[1], filename=value[0], content_type=value[2])
|
||||||
|
else:
|
||||||
|
data.add_field(key, value)
|
||||||
|
|
||||||
async with self.session.request(verb, url, headers=headers, data=data) as r:
|
async with self.session.request(verb, url, headers=headers, data=data) as r:
|
||||||
log.debug('Webhook ID %s with %s %s has returned status code %s', _id, verb, base_url, r.status)
|
log.debug('Webhook ID %s with %s %s has returned status code %s', _id, verb, base_url, r.status)
|
||||||
# Coerce empty strings to return None for hygiene purposes
|
# Coerce empty strings to return None for hygiene purposes
|
||||||
@ -508,7 +509,7 @@ class WebhookMessage(Message):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if delay is not None:
|
if delay is not None:
|
||||||
if self._state.parent._adapter.is_async():
|
if self._state._webhook._adapter.is_async():
|
||||||
return self._delete_delay_async(delay)
|
return self._delete_delay_async(delay)
|
||||||
else:
|
else:
|
||||||
return self._delete_delay_sync(delay)
|
return self._delete_delay_sync(delay)
|
||||||
@ -687,7 +688,7 @@ class Webhook(Hashable):
|
|||||||
A partial webhook is just a webhook object with an ID and a token.
|
A partial webhook is just a webhook object with an ID and a token.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
m = re.search(r'discord(?:app)?.com/api/webhooks/(?P<id>[0-9]{17,21})/(?P<token>[A-Za-z0-9\.\-\_]{60,68})', url)
|
m = re.search(r'discord(?:app)?.com/api/webhooks/(?P<id>[0-9]{17,20})/(?P<token>[A-Za-z0-9\.\-\_]{60,68})', url)
|
||||||
if m is None:
|
if m is None:
|
||||||
raise InvalidArgument('Invalid webhook URL given.')
|
raise InvalidArgument('Invalid webhook URL given.')
|
||||||
data = m.groupdict()
|
data = m.groupdict()
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Rapptz
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
@ -156,7 +156,7 @@ class WidgetMember(BaseUser):
|
|||||||
@property
|
@property
|
||||||
def display_name(self):
|
def display_name(self):
|
||||||
""":class:`str`: Returns the member's display name."""
|
""":class:`str`: Returns the member's display name."""
|
||||||
return self.nick if self.nick else self.name
|
return self.nick or self.name
|
||||||
|
|
||||||
class Widget:
|
class Widget:
|
||||||
"""Represents a :class:`Guild` widget.
|
"""Represents a :class:`Guild` widget.
|
||||||
|
144
docs/api.rst
144
docs/api.rst
@ -40,6 +40,10 @@ Client
|
|||||||
|
|
||||||
.. autoclass:: Client
|
.. autoclass:: Client
|
||||||
:members:
|
:members:
|
||||||
|
:exclude-members: fetch_guilds
|
||||||
|
|
||||||
|
.. automethod:: Client.fetch_guilds
|
||||||
|
:async-for:
|
||||||
|
|
||||||
AutoShardedClient
|
AutoShardedClient
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
@ -200,11 +204,11 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
|
|
||||||
.. function:: on_disconnect()
|
.. function:: on_disconnect()
|
||||||
|
|
||||||
Called when the client has disconnected from Discord. This could happen either through
|
Called when the client has disconnected from Discord, or a connection attempt to Discord has failed.
|
||||||
the internet being disconnected, explicit calls to logout, or Discord terminating the connection
|
This could happen either through the internet being disconnected, explicit calls to close,
|
||||||
one way or the other.
|
or Discord terminating the connection one way or the other.
|
||||||
|
|
||||||
This function can be called many times.
|
This function can be called many times without a corresponding :func:`on_connect` call.
|
||||||
|
|
||||||
.. function:: on_shard_disconnect(shard_id)
|
.. function:: on_shard_disconnect(shard_id)
|
||||||
|
|
||||||
@ -250,7 +254,7 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
:param shard_id: The shard ID that has resumed.
|
:param shard_id: The shard ID that has resumed.
|
||||||
:type shard_id: :class:`int`
|
:type shard_id: :class:`int`
|
||||||
|
|
||||||
.. function:: on_error(event, \*args, \*\*kwargs)
|
.. function:: on_error(event, *args, **kwargs)
|
||||||
|
|
||||||
Usually when an event raises an uncaught exception, a traceback is
|
Usually when an event raises an uncaught exception, a traceback is
|
||||||
printed to stderr and the exception is ignored. If you want to
|
printed to stderr and the exception is ignored. If you want to
|
||||||
@ -448,7 +452,10 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
regardless of the state of the internal message cache.
|
regardless of the state of the internal message cache.
|
||||||
|
|
||||||
If the message is found in the message cache,
|
If the message is found in the message cache,
|
||||||
it can be accessed via :attr:`RawMessageUpdateEvent.cached_message`
|
it can be accessed via :attr:`RawMessageUpdateEvent.cached_message`. The cached message represents
|
||||||
|
the message before it has been edited. For example, if the content of a message is modified and
|
||||||
|
triggers the :func:`on_raw_message_edit` coroutine, the :attr:`RawMessageUpdateEvent.cached_message`
|
||||||
|
will return a :class:`Message` object that represents the message before the content was modified.
|
||||||
|
|
||||||
Due to the inherently raw nature of this event, the data parameter coincides with
|
Due to the inherently raw nature of this event, the data parameter coincides with
|
||||||
the raw data given by the `gateway <https://discord.com/developers/docs/topics/gateway#message-update>`_.
|
the raw data given by the `gateway <https://discord.com/developers/docs/topics/gateway#message-update>`_.
|
||||||
@ -475,6 +482,14 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
|
|
||||||
This requires :attr:`Intents.reactions` to be enabled.
|
This requires :attr:`Intents.reactions` to be enabled.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This doesn't require :attr:`Intents.members` within a guild context,
|
||||||
|
but due to Discord not providing updated user information in a direct message
|
||||||
|
it's required for direct messages to receive this event.
|
||||||
|
Consider using :func:`on_raw_reaction_add` if you need this and do not otherwise want
|
||||||
|
to enable the members intent.
|
||||||
|
|
||||||
:param reaction: The current state of the reaction.
|
:param reaction: The current state of the reaction.
|
||||||
:type reaction: :class:`Reaction`
|
:type reaction: :class:`Reaction`
|
||||||
:param user: The user who added the reaction.
|
:param user: The user who added the reaction.
|
||||||
@ -500,7 +515,12 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
|
|
||||||
To get the message being reacted, access it via :attr:`Reaction.message`.
|
To get the message being reacted, access it via :attr:`Reaction.message`.
|
||||||
|
|
||||||
This requires :attr:`Intents.reactions` to be enabled.
|
This requires both :attr:`Intents.reactions` and :attr:`Intents.members` to be enabled.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Consider using :func:`on_raw_reaction_remove` if you need this and do not want
|
||||||
|
to enable the members intent.
|
||||||
|
|
||||||
:param reaction: The current state of the reaction.
|
:param reaction: The current state of the reaction.
|
||||||
:type reaction: :class:`Reaction`
|
:type reaction: :class:`Reaction`
|
||||||
@ -883,6 +903,8 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
Called when a :class:`Relationship` is added or removed from the
|
Called when a :class:`Relationship` is added or removed from the
|
||||||
:class:`ClientUser`.
|
:class:`ClientUser`.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
:param relationship: The relationship that was added or removed.
|
:param relationship: The relationship that was added or removed.
|
||||||
:type relationship: :class:`Relationship`
|
:type relationship: :class:`Relationship`
|
||||||
|
|
||||||
@ -891,6 +913,8 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
Called when a :class:`Relationship` is updated, e.g. when you
|
Called when a :class:`Relationship` is updated, e.g. when you
|
||||||
block a friend or a friendship is accepted.
|
block a friend or a friendship is accepted.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
:param before: The previous relationship status.
|
:param before: The previous relationship status.
|
||||||
:type before: :class:`Relationship`
|
:type before: :class:`Relationship`
|
||||||
:param after: The updated relationship status.
|
:param after: The updated relationship status.
|
||||||
@ -909,6 +933,8 @@ Utility Functions
|
|||||||
|
|
||||||
.. autofunction:: discord.utils.oauth_url
|
.. autofunction:: discord.utils.oauth_url
|
||||||
|
|
||||||
|
.. autofunction:: discord.utils.remove_markdown
|
||||||
|
|
||||||
.. autofunction:: discord.utils.escape_markdown
|
.. autofunction:: discord.utils.escape_markdown
|
||||||
|
|
||||||
.. autofunction:: discord.utils.escape_mentions
|
.. autofunction:: discord.utils.escape_mentions
|
||||||
@ -926,6 +952,8 @@ Profile
|
|||||||
|
|
||||||
A namedtuple representing a user's Discord public profile.
|
A namedtuple representing a user's Discord public profile.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. attribute:: user
|
.. attribute:: user
|
||||||
|
|
||||||
The :class:`User` the profile belongs to.
|
The :class:`User` the profile belongs to.
|
||||||
@ -1046,6 +1074,12 @@ of :class:`enum.Enum`.
|
|||||||
|
|
||||||
A guild store channel.
|
A guild store channel.
|
||||||
|
|
||||||
|
.. attribute:: stage_voice
|
||||||
|
|
||||||
|
A guild stage voice channel.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
.. class:: MessageType
|
.. class:: MessageType
|
||||||
|
|
||||||
Specifies the type of :class:`Message`. This is used to denote if a message
|
Specifies the type of :class:`Message`. This is used to denote if a message
|
||||||
@ -1108,6 +1142,35 @@ of :class:`enum.Enum`.
|
|||||||
The system message denoting that an announcement channel has been followed.
|
The system message denoting that an announcement channel has been followed.
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
|
.. attribute:: guild_stream
|
||||||
|
|
||||||
|
The system message denoting that a member is streaming in the guild.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
.. attribute:: guild_discovery_disqualified
|
||||||
|
|
||||||
|
The system message denoting that the guild is no longer eligible for Server
|
||||||
|
Discovery.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
.. attribute:: guild_discovery_requalified
|
||||||
|
|
||||||
|
The system message denoting that the guild has become eligible again for Server
|
||||||
|
Discovery.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
.. attribute:: guild_discovery_grace_period_initial_warning
|
||||||
|
|
||||||
|
The system message denoting that the guild has failed to meet the Server
|
||||||
|
Discovery requirements for one week.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
.. attribute:: guild_discovery_grace_period_final_warning
|
||||||
|
|
||||||
|
The system message denoting that the guild has failed to meet the Server
|
||||||
|
Discovery requirements for 3 weeks in a row.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
.. class:: ActivityType
|
.. class:: ActivityType
|
||||||
|
|
||||||
@ -1881,6 +1944,8 @@ of :class:`enum.Enum`.
|
|||||||
|
|
||||||
Specifies the type of :class:`Relationship`.
|
Specifies the type of :class:`Relationship`.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This only applies to users, *not* bots.
|
This only applies to users, *not* bots.
|
||||||
@ -1907,6 +1972,8 @@ of :class:`enum.Enum`.
|
|||||||
Represents the options found in ``Settings > Privacy & Safety > Safe Direct Messaging``
|
Represents the options found in ``Settings > Privacy & Safety > Safe Direct Messaging``
|
||||||
in the Discord client.
|
in the Discord client.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This only applies to users, *not* bots.
|
This only applies to users, *not* bots.
|
||||||
@ -1929,6 +1996,8 @@ of :class:`enum.Enum`.
|
|||||||
Represents the options found in ``Settings > Privacy & Safety > Who Can Add You As A Friend``
|
Represents the options found in ``Settings > Privacy & Safety > Who Can Add You As A Friend``
|
||||||
in the Discord client.
|
in the Discord client.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This only applies to users, *not* bots.
|
This only applies to users, *not* bots.
|
||||||
@ -1958,6 +2027,8 @@ of :class:`enum.Enum`.
|
|||||||
|
|
||||||
Represents the user's Discord Nitro subscription type.
|
Represents the user's Discord Nitro subscription type.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This only applies to users, *not* bots.
|
This only applies to users, *not* bots.
|
||||||
@ -1975,6 +2046,8 @@ of :class:`enum.Enum`.
|
|||||||
|
|
||||||
Represents the theme synced across all Discord clients.
|
Represents the theme synced across all Discord clients.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This only applies to users, *not* bots.
|
This only applies to users, *not* bots.
|
||||||
@ -2851,11 +2924,30 @@ Guild
|
|||||||
|
|
||||||
.. autoclass:: Guild()
|
.. autoclass:: Guild()
|
||||||
:members:
|
:members:
|
||||||
:exclude-members: audit_logs
|
:exclude-members: fetch_members, audit_logs
|
||||||
|
|
||||||
|
.. automethod:: fetch_members
|
||||||
|
:async-for:
|
||||||
|
|
||||||
.. automethod:: audit_logs
|
.. automethod:: audit_logs
|
||||||
:async-for:
|
:async-for:
|
||||||
|
|
||||||
|
.. class:: BanEntry
|
||||||
|
|
||||||
|
A namedtuple which represents a ban returned from :meth:`~Guild.bans`.
|
||||||
|
|
||||||
|
.. attribute:: reason
|
||||||
|
|
||||||
|
The reason this user was banned.
|
||||||
|
|
||||||
|
:type: Optional[:class:`str`]
|
||||||
|
.. attribute:: user
|
||||||
|
|
||||||
|
The :class:`User` that was banned.
|
||||||
|
|
||||||
|
:type: :class:`User`
|
||||||
|
|
||||||
|
|
||||||
Integration
|
Integration
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -2924,6 +3016,8 @@ Role
|
|||||||
RoleTags
|
RoleTags
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: RoleTags
|
||||||
|
|
||||||
.. autoclass:: RoleTags()
|
.. autoclass:: RoleTags()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
@ -2943,6 +3037,15 @@ TextChannel
|
|||||||
.. automethod:: typing
|
.. automethod:: typing
|
||||||
:async-with:
|
:async-with:
|
||||||
|
|
||||||
|
StoreChannel
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: StoreChannel
|
||||||
|
|
||||||
|
.. autoclass:: StoreChannel()
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
|
||||||
VoiceChannel
|
VoiceChannel
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -2952,6 +3055,15 @@ VoiceChannel
|
|||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
|
||||||
|
StageChannel
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: StageChannel
|
||||||
|
|
||||||
|
.. autoclass:: StageChannel()
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
|
||||||
CategoryChannel
|
CategoryChannel
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -3151,24 +3263,32 @@ AllowedMentions
|
|||||||
MessageReference
|
MessageReference
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: MessageReference
|
||||||
|
|
||||||
.. autoclass:: MessageReference
|
.. autoclass:: MessageReference
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
PartialMessage
|
PartialMessage
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: PartialMessage
|
||||||
|
|
||||||
.. autoclass:: PartialMessage
|
.. autoclass:: PartialMessage
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
Intents
|
Intents
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: Intents
|
||||||
|
|
||||||
.. autoclass:: Intents
|
.. autoclass:: Intents
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
MemberCacheFlags
|
MemberCacheFlags
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: MemberCacheFlags
|
||||||
|
|
||||||
.. autoclass:: MemberCacheFlags
|
.. autoclass:: MemberCacheFlags
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
@ -3247,24 +3367,32 @@ PermissionOverwrite
|
|||||||
ShardInfo
|
ShardInfo
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: ShardInfo
|
||||||
|
|
||||||
.. autoclass:: ShardInfo()
|
.. autoclass:: ShardInfo()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
SystemChannelFlags
|
SystemChannelFlags
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: SystemChannelFlags
|
||||||
|
|
||||||
.. autoclass:: SystemChannelFlags()
|
.. autoclass:: SystemChannelFlags()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
MessageFlags
|
MessageFlags
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: MessageFlags
|
||||||
|
|
||||||
.. autoclass:: MessageFlags()
|
.. autoclass:: MessageFlags()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
PublicUserFlags
|
PublicUserFlags
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: PublicUserFlags
|
||||||
|
|
||||||
.. autoclass:: PublicUserFlags()
|
.. autoclass:: PublicUserFlags()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ master_doc = 'index'
|
|||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'discord.py'
|
project = u'discord.py'
|
||||||
copyright = u'2015-2021, Rapptz'
|
copyright = u'2015-present, Rapptz'
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
@ -176,7 +176,8 @@ Paginator
|
|||||||
Enums
|
Enums
|
||||||
------
|
------
|
||||||
|
|
||||||
.. class:: discord.ext.commands.BucketType
|
.. class:: BucketType
|
||||||
|
:module: discord.ext.commands
|
||||||
|
|
||||||
Specifies a type of bucket for, e.g. a cooldown.
|
Specifies a type of bucket for, e.g. a cooldown.
|
||||||
|
|
||||||
@ -281,18 +282,30 @@ Converters
|
|||||||
.. autoclass:: discord.ext.commands.MessageConverter
|
.. autoclass:: discord.ext.commands.MessageConverter
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: discord.ext.commands.PartialMessageConverter
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.TextChannelConverter
|
.. autoclass:: discord.ext.commands.TextChannelConverter
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.VoiceChannelConverter
|
.. autoclass:: discord.ext.commands.VoiceChannelConverter
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: discord.ext.commands.StoreChannelConverter
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: discord.ext.commands.StageChannelConverter
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.CategoryChannelConverter
|
.. autoclass:: discord.ext.commands.CategoryChannelConverter
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.InviteConverter
|
.. autoclass:: discord.ext.commands.InviteConverter
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: discord.ext.commands.GuildConverter
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.RoleConverter
|
.. autoclass:: discord.ext.commands.RoleConverter
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
@ -407,6 +420,9 @@ Exceptions
|
|||||||
.. autoexception:: discord.ext.commands.MemberNotFound
|
.. autoexception:: discord.ext.commands.MemberNotFound
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoexception:: discord.ext.commands.GuildNotFound
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoexception:: discord.ext.commands.UserNotFound
|
.. autoexception:: discord.ext.commands.UserNotFound
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
@ -193,6 +193,8 @@ Converters come in a few flavours:
|
|||||||
|
|
||||||
- A custom class that inherits from :class:`~ext.commands.Converter`.
|
- A custom class that inherits from :class:`~ext.commands.Converter`.
|
||||||
|
|
||||||
|
.. _ext_commands_basic_converters:
|
||||||
|
|
||||||
Basic Converters
|
Basic Converters
|
||||||
++++++++++++++++++
|
++++++++++++++++++
|
||||||
|
|
||||||
@ -325,7 +327,7 @@ For example, a common idiom would be to have a class and a converter for that cl
|
|||||||
else:
|
else:
|
||||||
await ctx.send("Hm you're not so new.")
|
await ctx.send("Hm you're not so new.")
|
||||||
|
|
||||||
This can get tedious, so an inline advanced converter is possible through a ``classmethod`` inside the type:
|
This can get tedious, so an inline advanced converter is possible through a :func:`classmethod` inside the type:
|
||||||
|
|
||||||
.. code-block:: python3
|
.. code-block:: python3
|
||||||
|
|
||||||
@ -373,16 +375,20 @@ A lot of discord models work out of the gate as a parameter:
|
|||||||
|
|
||||||
- :class:`Member`
|
- :class:`Member`
|
||||||
- :class:`User`
|
- :class:`User`
|
||||||
|
- :class:`Message` (since v1.1)
|
||||||
|
- :class:`PartialMessage` (since v1.7)
|
||||||
- :class:`TextChannel`
|
- :class:`TextChannel`
|
||||||
- :class:`VoiceChannel`
|
- :class:`VoiceChannel`
|
||||||
|
- :class:`StageChannel` (since v1.7)
|
||||||
|
- :class:`StoreChannel` (since v1.7)
|
||||||
- :class:`CategoryChannel`
|
- :class:`CategoryChannel`
|
||||||
- :class:`Role`
|
|
||||||
- :class:`Message` (since v1.1)
|
|
||||||
- :class:`Invite`
|
- :class:`Invite`
|
||||||
|
- :class:`Guild` (since v1.7)
|
||||||
|
- :class:`Role`
|
||||||
- :class:`Game`
|
- :class:`Game`
|
||||||
|
- :class:`Colour`
|
||||||
- :class:`Emoji`
|
- :class:`Emoji`
|
||||||
- :class:`PartialEmoji`
|
- :class:`PartialEmoji`
|
||||||
- :class:`Colour`
|
|
||||||
|
|
||||||
Having any of these set as the converter will intelligently convert the argument to the appropriate target type you
|
Having any of these set as the converter will intelligently convert the argument to the appropriate target type you
|
||||||
specify.
|
specify.
|
||||||
@ -395,28 +401,36 @@ converter is given below:
|
|||||||
+--------------------------+-------------------------------------------------+
|
+--------------------------+-------------------------------------------------+
|
||||||
| :class:`Member` | :class:`~ext.commands.MemberConverter` |
|
| :class:`Member` | :class:`~ext.commands.MemberConverter` |
|
||||||
+--------------------------+-------------------------------------------------+
|
+--------------------------+-------------------------------------------------+
|
||||||
|
| :class:`User` | :class:`~ext.commands.UserConverter` |
|
||||||
|
+--------------------------+-------------------------------------------------+
|
||||||
| :class:`Message` | :class:`~ext.commands.MessageConverter` |
|
| :class:`Message` | :class:`~ext.commands.MessageConverter` |
|
||||||
+--------------------------+-------------------------------------------------+
|
+--------------------------+-------------------------------------------------+
|
||||||
| :class:`User` | :class:`~ext.commands.UserConverter` |
|
| :class:`PartialMessage` | :class:`~ext.commands.PartialMessageConverter` |
|
||||||
+--------------------------+-------------------------------------------------+
|
+--------------------------+-------------------------------------------------+
|
||||||
| :class:`TextChannel` | :class:`~ext.commands.TextChannelConverter` |
|
| :class:`TextChannel` | :class:`~ext.commands.TextChannelConverter` |
|
||||||
+--------------------------+-------------------------------------------------+
|
+--------------------------+-------------------------------------------------+
|
||||||
| :class:`VoiceChannel` | :class:`~ext.commands.VoiceChannelConverter` |
|
| :class:`VoiceChannel` | :class:`~ext.commands.VoiceChannelConverter` |
|
||||||
+--------------------------+-------------------------------------------------+
|
+--------------------------+-------------------------------------------------+
|
||||||
| :class:`CategoryChannel` | :class:`~ext.commands.CategoryChannelConverter` |
|
| :class:`StageChannel` | :class:`~ext.commands.StageChannelConverter` |
|
||||||
+--------------------------+-------------------------------------------------+
|
+--------------------------+-------------------------------------------------+
|
||||||
| :class:`Role` | :class:`~ext.commands.RoleConverter` |
|
| :class:`StoreChannel` | :class:`~ext.commands.StoreChannelConverter` |
|
||||||
|
+--------------------------+-------------------------------------------------+
|
||||||
|
| :class:`CategoryChannel` | :class:`~ext.commands.CategoryChannelConverter` |
|
||||||
+--------------------------+-------------------------------------------------+
|
+--------------------------+-------------------------------------------------+
|
||||||
| :class:`Invite` | :class:`~ext.commands.InviteConverter` |
|
| :class:`Invite` | :class:`~ext.commands.InviteConverter` |
|
||||||
+--------------------------+-------------------------------------------------+
|
+--------------------------+-------------------------------------------------+
|
||||||
|
| :class:`Guild` | :class:`~ext.commands.GuildConverter` |
|
||||||
|
+--------------------------+-------------------------------------------------+
|
||||||
|
| :class:`Role` | :class:`~ext.commands.RoleConverter` |
|
||||||
|
+--------------------------+-------------------------------------------------+
|
||||||
| :class:`Game` | :class:`~ext.commands.GameConverter` |
|
| :class:`Game` | :class:`~ext.commands.GameConverter` |
|
||||||
+--------------------------+-------------------------------------------------+
|
+--------------------------+-------------------------------------------------+
|
||||||
|
| :class:`Colour` | :class:`~ext.commands.ColourConverter` |
|
||||||
|
+--------------------------+-------------------------------------------------+
|
||||||
| :class:`Emoji` | :class:`~ext.commands.EmojiConverter` |
|
| :class:`Emoji` | :class:`~ext.commands.EmojiConverter` |
|
||||||
+--------------------------+-------------------------------------------------+
|
+--------------------------+-------------------------------------------------+
|
||||||
| :class:`PartialEmoji` | :class:`~ext.commands.PartialEmojiConverter` |
|
| :class:`PartialEmoji` | :class:`~ext.commands.PartialEmojiConverter` |
|
||||||
+--------------------------+-------------------------------------------------+
|
+--------------------------+-------------------------------------------------+
|
||||||
| :class:`Colour` | :class:`~ext.commands.ColourConverter` |
|
|
||||||
+--------------------------+-------------------------------------------------+
|
|
||||||
|
|
||||||
By providing the converter it allows us to use them as building blocks for another converter:
|
By providing the converter it allows us to use them as building blocks for another converter:
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ An example extension looks like this:
|
|||||||
def setup(bot):
|
def setup(bot):
|
||||||
bot.add_command(hello)
|
bot.add_command(hello)
|
||||||
|
|
||||||
In this example we define a simple command, and when the extension is loaded this command is added to the bot. Now the final step to this is loading the extension, which we do by calling :meth:`.commands.Bot.load_extension`. To load this extension we call ``bot.load_extension('hello')``.
|
In this example we define a simple command, and when the extension is loaded this command is added to the bot. Now the final step to this is loading the extension, which we do by calling :meth:`.Bot.load_extension`. To load this extension we call ``bot.load_extension('hello')``.
|
||||||
|
|
||||||
.. admonition:: Cogs
|
.. admonition:: Cogs
|
||||||
:class: helpful
|
:class: helpful
|
||||||
@ -41,7 +41,7 @@ In this example we define a simple command, and when the extension is loaded thi
|
|||||||
Reloading
|
Reloading
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
When you make a change to the extension and want to reload the references, the library comes with a function to do this for you, :meth:`Bot.reload_extension`.
|
When you make a change to the extension and want to reload the references, the library comes with a function to do this for you, :meth:`.Bot.reload_extension`.
|
||||||
|
|
||||||
.. code-block:: python3
|
.. code-block:: python3
|
||||||
|
|
||||||
|
@ -139,5 +139,6 @@ API Reference
|
|||||||
|
|
||||||
.. autoclass:: discord.ext.tasks.Loop()
|
.. autoclass:: discord.ext.tasks.Loop()
|
||||||
:members:
|
:members:
|
||||||
|
:special-members: __call__
|
||||||
|
|
||||||
.. autofunction:: discord.ext.tasks.loop
|
.. autofunction:: discord.ext.tasks.loop
|
||||||
|
@ -58,11 +58,13 @@ def add_custom_jinja2(app):
|
|||||||
|
|
||||||
def add_builders(app):
|
def add_builders(app):
|
||||||
"""This is necessary because RTD injects their own for some reason."""
|
"""This is necessary because RTD injects their own for some reason."""
|
||||||
|
app.set_translator('html', DPYHTML5Translator, override=True)
|
||||||
|
app.add_builder(DPYStandaloneHTMLBuilder, override=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
original = app.registry.builders['readthedocs']
|
original = app.registry.builders['readthedocs']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
app.set_translator('html', DPYHTML5Translator, override=True)
|
pass
|
||||||
app.add_builder(DPYStandaloneHTMLBuilder, override=True)
|
|
||||||
else:
|
else:
|
||||||
injected_mro = tuple(base if base is not StandaloneHTMLBuilder else DPYStandaloneHTMLBuilder
|
injected_mro = tuple(base if base is not StandaloneHTMLBuilder else DPYStandaloneHTMLBuilder
|
||||||
for base in original.mro()[1:])
|
for base in original.mro()[1:])
|
||||||
|
21
docs/faq.rst
21
docs/faq.rst
@ -85,8 +85,15 @@ in the repository.
|
|||||||
How do I set the "Playing" status?
|
How do I set the "Playing" status?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
There is a method for this under :class:`Client` called :meth:`Client.change_presence`.
|
The ``activity`` keyword argument may be passed in the :class:`Client` constructor or :meth:`Client.change_presence`, given an :class:`Activity` object.
|
||||||
The relevant aspect of this is its ``activity`` keyword argument which takes in an :class:`Activity` object.
|
|
||||||
|
The constructor may be used for static activities, while :meth:`Client.change_presence` may be used to update the activity at runtime.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
It is highly discouraged to use :meth:`Client.change_presence` or API calls in :func:`on_ready` as this event may be called many times while running, not just once.
|
||||||
|
|
||||||
|
There is a high chance of disconnecting if presences are changed right after connecting.
|
||||||
|
|
||||||
The status type (playing, listening, streaming, watching) can be set using the :class:`ActivityType` enum.
|
The status type (playing, listening, streaming, watching) can be set using the :class:`ActivityType` enum.
|
||||||
For memory optimisation purposes, some activities are offered in slimmed down versions:
|
For memory optimisation purposes, some activities are offered in slimmed down versions:
|
||||||
@ -96,11 +103,11 @@ For memory optimisation purposes, some activities are offered in slimmed down ve
|
|||||||
|
|
||||||
Putting both of these pieces of info together, you get the following: ::
|
Putting both of these pieces of info together, you get the following: ::
|
||||||
|
|
||||||
await client.change_presence(activity=discord.Game(name='my game'))
|
client = discord.Client(activity=discord.Game(name='my game'))
|
||||||
|
|
||||||
# or, for watching:
|
# or, for watching:
|
||||||
activity = discord.Activity(name='my activity', type=discord.ActivityType.watching)
|
activity = discord.Activity(name='my activity', type=discord.ActivityType.watching)
|
||||||
await client.change_presence(activity=activity)
|
client = discord.Client(activity=activity)
|
||||||
|
|
||||||
How do I send a message to a specific channel?
|
How do I send a message to a specific channel?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
@ -227,7 +234,7 @@ technically in another thread, we must take caution in calling thread-safe opera
|
|||||||
us, :mod:`asyncio` comes with a :func:`asyncio.run_coroutine_threadsafe` function that allows us to call
|
us, :mod:`asyncio` comes with a :func:`asyncio.run_coroutine_threadsafe` function that allows us to call
|
||||||
a coroutine from another thread.
|
a coroutine from another thread.
|
||||||
|
|
||||||
However, this function returns a :class:`concurrent.Future` and to actually call it we have to fetch its result. Putting all of
|
However, this function returns a :class:`~concurrent.futures.Future` and to actually call it we have to fetch its result. Putting all of
|
||||||
this together we can do the following: ::
|
this together we can do the following: ::
|
||||||
|
|
||||||
def my_after(error):
|
def my_after(error):
|
||||||
@ -288,7 +295,7 @@ How do I make a web request?
|
|||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
To make a request, you should use a non-blocking library.
|
To make a request, you should use a non-blocking library.
|
||||||
This library already uses and requires a 3rd party library for making requests, ``aiohttp``.
|
This library already uses and requires a 3rd party library for making requests, :doc:`aiohttp <aio:index>`.
|
||||||
|
|
||||||
Quick example: ::
|
Quick example: ::
|
||||||
|
|
||||||
@ -386,7 +393,7 @@ Example: ::
|
|||||||
How do I make a subcommand?
|
How do I make a subcommand?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Use the ``group`` decorator. This will transform the callback into a ``Group`` which will allow you to add commands into
|
Use the :func:`~ext.commands.group` decorator. This will transform the callback into a :class:`~ext.commands.Group` which will allow you to add commands into
|
||||||
the group operating as "subcommands". These groups can be arbitrarily nested as well.
|
the group operating as "subcommands". These groups can be arbitrarily nested as well.
|
||||||
|
|
||||||
Example: ::
|
Example: ::
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
A Primer to Gateway Intents
|
A Primer to Gateway Intents
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
In version 1.5 comes the introduction of :class:`Intents`. This is a radical change in how bots are written. An intent basically allows a bot to subscribe into specific buckets of events. The events that correspond to each intent is documented in the individual attribute of the :class:`Intents` documentation.
|
In version 1.5 comes the introduction of :class:`Intents`. This is a radical change in how bots are written. An intent basically allows a bot to subscribe to specific buckets of events. The events that correspond to each intent is documented in the individual attribute of the :class:`Intents` documentation.
|
||||||
|
|
||||||
These intents are passed to the constructor of :class:`Client` or its subclasses (:class:`AutoShardedClient`, :class:`~.AutoShardedBot`, :class:`~.Bot`) with the ``intents`` argument.
|
These intents are passed to the constructor of :class:`Client` or its subclasses (:class:`AutoShardedClient`, :class:`~.AutoShardedBot`, :class:`~.Bot`) with the ``intents`` argument.
|
||||||
|
|
||||||
@ -118,9 +118,10 @@ It should be noted that certain things do not need a member cache since Discord
|
|||||||
|
|
||||||
- :func:`on_message` will have :attr:`Message.author` be a member even if cache is disabled.
|
- :func:`on_message` will have :attr:`Message.author` be a member even if cache is disabled.
|
||||||
- :func:`on_voice_state_update` will have the ``member`` parameter be a member even if cache is disabled.
|
- :func:`on_voice_state_update` will have the ``member`` parameter be a member even if cache is disabled.
|
||||||
- :func:`on_reaction_add` will have the ``user`` parameter be a member even if cache is disabled.
|
- :func:`on_reaction_add` will have the ``user`` parameter be a member when in a guild even if cache is disabled.
|
||||||
- :func:`on_raw_reaction_add` will have :attr:`RawReactionActionEvent.member` be a member even if cache is disabled.
|
- :func:`on_raw_reaction_add` will have :attr:`RawReactionActionEvent.member` be a member when in a guild even if cache is disabled.
|
||||||
- The reaction removal events do not have the member information. This is a Discord limitation.
|
- The reaction add events do not contain additional information when in direct messages. This is a Discord limitation.
|
||||||
|
- The reaction removal events do not have member information. This is a Discord limitation.
|
||||||
|
|
||||||
Other events that take a :class:`Member` will require the use of the member cache. If absolute accuracy over the member cache is desirable, then it is advisable to have the :attr:`Intents.members` intent enabled.
|
Other events that take a :class:`Member` will require the use of the member cache. If absolute accuracy over the member cache is desirable, then it is advisable to have the :attr:`Intents.members` intent enabled.
|
||||||
|
|
||||||
@ -129,7 +130,7 @@ Other events that take a :class:`Member` will require the use of the member cach
|
|||||||
Retrieving Members
|
Retrieving Members
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
If cache is disabled or you disable chunking guilds at startup, we might still need a way to load members. The library offers a few ways to do this:
|
If the cache is disabled or you disable chunking guilds at startup, we might still need a way to load members. The library offers a few ways to do this:
|
||||||
|
|
||||||
- :meth:`Guild.query_members`
|
- :meth:`Guild.query_members`
|
||||||
- Used to query members by a prefix matching nickname or username.
|
- Used to query members by a prefix matching nickname or username.
|
||||||
@ -180,27 +181,10 @@ The first solution is to request the privileged presences intent along with the
|
|||||||
|
|
||||||
The second solution is to disable member chunking by setting ``chunk_guilds_at_startup`` to ``False`` when constructing a client. Then, when chunking for a guild is necessary you can use the various techniques to :ref:`retrieve members <retrieving_members>`.
|
The second solution is to disable member chunking by setting ``chunk_guilds_at_startup`` to ``False`` when constructing a client. Then, when chunking for a guild is necessary you can use the various techniques to :ref:`retrieve members <retrieving_members>`.
|
||||||
|
|
||||||
To illustrate the slowdown caused the API change, take a bot who is in 840 guilds and 95 of these guilds are "large" (over 250 members).
|
To illustrate the slowdown caused by the API change, take a bot who is in 840 guilds and 95 of these guilds are "large" (over 250 members).
|
||||||
|
|
||||||
Under the original system this would result in 2 requests to fetch the member list (75 guilds, 20 guilds) roughly taking 60 seconds. With :attr:`Intents.members` but not :attr:`Intents.presences` this requires 840 requests, with a rate limit of 120 requests per 60 seconds means that due to waiting for the rate limit it totals to around 7 minutes of waiting for the rate limit to fetch all the members. With both :attr:`Intents.members` and :attr:`Intents.presences` we mostly get the old behaviour so we're only required to request for the 95 guilds that are large, this is slightly less than our rate limit so it's close to the original timing to fetch the member list.
|
Under the original system this would result in 2 requests to fetch the member list (75 guilds, 20 guilds) roughly taking 60 seconds. With :attr:`Intents.members` but not :attr:`Intents.presences` this requires 840 requests, with a rate limit of 120 requests per 60 seconds means that due to waiting for the rate limit it totals to around 7 minutes of waiting for the rate limit to fetch all the members. With both :attr:`Intents.members` and :attr:`Intents.presences` we mostly get the old behaviour so we're only required to request for the 95 guilds that are large, this is slightly less than our rate limit so it's close to the original timing to fetch the member list.
|
||||||
|
|
||||||
Unfortunately due to this change being required from Discord there is nothing that the library can do to mitigate this.
|
Unfortunately due to this change being required from Discord there is nothing that the library can do to mitigate this.
|
||||||
|
|
||||||
I don't like this, can I go back?
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
For now, the old gateway will still work so downgrading to discord.py v1.4 is still possible and will continue to be supported until Discord officially kills the v6 gateway, which is imminent. However it is paramount that for the future of your bot that you upgrade your code to the new way things are done.
|
|
||||||
|
|
||||||
To downgrade you can do the following:
|
|
||||||
|
|
||||||
.. code-block:: python3
|
|
||||||
|
|
||||||
python3 -m pip install -U "discord.py>=1.4,<1.5"
|
|
||||||
|
|
||||||
On Windows use ``py -3`` instead of ``python3``.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
There is no currently set date in which the old gateway will stop working so it is recommended to update your code instead.
|
|
||||||
|
|
||||||
If you truly dislike the direction Discord is going with their API, you can contact them via `support <https://dis.gd/contact>`_.
|
If you truly dislike the direction Discord is going with their API, you can contact them via `support <https://dis.gd/contact>`_.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# SOME DESCRIPTIVE TITLE.
|
# SOME DESCRIPTIVE TITLE.
|
||||||
# Copyright (C) 2015-2020, Rapptz
|
# Copyright (C) 2015-present, Rapptz
|
||||||
# This file is distributed under the same license as the discord.py package.
|
# This file is distributed under the same license as the discord.py package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
|
||||||
#
|
#
|
||||||
@ -427,4 +427,3 @@ msgid ""
|
|||||||
"If you truly dislike the direction Discord is going with their API, you "
|
"If you truly dislike the direction Discord is going with their API, you "
|
||||||
"can contact them via `support <https://dis.gd/contact>`_"
|
"can contact them via `support <https://dis.gd/contact>`_"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -52,8 +52,9 @@ There's a lot going on here, so let's walk you through it step by step.
|
|||||||
4. Since the :func:`on_message` event triggers for *every* message received, we have to make
|
4. Since the :func:`on_message` event triggers for *every* message received, we have to make
|
||||||
sure that we ignore messages from ourselves. We do this by checking if the :attr:`Message.author`
|
sure that we ignore messages from ourselves. We do this by checking if the :attr:`Message.author`
|
||||||
is the same as the :attr:`Client.user`.
|
is the same as the :attr:`Client.user`.
|
||||||
5. Afterwards, we check if the :class:`Message.content` starts with ``'$hello'``. If it is,
|
5. Afterwards, we check if the :class:`Message.content` starts with ``'$hello'``. If it does,
|
||||||
then we send a message in the channel it was used in with ``'Hello!'``.
|
then we send a message in the channel it was used in with ``'Hello!'``. This is a basic way of
|
||||||
|
handling commands, which can be later automated with the :ref:`ext.commands` framework.
|
||||||
6. Finally, we run the bot with our login token. If you need help getting your token or creating a bot,
|
6. Finally, we run the bot with our login token. If you need help getting your token or creating a bot,
|
||||||
look in the :ref:`discord-intro` section.
|
look in the :ref:`discord-intro` section.
|
||||||
|
|
||||||
|
@ -11,6 +11,96 @@ Changelog
|
|||||||
This page keeps a detailed human friendly rendering of what's new and changed
|
This page keeps a detailed human friendly rendering of what's new and changed
|
||||||
in specific versions.
|
in specific versions.
|
||||||
|
|
||||||
|
.. _vp1p7p1:
|
||||||
|
|
||||||
|
v1.7.1
|
||||||
|
-------
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
- |commands| Fix :meth:`Cog.has_error_handler <ext.commands.Cog.has_error_handler>` not working as intended.
|
||||||
|
|
||||||
|
.. _vp1p7p0:
|
||||||
|
|
||||||
|
v1.7.0
|
||||||
|
--------
|
||||||
|
|
||||||
|
This version is mainly for improvements and bug fixes. This is more than likely the last major version in the 1.x series.
|
||||||
|
Work after this will be spent on v2.0. As a result, **this is the last version to support Python 3.5**.
|
||||||
|
Likewise, **this is the last version to support user bots**.
|
||||||
|
|
||||||
|
Development of v2.0 will have breaking changes and support for newer API features.
|
||||||
|
|
||||||
|
- Add support for stage channels via :class:`StageChannel` (:issue:`6602`, :issue:`6608`)
|
||||||
|
- Add support for :attr:`MessageReference.fail_if_not_exists` (:issue:`6484`)
|
||||||
|
- By default, if the message you're replying to doesn't exist then the API errors out.
|
||||||
|
This attribute tells the Discord API that it's okay for that message to be missing.
|
||||||
|
|
||||||
|
- Add support for Discord's new permission serialisation scheme.
|
||||||
|
- Add an easier way to move channels using :meth:`abc.GuildChannel.move`
|
||||||
|
- Add :attr:`Permissions.use_slash_commands`
|
||||||
|
- Add :attr:`Permissions.request_to_speak`
|
||||||
|
- Add support for voice regions in voice channels via :attr:`VoiceChannel.rtc_region` (:issue:`6606`)
|
||||||
|
- Add support for :meth:`PartialEmoji.url_as` (:issue:`6341`)
|
||||||
|
- Add :attr:`MessageReference.jump_url` (:issue:`6318`)
|
||||||
|
- Add :attr:`File.spoiler` (:issue:`6317`)
|
||||||
|
- Add support for passing ``roles`` to :meth:`Guild.estimate_pruned_members` (:issue:`6538`)
|
||||||
|
- Allow callable class factories to be used in :meth:`abc.Connectable.play` (:issue:`6478`)
|
||||||
|
- Add a way to get mutual guilds from the client's cache via :attr:`User.mutual_guilds` (:issue:`2539`, :issue:`6444`)
|
||||||
|
- :meth:`PartialMessage.edit` now returns a full :class:`Message` upon success (:issue:`6309`)
|
||||||
|
- Add :attr:`RawMessageUpdateEvent.guild_id` (:issue:`6489`)
|
||||||
|
- :class:`AuditLogEntry` is now hashable (:issue:`6495`)
|
||||||
|
- :class:`Attachment` is now hashable
|
||||||
|
- Add :attr:`Attachment.content_type` attribute (:issue:`6618`)
|
||||||
|
- Add support for casting :class:`Attachment` to :class:`str` to get the URL.
|
||||||
|
- Add ``seed`` parameter for :class:`Colour.random` (:issue:`6562`)
|
||||||
|
- This only seeds it for one call. If seeding for multiple calls is desirable, use :func:`random.seed`.
|
||||||
|
|
||||||
|
- Add a :func:`utils.remove_markdown` helper function (:issue:`6573`)
|
||||||
|
- Add support for passing scopes to :func:`utils.oauth_url` (:issue:`6568`)
|
||||||
|
- |commands| Add support for ``rgb`` CSS function as a parameter to :class:`ColourConverter <ext.commands.ColourConverter>` (:issue:`6374`)
|
||||||
|
- |commands| Add support for converting :class:`StoreChannel` via :class:`StoreChannelConverter <ext.commands.StoreChannelConverter>` (:issue:`6603`)
|
||||||
|
- |commands| Add support for stripping whitespace after the prefix is encountered using the ``strip_after_prefix`` :class:`~ext.commands.Bot` constructor parameter.
|
||||||
|
- |commands| Add :attr:`Context.invoked_parents <ext.commands.Context.invoked_parents>` to get the aliases a command's parent was invoked with (:issue:`1874`, :issue:`6462`)
|
||||||
|
- |commands| Add a converter for :class:`PartialMessage` under :class:`ext.commands.PartialMessageConverter` (:issue:`6308`)
|
||||||
|
- |commands| Add a converter for :class:`Guild` under :class:`ext.commands.GuildConverter` (:issue:`6016`, :issue:`6365`)
|
||||||
|
- |commands| Add :meth:`Command.has_error_handler <ext.commands.Command.has_error_handler>`
|
||||||
|
- This is also adds :meth:`Cog.has_error_handler <ext.commands.Cog.has_error_handler>`
|
||||||
|
- |commands| Allow callable types to act as a bucket key for cooldowns (:issue:`6563`)
|
||||||
|
- |commands| Add ``linesep`` keyword argument to :class:`Paginator <ext.commands.Paginator>` (:issue:`5975`)
|
||||||
|
- |commands| Allow ``None`` to be passed to :attr:`HelpCommand.verify_checks <ext.commands.HelpCommand.verify_checks>` to only verify in a guild context (:issue:`2008`, :issue:`6446`)
|
||||||
|
- |commands| Allow relative paths when loading extensions via a ``package`` keyword argument (:issue:`2465`, :issue:`6445`)
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
- Fix mentions not working if ``mention_author`` is passed in :meth:`abc.Messageable.send` without :attr:`Client.allowed_mentions` set (:issue:`6192`, :issue:`6458`)
|
||||||
|
- Fix user created instances of :class:`CustomActivity` triggering an error (:issue:`4049`)
|
||||||
|
- Note that currently, bot users still cannot set a custom activity due to a Discord limitation.
|
||||||
|
- Fix :exc:`ZeroDivisionError` being raised from :attr:`VoiceClient.average_latency` (:issue:`6430`, :issue:`6436`)
|
||||||
|
- Fix :attr:`User.public_flags` not updating upon edit (:issue:`6315`)
|
||||||
|
- Fix :attr:`Message.call` sometimes causing attribute errors (:issue:`6390`)
|
||||||
|
- Fix issue resending a file during request retries on newer versions of ``aiohttp`` (:issue:`6531`)
|
||||||
|
- Raise an error when ``user_ids`` is empty in :meth:`Guild.query_members`
|
||||||
|
- Fix ``__str__`` magic method raising when a :class:`Guild` is unavailable.
|
||||||
|
- Fix potential :exc:`AttributeError` when accessing :attr:`VoiceChannel.members` (:issue:`6602`)
|
||||||
|
- :class:`Embed` constructor parameters now implicitly convert to :class:`str` (:issue:`6574`)
|
||||||
|
- Ensure ``discord`` package is only run if executed as a script (:issue:`6483`)
|
||||||
|
- |commands| Fix irrelevant commands potentially being unloaded during cog unload due to failure.
|
||||||
|
- |commands| Fix attribute errors when setting a cog to :class:`~.ext.commands.HelpCommand` (:issue:`5154`)
|
||||||
|
- |commands| Fix :attr:`Context.invoked_with <ext.commands.Context.invoked_with>` being improperly reassigned during a :meth:`~ext.commands.Context.reinvoke` (:issue:`6451`, :issue:`6462`)
|
||||||
|
- |commands| Remove duplicates from :meth:`HelpCommand.get_bot_mapping <ext.commands.HelpCommand.get_bot_mapping>` (:issue:`6316`)
|
||||||
|
- |commands| Properly handle positional-only parameters in bot command signatures (:issue:`6431`)
|
||||||
|
- |commands| Group signatures now properly show up in :attr:`Command.signature <ext.commands.Command.signature>` (:issue:`6529`, :issue:`6530`)
|
||||||
|
|
||||||
|
Miscellaneous
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- User endpoints and all userbot related functionality has been deprecated and will be removed in the next major version of the library.
|
||||||
|
- :class:`Permission` class methods were updated to match the UI of the Discord client (:issue:`6476`)
|
||||||
|
- ``_`` and ``-`` characters are now stripped when making a new cog using the ``discord`` package (:issue:`6313`)
|
||||||
|
|
||||||
.. _vp1p6p0p7:
|
.. _vp1p6p0p7:
|
||||||
|
|
||||||
v1.6.0.7
|
v1.6.0.7
|
||||||
@ -24,49 +114,6 @@ New Features
|
|||||||
- Add ``silent`` kwargs to :meth:`Message.delete`
|
- Add ``silent`` kwargs to :meth:`Message.delete`
|
||||||
- Add :meth:`Client.get_message`
|
- Add :meth:`Client.get_message`
|
||||||
|
|
||||||
.. _vp1p5p1p6:
|
|
||||||
|
|
||||||
v1.5.1.6
|
|
||||||
--------
|
|
||||||
|
|
||||||
New Features
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
- Add :meth:`Colour.random`
|
|
||||||
|
|
||||||
.. _vp1p5p1p5:
|
|
||||||
|
|
||||||
v1.5.1.5
|
|
||||||
--------
|
|
||||||
|
|
||||||
New Features
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
- Add :meth:`Colour.nitro_booster`
|
|
||||||
- Add :attr:`Permissions.admin` as alias to :attr:`Permissions.administrator`
|
|
||||||
|
|
||||||
New Beta Features
|
|
||||||
~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
These are all for message replies. I have added them to 1.5.1.5 but they will most likely officially get added in the original lib in 1.6 or 2.0
|
|
||||||
|
|
||||||
- |commands| Add :meth:`Context.reply <ext.commands.Context>`
|
|
||||||
- Add :meth:`Message.reply`
|
|
||||||
- Add ``replied_user`` to :class:`AllowedMentions`
|
|
||||||
- Add :meth:`MessageReference.to_dict`
|
|
||||||
- Add :meth:`MessageReference.from_message`
|
|
||||||
- Add ``message_reference`` kwarg to :meth:`abc.Messageable.send`
|
|
||||||
|
|
||||||
.. _vp1p5p1p4:
|
|
||||||
|
|
||||||
v1.5.1.4
|
|
||||||
--------
|
|
||||||
|
|
||||||
New Features
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
- |commands| Add :attr:`Context.clean_prefix <ext.commands.Context>`
|
|
||||||
|
|
||||||
.. _vp1p6p0:
|
.. _vp1p6p0:
|
||||||
|
|
||||||
v1.6.0
|
v1.6.0
|
||||||
@ -139,6 +186,49 @@ Miscellaneous
|
|||||||
- |commands| :class:`UserConverter <ext.commands.UserConverter>` now fetches the API if an ID is passed and the user is not cached.
|
- |commands| :class:`UserConverter <ext.commands.UserConverter>` now fetches the API if an ID is passed and the user is not cached.
|
||||||
- |commands| :func:`max_concurrency <ext.commands.max_concurrency>` is now called before cooldowns (:issue:`6172`)
|
- |commands| :func:`max_concurrency <ext.commands.max_concurrency>` is now called before cooldowns (:issue:`6172`)
|
||||||
|
|
||||||
|
.. _vp1p5p1p6:
|
||||||
|
|
||||||
|
v1.5.1.6
|
||||||
|
--------
|
||||||
|
|
||||||
|
New Features
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- Add :meth:`Colour.random`
|
||||||
|
|
||||||
|
.. _vp1p5p1p5:
|
||||||
|
|
||||||
|
v1.5.1.5
|
||||||
|
--------
|
||||||
|
|
||||||
|
New Features
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- Add :meth:`Colour.nitro_booster`
|
||||||
|
- Add :attr:`Permissions.admin` as alias to :attr:`Permissions.administrator`
|
||||||
|
|
||||||
|
New Beta Features
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
These are all for message replies. I have added them to 1.5.1.5 but they will most likely officially get added in the original lib in 1.6 or 2.0
|
||||||
|
|
||||||
|
- |commands| Add :meth:`Context.reply <ext.commands.Context>`
|
||||||
|
- Add :meth:`Message.reply`
|
||||||
|
- Add ``replied_user`` to :class:`AllowedMentions`
|
||||||
|
- Add :meth:`MessageReference.to_dict`
|
||||||
|
- Add :meth:`MessageReference.from_message`
|
||||||
|
- Add ``message_reference`` kwarg to :meth:`abc.Messageable.send`
|
||||||
|
|
||||||
|
.. _vp1p5p1p4:
|
||||||
|
|
||||||
|
v1.5.1.4
|
||||||
|
--------
|
||||||
|
|
||||||
|
New Features
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- |commands| Add :attr:`Context.clean_prefix <ext.commands.Context>`
|
||||||
|
|
||||||
.. _vp1p5p1:
|
.. _vp1p5p1:
|
||||||
|
|
||||||
v1.5.1
|
v1.5.1
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
|
from discord.ext import tasks
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import asyncio
|
|
||||||
|
|
||||||
class MyClient(discord.Client):
|
class MyClient(discord.Client):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
# create the background task and run it in the background
|
# an attribute we can access from our task
|
||||||
self.bg_task = self.loop.create_task(self.my_background_task())
|
self.counter = 0
|
||||||
|
|
||||||
|
# start the task to run in the background
|
||||||
|
self.my_background_task.start()
|
||||||
|
|
||||||
async def on_ready(self):
|
async def on_ready(self):
|
||||||
print('Logged in as')
|
print('Logged in as')
|
||||||
@ -14,15 +18,15 @@ class MyClient(discord.Client):
|
|||||||
print(self.user.id)
|
print(self.user.id)
|
||||||
print('------')
|
print('------')
|
||||||
|
|
||||||
|
@tasks.loop(seconds=60) # task runs every 60 seconds
|
||||||
async def my_background_task(self):
|
async def my_background_task(self):
|
||||||
await self.wait_until_ready()
|
|
||||||
counter = 0
|
|
||||||
channel = self.get_channel(1234567) # channel ID goes here
|
channel = self.get_channel(1234567) # channel ID goes here
|
||||||
while not self.is_closed():
|
self.counter += 1
|
||||||
counter += 1
|
await channel.send(self.counter)
|
||||||
await channel.send(counter)
|
|
||||||
await asyncio.sleep(60) # task runs every 60 seconds
|
|
||||||
|
|
||||||
|
@my_background_task.before_loop
|
||||||
|
async def before_my_task(self):
|
||||||
|
await self.wait_until_ready() # wait until the bot logs in
|
||||||
|
|
||||||
client = MyClient()
|
client = MyClient()
|
||||||
client.run('token')
|
client.run('token')
|
||||||
|
28
examples/background_task_asyncio.py
Normal file
28
examples/background_task_asyncio.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import discord
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
class MyClient(discord.Client):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# create the background task and run it in the background
|
||||||
|
self.bg_task = self.loop.create_task(self.my_background_task())
|
||||||
|
|
||||||
|
async def on_ready(self):
|
||||||
|
print('Logged in as')
|
||||||
|
print(self.user.name)
|
||||||
|
print(self.user.id)
|
||||||
|
print('------')
|
||||||
|
|
||||||
|
async def my_background_task(self):
|
||||||
|
await self.wait_until_ready()
|
||||||
|
counter = 0
|
||||||
|
channel = self.get_channel(1234567) # channel ID goes here
|
||||||
|
while not self.is_closed():
|
||||||
|
counter += 1
|
||||||
|
await channel.send(counter)
|
||||||
|
await asyncio.sleep(60) # task runs every 60 seconds
|
||||||
|
|
||||||
|
|
||||||
|
client = MyClient()
|
||||||
|
client.run('token')
|
113
examples/converters.py
Normal file
113
examples/converters.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# This example requires the 'members' privileged intent to use the Member converter.
|
||||||
|
|
||||||
|
import typing
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
intents = discord.Intents.default()
|
||||||
|
intents.members = True
|
||||||
|
|
||||||
|
bot = commands.Bot('!', intents=intents)
|
||||||
|
|
||||||
|
|
||||||
|
@bot.command()
|
||||||
|
async def userinfo(ctx: commands.Context, user: discord.User):
|
||||||
|
# In the command signature above, you can see that the `user`
|
||||||
|
# parameter is typehinted to `discord.User`. This means that
|
||||||
|
# during command invocation we will attempt to convert
|
||||||
|
# the value passed as `user` to a `discord.User` instance.
|
||||||
|
# The documentation notes what can be converted, in the case of `discord.User`
|
||||||
|
# you pass an ID, mention or username (discrim optional)
|
||||||
|
# E.g. 80088516616269824, @Danny or Danny#0007
|
||||||
|
|
||||||
|
# NOTE: typehinting acts as a converter within the `commands` framework only.
|
||||||
|
# In standard Python, it is use for documentation and IDE assistance purposes.
|
||||||
|
|
||||||
|
# If the conversion is successful, we will have a `discord.User` instance
|
||||||
|
# and can do the following:
|
||||||
|
user_id = user.id
|
||||||
|
username = user.name
|
||||||
|
avatar = user.avatar_url
|
||||||
|
await ctx.send('User found: {} -- {}\n{}'.format(user_id, username, avatar))
|
||||||
|
|
||||||
|
@userinfo.error
|
||||||
|
async def userinfo_error(ctx: commands.Context, error: commands.CommandError):
|
||||||
|
# if the conversion above fails for any reason, it will raise `commands.BadArgument`
|
||||||
|
# so we handle this in this error handler:
|
||||||
|
if isinstance(error, commands.BadArgument):
|
||||||
|
return await ctx.send('Couldn\'t find that user.')
|
||||||
|
|
||||||
|
# Custom Converter here
|
||||||
|
class ChannelOrMemberConverter(commands.Converter):
|
||||||
|
async def convert(self, ctx: commands.Context, argument: str):
|
||||||
|
# In this example we have made a custom converter.
|
||||||
|
# This checks if an input is convertible to a
|
||||||
|
# `discord.Member` or `discord.TextChannel` instance from the
|
||||||
|
# input the user has given us using the pre-existing converters
|
||||||
|
# that the library provides.
|
||||||
|
|
||||||
|
member_converter = commands.MemberConverter()
|
||||||
|
try:
|
||||||
|
# Try and convert to a Member instance.
|
||||||
|
# If this fails, then an exception is raised.
|
||||||
|
# Otherwise, we just return the converted member value.
|
||||||
|
member = await member_converter.convert(ctx, argument)
|
||||||
|
except commands.MemberNotFound:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return member
|
||||||
|
|
||||||
|
# Do the same for TextChannel...
|
||||||
|
textchannel_converter = commands.TextChannelConverter()
|
||||||
|
try:
|
||||||
|
channel = await textchannel_converter.convert(ctx, argument)
|
||||||
|
except commands.ChannelNotFound:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return channel
|
||||||
|
|
||||||
|
# If the value could not be converted we can raise an error
|
||||||
|
# so our error handlers can deal with it in one place.
|
||||||
|
# The error has to be CommandError derived, so BadArgument works fine here.
|
||||||
|
raise commands.BadArgument('No Member or TextChannel could be converted from "{}"'.format(argument))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@bot.command()
|
||||||
|
async def notify(ctx: commands.Context, target: ChannelOrMemberConverter):
|
||||||
|
# This command signature utilises the custom converter written above
|
||||||
|
# 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 conversion will go through the process defined there.
|
||||||
|
|
||||||
|
await target.send('Hello, {}!'.format(target.name))
|
||||||
|
|
||||||
|
@bot.command()
|
||||||
|
async def ignore(ctx: commands.Context, target: typing.Union[discord.Member, discord.TextChannel]):
|
||||||
|
# This command signature utilises the `typing.Union` typehint.
|
||||||
|
# The `commands` framework attempts a conversion of each type in this Union *in order*.
|
||||||
|
# So, it will attempt to convert whatever is passed to `target` to a `discord.Member` instance.
|
||||||
|
# If that fails, it will attempt to convert it to a `discord.TextChannel` instance.
|
||||||
|
# See: https://discordpy.readthedocs.io/en/latest/ext/commands/commands.html#typing-union
|
||||||
|
# NOTE: If a Union typehint converter fails it will raise `commands.BadUnionArgument`
|
||||||
|
# instead of `commands.BadArgument`.
|
||||||
|
|
||||||
|
# To check the resulting type, `isinstance` is used
|
||||||
|
if isinstance(target, discord.Member):
|
||||||
|
await ctx.send('Member found: {}, adding them to the ignore list.'.format(target.mention))
|
||||||
|
elif isinstance(target, discord.TextChannel): # this could be an `else` but for completeness' sake.
|
||||||
|
await ctx.send('Channel found: {}, adding it to the ignore list.'.format(target.mention))
|
||||||
|
|
||||||
|
# Built-in type converters.
|
||||||
|
@bot.command()
|
||||||
|
async def multiply(ctx: commands.Context, number: int, maybe: bool):
|
||||||
|
# We want an `int` and a `bool` parameter here.
|
||||||
|
# `bool` is a slightly special case, as shown here:
|
||||||
|
# See: https://discordpy.readthedocs.io/en/latest/ext/commands/commands.html#bool
|
||||||
|
|
||||||
|
if maybe is True:
|
||||||
|
return await ctx.send(number * 2)
|
||||||
|
await ctx.send(number * 5)
|
||||||
|
|
||||||
|
bot.run('token')
|
@ -1,83 +1,85 @@
|
|||||||
"""Uses a messages to add and remove roles through reactions."""
|
# This example requires the 'members' privileged intents
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
class RoleReactClient(discord.Client):
|
class MyClient(discord.Client):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.role_message_id = 0 # ID of message that can be reacted to to add role
|
self.role_message_id = 0 # ID of the message that can be reacted to to add/remove a role.
|
||||||
self.emoji_to_role = {
|
self.emoji_to_role = {
|
||||||
partial_emoji_1: 0, # ID of role associated with partial emoji object 'partial_emoji_1'
|
discord.PartialEmoji(name='🔴'): 0, # ID of the role associated with unicode emoji '🔴'.
|
||||||
partial_emoji_2: 0 # ID of role associated with partial emoji object 'partial_emoji_2'
|
discord.PartialEmoji(name='🟡'): 0, # ID of the role associated with unicode emoji '🟡'.
|
||||||
|
discord.PartialEmoji(name='green', id=0): 0, # ID of the role associated with a partial emoji's ID.
|
||||||
}
|
}
|
||||||
|
|
||||||
async def on_raw_reaction_add(self, payload):
|
async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent):
|
||||||
"""Gives a role based on a reaction emoji."""
|
"""Gives a role based on a reaction emoji."""
|
||||||
# Make sure that the message the user is reacting to is the one we care about
|
# Make sure that the message the user is reacting to is the one we care about.
|
||||||
if payload.message_id != self.role_message_id:
|
if payload.message_id != self.role_message_id:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
guild = self.get_guild(payload.guild_id)
|
||||||
|
if guild is None:
|
||||||
|
# Check if we're still in the guild and it's cached.
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
role_id = self.emoji_to_role[payload.emoji]
|
role_id = self.emoji_to_role[payload.emoji]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# If the emoji isn't the one we care about then exit as well.
|
# If the emoji isn't the one we care about then exit as well.
|
||||||
return
|
return
|
||||||
|
|
||||||
guild = self.get_guild(payload.guild_id)
|
|
||||||
if guild is None:
|
|
||||||
# Check if we're still in the guild and it's cached.
|
|
||||||
return
|
|
||||||
|
|
||||||
role = guild.get_role(role_id)
|
role = guild.get_role(role_id)
|
||||||
if role is None:
|
if role is None:
|
||||||
# Make sure the role still exists and is valid.
|
# Make sure the role still exists and is valid.
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Finally add the role
|
# Finally, add the role.
|
||||||
await payload.member.add_roles(role)
|
await payload.member.add_roles(role)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
# If we want to do something in case of errors we'd do it here.
|
# If we want to do something in case of errors we'd do it here.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def on_raw_reaction_remove(self, payload):
|
async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent):
|
||||||
"""Removes a role based on a reaction emoji."""
|
"""Removes a role based on a reaction emoji."""
|
||||||
# Make sure that the message the user is reacting to is the one we care about
|
# Make sure that the message the user is reacting to is the one we care about.
|
||||||
if payload.message_id != self.role_message_id:
|
if payload.message_id != self.role_message_id:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
guild = self.get_guild(payload.guild_id)
|
||||||
|
if guild is None:
|
||||||
|
# Check if we're still in the guild and it's cached.
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
role_id = self.emoji_to_role[payload.emoji]
|
role_id = self.emoji_to_role[payload.emoji]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# If the emoji isn't the one we care about then exit as well.
|
# If the emoji isn't the one we care about then exit as well.
|
||||||
return
|
return
|
||||||
|
|
||||||
guild = self.get_guild(payload.guild_id)
|
|
||||||
if guild is None:
|
|
||||||
# Check if we're still in the guild and it's cached.
|
|
||||||
return
|
|
||||||
|
|
||||||
role = guild.get_role(role_id)
|
role = guild.get_role(role_id)
|
||||||
if role is None:
|
if role is None:
|
||||||
# Make sure the role still exists and is valid.
|
# Make sure the role still exists and is valid.
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# The payload for `on_raw_reaction_remove` does not provide `.member`
|
||||||
|
# so we must get the member ourselves from the payload's `.user_id`.
|
||||||
member = guild.get_member(payload.user_id)
|
member = guild.get_member(payload.user_id)
|
||||||
if member is None:
|
if member is None:
|
||||||
# Makes sure the member still exists and is valid
|
# Make sure the member still exists and is valid.
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Finally, remove the role
|
# Finally, remove the role.
|
||||||
await member.remove_roles(role)
|
await member.remove_roles(role)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
# If we want to do something in case of errors we'd do it here.
|
# If we want to do something in case of errors we'd do it here.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# This bot requires the members and reactions intents.
|
|
||||||
intents = discord.Intents.default()
|
intents = discord.Intents.default()
|
||||||
intents.members = True
|
intents.members = True
|
||||||
|
|
||||||
client = RoleReactClient(intents=intents)
|
client = MyClient(intents=intents)
|
||||||
client.run("token")
|
client.run('token')
|
||||||
|
96
examples/secret.py
Normal file
96
examples/secret.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import typing
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
bot = commands.Bot(command_prefix=commands.when_mentioned, description="Nothing to see here!")
|
||||||
|
|
||||||
|
# the `hidden` keyword argument hides it from the help command.
|
||||||
|
@bot.group(hidden=True)
|
||||||
|
async def secret(ctx: commands.Context):
|
||||||
|
"""What is this "secret" you speak of?"""
|
||||||
|
if ctx.invoked_subcommand is None:
|
||||||
|
await ctx.send('Shh!', delete_after=5)
|
||||||
|
|
||||||
|
def create_overwrites(ctx, *objects):
|
||||||
|
"""This is just a helper function that creates the overwrites for the
|
||||||
|
voice/text channels.
|
||||||
|
|
||||||
|
A `discord.PermissionOverwrite` allows you to determine the permissions
|
||||||
|
of an object, whether it be a `discord.Role` or a `discord.Member`.
|
||||||
|
|
||||||
|
In this case, the `view_channel` permission is being used to hide the channel
|
||||||
|
from being viewed by whoever does not meet the criteria, thus creating a
|
||||||
|
secret channel.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# a dict comprehension is being utilised here to set the same permission overwrites
|
||||||
|
# for each `discord.Role` or `discord.Member`.
|
||||||
|
overwrites = {
|
||||||
|
obj: discord.PermissionOverwrite(view_channel=True)
|
||||||
|
for obj in objects
|
||||||
|
}
|
||||||
|
|
||||||
|
# prevents the default role (@everyone) from viewing the channel
|
||||||
|
# if it isn't already allowed to view the channel.
|
||||||
|
overwrites.setdefault(ctx.guild.default_role, discord.PermissionOverwrite(view_channel=False))
|
||||||
|
|
||||||
|
# makes sure the client is always allowed to view the channel.
|
||||||
|
overwrites[ctx.guild.me] = discord.PermissionOverwrite(view_channel=True)
|
||||||
|
|
||||||
|
return overwrites
|
||||||
|
|
||||||
|
# since these commands rely on guild related features,
|
||||||
|
# it is best to lock it to be guild-only.
|
||||||
|
@secret.command()
|
||||||
|
@commands.guild_only()
|
||||||
|
async def text(ctx: commands.Context, name: str, *objects: typing.Union[discord.Role, discord.Member]):
|
||||||
|
"""This makes a text channel with a specified name
|
||||||
|
that is only visible to roles or members that are specified.
|
||||||
|
"""
|
||||||
|
|
||||||
|
overwrites = create_overwrites(ctx, *objects)
|
||||||
|
|
||||||
|
await ctx.guild.create_text_channel(
|
||||||
|
name,
|
||||||
|
overwrites=overwrites,
|
||||||
|
topic='Top secret text channel. Any leakage of this channel may result in serious trouble.',
|
||||||
|
reason='Very secret business.',
|
||||||
|
)
|
||||||
|
|
||||||
|
@secret.command()
|
||||||
|
@commands.guild_only()
|
||||||
|
async def voice(ctx: commands.Context, name: str, *objects: typing.Union[discord.Role, discord.Member]):
|
||||||
|
"""This does the same thing as the `text` subcommand
|
||||||
|
but instead creates a voice channel.
|
||||||
|
"""
|
||||||
|
|
||||||
|
overwrites = create_overwrites(ctx, *objects)
|
||||||
|
|
||||||
|
await ctx.guild.create_voice_channel(
|
||||||
|
name,
|
||||||
|
overwrites=overwrites,
|
||||||
|
reason='Very secret business.'
|
||||||
|
)
|
||||||
|
|
||||||
|
@secret.command()
|
||||||
|
@commands.guild_only()
|
||||||
|
async def emoji(ctx: commands.Context, emoji: discord.PartialEmoji, *roles: discord.Role):
|
||||||
|
"""This clones a specified emoji that only specified roles
|
||||||
|
are allowed to use.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# fetch the emoji asset and read it as bytes.
|
||||||
|
emoji_bytes = await emoji.url.read()
|
||||||
|
|
||||||
|
# the key parameter here is `roles`, which controls
|
||||||
|
# what roles are able to use the emoji.
|
||||||
|
await ctx.guild.create_custom_emoji(
|
||||||
|
name=emoji.name,
|
||||||
|
image=emoji_bytes,
|
||||||
|
roles=roles,
|
||||||
|
reason='Very secret business.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
bot.run('token')
|
Loading…
x
Reference in New Issue
Block a user