Merge branch '2.0' into pr7422

This commit is contained in:
Arthur 2021-09-01 22:09:59 +02:00 committed by GitHub
commit 28c76873de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
101 changed files with 2648 additions and 37018 deletions

View File

@ -2,3 +2,4 @@ include README.rst
include LICENSE include LICENSE
include requirements.txt include requirements.txt
include discord/bin/*.dll include discord/bin/*.dll
include discord/py.typed

View File

@ -1,113 +0,0 @@
discord.py
==========
.. image:: https://discord.com/api/guilds/336642139381301249/embed.png
:target: https://discord.gg/nXzj3dg
:alt: Discordサーバーの招待
.. image:: https://img.shields.io/pypi/v/discord.py.svg
:target: https://pypi.python.org/pypi/discord.py
:alt: PyPIのバージョン情報
.. image:: https://img.shields.io/pypi/pyversions/discord.py.svg
:target: https://pypi.python.org/pypi/discord.py
:alt: PyPIのサポートしているPythonのバージョン
discord.py は機能豊富かつモダンで使いやすい、非同期処理にも対応したDiscord用のAPIラッパーです。
主な特徴
-------------
- ``async````await`` を使ったモダンなPythonらしいAPI。
- 適切なレート制限処理
- メモリと速度の両方を最適化。
インストール
-------------
**Python 3.8 以降のバージョンが必須です**
完全な音声サポートなしでライブラリをインストールする場合は次のコマンドを実行してください:
.. code:: sh
# Linux/OS X
python3 -m pip install -U discord.py
# Windows
py -3 -m pip install -U discord.py
音声サポートが必要なら、次のコマンドを実行しましょう:
.. code:: sh
# Linux/OS X
python3 -m pip install -U discord.py[voice]
# Windows
py -3 -m pip install -U discord.py[voice]
開発版をインストールしたいのならば、次の手順に従ってください:
.. code:: sh
$ git clone https://github.com/Rapptz/discord.py
$ cd discord.py
$ python3 -m pip install -U .[voice]
オプションパッケージ
~~~~~~~~~~~~~~~~~~~~~~
* PyNaCl (音声サポート用)
Linuxで音声サポートを導入するには、前述のコマンドを実行する前にお気に入りのパッケージマネージャー(例えば ``apt````dnf`` など)を使って以下のパッケージをインストールする必要があります:
* libffi-dev (システムによっては ``libffi-devel``)
* python-dev (例えばPython 3.6用の ``python3.6-dev``)
簡単な例
--------------
.. code:: py
import discord
class MyClient(discord.Client):
async def on_ready(self):
print('Logged on as', self.user)
async def on_message(self, message):
# don't respond to ourselves
if message.author == self.user:
return
if message.content == 'ping':
await message.channel.send('pong')
client = MyClient()
client.run('token')
Botの例
~~~~~~~~~~~~~
.. code:: py
import discord
from discord.ext import commands
bot = commands.Bot(command_prefix='>')
@bot.command()
async def ping(ctx):
await ctx.send('pong')
bot.run('token')
examplesディレクトリに更に多くのサンプルがあります。
リンク
------
- `ドキュメント <https://discordpy.readthedocs.io/ja/latest/index.html>`_
- `公式Discordサーバー <https://discord.gg/nXzj3dg>`_
- `Discord API <https://discord.gg/discord-api>`_

View File

@ -1,17 +1,35 @@
discord.py discord.py
========== ==========
.. image:: https://discord.com/api/guilds/336642139381301249/embed.png .. image:: https://discord.com/api/guilds/514232441498763279/embed.png
:target: https://discord.gg/r3sSKJJ :target: https://discord.gg/PYAfZzpsjG
:alt: Discord server invite :alt: Discord server invite
.. image:: https://img.shields.io/pypi/v/discord.py.svg .. image:: https://img.shields.io/pypi/v/enhanced-dpy.svg
:target: https://pypi.python.org/pypi/discord.py :target: https://pypi.python.org/pypi/enhanced-dpy
:alt: PyPI version info :alt: PyPI version info
.. image:: https://img.shields.io/pypi/pyversions/discord.py.svg .. image:: https://img.shields.io/pypi/pyversions/enhanced-dpy.svg
:target: https://pypi.python.org/pypi/discord.py :target: https://pypi.python.org/pypi/enhanced-dpy
:alt: PyPI supported Python versions :alt: PyPI supported Python versions
A modern, easy to use, feature-rich, and async ready API wrapper for Discord written in Python. A modern, maintained, easy to use, feature-rich, and async ready API wrapper for Discord written in Python.
The Future of enhanced-discord.py
--------------------------
Enhanced discord.py is a fork of Rapptz's discord.py, that went unmaintained (`gist <https://gist.github.com/Rapptz/4a2f62751b9600a31a0d3c78100287f1>`_)
It is currently maintained by (in alphabetical order)
- Chillymosh#8175
- Daggy#9889
- dank Had0cK#6081
- Dutchy#6127
- Eyesofcreeper#0001
- Gnome!#6669
- IAmTomahawkx#1000
- Jadon#2494
An overview of added features is available on the `custom features page <https://enhanced-dpy.readthedocs.io/en/latest/index.html#custom-features>`_.
Key Features Key Features
------------- -------------
@ -30,27 +48,17 @@ To install the library without full voice support, you can just run the followin
.. code:: sh .. code:: sh
# Linux/macOS # Linux/macOS
python3 -m pip install -U discord.py python3 -m pip install -U enhanced-dpy
# Windows # Windows
py -3 -m pip install -U discord.py py -3 -m pip install -U enhanced-dpy
Otherwise to get voice support you should run the following command:
.. code:: sh
# Linux/macOS
python3 -m pip install -U "discord.py[voice]"
# Windows
py -3 -m pip install -U discord.py[voice]
To install the development version, do the following: To install the development version, do the following:
.. code:: sh .. code:: sh
$ git clone https://github.com/Rapptz/discord.py $ git clone https://github.com/iDevision/enhanced-discord.py
$ cd discord.py $ cd discord.py
$ python3 -m pip install -U .[voice] $ python3 -m pip install -U .[voice]
@ -108,6 +116,6 @@ You can find more examples in the examples directory.
Links Links
------ ------
- `Documentation <https://discordpy.readthedocs.io/en/latest/index.html>`_ - `Documentation <https://enhanced-dpy.readthedocs.io/en/latest/index.html>`_
- `Official Discord Server <https://discord.gg/r3sSKJJ>`_ - `Official Discord Server <https://discord.gg/PYAfZzpsjG>`_
- `Discord API <https://discord.gg/discord-api>`_ - `Discord API <https://discord.gg/discord-api>`_

View File

@ -60,6 +60,7 @@ from .interactions import *
from .components import * from .components import *
from .threads import * from .threads import *
class VersionInfo(NamedTuple): class VersionInfo(NamedTuple):
major: int major: int
minor: int minor: int
@ -67,6 +68,7 @@ class VersionInfo(NamedTuple):
releaselevel: Literal["alpha", "beta", "candidate", "final"] releaselevel: Literal["alpha", "beta", "candidate", "final"]
serial: int serial: int
version_info = VersionInfo(major=2, minor=0, micro=0, releaselevel='alpha', serial=0)
version_info: VersionInfo = VersionInfo(major=2, minor=0, micro=0, releaselevel='alpha', serial=0)
logging.getLogger(__name__).addHandler(logging.NullHandler()) logging.getLogger(__name__).addHandler(logging.NullHandler())

View File

@ -51,7 +51,7 @@ def core(parser, args):
if args.version: if args.version:
show_version() show_version()
bot_template = """#!/usr/bin/env python3 _bot_template = """#!/usr/bin/env python3
from discord.ext import commands from discord.ext import commands
import discord import discord
@ -77,7 +77,7 @@ bot = Bot()
bot.run(config.token) bot.run(config.token)
""" """
gitignore_template = """# Byte-compiled / optimized / DLL files _gitignore_template = """# Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
*$py.class *$py.class
@ -107,7 +107,7 @@ var/
config.py config.py
""" """
cog_template = '''from discord.ext import commands _cog_template = '''from discord.ext import commands
import discord import discord
class {name}(commands.Cog{attrs}): class {name}(commands.Cog{attrs}):
@ -120,7 +120,7 @@ def setup(bot):
bot.add_cog({name}(bot)) bot.add_cog({name}(bot))
''' '''
cog_extras = ''' _cog_extras = '''
def cog_unload(self): def cog_unload(self):
# clean up logic goes here # clean up logic goes here
pass pass
@ -170,7 +170,7 @@ _base_table = {
# NUL (0) and 1-31 are disallowed # NUL (0) and 1-31 are disallowed
_base_table.update((chr(i), None) for i in range(32)) _base_table.update((chr(i), None) for i in range(32))
translation_table = str.maketrans(_base_table) _translation_table = str.maketrans(_base_table)
def to_path(parser, name, *, replace_spaces=False): def to_path(parser, name, *, replace_spaces=False):
if isinstance(name, Path): if isinstance(name, Path):
@ -182,7 +182,7 @@ def to_path(parser, name, *, replace_spaces=False):
if len(name) <= 4 and name.upper() in forbidden: if len(name) <= 4 and name.upper() in forbidden:
parser.error('invalid directory name given, use a different one') parser.error('invalid directory name given, use a different one')
name = name.translate(translation_table) name = name.translate(_translation_table)
if replace_spaces: if replace_spaces:
name = name.replace(' ', '-') name = name.replace(' ', '-')
return Path(name) return Path(name)
@ -215,14 +215,14 @@ def newbot(parser, args):
try: try:
with open(str(new_directory / 'bot.py'), 'w', encoding='utf-8') as fp: with open(str(new_directory / 'bot.py'), 'w', encoding='utf-8') as fp:
base = 'Bot' if not args.sharded else 'AutoShardedBot' base = 'Bot' if not args.sharded else 'AutoShardedBot'
fp.write(bot_template.format(base=base, prefix=args.prefix)) fp.write(_bot_template.format(base=base, prefix=args.prefix))
except OSError as exc: except OSError as exc:
parser.error(f'could not create bot file ({exc})') parser.error(f'could not create bot file ({exc})')
if not args.no_git: if not args.no_git:
try: try:
with open(str(new_directory / '.gitignore'), 'w', encoding='utf-8') as fp: with open(str(new_directory / '.gitignore'), 'w', encoding='utf-8') as fp:
fp.write(gitignore_template) fp.write(_gitignore_template)
except OSError as exc: except OSError as exc:
print(f'warning: could not create .gitignore file ({exc})') print(f'warning: could not create .gitignore file ({exc})')
@ -240,7 +240,7 @@ def newcog(parser, args):
try: try:
with open(str(directory), 'w', encoding='utf-8') as fp: with open(str(directory), 'w', encoding='utf-8') as fp:
attrs = '' attrs = ''
extra = cog_extras if args.full else '' extra = _cog_extras if args.full else ''
if args.class_name: if args.class_name:
name = args.class_name name = args.class_name
else: else:
@ -255,7 +255,7 @@ def newcog(parser, args):
attrs += f', name="{args.display_name}"' attrs += f', name="{args.display_name}"'
if args.hide_commands: if args.hide_commands:
attrs += ', command_attrs=dict(hidden=True)' attrs += ', command_attrs=dict(hidden=True)'
fp.write(cog_template.format(name=name, extra=extra, attrs=attrs)) fp.write(_cog_template.format(name=name, extra=extra, attrs=attrs))
except OSError as exc: except OSError as exc:
parser.error(f'could not create cog file ({exc})') parser.error(f'could not create cog file ({exc})')
else: else:

View File

@ -84,6 +84,7 @@ if TYPE_CHECKING:
from .ui.view import View from .ui.view import View
from .types.channel import ( from .types.channel import (
PermissionOverwrite as PermissionOverwritePayload, PermissionOverwrite as PermissionOverwritePayload,
Channel as ChannelPayload,
GuildChannel as GuildChannelPayload, GuildChannel as GuildChannelPayload,
OverwriteType, OverwriteType,
) )
@ -122,11 +123,6 @@ class Snowflake(Protocol):
__slots__ = () __slots__ = ()
id: int id: int
@property
def created_at(self) -> datetime:
""":class:`datetime.datetime`: Returns the model's creation time as an aware datetime in UTC."""
raise NotImplementedError
@runtime_checkable @runtime_checkable
class User(Snowflake, Protocol): class User(Snowflake, Protocol):
@ -307,11 +303,8 @@ class GuildChannel:
payload.append(d) payload.append(d)
await http.bulk_channel_update(self.guild.id, payload, reason=reason) await http.bulk_channel_update(self.guild.id, payload, reason=reason)
self.position = position
if parent_id is not _undefined:
self.category_id = int(parent_id) if parent_id else None
async def _edit(self, options: Dict[str, Any], reason: Optional[str]): async def _edit(self, options: Dict[str, Any], reason: Optional[str]) -> Optional[ChannelPayload]:
try: try:
parent = options.pop('category') parent = options.pop('category')
except KeyError: except KeyError:
@ -390,8 +383,7 @@ class GuildChannel:
options['type'] = ch_type.value options['type'] = ch_type.value
if options: if options:
data = await self._state.http.edit_channel(self.id, reason=reason, **options) return await self._state.http.edit_channel(self.id, reason=reason, **options)
self._update(self.guild, data)
def _fill_overwrites(self, data: GuildChannelPayload) -> None: def _fill_overwrites(self, data: GuildChannelPayload) -> None:
self._overwrites = [] self._overwrites = []
@ -1641,6 +1633,8 @@ class Connectable(Protocol):
Connects to voice and creates a :class:`VoiceClient` to establish Connects to voice and creates a :class:`VoiceClient` to establish
your connection to the voice server. your connection to the voice server.
This requires :attr:`Intents.voice_states`.
Parameters Parameters
----------- -----------
timeout: :class:`float` timeout: :class:`float`

View File

@ -830,10 +830,12 @@ def create_activity(data: Optional[ActivityPayload]) -> Optional[ActivityTypes]:
except KeyError: except KeyError:
return Activity(**data) return Activity(**data)
else: else:
return CustomActivity(name=name, **data) # we removed the name key from data already
return CustomActivity(name=name, **data) # type: ignore
elif game_type is ActivityType.streaming: elif game_type is ActivityType.streaming:
if 'url' in data: if 'url' in data:
return Streaming(**data) # the url won't be None here
return Streaming(**data) # type: ignore
return Activity(**data) return Activity(**data)
elif game_type is ActivityType.listening and 'sync_id' in data and 'session_id' in data: elif game_type is ActivityType.listening and 'sync_id' in data and 'session_id' in data:
return Spotify(**data) return Spotify(**data)

View File

@ -177,6 +177,17 @@ class Asset(AssetMixin):
animated=animated, animated=animated,
) )
@classmethod
def _from_guild_avatar(cls, state, guild_id: int, member_id: int, avatar: str) -> Asset:
animated = avatar.startswith('a_')
format = 'gif' if animated else 'png'
return cls(
state,
url=f"{cls.BASE}/guilds/{guild_id}/users/{member_id}/avatars/{avatar}.{format}?size=1024",
key=avatar,
animated=animated,
)
@classmethod @classmethod
def _from_icon(cls, state, object_id: int, icon_hash: str, path: str) -> Asset: def _from_icon(cls, state, object_id: int, icon_hash: str, path: str) -> Asset:
return cls( return cls(
@ -302,7 +313,8 @@ class Asset(AssetMixin):
if self._animated: if self._animated:
if format not in VALID_ASSET_FORMATS: if format not in VALID_ASSET_FORMATS:
raise InvalidArgument(f'format must be one of {VALID_ASSET_FORMATS}') raise InvalidArgument(f'format must be one of {VALID_ASSET_FORMATS}')
else: url = url.with_path(f'{path}.{format}')
elif static_format is MISSING:
if format not in VALID_STATIC_FORMATS: if format not in VALID_STATIC_FORMATS:
raise InvalidArgument(f'format must be one of {VALID_STATIC_FORMATS}') raise InvalidArgument(f'format must be one of {VALID_STATIC_FORMATS}')
url = url.with_path(f'{path}.{format}') url = url.with_path(f'{path}.{format}')

View File

@ -330,6 +330,10 @@ class AuditLogEntry(Hashable):
Returns the entry's hash. Returns the entry's hash.
.. describe:: int(x)
Returns the entry's ID.
.. versionchanged:: 1.7 .. versionchanged:: 1.7
Audit log entries are now comparable and hashable. Audit log entries are now comparable and hashable.

View File

@ -86,6 +86,7 @@ if TYPE_CHECKING:
StoreChannel as StoreChannelPayload, StoreChannel as StoreChannelPayload,
GroupDMChannel as GroupChannelPayload, GroupDMChannel as GroupChannelPayload,
) )
from .types.snowflake import SnowflakeList
async def _single_delete_strategy(messages: Iterable[Message]): async def _single_delete_strategy(messages: Iterable[Message]):
@ -114,6 +115,10 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
Returns the channel's name. Returns the channel's name.
.. describe:: int(x)
Returns the channel's ID.
Attributes Attributes
----------- -----------
name: :class:`str` name: :class:`str`
@ -229,7 +234,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
return [thread for thread in self.guild.threads if thread.parent_id == self.id] return [thread for thread in self.guild._threads.values() if thread.parent_id == self.id]
def is_nsfw(self) -> bool: def is_nsfw(self) -> bool:
""":class:`bool`: Checks if the channel is NSFW.""" """:class:`bool`: Checks if the channel is NSFW."""
@ -275,11 +280,11 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
default_auto_archive_duration: ThreadArchiveDuration = ..., default_auto_archive_duration: ThreadArchiveDuration = ...,
type: ChannelType = ..., type: ChannelType = ...,
overwrites: Mapping[Union[Role, Member, Snowflake], PermissionOverwrite] = ..., overwrites: Mapping[Union[Role, Member, Snowflake], PermissionOverwrite] = ...,
) -> None: ) -> Optional[TextChannel]:
... ...
@overload @overload
async def edit(self) -> None: async def edit(self) -> Optional[TextChannel]:
... ...
async def edit(self, *, reason=None, **options): async def edit(self, *, reason=None, **options):
@ -296,6 +301,9 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
.. versionchanged:: 1.4 .. versionchanged:: 1.4
The ``type`` keyword-only parameter was added. The ``type`` keyword-only parameter was added.
.. versionchanged:: 2.0
Edits are no longer in-place, the newly edited channel is returned instead.
Parameters Parameters
---------- ----------
name: :class:`str` name: :class:`str`
@ -337,8 +345,18 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
You do not have permissions to edit the channel. You do not have permissions to edit the channel.
HTTPException HTTPException
Editing the channel failed. Editing the channel failed.
Returns
--------
Optional[:class:`.TextChannel`]
The newly edited text channel. If the edit was only positional
then ``None`` is returned instead.
""" """
await self._edit(options, reason=reason)
payload = await self._edit(options, reason=reason)
if payload is not None:
# the payload will always be the proper channel payload
return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore
@utils.copy_doc(discord.abc.GuildChannel.clone) @utils.copy_doc(discord.abc.GuildChannel.clone)
async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> TextChannel: async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> TextChannel:
@ -392,7 +410,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
if len(messages) > 100: if len(messages) > 100:
raise ClientException('Can only bulk delete messages up to 100 messages') raise ClientException('Can only bulk delete messages up to 100 messages')
message_ids: List[int] = [m.id for m in messages] message_ids: SnowflakeList = [m.id for m in messages]
await self._state.http.delete_messages(self.id, message_ids) await self._state.http.delete_messages(self.id, message_ids)
async def purge( async def purge(
@ -670,15 +688,8 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
Creates a thread in this text channel. Creates a thread in this text channel.
If no starter message is passed with the ``message`` parameter then To create a public thread, you must have :attr:`~discord.Permissions.create_public_threads`.
you must have :attr:`~discord.Permissions.send_messages` and For a private thread, :attr:`~discord.Permissions.create_private_threads` is needed instead.
:attr:`~discord.Permissions.use_private_threads` in order to create the thread
if the ``type`` parameter is :attr:`~discord.ChannelType.private_thread`.
Otherwise :attr:`~discord.Permissions.use_threads` is needed.
If a starter message is passed with the ``message`` parameter then
you must have :attr:`~discord.Permissions.send_messages` and
:attr:`~discord.Permissions.use_threads` in order to create the thread.
.. versionadded:: 2.0 .. versionadded:: 2.0
@ -957,11 +968,11 @@ class VoiceChannel(VocalGuildChannel):
rtc_region: Optional[VoiceRegion] = ..., rtc_region: Optional[VoiceRegion] = ...,
video_quality_mode: VideoQualityMode = ..., video_quality_mode: VideoQualityMode = ...,
reason: Optional[str] = ..., reason: Optional[str] = ...,
) -> None: ) -> Optional[VoiceChannel]:
... ...
@overload @overload
async def edit(self) -> None: async def edit(self) -> Optional[VoiceChannel]:
... ...
async def edit(self, *, reason=None, **options): async def edit(self, *, reason=None, **options):
@ -975,6 +986,9 @@ class VoiceChannel(VocalGuildChannel):
.. versionchanged:: 1.3 .. versionchanged:: 1.3
The ``overwrites`` keyword-only parameter was added. The ``overwrites`` keyword-only parameter was added.
.. versionchanged:: 2.0
Edits are no longer in-place, the newly edited channel is returned instead.
Parameters Parameters
---------- ----------
name: :class:`str` name: :class:`str`
@ -1014,9 +1028,18 @@ class VoiceChannel(VocalGuildChannel):
You do not have permissions to edit the channel. You do not have permissions to edit the channel.
HTTPException HTTPException
Editing the channel failed. Editing the channel failed.
Returns
--------
Optional[:class:`.VoiceChannel`]
The newly edited voice channel. If the edit was only positional
then ``None`` is returned instead.
""" """
await self._edit(options, reason=reason) payload = await self._edit(options, reason=reason)
if payload is not None:
# the payload will always be the proper channel payload
return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore
class StageChannel(VocalGuildChannel): class StageChannel(VocalGuildChannel):
@ -1224,11 +1247,11 @@ class StageChannel(VocalGuildChannel):
rtc_region: Optional[VoiceRegion] = ..., rtc_region: Optional[VoiceRegion] = ...,
video_quality_mode: VideoQualityMode = ..., video_quality_mode: VideoQualityMode = ...,
reason: Optional[str] = ..., reason: Optional[str] = ...,
) -> None: ) -> Optional[StageChannel]:
... ...
@overload @overload
async def edit(self) -> None: async def edit(self) -> Optional[StageChannel]:
... ...
async def edit(self, *, reason=None, **options): async def edit(self, *, reason=None, **options):
@ -1242,6 +1265,9 @@ class StageChannel(VocalGuildChannel):
.. versionchanged:: 2.0 .. versionchanged:: 2.0
The ``topic`` parameter must now be set via :attr:`create_instance`. The ``topic`` parameter must now be set via :attr:`create_instance`.
.. versionchanged:: 2.0
Edits are no longer in-place, the newly edited channel is returned instead.
Parameters Parameters
---------- ----------
name: :class:`str` name: :class:`str`
@ -1275,9 +1301,18 @@ class StageChannel(VocalGuildChannel):
You do not have permissions to edit the channel. You do not have permissions to edit the channel.
HTTPException HTTPException
Editing the channel failed. Editing the channel failed.
Returns
--------
Optional[:class:`.StageChannel`]
The newly edited stage channel. If the edit was only positional
then ``None`` is returned instead.
""" """
await self._edit(options, reason=reason) payload = await self._edit(options, reason=reason)
if payload is not None:
# the payload will always be the proper channel payload
return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore
class CategoryChannel(discord.abc.GuildChannel, Hashable): class CategoryChannel(discord.abc.GuildChannel, Hashable):
@ -1303,6 +1338,10 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
Returns the category's name. Returns the category's name.
.. describe:: int(x)
Returns the category's ID.
Attributes Attributes
----------- -----------
name: :class:`str` name: :class:`str`
@ -1366,11 +1405,11 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
nsfw: bool = ..., nsfw: bool = ...,
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ..., overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ...,
reason: Optional[str] = ..., reason: Optional[str] = ...,
) -> None: ) -> Optional[CategoryChannel]:
... ...
@overload @overload
async def edit(self) -> None: async def edit(self) -> Optional[CategoryChannel]:
... ...
async def edit(self, *, reason=None, **options): async def edit(self, *, reason=None, **options):
@ -1384,6 +1423,9 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
.. versionchanged:: 1.3 .. versionchanged:: 1.3
The ``overwrites`` keyword-only parameter was added. The ``overwrites`` keyword-only parameter was added.
.. versionchanged:: 2.0
Edits are no longer in-place, the newly edited channel is returned instead.
Parameters Parameters
---------- ----------
name: :class:`str` name: :class:`str`
@ -1406,9 +1448,18 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
You do not have permissions to edit the category. You do not have permissions to edit the category.
HTTPException HTTPException
Editing the category failed. Editing the category failed.
Returns
--------
Optional[:class:`.CategoryChannel`]
The newly edited category channel. If the edit was only positional
then ``None`` is returned instead.
""" """
await self._edit(options=options, reason=reason) payload = await self._edit(options, reason=reason)
if payload is not None:
# the payload will always be the proper channel payload
return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore
@utils.copy_doc(discord.abc.GuildChannel.move) @utils.copy_doc(discord.abc.GuildChannel.move)
async def move(self, **kwargs): async def move(self, **kwargs):
@ -1513,6 +1564,10 @@ class StoreChannel(discord.abc.GuildChannel, Hashable):
Returns the channel's name. Returns the channel's name.
.. describe:: int(x)
Returns the channel's ID.
Attributes Attributes
----------- -----------
name: :class:`str` name: :class:`str`
@ -1598,11 +1653,11 @@ class StoreChannel(discord.abc.GuildChannel, Hashable):
category: Optional[CategoryChannel], category: Optional[CategoryChannel],
reason: Optional[str], reason: Optional[str],
overwrites: Mapping[Union[Role, Member], PermissionOverwrite], overwrites: Mapping[Union[Role, Member], PermissionOverwrite],
) -> None: ) -> Optional[StoreChannel]:
... ...
@overload @overload
async def edit(self) -> None: async def edit(self) -> Optional[StoreChannel]:
... ...
async def edit(self, *, reason=None, **options): async def edit(self, *, reason=None, **options):
@ -1613,6 +1668,9 @@ class StoreChannel(discord.abc.GuildChannel, Hashable):
You must have the :attr:`~Permissions.manage_channels` permission to You must have the :attr:`~Permissions.manage_channels` permission to
use this. use this.
.. versionchanged:: 2.0
Edits are no longer in-place, the newly edited channel is returned instead.
Parameters Parameters
---------- ----------
name: :class:`str` name: :class:`str`
@ -1644,8 +1702,18 @@ class StoreChannel(discord.abc.GuildChannel, Hashable):
You do not have permissions to edit the channel. You do not have permissions to edit the channel.
HTTPException HTTPException
Editing the channel failed. Editing the channel failed.
Returns
--------
Optional[:class:`.StoreChannel`]
The newly edited store channel. If the edit was only positional
then ``None`` is returned instead.
""" """
await self._edit(options, reason=reason)
payload = await self._edit(options, reason=reason)
if payload is not None:
# the payload will always be the proper channel payload
return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore
DMC = TypeVar('DMC', bound='DMChannel') DMC = TypeVar('DMC', bound='DMChannel')
@ -1672,6 +1740,10 @@ class DMChannel(discord.abc.Messageable, Hashable):
Returns a string representation of the channel Returns a string representation of the channel
.. describe:: int(x)
Returns the channel's ID.
Attributes Attributes
---------- ----------
recipient: Optional[:class:`User`] recipient: Optional[:class:`User`]
@ -1709,6 +1781,7 @@ class DMChannel(discord.abc.Messageable, Hashable):
self._state = state self._state = state
self.id = channel_id self.id = channel_id
self.recipient = None self.recipient = None
# state.user won't be None here
self.me = state.user # type: ignore self.me = state.user # type: ignore
return self return self
@ -1797,6 +1870,10 @@ class GroupChannel(discord.abc.Messageable, Hashable):
Returns a string representation of the channel Returns a string representation of the channel
.. describe:: int(x)
Returns the channel's ID.
Attributes Attributes
---------- ----------
recipients: List[:class:`User`] recipients: List[:class:`User`]
@ -1943,6 +2020,10 @@ class PartialMessageable(discord.abc.Messageable, Hashable):
Returns the partial messageable's hash. Returns the partial messageable's hash.
.. describe:: int(x)
Returns the messageable's ID.
Attributes Attributes
----------- -----------
id: :class:`int` id: :class:`int`

View File

@ -29,7 +29,7 @@ import logging
import signal import signal
import sys import sys
import traceback import traceback
from typing import Any, Callable, Coroutine, Dict, Generator, Iterable, List, Optional, Sequence, TYPE_CHECKING, Tuple, TypeVar, Union from typing import Any, Callable, Coroutine, Dict, Generator, List, Optional, Sequence, TYPE_CHECKING, Tuple, TypeVar, Union
import aiohttp import aiohttp
@ -76,7 +76,7 @@ __all__ = (
Coro = TypeVar('Coro', bound=Callable[..., Coroutine[Any, Any, Any]]) Coro = TypeVar('Coro', bound=Callable[..., Coroutine[Any, Any, Any]])
log: logging.Logger = logging.getLogger(__name__) _log = logging.getLogger(__name__)
def _cancel_tasks(loop: asyncio.AbstractEventLoop) -> None: def _cancel_tasks(loop: asyncio.AbstractEventLoop) -> None:
tasks = {t for t in asyncio.all_tasks(loop=loop) if not t.done()} tasks = {t for t in asyncio.all_tasks(loop=loop) if not t.done()}
@ -84,12 +84,12 @@ def _cancel_tasks(loop: asyncio.AbstractEventLoop) -> None:
if not tasks: if not tasks:
return return
log.info('Cleaning up after %d tasks.', len(tasks)) _log.info('Cleaning up after %d tasks.', len(tasks))
for task in tasks: for task in tasks:
task.cancel() task.cancel()
loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True)) loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True))
log.info('All tasks finished cancelling.') _log.info('All tasks finished cancelling.')
for task in tasks: for task in tasks:
if task.cancelled(): if task.cancelled():
@ -106,7 +106,7 @@ def _cleanup_loop(loop: asyncio.AbstractEventLoop) -> None:
_cancel_tasks(loop) _cancel_tasks(loop)
loop.run_until_complete(loop.shutdown_asyncgens()) loop.run_until_complete(loop.shutdown_asyncgens())
finally: finally:
log.info('Closing the event loop.') _log.info('Closing the event loop.')
loop.close() loop.close()
class Client: class Client:
@ -142,7 +142,6 @@ class Client:
intents: :class:`Intents` intents: :class:`Intents`
The intents that you want to enable for the session. This is a way of The intents that you want to enable for the session. This is a way of
disabling and enabling certain gateway events from triggering and being sent. disabling and enabling certain gateway events from triggering and being sent.
If not given, defaults to a regularly constructed :class:`Intents` class.
.. versionadded:: 1.5 .. versionadded:: 1.5
member_cache_flags: :class:`MemberCacheFlags` member_cache_flags: :class:`MemberCacheFlags`
@ -187,7 +186,7 @@ class Client:
enable_debug_events: :class:`bool` enable_debug_events: :class:`bool`
Whether to enable events that are useful only for debugging gateway related information. Whether to enable events that are useful only for debugging gateway related information.
Right now this involves :func:`on_socket_raw_receive` and :func`:`on_socket_raw_send`. If Right now this involves :func:`on_socket_raw_receive` and :func:`on_socket_raw_send`. If
this is ``False`` then those events will not be dispatched (due to performance considerations). this is ``False`` then those events will not be dispatched (due to performance considerations).
To enable these events, this must be set to ``True``. Defaults to ``False``. To enable these events, this must be set to ``True``. Defaults to ``False``.
@ -203,9 +202,13 @@ class Client:
def __init__( def __init__(
self, self,
*, *,
intents: Intents,
loop: Optional[asyncio.AbstractEventLoop] = None, loop: Optional[asyncio.AbstractEventLoop] = None,
**options: Any, **options: Any,
): ):
options["intents"] = intents
# self.ws is set in the connect method
self.ws: DiscordWebSocket = None # type: ignore self.ws: DiscordWebSocket = None # type: ignore
self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() if loop is None else loop self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() if loop is None else loop
self._listeners: Dict[str, List[Tuple[asyncio.Future, Callable[..., bool]]]] = {} self._listeners: Dict[str, List[Tuple[asyncio.Future, Callable[..., bool]]]] = {}
@ -236,7 +239,7 @@ class Client:
if VoiceClient.warn_nacl: if VoiceClient.warn_nacl:
VoiceClient.warn_nacl = False VoiceClient.warn_nacl = False
log.warning("PyNaCl is not installed, voice will NOT be supported") _log.warning("PyNaCl is not installed, voice will NOT be supported")
# internals # internals
@ -362,7 +365,7 @@ class Client:
return asyncio.create_task(wrapped, name=f'discord.py: {event_name}') return asyncio.create_task(wrapped, name=f'discord.py: {event_name}')
def dispatch(self, event: str, *args: Any, **kwargs: Any) -> None: def dispatch(self, event: str, *args: Any, **kwargs: Any) -> None:
log.debug('Dispatching event %s', event) _log.debug('Dispatching event %s', event)
method = 'on_' + event method = 'on_' + event
listeners = self._listeners.get(event) listeners = self._listeners.get(event)
@ -467,7 +470,7 @@ class Client:
passing status code. passing status code.
""" """
log.info('logging in using static token') _log.info('logging in using static token')
data = await self.http.static_login(token.strip()) data = await self.http.static_login(token.strip())
self._connection.user = ClientUser(state=self._connection, data=data) self._connection.user = ClientUser(state=self._connection, data=data)
@ -510,7 +513,7 @@ class Client:
while True: while True:
await self.ws.poll_event() await self.ws.poll_event()
except ReconnectWebSocket as e: except ReconnectWebSocket as e:
log.info('Got a request to %s the websocket.', e.op) _log.info('Got a request to %s the websocket.', e.op)
self.dispatch('disconnect') self.dispatch('disconnect')
ws_params.update(sequence=self.ws.sequence, resume=e.resume, session=self.ws.session_id) ws_params.update(sequence=self.ws.sequence, resume=e.resume, session=self.ws.session_id)
continue continue
@ -549,7 +552,7 @@ class Client:
raise raise
retry = backoff.delay() retry = backoff.delay()
log.exception("Attempting a reconnect in %.2fs", retry) _log.exception("Attempting a reconnect in %.2fs", retry)
await asyncio.sleep(retry) await asyncio.sleep(retry)
# Always try to RESUME the connection # Always try to RESUME the connection
# If the connection is not RESUME-able then the gateway will invalidate the session. # If the connection is not RESUME-able then the gateway will invalidate the session.
@ -651,10 +654,10 @@ class Client:
try: try:
loop.run_forever() loop.run_forever()
except KeyboardInterrupt: except KeyboardInterrupt:
log.info('Received signal to terminate bot and event loop.') _log.info('Received signal to terminate bot and event loop.')
finally: finally:
future.remove_done_callback(stop_loop_on_completion) future.remove_done_callback(stop_loop_on_completion)
log.info('Cleaning up tasks.') _log.info('Cleaning up tasks.')
_cleanup_loop(loop) _cleanup_loop(loop)
if not future.cancelled(): if not future.cancelled():
@ -682,10 +685,31 @@ class Client:
if value is None: if value is None:
self._connection._activity = None self._connection._activity = None
elif isinstance(value, BaseActivity): elif isinstance(value, BaseActivity):
self._connection._activity = value.to_dict() # ConnectionState._activity is typehinted as ActivityPayload, we're passing Dict[str, Any]
self._connection._activity = value.to_dict() # type: ignore
else: else:
raise TypeError('activity must derive from BaseActivity.') raise TypeError('activity must derive from BaseActivity.')
@property
def status(self):
""":class:`.Status`:
The status being used upon logging on to Discord.
.. versionadded: 2.0
"""
if self._connection._status in set(state.value for state in Status):
return Status(self._connection._status)
return Status.online
@status.setter
def status(self, value):
if value is Status.offline:
self._connection._status = 'invisible'
elif isinstance(value, Status):
self._connection._status = str(value)
else:
raise TypeError('status must derive from Status.')
@property @property
def allowed_mentions(self) -> Optional[AllowedMentions]: def allowed_mentions(self) -> Optional[AllowedMentions]:
"""Optional[:class:`~discord.AllowedMentions`]: The allowed mention configuration. """Optional[:class:`~discord.AllowedMentions`]: The allowed mention configuration.
@ -716,8 +740,8 @@ class Client:
"""List[:class:`~discord.User`]: Returns a list of all the users the bot can see.""" """List[:class:`~discord.User`]: Returns a list of all the users the bot can see."""
return list(self._connection._users.values()) return list(self._connection._users.values())
def get_channel(self, id: int) -> Optional[Union[GuildChannel, PrivateChannel]]: def get_channel(self, id: int, /) -> Optional[Union[GuildChannel, Thread, PrivateChannel]]:
"""Returns a channel with the given ID. """Returns a channel or thread with the given ID.
Parameters Parameters
----------- -----------
@ -726,7 +750,7 @@ class Client:
Returns Returns
-------- --------
Optional[Union[:class:`.abc.GuildChannel`, :class:`.abc.PrivateChannel`]] Optional[Union[:class:`.abc.GuildChannel`, :class:`.Thread`, :class:`.abc.PrivateChannel`]]
The returned channel or ``None`` if not found. The returned channel or ``None`` if not found.
""" """
return self._connection.get_channel(id) return self._connection.get_channel(id)
@ -743,7 +767,7 @@ class Client:
----------- -----------
id: :class:`int` id: :class:`int`
The channel ID to create a partial messageable for. The channel ID to create a partial messageable for.
type: Optional[:class:`ChannelType`] type: Optional[:class:`.ChannelType`]
The underlying channel type for the partial messageable. The underlying channel type for the partial messageable.
Returns Returns
@ -753,7 +777,7 @@ class Client:
""" """
return PartialMessageable(state=self._connection, id=id, type=type) return PartialMessageable(state=self._connection, id=id, type=type)
def get_stage_instance(self, id) -> Optional[StageInstance]: def get_stage_instance(self, id: int, /) -> Optional[StageInstance]:
"""Returns a stage instance with the given stage channel ID. """Returns a stage instance with the given stage channel ID.
.. versionadded:: 2.0 .. versionadded:: 2.0
@ -775,7 +799,7 @@ class Client:
if isinstance(channel, StageChannel): if isinstance(channel, StageChannel):
return channel.instance return channel.instance
def get_guild(self, id) -> Optional[Guild]: def get_guild(self, id: int, /) -> Optional[Guild]:
"""Returns a guild with the given ID. """Returns a guild with the given ID.
Parameters Parameters
@ -790,7 +814,7 @@ class Client:
""" """
return self._connection._get_guild(id) return self._connection._get_guild(id)
def get_user(self, id) -> Optional[User]: def get_user(self, id: int, /) -> Optional[User]:
"""Returns a user with the given ID. """Returns a user with the given ID.
Parameters Parameters
@ -805,7 +829,7 @@ class Client:
""" """
return self._connection.get_user(id) return self._connection.get_user(id)
def get_emoji(self, id) -> Optional[Emoji]: def get_emoji(self, id: int, /) -> Optional[Emoji]:
"""Returns an emoji with the given ID. """Returns an emoji with the given ID.
Parameters Parameters
@ -820,7 +844,7 @@ class Client:
""" """
return self._connection.get_emoji(id) return self._connection.get_emoji(id)
def get_sticker(self, id: int) -> Optional[GuildSticker]: def get_sticker(self, id: int, /) -> Optional[GuildSticker]:
"""Returns a guild sticker with the given ID. """Returns a guild sticker with the given ID.
.. versionadded:: 2.0 .. versionadded:: 2.0
@ -1019,7 +1043,7 @@ class Client:
raise TypeError('event registered must be a coroutine function') raise TypeError('event registered must be a coroutine function')
setattr(self, coro.__name__, coro) setattr(self, coro.__name__, coro)
log.debug('%s has successfully been registered as an event', coro.__name__) _log.debug('%s has successfully been registered as an event', coro.__name__)
return coro return coro
async def change_presence( async def change_presence(
@ -1169,7 +1193,7 @@ class Client:
data = await self.http.get_template(code) data = await self.http.get_template(code)
return Template(data=data, state=self._connection) # type: ignore return Template(data=data, state=self._connection) # type: ignore
async def fetch_guild(self, guild_id: int) -> Guild: async def fetch_guild(self, guild_id: int, /) -> Guild:
"""|coro| """|coro|
Retrieves a :class:`.Guild` from an ID. Retrieves a :class:`.Guild` from an ID.
@ -1258,7 +1282,7 @@ class Client:
data = await self.http.create_guild(name, region_value, icon_base64) data = await self.http.create_guild(name, region_value, icon_base64)
return Guild(data=data, state=self._connection) return Guild(data=data, state=self._connection)
async def fetch_stage_instance(self, channel_id: int) -> StageInstance: async def fetch_stage_instance(self, channel_id: int, /) -> StageInstance:
"""|coro| """|coro|
Gets a :class:`.StageInstance` for a stage channel id. Gets a :class:`.StageInstance` for a stage channel id.
@ -1358,7 +1382,7 @@ class Client:
# Miscellaneous stuff # Miscellaneous stuff
async def fetch_widget(self, guild_id: int) -> Widget: async def fetch_widget(self, guild_id: int, /) -> Widget:
"""|coro| """|coro|
Gets a :class:`.Widget` from a guild ID. Gets a :class:`.Widget` from a guild ID.
@ -1408,7 +1432,7 @@ class Client:
data['rpc_origins'] = None data['rpc_origins'] = None
return AppInfo(self._connection, data) return AppInfo(self._connection, data)
async def fetch_user(self, user_id: int) -> User: async def fetch_user(self, user_id: int, /) -> User:
"""|coro| """|coro|
Retrieves a :class:`~discord.User` based on their ID. Retrieves a :class:`~discord.User` based on their ID.
@ -1439,7 +1463,7 @@ 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)
async def fetch_channel(self, channel_id: int) -> Union[GuildChannel, PrivateChannel, Thread]: async def fetch_channel(self, channel_id: int, /) -> Union[GuildChannel, PrivateChannel, Thread]:
"""|coro| """|coro|
Retrieves a :class:`.abc.GuildChannel`, :class:`.abc.PrivateChannel`, or :class:`.Thread` with the specified ID. Retrieves a :class:`.abc.GuildChannel`, :class:`.abc.PrivateChannel`, or :class:`.Thread` with the specified ID.
@ -1473,15 +1497,18 @@ class Client:
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(data))
if ch_type in (ChannelType.group, ChannelType.private): if ch_type in (ChannelType.group, ChannelType.private):
channel = factory(me=self.user, data=data, state=self._connection) # the factory will be a DMChannel or GroupChannel here
channel = factory(me=self.user, data=data, state=self._connection) # type: ignore
else: else:
guild_id = int(data['guild_id']) # the factory can't be a DMChannel or GroupChannel here
guild_id = int(data['guild_id']) # type: ignore
guild = self.get_guild(guild_id) or Object(id=guild_id) guild = self.get_guild(guild_id) or Object(id=guild_id)
channel = factory(guild=guild, state=self._connection, data=data) # GuildChannels expect a Guild, we may be passing an Object
channel = factory(guild=guild, state=self._connection, data=data) # type: ignore
return channel return channel
async def fetch_webhook(self, webhook_id: int) -> Webhook: async def fetch_webhook(self, webhook_id: int, /) -> Webhook:
"""|coro| """|coro|
Retrieves a :class:`.Webhook` with the specified ID. Retrieves a :class:`.Webhook` with the specified ID.
@ -1503,7 +1530,7 @@ class Client:
data = await self.http.get_webhook(webhook_id) data = await self.http.get_webhook(webhook_id)
return Webhook.from_state(data, state=self._connection) return Webhook.from_state(data, state=self._connection)
async def fetch_sticker(self, sticker_id: int) -> Union[StandardSticker, GuildSticker]: async def fetch_sticker(self, sticker_id: int, /) -> Union[StandardSticker, GuildSticker]:
"""|coro| """|coro|
Retrieves a :class:`.Sticker` with the specified ID. Retrieves a :class:`.Sticker` with the specified ID.

View File

@ -78,7 +78,7 @@ class Colour:
__slots__ = ('value',) __slots__ = ('value',)
def __init__(self, value): def __init__(self, value: int):
if not isinstance(value, int): if not isinstance(value, int):
raise TypeError(f'Expected int parameter, received {value.__class__.__name__} instead.') raise TypeError(f'Expected int parameter, received {value.__class__.__name__} instead.')
@ -171,6 +171,14 @@ class Colour:
"""A factory method that returns a :class:`Colour` with a value of ``0x11806a``.""" """A factory method that returns a :class:`Colour` with a value of ``0x11806a``."""
return cls(0x11806a) return cls(0x11806a)
@classmethod
def brand_green(cls: Type[CT]) -> CT:
"""A factory method that returns a :class:`Colour` with a value of ``0x57F287``.
.. versionadded:: 2.0
"""
return cls(0x57F287)
@classmethod @classmethod
def green(cls: Type[CT]) -> CT: def green(cls: Type[CT]) -> CT:
"""A factory method that returns a :class:`Colour` with a value of ``0x2ecc71``.""" """A factory method that returns a :class:`Colour` with a value of ``0x2ecc71``."""
@ -231,6 +239,14 @@ class Colour:
"""A factory method that returns a :class:`Colour` with a value of ``0xa84300``.""" """A factory method that returns a :class:`Colour` with a value of ``0xa84300``."""
return cls(0xa84300) return cls(0xa84300)
@classmethod
def brand_red(cls: Type[CT]) -> CT:
"""A factory method that returns a :class:`Colour` with a value of ``0xED4245``.
.. versionadded:: 2.0
"""
return cls(0xED4245)
@classmethod @classmethod
def red(cls: Type[CT]) -> CT: def red(cls: Type[CT]) -> CT:
"""A factory method that returns a :class:`Colour` with a value of ``0xe74c3c``.""" """A factory method that returns a :class:`Colour` with a value of ``0xe74c3c``."""
@ -309,5 +325,14 @@ class Colour:
""" """
return cls(0xFEE75C) return cls(0xFEE75C)
@classmethod
def dark_blurple(cls: Type[CT]) -> CT:
"""A factory method that returns a :class:`Colour` with a value of ``0x4E5D94``.
This is the original Dark Blurple branding.
.. versionadded:: 2.0
"""
return cls(0x4E5D94)
Color = Colour Color = Colour

View File

@ -277,14 +277,14 @@ class SelectOption:
----------- -----------
label: :class:`str` label: :class:`str`
The label of the option. This is displayed to users. The label of the option. This is displayed to users.
Can only be up to 25 characters. Can only be up to 100 characters.
value: :class:`str` value: :class:`str`
The value of the option. This is not displayed to users. The value of the option. This is not displayed to users.
If not provided when constructed then it defaults to the If not provided when constructed then it defaults to the
label. Can only be up to 100 characters. label. Can only be up to 100 characters.
description: Optional[:class:`str`] description: Optional[:class:`str`]
An additional description of the option, if any. An additional description of the option, if any.
Can only be up to 50 characters. Can only be up to 100 characters.
emoji: Optional[Union[:class:`str`, :class:`Emoji`, :class:`PartialEmoji`]] emoji: Optional[Union[:class:`str`, :class:`Emoji`, :class:`PartialEmoji`]]
The emoji of the option, if available. The emoji of the option, if available.
default: :class:`bool` default: :class:`bool`

View File

@ -22,13 +22,23 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
""" """
from __future__ import annotations
import asyncio import asyncio
from typing import TYPE_CHECKING, TypeVar, Optional, Type
if TYPE_CHECKING:
from .abc import Messageable
from types import TracebackType
TypingT = TypeVar('TypingT', bound='Typing')
__all__ = ( __all__ = (
'Typing', 'Typing',
) )
def _typing_done_callback(fut): def _typing_done_callback(fut: asyncio.Future) -> None:
# just retrieve any exception and call it a day # just retrieve any exception and call it a day
try: try:
fut.exception() fut.exception()
@ -36,11 +46,11 @@ def _typing_done_callback(fut):
pass pass
class Typing: class Typing:
def __init__(self, messageable): def __init__(self, messageable: Messageable) -> None:
self.loop = messageable._state.loop self.loop: asyncio.AbstractEventLoop = messageable._state.loop
self.messageable = messageable self.messageable: Messageable = messageable
async def do_typing(self): async def do_typing(self) -> None:
try: try:
channel = self._channel channel = self._channel
except AttributeError: except AttributeError:
@ -52,18 +62,26 @@ class Typing:
await typing(channel.id) await typing(channel.id)
await asyncio.sleep(5) await asyncio.sleep(5)
def __enter__(self): def __enter__(self: TypingT) -> TypingT:
self.task = asyncio.ensure_future(self.do_typing(), loop=self.loop) self.task: asyncio.Task = self.loop.create_task(self.do_typing())
self.task.add_done_callback(_typing_done_callback) self.task.add_done_callback(_typing_done_callback)
return self return self
def __exit__(self, exc_type, exc, tb): def __exit__(self,
exc_type: Optional[Type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[TracebackType],
) -> None:
self.task.cancel() self.task.cancel()
async def __aenter__(self): async def __aenter__(self: TypingT) -> TypingT:
self._channel = channel = await self.messageable._get_channel() self._channel = channel = await self.messageable._get_channel()
await channel._state.http.send_typing(channel.id) await channel._state.http.send_typing(channel.id)
return self.__enter__() return self.__enter__()
async def __aexit__(self, exc_type, exc, tb): async def __aexit__(self,
exc_type: Optional[Type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[TracebackType],
) -> None:
self.task.cancel() self.task.cancel()

View File

@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
import datetime import datetime
from typing import Any, Dict, Final, List, Protocol, TYPE_CHECKING, Type, TypeVar, Union from typing import Any, Dict, Final, List, Mapping, Protocol, TYPE_CHECKING, Type, TypeVar, Union
from . import utils from . import utils
from .colour import Colour from .colour import Colour
@ -72,30 +72,36 @@ if TYPE_CHECKING:
T = TypeVar('T') T = TypeVar('T')
MaybeEmpty = Union[T, _EmptyEmbed] MaybeEmpty = Union[T, _EmptyEmbed]
class _EmbedFooterProxy(Protocol): class _EmbedFooterProxy(Protocol):
text: MaybeEmpty[str] text: MaybeEmpty[str]
icon_url: MaybeEmpty[str] icon_url: MaybeEmpty[str]
class _EmbedFieldProxy(Protocol): class _EmbedFieldProxy(Protocol):
name: MaybeEmpty[str] name: MaybeEmpty[str]
value: MaybeEmpty[str] value: MaybeEmpty[str]
inline: bool inline: bool
class _EmbedMediaProxy(Protocol): class _EmbedMediaProxy(Protocol):
url: MaybeEmpty[str] url: MaybeEmpty[str]
proxy_url: MaybeEmpty[str] proxy_url: MaybeEmpty[str]
height: MaybeEmpty[int] height: MaybeEmpty[int]
width: MaybeEmpty[int] width: MaybeEmpty[int]
class _EmbedVideoProxy(Protocol): class _EmbedVideoProxy(Protocol):
url: MaybeEmpty[str] url: MaybeEmpty[str]
height: MaybeEmpty[int] height: MaybeEmpty[int]
width: MaybeEmpty[int] width: MaybeEmpty[int]
class _EmbedProviderProxy(Protocol): class _EmbedProviderProxy(Protocol):
name: MaybeEmpty[str] name: MaybeEmpty[str]
url: MaybeEmpty[str] url: MaybeEmpty[str]
class _EmbedAuthorProxy(Protocol): class _EmbedAuthorProxy(Protocol):
name: MaybeEmpty[str] name: MaybeEmpty[str]
url: MaybeEmpty[str] url: MaybeEmpty[str]
@ -205,7 +211,7 @@ class Embed:
self.timestamp = timestamp self.timestamp = timestamp
@classmethod @classmethod
def from_dict(cls: Type[E], data: EmbedData) -> E: def from_dict(cls: Type[E], data: Mapping[str, Any]) -> E:
"""Converts a :class:`dict` to a :class:`Embed` provided it is in the """Converts a :class:`dict` to a :class:`Embed` provided it is in the
format that Discord expects it to be in. format that Discord expects it to be in.
@ -397,6 +403,19 @@ class Embed:
""" """
return EmbedProxy(getattr(self, '_image', {})) # type: ignore return EmbedProxy(getattr(self, '_image', {})) # type: ignore
@image.setter
def image(self: E, *, url: Any):
self._image = {
'url': str(url),
}
@image.deleter
def image(self: E):
try:
del self._image
except AttributeError:
pass
def set_image(self: E, *, url: MaybeEmpty[Any]) -> E: def set_image(self: E, *, url: MaybeEmpty[Any]) -> E:
"""Sets the image for the embed content. """Sets the image for the embed content.
@ -413,14 +432,9 @@ class Embed:
""" """
if url is EmptyEmbed: if url is EmptyEmbed:
try: del self.image
del self._image
except AttributeError:
pass
else: else:
self._image = { self.image = url
'url': str(url),
}
return self return self
@ -439,7 +453,25 @@ class Embed:
""" """
return EmbedProxy(getattr(self, '_thumbnail', {})) # type: ignore return EmbedProxy(getattr(self, '_thumbnail', {})) # type: ignore
def set_thumbnail(self: E, *, url: MaybeEmpty[Any]) -> E: @thumbnail.setter
def thumbnail(self: E, *, url: Any):
"""Sets the thumbnail for the embed content.
"""
self._thumbnail = {
'url': str(url),
}
return
@thumbnail.deleter
def thumbnail(self):
try:
del self.thumbnail
except AttributeError:
pass
def set_thumbnail(self: E, *, url: MaybeEmpty[Any]):
"""Sets the thumbnail for the embed content. """Sets the thumbnail for the embed content.
This function returns the class instance to allow for fluent-style This function returns the class instance to allow for fluent-style
@ -453,16 +485,10 @@ class Embed:
url: :class:`str` url: :class:`str`
The source URL for the thumbnail. Only HTTP(S) is supported. The source URL for the thumbnail. Only HTTP(S) is supported.
""" """
if url is EmptyEmbed: if url is EmptyEmbed:
try: del self.thumbnail
del self._thumbnail
except AttributeError:
pass
else: else:
self._thumbnail = { self.thumbnail = url
'url': str(url),
}
return self return self

View File

@ -72,6 +72,10 @@ class Emoji(_EmojiTag, AssetMixin):
Returns the emoji rendered for discord. Returns the emoji rendered for discord.
.. describe:: int(x)
Returns the emoji ID.
Attributes Attributes
----------- -----------
name: :class:`str` name: :class:`str`
@ -137,6 +141,9 @@ class Emoji(_EmojiTag, AssetMixin):
return f'<a:{self.name}:{self.id}>' return f'<a:{self.name}:{self.id}>'
return f'<:{self.name}:{self.id}>' return f'<:{self.name}:{self.id}>'
def __int__(self) -> int:
return self.id
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<Emoji id={self.id} name={self.name!r} animated={self.animated} managed={self.managed}>' return f'<Emoji id={self.id} name={self.name!r} animated={self.animated} managed={self.managed}>'
@ -212,7 +219,7 @@ class Emoji(_EmojiTag, AssetMixin):
await self._state.http.delete_custom_emoji(self.guild.id, self.id, reason=reason) await self._state.http.delete_custom_emoji(self.guild.id, self.id, reason=reason)
async def edit(self, *, name: str = MISSING, roles: List[Snowflake] = MISSING, reason: Optional[str] = None) -> None: async def edit(self, *, name: str = MISSING, roles: List[Snowflake] = MISSING, reason: Optional[str] = None) -> Emoji:
r"""|coro| r"""|coro|
Edits the custom emoji. Edits the custom emoji.
@ -220,6 +227,9 @@ class Emoji(_EmojiTag, AssetMixin):
You must have :attr:`~Permissions.manage_emojis` permission to You must have :attr:`~Permissions.manage_emojis` permission to
do this. do this.
.. versionchanged:: 2.0
The newly updated emoji is returned.
Parameters Parameters
----------- -----------
name: :class:`str` name: :class:`str`
@ -235,6 +245,11 @@ class Emoji(_EmojiTag, AssetMixin):
You are not allowed to edit emojis. You are not allowed to edit emojis.
HTTPException HTTPException
An error occurred editing the emoji. An error occurred editing the emoji.
Returns
--------
:class:`Emoji`
The newly updated emoji.
""" """
payload = {} payload = {}
@ -243,4 +258,5 @@ class Emoji(_EmojiTag, AssetMixin):
if roles is not MISSING: if roles is not MISSING:
payload['roles'] = [role.id for role in roles] payload['roles'] = [role.id for role in roles]
await self._state.http.edit_custom_emoji(self.guild.id, self.id, payload=payload, reason=reason) data = await self._state.http.edit_custom_emoji(self.guild.id, self.id, payload=payload, reason=reason)
return Emoji(guild=self.guild, data=data, state=self._state)

View File

@ -58,13 +58,17 @@ __all__ = (
) )
def _create_value_cls(name): def _create_value_cls(name, comparable):
cls = namedtuple('_EnumValue_' + name, 'name value') cls = namedtuple('_EnumValue_' + name, 'name value')
cls.__repr__ = lambda self: f'<{name}.{self.name}: {self.value!r}>' cls.__repr__ = lambda self: f'<{name}.{self.name}: {self.value!r}>'
cls.__str__ = lambda self: f'{name}.{self.name}' cls.__str__ = lambda self: f'{name}.{self.name}'
if comparable:
cls.__le__ = lambda self, other: isinstance(other, self.__class__) and self.value <= other.value
cls.__ge__ = lambda self, other: isinstance(other, self.__class__) and self.value >= other.value
cls.__lt__ = lambda self, other: isinstance(other, self.__class__) and self.value < other.value
cls.__gt__ = lambda self, other: isinstance(other, self.__class__) and self.value > other.value
return cls return cls
def _is_descriptor(obj): def _is_descriptor(obj):
return hasattr(obj, '__get__') or hasattr(obj, '__set__') or hasattr(obj, '__delete__') return hasattr(obj, '__get__') or hasattr(obj, '__set__') or hasattr(obj, '__delete__')
@ -76,12 +80,12 @@ class EnumMeta(type):
_enum_member_map_: ClassVar[Dict[str, Any]] _enum_member_map_: ClassVar[Dict[str, Any]]
_enum_value_map_: ClassVar[Dict[Any, Any]] _enum_value_map_: ClassVar[Dict[Any, Any]]
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs, *, comparable: bool = False):
value_mapping = {} value_mapping = {}
member_mapping = {} member_mapping = {}
member_names = [] member_names = []
value_cls = _create_value_cls(name) value_cls = _create_value_cls(name, comparable)
for key, value in list(attrs.items()): for key, value in list(attrs.items()):
is_descriptor = _is_descriptor(value) is_descriptor = _is_descriptor(value)
if key[0] == '_' and not is_descriptor: if key[0] == '_' and not is_descriptor:
@ -252,7 +256,7 @@ class SpeakingState(Enum):
return self.value return self.value
class VerificationLevel(Enum): class VerificationLevel(Enum, comparable=True):
none = 0 none = 0
low = 1 low = 1
medium = 2 medium = 2
@ -263,7 +267,7 @@ class VerificationLevel(Enum):
return self.name return self.name
class ContentFilter(Enum): class ContentFilter(Enum, comparable=True):
disabled = 0 disabled = 0
no_role = 1 no_role = 1
all_members = 2 all_members = 2
@ -296,7 +300,7 @@ class DefaultAvatar(Enum):
return self.name return self.name
class NotificationLevel(Enum): class NotificationLevel(Enum, comparable=True):
all_messages = 0 all_messages = 0
only_mentions = 1 only_mentions = 1
@ -578,7 +582,7 @@ class StagePrivacyLevel(Enum):
guild_only = 2 guild_only = 2
class NSFWLevel(Enum): class NSFWLevel(Enum, comparable=True):
default = 0 default = 0
explicit = 1 explicit = 1
safe = 2 safe = 2

View File

@ -31,9 +31,9 @@ if TYPE_CHECKING:
try: try:
from requests import Response from requests import Response
ResponseType = Union[ClientResponse, Response] _ResponseType = Union[ClientResponse, Response]
except ModuleNotFoundError: except ModuleNotFoundError:
ResponseType = ClientResponse _ResponseType = ClientResponse
from .interactions import Interaction from .interactions import Interaction
@ -123,8 +123,8 @@ class HTTPException(DiscordException):
The Discord specific error code for the failure. The Discord specific error code for the failure.
""" """
def __init__(self, response: ResponseType, message: Optional[Union[str, Dict[str, Any]]]): def __init__(self, response: _ResponseType, message: Optional[Union[str, Dict[str, Any]]]):
self.response: ResponseType = response self.response: _ResponseType = response
self.status: int = response.status # type: ignore self.status: int = response.status # type: ignore
self.code: int self.code: int
self.text: str self.text: str

View File

@ -22,6 +22,26 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
""" """
from typing import Any, Callable, Coroutine, TYPE_CHECKING, TypeVar, Union
if TYPE_CHECKING:
from .context import Context
from .cog import Cog
from .errors import CommandError
T = TypeVar('T')
Coro = Coroutine[Any, Any, T]
MaybeCoro = Union[T, Coro[T]]
CoroFunc = Callable[..., Coro[Any]]
Check = Union[Callable[["Cog", "Context[Any]"], MaybeCoro[bool]], Callable[["Context[Any]"], MaybeCoro[bool]]]
Hook = Union[Callable[["Cog", "Context[Any]"], Coro[Any]], Callable[["Context[Any]"], Coro[Any]]]
Error = Union[Callable[["Cog", "Context[Any]", "CommandError"], Coro[Any]], Callable[["Context[Any]", "CommandError"], Coro[Any]]]
# This is merely a tag type to avoid circular import issues. # This is merely a tag type to avoid circular import issues.
# Yes, this is a terrible solution but ultimately it is the only solution. # Yes, this is a terrible solution but ultimately it is the only solution.
class _BaseCommand: class _BaseCommand:

View File

@ -22,13 +22,18 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
""" """
from __future__ import annotations
import asyncio import asyncio
import collections import collections
import collections.abc
import inspect import inspect
import importlib.util import importlib.util
import sys import sys
import traceback import traceback
import types import types
from typing import Any, Callable, Mapping, List, Dict, TYPE_CHECKING, Optional, TypeVar, Type, Union
import discord import discord
@ -39,6 +44,15 @@ from . import errors
from .help import HelpCommand, DefaultHelpCommand from .help import HelpCommand, DefaultHelpCommand
from .cog import Cog from .cog import Cog
if TYPE_CHECKING:
import importlib.machinery
from discord.message import Message
from ._types import (
Check,
CoroFunc,
)
__all__ = ( __all__ = (
'when_mentioned', 'when_mentioned',
'when_mentioned_or', 'when_mentioned_or',
@ -46,14 +60,21 @@ __all__ = (
'AutoShardedBot', 'AutoShardedBot',
) )
def when_mentioned(bot, msg): MISSING: Any = discord.utils.MISSING
T = TypeVar('T')
CFT = TypeVar('CFT', bound='CoroFunc')
CXT = TypeVar('CXT', bound='Context')
def when_mentioned(bot: Union[Bot, AutoShardedBot], msg: Message) -> List[str]:
"""A callable that implements a command prefix equivalent to being mentioned. """A callable that implements a command prefix equivalent to being mentioned.
These are meant to be passed into the :attr:`.Bot.command_prefix` attribute. These are meant to be passed into the :attr:`.Bot.command_prefix` attribute.
""" """
return [f'<@{bot.user.id}> ', f'<@!{bot.user.id}> '] # bot.user will never be None when this is called
return [f'<@{bot.user.id}> ', f'<@!{bot.user.id}> '] # type: ignore
def when_mentioned_or(*prefixes): def when_mentioned_or(*prefixes: str) -> Callable[[Union[Bot, AutoShardedBot], Message], List[str]]:
"""A callable that implements when mentioned or other prefixes provided. """A callable that implements when mentioned or other prefixes provided.
These are meant to be passed into the :attr:`.Bot.command_prefix` attribute. These are meant to be passed into the :attr:`.Bot.command_prefix` attribute.
@ -89,7 +110,7 @@ def when_mentioned_or(*prefixes):
return inner return inner
def _is_submodule(parent, child): def _is_submodule(parent: str, child: str) -> bool:
return parent == child or child.startswith(parent + ".") return parent == child or child.startswith(parent + ".")
class _DefaultRepr: class _DefaultRepr:
@ -99,13 +120,13 @@ class _DefaultRepr:
_default = _DefaultRepr() _default = _DefaultRepr()
class BotBase(GroupMixin): class BotBase(GroupMixin):
def __init__(self, command_prefix, help_command=_default, description=None, **options): def __init__(self, command_prefix, help_command=_default, description=None, *, intents: discord.Intents, **options):
super().__init__(**options) super().__init__(**options, intents=intents)
self.command_prefix = command_prefix self.command_prefix = command_prefix
self.extra_events = {} self.extra_events: Dict[str, List[CoroFunc]] = {}
self.__cogs = {} self.__cogs: Dict[str, Cog] = {}
self.__extensions = {} self.__extensions: Dict[str, types.ModuleType] = {}
self._checks = [] self._checks: List[Check] = []
self._check_once = [] self._check_once = []
self._before_invoke = None self._before_invoke = None
self._after_invoke = None self._after_invoke = None
@ -128,13 +149,15 @@ class BotBase(GroupMixin):
# internal helpers # internal helpers
def dispatch(self, event_name, *args, **kwargs): def dispatch(self, event_name: str, *args: Any, **kwargs: Any) -> None:
super().dispatch(event_name, *args, **kwargs) # super() will resolve to Client
super().dispatch(event_name, *args, **kwargs) # type: ignore
ev = 'on_' + event_name ev = 'on_' + event_name
for event in self.extra_events.get(ev, []): for event in self.extra_events.get(ev, []):
self._schedule_event(event, ev, *args, **kwargs) self._schedule_event(event, ev, *args, **kwargs) # type: ignore
async def close(self): @discord.utils.copy_doc(discord.Client.close)
async def close(self) -> None:
for extension in tuple(self.__extensions): for extension in tuple(self.__extensions):
try: try:
self.unload_extension(extension) self.unload_extension(extension)
@ -147,9 +170,9 @@ class BotBase(GroupMixin):
except Exception: except Exception:
pass pass
await super().close() await super().close() # type: ignore
async def on_command_error(self, context, exception): async def on_command_error(self, context: Context, exception: errors.CommandError) -> None:
"""|coro| """|coro|
The default command error handler provided by the bot. The default command error handler provided by the bot.
@ -175,7 +198,7 @@ class BotBase(GroupMixin):
# global check registration # global check registration
def check(self, func): def check(self, func: T) -> T:
r"""A decorator that adds a global check to the bot. r"""A decorator that adds a global check to the bot.
A global check is similar to a :func:`.check` that is applied A global check is similar to a :func:`.check` that is applied
@ -200,10 +223,11 @@ class BotBase(GroupMixin):
return ctx.command.qualified_name in allowed_commands return ctx.command.qualified_name in allowed_commands
""" """
self.add_check(func) # T was used instead of Check to ensure the type matches on return
self.add_check(func) # type: ignore
return func return func
def add_check(self, func, *, call_once=False): def add_check(self, func: Check, *, call_once: bool = False) -> None:
"""Adds a global check to the bot. """Adds a global check to the bot.
This is the non-decorator interface to :meth:`.check` This is the non-decorator interface to :meth:`.check`
@ -223,7 +247,7 @@ class BotBase(GroupMixin):
else: else:
self._checks.append(func) self._checks.append(func)
def remove_check(self, func, *, call_once=False): def remove_check(self, func: Check, *, call_once: bool = False) -> None:
"""Removes a global check from the bot. """Removes a global check from the bot.
This function is idempotent and will not raise an exception This function is idempotent and will not raise an exception
@ -244,7 +268,7 @@ class BotBase(GroupMixin):
except ValueError: except ValueError:
pass pass
def check_once(self, func): def check_once(self, func: CFT) -> CFT:
r"""A decorator that adds a "call once" global check to the bot. r"""A decorator that adds a "call once" global check to the bot.
Unlike regular global checks, this one is called only once Unlike regular global checks, this one is called only once
@ -282,15 +306,16 @@ class BotBase(GroupMixin):
self.add_check(func, call_once=True) self.add_check(func, call_once=True)
return func return func
async def can_run(self, ctx, *, call_once=False): async def can_run(self, ctx: Context, *, call_once: bool = False) -> bool:
data = self._check_once if call_once else self._checks data = self._check_once if call_once else self._checks
if len(data) == 0: if len(data) == 0:
return True return True
return await discord.utils.async_all(f(ctx) for f in data) # type-checker doesn't distinguish between functions and methods
return await discord.utils.async_all(f(ctx) for f in data) # type: ignore
async def is_owner(self, user): async def is_owner(self, user: discord.User) -> bool:
"""|coro| """|coro|
Checks if a :class:`~discord.User` or :class:`~discord.Member` is the owner of Checks if a :class:`~discord.User` or :class:`~discord.Member` is the owner of
@ -319,7 +344,8 @@ class BotBase(GroupMixin):
elif self.owner_ids: elif self.owner_ids:
return user.id in self.owner_ids return user.id in self.owner_ids
else: else:
app = await self.application_info()
app = await self.application_info() # type: ignore
if app.team: if app.team:
self.owner_ids = ids = {m.id for m in app.team.members} self.owner_ids = ids = {m.id for m in app.team.members}
return user.id in ids return user.id in ids
@ -327,7 +353,7 @@ class BotBase(GroupMixin):
self.owner_id = owner_id = app.owner.id self.owner_id = owner_id = app.owner.id
return user.id == owner_id return user.id == owner_id
def before_invoke(self, coro): def before_invoke(self, coro: CFT) -> CFT:
"""A decorator that registers a coroutine as a pre-invoke hook. """A decorator that registers a coroutine as a pre-invoke hook.
A pre-invoke hook is called directly before the command is A pre-invoke hook is called directly before the command is
@ -359,7 +385,7 @@ class BotBase(GroupMixin):
self._before_invoke = coro self._before_invoke = coro
return coro return coro
def after_invoke(self, coro): def after_invoke(self, coro: CFT) -> CFT:
r"""A decorator that registers a coroutine as a post-invoke hook. r"""A decorator that registers a coroutine as a post-invoke hook.
A post-invoke hook is called directly after the command is A post-invoke hook is called directly after the command is
@ -394,14 +420,14 @@ class BotBase(GroupMixin):
# listener registration # listener registration
def add_listener(self, func, name=None): def add_listener(self, func: CoroFunc, name: str = MISSING) -> None:
"""The non decorator alternative to :meth:`.listen`. """The non decorator alternative to :meth:`.listen`.
Parameters Parameters
----------- -----------
func: :ref:`coroutine <coroutine>` func: :ref:`coroutine <coroutine>`
The function to call. The function to call.
name: Optional[:class:`str`] name: :class:`str`
The name of the event to listen for. Defaults to ``func.__name__``. The name of the event to listen for. Defaults to ``func.__name__``.
Example Example
@ -416,7 +442,7 @@ class BotBase(GroupMixin):
bot.add_listener(my_message, 'on_message') bot.add_listener(my_message, 'on_message')
""" """
name = func.__name__ if name is None else name name = func.__name__ if name is MISSING else name
if not asyncio.iscoroutinefunction(func): if not asyncio.iscoroutinefunction(func):
raise TypeError('Listeners must be coroutines') raise TypeError('Listeners must be coroutines')
@ -426,7 +452,7 @@ class BotBase(GroupMixin):
else: else:
self.extra_events[name] = [func] self.extra_events[name] = [func]
def remove_listener(self, func, name=None): def remove_listener(self, func: CoroFunc, name: str = MISSING) -> None:
"""Removes a listener from the pool of listeners. """Removes a listener from the pool of listeners.
Parameters Parameters
@ -438,7 +464,7 @@ class BotBase(GroupMixin):
``func.__name__``. ``func.__name__``.
""" """
name = func.__name__ if name is None else name name = func.__name__ if name is MISSING else name
if name in self.extra_events: if name in self.extra_events:
try: try:
@ -446,7 +472,7 @@ class BotBase(GroupMixin):
except ValueError: except ValueError:
pass pass
def listen(self, name=None): def listen(self, name: str = MISSING) -> Callable[[CFT], CFT]:
"""A decorator that registers another function as an external """A decorator that registers another function as an external
event listener. Basically this allows you to listen to multiple event listener. Basically this allows you to listen to multiple
events from different places e.g. such as :func:`.on_ready` events from different places e.g. such as :func:`.on_ready`
@ -476,7 +502,7 @@ class BotBase(GroupMixin):
The function being listened to is not a coroutine. The function being listened to is not a coroutine.
""" """
def decorator(func): def decorator(func: CFT) -> CFT:
self.add_listener(func, name) self.add_listener(func, name)
return func return func
@ -528,7 +554,7 @@ class BotBase(GroupMixin):
cog = cog._inject(self) cog = cog._inject(self)
self.__cogs[cog_name] = cog self.__cogs[cog_name] = cog
def get_cog(self, name): def get_cog(self, name: str) -> Optional[Cog]:
"""Gets the cog instance requested. """Gets the cog instance requested.
If the cog is not found, ``None`` is returned instead. If the cog is not found, ``None`` is returned instead.
@ -547,7 +573,7 @@ class BotBase(GroupMixin):
""" """
return self.__cogs.get(name) return self.__cogs.get(name)
def remove_cog(self, name): def remove_cog(self, name: str) -> Optional[Cog]:
"""Removes a cog from the bot and returns it. """Removes a cog from the bot and returns it.
All registered commands and event listeners that the All registered commands and event listeners that the
@ -578,13 +604,13 @@ class BotBase(GroupMixin):
return cog return cog
@property @property
def cogs(self): def cogs(self) -> Mapping[str, Cog]:
"""Mapping[:class:`str`, :class:`Cog`]: A read-only mapping of cog name to cog.""" """Mapping[:class:`str`, :class:`Cog`]: A read-only mapping of cog name to cog."""
return types.MappingProxyType(self.__cogs) return types.MappingProxyType(self.__cogs)
# extensions # extensions
def _remove_module_references(self, name): def _remove_module_references(self, name: str) -> None:
# find all references to the module # find all references to the module
# remove the cogs registered from the module # remove the cogs registered from the module
for cogname, cog in self.__cogs.copy().items(): for cogname, cog in self.__cogs.copy().items():
@ -608,7 +634,7 @@ class BotBase(GroupMixin):
for index in reversed(remove): for index in reversed(remove):
del event_list[index] del event_list[index]
def _call_module_finalizers(self, lib, key): def _call_module_finalizers(self, lib: types.ModuleType, key: str) -> None:
try: try:
func = getattr(lib, 'teardown') func = getattr(lib, 'teardown')
except AttributeError: except AttributeError:
@ -626,12 +652,12 @@ class BotBase(GroupMixin):
if _is_submodule(name, module): if _is_submodule(name, module):
del sys.modules[module] del sys.modules[module]
def _load_from_module_spec(self, spec, key): def _load_from_module_spec(self, spec: importlib.machinery.ModuleSpec, key: str) -> None:
# precondition: key not in self.__extensions # precondition: key not in self.__extensions
lib = importlib.util.module_from_spec(spec) lib = importlib.util.module_from_spec(spec)
sys.modules[key] = lib sys.modules[key] = lib
try: try:
spec.loader.exec_module(lib) spec.loader.exec_module(lib) # type: ignore
except Exception as e: except Exception as e:
del sys.modules[key] del sys.modules[key]
raise errors.ExtensionFailed(key, e) from e raise errors.ExtensionFailed(key, e) from e
@ -652,13 +678,13 @@ class BotBase(GroupMixin):
else: else:
self.__extensions[key] = lib self.__extensions[key] = lib
def _resolve_name(self, name, package): def _resolve_name(self, name: str, package: Optional[str]) -> str:
try: try:
return importlib.util.resolve_name(name, package) return importlib.util.resolve_name(name, package)
except ImportError: except ImportError:
raise errors.ExtensionNotFound(name) raise errors.ExtensionNotFound(name)
def load_extension(self, name, *, package=None): def load_extension(self, name: str, *, package: Optional[str] = None) -> 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
@ -705,7 +731,7 @@ class BotBase(GroupMixin):
self._load_from_module_spec(spec, name) self._load_from_module_spec(spec, name)
def unload_extension(self, name, *, package=None): def unload_extension(self, name: str, *, package: Optional[str] = None) -> 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
@ -746,7 +772,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, *, package=None): def reload_extension(self, name: str, *, package: Optional[str] = None) -> 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
@ -802,7 +828,7 @@ class BotBase(GroupMixin):
# 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.
lib.setup(self) lib.setup(self) # type: ignore
self.__extensions[name] = lib self.__extensions[name] = lib
# revert sys.modules back to normal and raise back to caller # revert sys.modules back to normal and raise back to caller
@ -810,18 +836,18 @@ class BotBase(GroupMixin):
raise raise
@property @property
def extensions(self): def extensions(self) -> Mapping[str, types.ModuleType]:
"""Mapping[:class:`str`, :class:`py:types.ModuleType`]: A read-only mapping of extension name to extension.""" """Mapping[:class:`str`, :class:`py:types.ModuleType`]: A read-only mapping of extension name to extension."""
return types.MappingProxyType(self.__extensions) return types.MappingProxyType(self.__extensions)
# help command stuff # help command stuff
@property @property
def help_command(self): def help_command(self) -> Optional[HelpCommand]:
return self._help_command return self._help_command
@help_command.setter @help_command.setter
def help_command(self, value): def help_command(self, value: Optional[HelpCommand]) -> None:
if value is not None: if value is not None:
if not isinstance(value, HelpCommand): if not isinstance(value, HelpCommand):
raise TypeError('help_command must be a subclass of HelpCommand') raise TypeError('help_command must be a subclass of HelpCommand')
@ -837,7 +863,7 @@ class BotBase(GroupMixin):
# command processing # command processing
async def get_prefix(self, message): async def get_prefix(self, message: Message) -> Union[List[str], str]:
"""|coro| """|coro|
Retrieves the prefix the bot is listening to Retrieves the prefix the bot is listening to
@ -875,7 +901,7 @@ class BotBase(GroupMixin):
return ret return ret
async def get_context(self, message, *, cls=Context): async def get_context(self, message: Message, *, cls: Type[CXT] = Context) -> CXT:
r"""|coro| r"""|coro|
Returns the invocation context from the message. Returns the invocation context from the message.
@ -908,7 +934,7 @@ class BotBase(GroupMixin):
view = StringView(message.content) view = StringView(message.content)
ctx = cls(prefix=None, view=view, bot=self, message=message) ctx = cls(prefix=None, view=view, bot=self, message=message)
if message.author.id == self.user.id: if message.author.id == self.user.id: # type: ignore
return ctx return ctx
prefix = await self.get_prefix(message) prefix = await self.get_prefix(message)
@ -945,11 +971,12 @@ class BotBase(GroupMixin):
invoker = view.get_word() invoker = view.get_word()
ctx.invoked_with = invoker ctx.invoked_with = invoker
ctx.prefix = invoked_prefix # type-checker fails to narrow invoked_prefix type.
ctx.prefix = invoked_prefix # type: ignore
ctx.command = self.all_commands.get(invoker) ctx.command = self.all_commands.get(invoker)
return ctx return ctx
async def invoke(self, ctx): async def invoke(self, ctx: Context) -> None:
"""|coro| """|coro|
Invokes the command given under the invocation context and Invokes the command given under the invocation context and
@ -975,7 +1002,7 @@ class BotBase(GroupMixin):
exc = errors.CommandNotFound(f'Command "{ctx.invoked_with}" is not found') exc = errors.CommandNotFound(f'Command "{ctx.invoked_with}" is not found')
self.dispatch('command_error', ctx, exc) self.dispatch('command_error', ctx, exc)
async def process_commands(self, message): async def process_commands(self, message: Message) -> None:
"""|coro| """|coro|
This function processes the commands that have been registered This function processes the commands that have been registered

View File

@ -21,15 +21,30 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
""" """
from __future__ import annotations
import inspect import inspect
import discord.utils
from typing import Any, Callable, ClassVar, Dict, Generator, List, Optional, TYPE_CHECKING, Tuple, TypeVar, Type
from ._types import _BaseCommand from ._types import _BaseCommand
if TYPE_CHECKING:
from .bot import BotBase
from .context import Context
from .core import Command
__all__ = ( __all__ = (
'CogMeta', 'CogMeta',
'Cog', 'Cog',
) )
CogT = TypeVar('CogT', bound='Cog')
FuncT = TypeVar('FuncT', bound=Callable[..., Any])
MISSING: Any = discord.utils.MISSING
class CogMeta(type): class CogMeta(type):
"""A metaclass for defining a cog. """A metaclass for defining a cog.
@ -89,8 +104,12 @@ class CogMeta(type):
async def bar(self, ctx): async def bar(self, ctx):
pass # hidden -> False pass # hidden -> False
""" """
__cog_name__: str
__cog_settings__: Dict[str, Any]
__cog_commands__: List[Command]
__cog_listeners__: List[Tuple[str, str]]
def __new__(cls, *args, **kwargs): def __new__(cls: Type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta:
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__'] = kwargs.pop('command_attrs', {}) attrs['__cog_settings__'] = kwargs.pop('command_attrs', {})
@ -143,14 +162,14 @@ class CogMeta(type):
new_cls.__cog_listeners__ = listeners_as_list new_cls.__cog_listeners__ = listeners_as_list
return new_cls return new_cls
def __init__(self, *args, **kwargs): def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args) super().__init__(*args)
@classmethod @classmethod
def qualified_name(cls): def qualified_name(cls) -> str:
return cls.__cog_name__ return cls.__cog_name__
def _cog_special_method(func): def _cog_special_method(func: FuncT) -> FuncT:
func.__cog_special_method__ = None func.__cog_special_method__ = None
return func return func
@ -164,8 +183,12 @@ class Cog(metaclass=CogMeta):
When inheriting from this class, the options shown in :class:`CogMeta` When inheriting from this class, the options shown in :class:`CogMeta`
are equally valid here. are equally valid here.
""" """
__cog_name__: ClassVar[str]
__cog_settings__: ClassVar[Dict[str, Any]]
__cog_commands__: ClassVar[List[Command]]
__cog_listeners__: ClassVar[List[Tuple[str, str]]]
def __new__(cls, *args, **kwargs): def __new__(cls: Type[CogT], *args: Any, **kwargs: Any) -> CogT:
# For issue 426, we need to store a copy of the command objects # For issue 426, we need to store a copy of the command objects
# since we modify them to inject `self` to them. # since we modify them to inject `self` to them.
# To do this, we need to interfere with the Cog creation process. # To do this, we need to interfere with the Cog creation process.
@ -173,7 +196,8 @@ class Cog(metaclass=CogMeta):
cmd_attrs = cls.__cog_settings__ cmd_attrs = cls.__cog_settings__
# Either update the command with the cog provided defaults or copy it. # Either update the command with the cog provided defaults or copy it.
self.__cog_commands__ = tuple(c._update_copy(cmd_attrs) for c in cls.__cog_commands__) # r.e type ignore, type-checker complains about overriding a ClassVar
self.__cog_commands__ = tuple(c._update_copy(cmd_attrs) for c in cls.__cog_commands__) # type: ignore
lookup = { lookup = {
cmd.qualified_name: cmd cmd.qualified_name: cmd
@ -186,15 +210,15 @@ class Cog(metaclass=CogMeta):
parent = command.parent parent = command.parent
if parent is not None: if parent is not None:
# Get the latest parent reference # Get the latest parent reference
parent = lookup[parent.qualified_name] parent = lookup[parent.qualified_name] # type: ignore
# Update our parent's reference to our self # Update our parent's reference to our self
parent.remove_command(command.name) parent.remove_command(command.name) # type: ignore
parent.add_command(command) parent.add_command(command) # type: ignore
return self return self
def get_commands(self): def get_commands(self) -> List[Command]:
r""" r"""
Returns Returns
-------- --------
@ -209,20 +233,20 @@ class Cog(metaclass=CogMeta):
return [c for c in self.__cog_commands__ if c.parent is None] return [c for c in self.__cog_commands__ if c.parent is None]
@property @property
def qualified_name(self): def qualified_name(self) -> str:
""":class:`str`: Returns the cog's specified name, not the class name.""" """:class:`str`: Returns the cog's specified name, not the class name."""
return self.__cog_name__ return self.__cog_name__
@property @property
def description(self): def description(self) -> str:
""":class:`str`: Returns the cog's description, typically the cleaned docstring.""" """:class:`str`: Returns the cog's description, typically the cleaned docstring."""
return self.__cog_description__ return self.__cog_description__
@description.setter @description.setter
def description(self, description): def description(self, description: str) -> None:
self.__cog_description__ = description self.__cog_description__ = description
def walk_commands(self): def walk_commands(self) -> Generator[Command, None, None]:
"""An iterator that recursively walks through this cog's commands and subcommands. """An iterator that recursively walks through this cog's commands and subcommands.
Yields Yields
@ -237,7 +261,7 @@ class Cog(metaclass=CogMeta):
if isinstance(command, GroupMixin): if isinstance(command, GroupMixin):
yield from command.walk_commands() yield from command.walk_commands()
def get_listeners(self): def get_listeners(self) -> List[Tuple[str, Callable[..., Any]]]:
"""Returns a :class:`list` of (name, function) listener pairs that are defined in this cog. """Returns a :class:`list` of (name, function) listener pairs that are defined in this cog.
Returns Returns
@ -248,12 +272,12 @@ class Cog(metaclass=CogMeta):
return [(name, getattr(self, method_name)) for name, method_name in self.__cog_listeners__] return [(name, getattr(self, method_name)) for name, method_name in self.__cog_listeners__]
@classmethod @classmethod
def _get_overridden_method(cls, method): def _get_overridden_method(cls, method: FuncT) -> Optional[FuncT]:
"""Return None if the method is not overridden. Otherwise returns the overridden method.""" """Return None if the method is not overridden. Otherwise returns the overridden method."""
return getattr(method.__func__, '__cog_special_method__', method) return getattr(method.__func__, '__cog_special_method__', method)
@classmethod @classmethod
def listener(cls, name=None): def listener(cls, name: str = MISSING) -> Callable[[FuncT], FuncT]:
"""A decorator that marks a function as a listener. """A decorator that marks a function as a listener.
This is the cog equivalent of :meth:`.Bot.listen`. This is the cog equivalent of :meth:`.Bot.listen`.
@ -271,10 +295,10 @@ class Cog(metaclass=CogMeta):
the name. the name.
""" """
if name is not None and not isinstance(name, str): if name is not MISSING and not isinstance(name, str):
raise TypeError(f'Cog.listener expected str but received {name.__class__.__name__!r} instead.') raise TypeError(f'Cog.listener expected str but received {name.__class__.__name__!r} instead.')
def decorator(func): def decorator(func: FuncT) -> FuncT:
actual = func actual = func
if isinstance(actual, staticmethod): if isinstance(actual, staticmethod):
actual = actual.__func__ actual = actual.__func__
@ -293,7 +317,7 @@ class Cog(metaclass=CogMeta):
return func return func
return decorator return decorator
def has_error_handler(self): def has_error_handler(self) -> bool:
""":class:`bool`: Checks whether the cog has an error handler. """:class:`bool`: Checks whether the cog has an error handler.
.. versionadded:: 1.7 .. versionadded:: 1.7
@ -301,7 +325,7 @@ class Cog(metaclass=CogMeta):
return not hasattr(self.cog_command_error.__func__, '__cog_special_method__') 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) -> None:
"""A special method that is called when the cog gets removed. """A special method that is called when the cog gets removed.
This function **cannot** be a coroutine. It must be a regular This function **cannot** be a coroutine. It must be a regular
@ -312,7 +336,7 @@ class Cog(metaclass=CogMeta):
pass pass
@_cog_special_method @_cog_special_method
def bot_check_once(self, ctx): def bot_check_once(self, ctx: Context) -> bool:
"""A special method that registers as a :meth:`.Bot.check_once` """A special method that registers as a :meth:`.Bot.check_once`
check. check.
@ -322,7 +346,7 @@ class Cog(metaclass=CogMeta):
return True return True
@_cog_special_method @_cog_special_method
def bot_check(self, ctx): def bot_check(self, ctx: Context) -> bool:
"""A special method that registers as a :meth:`.Bot.check` """A special method that registers as a :meth:`.Bot.check`
check. check.
@ -332,7 +356,7 @@ class Cog(metaclass=CogMeta):
return True return True
@_cog_special_method @_cog_special_method
def cog_check(self, ctx): def cog_check(self, ctx: Context) -> bool:
"""A special method that registers as a :func:`~discord.ext.commands.check` """A special method that registers as a :func:`~discord.ext.commands.check`
for every command and subcommand in this cog. for every command and subcommand in this cog.
@ -342,7 +366,7 @@ class Cog(metaclass=CogMeta):
return True return True
@_cog_special_method @_cog_special_method
async def cog_command_error(self, ctx, error): async def cog_command_error(self, ctx: Context, error: Exception) -> None:
"""A special method that is called whenever an error """A special method that is called whenever an error
is dispatched inside this cog. is dispatched inside this cog.
@ -361,7 +385,7 @@ class Cog(metaclass=CogMeta):
pass pass
@_cog_special_method @_cog_special_method
async def cog_before_invoke(self, ctx): async def cog_before_invoke(self, ctx: Context) -> None:
"""A special method that acts as a cog local pre-invoke hook. """A special method that acts as a cog local pre-invoke hook.
This is similar to :meth:`.Command.before_invoke`. This is similar to :meth:`.Command.before_invoke`.
@ -376,7 +400,7 @@ class Cog(metaclass=CogMeta):
pass pass
@_cog_special_method @_cog_special_method
async def cog_after_invoke(self, ctx): async def cog_after_invoke(self, ctx: Context) -> None:
"""A special method that acts as a cog local post-invoke hook. """A special method that acts as a cog local post-invoke hook.
This is similar to :meth:`.Command.after_invoke`. This is similar to :meth:`.Command.after_invoke`.
@ -390,7 +414,7 @@ class Cog(metaclass=CogMeta):
""" """
pass pass
def _inject(self, bot): def _inject(self: CogT, bot: BotBase) -> CogT:
cls = self.__class__ cls = self.__class__
# realistically, the only thing that can cause loading errors # realistically, the only thing that can cause loading errors
@ -425,7 +449,7 @@ class Cog(metaclass=CogMeta):
return self return self
def _eject(self, bot): def _eject(self, bot: BotBase) -> None:
cls = self.__class__ cls = self.__class__
try: try:

View File

@ -21,16 +21,52 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
""" """
from __future__ import annotations
import inspect
import re
from typing import Any, Dict, Generic, List, Optional, TYPE_CHECKING, TypeVar, Union
import discord.abc import discord.abc
import discord.utils import discord.utils
import re
from discord.message import Message
if TYPE_CHECKING:
from typing_extensions import ParamSpec
from discord.abc import MessageableChannel
from discord.guild import Guild
from discord.member import Member
from discord.state import ConnectionState
from discord.user import ClientUser, User
from discord.voice_client import VoiceProtocol
from .bot import Bot, AutoShardedBot
from .cog import Cog
from .core import Command
from .help import HelpCommand
from .view import StringView
__all__ = ( __all__ = (
'Context', 'Context',
) )
class Context(discord.abc.Messageable): MISSING: Any = discord.utils.MISSING
T = TypeVar('T')
BotT = TypeVar('BotT', bound="Union[Bot, AutoShardedBot]")
CogT = TypeVar('CogT', bound="Cog")
if TYPE_CHECKING:
P = ParamSpec('P')
else:
P = TypeVar('P')
class Context(discord.abc.Messageable, Generic[BotT]):
r"""Represents the context in which a command is being invoked under. r"""Represents the context in which a command is being invoked under.
This class contains a lot of meta data to help you understand more about This class contains a lot of meta data to help you understand more about
@ -58,11 +94,11 @@ class Context(discord.abc.Messageable):
This is only of use for within converters. This is only of use for within converters.
.. versionadded:: 2.0 .. versionadded:: 2.0
prefix: :class:`str` prefix: Optional[:class:`str`]
The prefix that was used to invoke the command. The prefix that was used to invoke the command.
command: :class:`Command` command: Optional[:class:`Command`]
The command that is being invoked currently. The command that is being invoked currently.
invoked_with: :class:`str` invoked_with: Optional[: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`] invoked_parents: List[:class:`str`]
@ -73,7 +109,7 @@ class Context(discord.abc.Messageable):
.. versionadded:: 1.7 .. versionadded:: 1.7
invoked_subcommand: :class:`Command` invoked_subcommand: Optional[: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``.
subcommand_passed: Optional[:class:`str`] subcommand_passed: Optional[:class:`str`]
@ -86,23 +122,38 @@ class Context(discord.abc.Messageable):
or invoked. or invoked.
""" """
def __init__(self, **attrs): def __init__(self,
self.message = attrs.pop('message', None) *,
self.bot = attrs.pop('bot', None) message: Message,
self.args = attrs.pop('args', []) bot: BotT,
self.kwargs = attrs.pop('kwargs', {}) view: StringView,
self.prefix = attrs.pop('prefix') args: List[Any] = MISSING,
self.command = attrs.pop('command', None) kwargs: Dict[str, Any] = MISSING,
self.view = attrs.pop('view', None) prefix: Optional[str] = None,
self.invoked_with = attrs.pop('invoked_with', None) command: Optional[Command] = None,
self.invoked_parents = attrs.pop('invoked_parents', []) invoked_with: Optional[str] = None,
self.invoked_subcommand = attrs.pop('invoked_subcommand', None) invoked_parents: List[str] = MISSING,
self.subcommand_passed = attrs.pop('subcommand_passed', None) invoked_subcommand: Optional[Command] = None,
self.command_failed = attrs.pop('command_failed', False) subcommand_passed: Optional[str] = None,
self.current_parameter = attrs.pop('current_parameter', None) command_failed: bool = False,
self._state = self.message._state current_parameter: Optional[inspect.Parameter] = None,
):
self.message: Message = message
self.bot: BotT = bot
self.args: List[Any] = args or []
self.kwargs: Dict[str, Any] = kwargs or {}
self.prefix: Optional[str] = prefix
self.command: Optional[Command] = command
self.view: StringView = view
self.invoked_with: Optional[str] = invoked_with
self.invoked_parents: List[str] = invoked_parents or []
self.invoked_subcommand: Optional[Command] = invoked_subcommand
self.subcommand_passed: Optional[str] = subcommand_passed
self.command_failed: bool = command_failed
self.current_parameter: Optional[inspect.Parameter] = current_parameter
self._state: ConnectionState = self.message._state
async def invoke(self, command, /, *args, **kwargs): async def invoke(self, command: Command[CogT, P, T], /, *args: P.args, **kwargs: P.kwargs) -> T:
r"""|coro| r"""|coro|
Calls a command with the arguments given. Calls a command with the arguments given.
@ -124,7 +175,7 @@ class Context(discord.abc.Messageable):
command: :class:`.Command` command: :class:`.Command`
The command that is going to be called. The command that is going to be called.
\*args \*args
The arguments to to use. The arguments to use.
\*\*kwargs \*\*kwargs
The keyword arguments to use. The keyword arguments to use.
@ -133,17 +184,9 @@ class Context(discord.abc.Messageable):
TypeError TypeError
The command argument to invoke is missing. The command argument to invoke is missing.
""" """
arguments = [] return await command(self, *args, **kwargs)
if command.cog is not None:
arguments.append(command.cog)
arguments.append(self) async def reinvoke(self, *, call_hooks: bool = False, restart: bool = True) -> None:
arguments.extend(args)
ret = await command.callback(*arguments, **kwargs)
return ret
async def reinvoke(self, *, call_hooks: bool = False, restart: bool = True):
"""|coro| """|coro|
Calls the command again. Calls the command again.
@ -187,7 +230,7 @@ class Context(discord.abc.Messageable):
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 or '')
view.previous = 0 view.previous = 0
self.invoked_parents = [] self.invoked_parents = []
self.invoked_with = view.get_word() # advance to get the root command self.invoked_with = view.get_word() # advance to get the root command
@ -206,20 +249,23 @@ class Context(discord.abc.Messageable):
self.subcommand_passed = subcommand_passed self.subcommand_passed = subcommand_passed
@property @property
def valid(self): def valid(self) -> bool:
""":class:`bool`: Checks if the invocation context is valid to be invoked with.""" """:class:`bool`: Checks if the invocation context is valid to be invoked with."""
return self.prefix is not None and self.command is not None return self.prefix is not None and self.command is not None
async def _get_channel(self): async def _get_channel(self) -> discord.abc.Messageable:
return self.channel return self.channel
@property @property
def clean_prefix(self): def clean_prefix(self) -> str:
""":class:`str`: The cleaned up invoke prefix. i.e. mentions are ``@name`` instead of ``<@id>``. """:class:`str`: The cleaned up invoke prefix. i.e. mentions are ``@name`` instead of ``<@id>``.
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
user = self.guild.me if self.guild else self.bot.user if self.prefix is None:
return ''
user = self.me
# this breaks if the prefix mention is not the bot itself but I # this breaks if the prefix mention is not the bot itself but I
# consider this to be an *incredibly* strange use case. I'd rather go # consider this to be an *incredibly* strange use case. I'd rather go
# for this common use case rather than waste performance for the # for this common use case rather than waste performance for the
@ -228,7 +274,7 @@ class Context(discord.abc.Messageable):
return pattern.sub("@%s" % user.display_name.replace('\\', r'\\'), self.prefix) return pattern.sub("@%s" % user.display_name.replace('\\', r'\\'), self.prefix)
@property @property
def cog(self): def cog(self) -> Optional[Cog]:
"""Optional[: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:
@ -236,38 +282,39 @@ class Context(discord.abc.Messageable):
return self.command.cog return self.command.cog
@discord.utils.cached_property @discord.utils.cached_property
def guild(self): def guild(self) -> Optional[Guild]:
"""Optional[:class:`.Guild`]: Returns the guild associated with this context's command. None if not available.""" """Optional[:class:`.Guild`]: Returns the guild associated with this context's command. None if not available."""
return self.message.guild return self.message.guild
@discord.utils.cached_property @discord.utils.cached_property
def channel(self): def channel(self) -> MessageableChannel:
"""Union[:class:`.abc.Messageable`]: Returns the channel associated with this context's command. """Union[:class:`.abc.Messageable`]: 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
@discord.utils.cached_property @discord.utils.cached_property
def author(self): def author(self) -> Union[User, Member]:
"""Union[:class:`~discord.User`, :class:`.Member`]: """Union[:class:`~discord.User`, :class:`.Member`]:
Returns the author associated with this context's command. Shorthand for :attr:`.Message.author` Returns the author associated with this context's command. Shorthand for :attr:`.Message.author`
""" """
return self.message.author return self.message.author
@discord.utils.cached_property @discord.utils.cached_property
def me(self): def me(self) -> Union[Member, ClientUser]:
"""Union[:class:`.Member`, :class:`.ClientUser`]: """Union[:class:`.Member`, :class:`.ClientUser`]:
Similar to :attr:`.Guild.me` except it may return the :class:`.ClientUser` in private message contexts. Similar to :attr:`.Guild.me` except it may return the :class:`.ClientUser` in private message contexts.
""" """
return self.guild.me if self.guild is not None else self.bot.user # bot.user will never be None at this point.
return self.guild.me if self.guild is not None else self.bot.user # type: ignore
@property @property
def voice_client(self): def voice_client(self) -> Optional[VoiceProtocol]:
r"""Optional[:class:`.VoiceProtocol`]: A shortcut to :attr:`.Guild.voice_client`\, if applicable.""" r"""Optional[:class:`.VoiceProtocol`]: A shortcut to :attr:`.Guild.voice_client`\, if applicable."""
g = self.guild g = self.guild
return g.voice_client if g else None return g.voice_client if g else None
async def send_help(self, *args): async def send_help(self, *args: Any) -> Any:
"""send_help(entity=<bot>) """send_help(entity=<bot>)
|coro| |coro|
@ -319,12 +366,12 @@ class Context(discord.abc.Messageable):
return None return None
entity = args[0] entity = args[0]
if entity is None:
return None
if isinstance(entity, str): if isinstance(entity, str):
entity = bot.get_cog(entity) or bot.get_command(entity) entity = bot.get_cog(entity) or bot.get_command(entity)
if entity is None:
return None
try: try:
entity.qualified_name entity.qualified_name
except AttributeError: except AttributeError:
@ -348,6 +395,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) @discord.utils.copy_doc(Message.reply)
async def reply(self, content=None, **kwargs): async def reply(self, content: Optional[str] = None, **kwargs: Any) -> Message:
return await self.message.reply(content, **kwargs) return await self.message.reply(content, **kwargs)

File diff suppressed because it is too large Load Diff

View File

@ -330,7 +330,7 @@ class ChannelNotReadable(BadArgument):
Attributes Attributes
----------- -----------
argument: Union[:class:`.abc.GuildChannel`, :class:`Thread`] argument: Union[:class:`.abc.GuildChannel`, :class:`.Thread`]
The channel supplied by the caller that was not readable The channel supplied by the caller that was not readable
""" """
def __init__(self, argument: Union[GuildChannel, Thread]) -> None: def __init__(self, argument: Union[GuildChannel, Thread]) -> None:
@ -645,7 +645,7 @@ class NSFWChannelRequired(CheckFailure):
Parameters Parameters
----------- -----------
channel: Union[:class:`.abc.GuildChannel`, :class:`Thread`] channel: Union[:class:`.abc.GuildChannel`, :class:`.Thread`]
The channel that does not have NSFW enabled. The channel that does not have NSFW enabled.
""" """
def __init__(self, channel: Union[GuildChannel, Thread]) -> None: def __init__(self, channel: Union[GuildChannel, Thread]) -> None:

View File

@ -27,11 +27,17 @@ import copy
import functools import functools
import inspect import inspect
import re import re
from typing import Optional, TYPE_CHECKING
import discord.utils import discord.utils
from .core import Group, Command from .core import Group, Command
from .errors import CommandError from .errors import CommandError
if TYPE_CHECKING:
from .context import Context
__all__ = ( __all__ = (
'Paginator', 'Paginator',
'HelpCommand', 'HelpCommand',
@ -320,7 +326,7 @@ class HelpCommand:
self.command_attrs = attrs = options.pop('command_attrs', {}) self.command_attrs = attrs = options.pop('command_attrs', {})
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: Context = discord.utils.MISSING
self._command_impl = _HelpCommandImpl(self, **self.command_attrs) self._command_impl = _HelpCommandImpl(self, **self.command_attrs)
def copy(self): def copy(self):

View File

@ -36,13 +36,11 @@ from typing import (
Type, Type,
TypeVar, TypeVar,
Union, Union,
cast,
) )
import aiohttp import aiohttp
import discord import discord
import inspect import inspect
import logging
import sys import sys
import traceback import traceback
@ -50,8 +48,6 @@ from collections.abc import Sequence
from discord.backoff import ExponentialBackoff from discord.backoff import ExponentialBackoff
from discord.utils import MISSING from discord.utils import MISSING
log = logging.getLogger(__name__)
__all__ = ( __all__ = (
'loop', 'loop',
) )
@ -61,7 +57,6 @@ _func = Callable[..., Awaitable[Any]]
LF = TypeVar('LF', bound=_func) LF = TypeVar('LF', bound=_func)
FT = TypeVar('FT', bound=_func) FT = TypeVar('FT', bound=_func)
ET = TypeVar('ET', bound=Callable[[Any, BaseException], Awaitable[Any]]) ET = TypeVar('ET', bound=Callable[[Any, BaseException], Awaitable[Any]])
LT = TypeVar('LT', bound='Loop')
class SleepHandle: class SleepHandle:
@ -78,7 +73,7 @@ class SleepHandle:
relative_delta = discord.utils.compute_timedelta(dt) relative_delta = discord.utils.compute_timedelta(dt)
self.handle = self.loop.call_later(relative_delta, self.future.set_result, True) self.handle = self.loop.call_later(relative_delta, self.future.set_result, True)
def wait(self) -> asyncio.Future: def wait(self) -> asyncio.Future[Any]:
return self.future return self.future
def done(self) -> bool: def done(self) -> bool:
@ -94,7 +89,9 @@ class Loop(Generic[LF]):
The main interface to create this is through :func:`loop`. The main interface to create this is through :func:`loop`.
""" """
def __init__(self,
def __init__(
self,
coro: LF, coro: LF,
seconds: float, seconds: float,
hours: float, hours: float,
@ -102,15 +99,15 @@ class Loop(Generic[LF]):
time: Union[datetime.time, Sequence[datetime.time]], time: Union[datetime.time, Sequence[datetime.time]],
count: Optional[int], count: Optional[int],
reconnect: bool, reconnect: bool,
loop: Optional[asyncio.AbstractEventLoop], loop: asyncio.AbstractEventLoop,
) -> None: ) -> None:
self.coro: LF = coro self.coro: LF = coro
self.reconnect: bool = reconnect self.reconnect: bool = reconnect
self.loop: Optional[asyncio.AbstractEventLoop] = loop self.loop: asyncio.AbstractEventLoop = loop
self.count: Optional[int] = count self.count: Optional[int] = count
self._current_loop = 0 self._current_loop = 0
self._handle = None self._handle: SleepHandle = MISSING
self._task = None self._task: asyncio.Task[None] = MISSING
self._injected = None self._injected = None
self._valid_exception = ( self._valid_exception = (
OSError, OSError,
@ -131,7 +128,7 @@ class Loop(Generic[LF]):
self.change_interval(seconds=seconds, minutes=minutes, hours=hours, time=time) self.change_interval(seconds=seconds, minutes=minutes, hours=hours, time=time)
self._last_iteration_failed = False self._last_iteration_failed = False
self._last_iteration = None self._last_iteration: datetime.datetime = MISSING
self._next_iteration = None self._next_iteration = None
if not inspect.iscoroutinefunction(self.coro): if not inspect.iscoroutinefunction(self.coro):
@ -147,9 +144,8 @@ class Loop(Generic[LF]):
else: else:
await coro(*args, **kwargs) await coro(*args, **kwargs)
def _try_sleep_until(self, dt: datetime.datetime): def _try_sleep_until(self, dt: datetime.datetime):
self._handle = SleepHandle(dt=dt, loop=self.loop) # type: ignore self._handle = SleepHandle(dt=dt, loop=self.loop)
return self._handle.wait() return self._handle.wait()
async def _loop(self, *args: Any, **kwargs: Any) -> None: async def _loop(self, *args: Any, **kwargs: Any) -> None:
@ -211,7 +207,7 @@ class Loop(Generic[LF]):
if obj is None: if obj is None:
return self return self
copy = Loop( copy: Loop[LF] = Loop(
self.coro, self.coro,
seconds=self._seconds, seconds=self._seconds,
hours=self._hours, hours=self._hours,
@ -279,7 +275,7 @@ class Loop(Generic[LF]):
.. versionadded:: 1.3 .. versionadded:: 1.3
""" """
if self._task is None: if self._task is MISSING:
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
@ -305,7 +301,7 @@ class Loop(Generic[LF]):
return await self.coro(*args, **kwargs) return await self.coro(*args, **kwargs)
def start(self, *args: Any, **kwargs: Any) -> asyncio.Task: def start(self, *args: Any, **kwargs: Any) -> asyncio.Task[None]:
r"""Starts the internal task in the event loop. r"""Starts the internal task in the event loop.
Parameters Parameters
@ -326,13 +322,13 @@ class Loop(Generic[LF]):
The task that has been created. The task that has been created.
""" """
if self._task is not None and not self._task.done(): if self._task is not MISSING and not self._task.done():
raise RuntimeError('Task is already launched and is not completed.') raise RuntimeError('Task is already launched and is not completed.')
if self._injected is not None: if self._injected is not None:
args = (self._injected, *args) args = (self._injected, *args)
if self.loop is None: if self.loop is MISSING:
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self._task = self.loop.create_task(self._loop(*args, **kwargs)) self._task = self.loop.create_task(self._loop(*args, **kwargs))
@ -356,7 +352,7 @@ class Loop(Generic[LF]):
.. versionadded:: 1.2 .. versionadded:: 1.2
""" """
if self._task and not self._task.done(): if self._task is not MISSING and not self._task.done():
self._stop_next_iteration = True self._stop_next_iteration = True
def _can_be_cancelled(self) -> bool: def _can_be_cancelled(self) -> bool:
@ -383,7 +379,7 @@ class Loop(Generic[LF]):
The keyword arguments to use. The keyword arguments to use.
""" """
def restart_when_over(fut, *, args=args, kwargs=kwargs): def restart_when_over(fut: Any, *, args: Any = args, kwargs: Any = kwargs) -> None:
self._task.remove_done_callback(restart_when_over) self._task.remove_done_callback(restart_when_over)
self.start(*args, **kwargs) self.start(*args, **kwargs)
@ -446,9 +442,9 @@ class Loop(Generic[LF]):
self._valid_exception = tuple(x for x in self._valid_exception if x not in exceptions) self._valid_exception = tuple(x for x in self._valid_exception if x not in exceptions)
return len(self._valid_exception) == old_length - len(exceptions) return len(self._valid_exception) == old_length - len(exceptions)
def get_task(self) -> Optional[asyncio.Task]: def get_task(self) -> Optional[asyncio.Task[None]]:
"""Optional[:class:`asyncio.Task`]: Fetches the internal task or ``None`` if there isn't one running.""" """Optional[:class:`asyncio.Task`]: Fetches the internal task or ``None`` if there isn't one running."""
return self._task return self._task if self._task is not MISSING else None
def is_being_cancelled(self) -> bool: def is_being_cancelled(self) -> bool:
"""Whether the task is being cancelled.""" """Whether the task is being cancelled."""
@ -466,7 +462,7 @@ class Loop(Generic[LF]):
.. versionadded:: 1.4 .. versionadded:: 1.4
""" """
return not bool(self._task.done()) if self._task else False return not bool(self._task.done()) if self._task is not MISSING else False
async def _error(self, *args: Any) -> None: async def _error(self, *args: Any) -> None:
exception: Exception = args[-1] exception: Exception = args[-1]
@ -560,7 +556,9 @@ class Loop(Generic[LF]):
self._time_index = 0 self._time_index = 0
if self._current_loop == 0: if self._current_loop == 0:
# if we're at the last index on the first iteration, we need to sleep until tomorrow # if we're at the last index on the first iteration, we need to sleep until tomorrow
return datetime.datetime.combine(datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1), self._time[0]) return datetime.datetime.combine(
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1), self._time[0]
)
next_time = self._time[self._time_index] next_time = self._time[self._time_index]
@ -568,7 +566,7 @@ class Loop(Generic[LF]):
self._time_index += 1 self._time_index += 1
return datetime.datetime.combine(datetime.datetime.now(datetime.timezone.utc), next_time) return datetime.datetime.combine(datetime.datetime.now(datetime.timezone.utc), next_time)
next_date = cast(datetime.datetime, self._last_iteration) next_date = self._last_iteration
if self._time_index == 0: if self._time_index == 0:
# we can assume that the earliest time should be scheduled for "tomorrow" # we can assume that the earliest time should be scheduled for "tomorrow"
next_date += datetime.timedelta(days=1) next_date += datetime.timedelta(days=1)
@ -576,12 +574,14 @@ class Loop(Generic[LF]):
self._time_index += 1 self._time_index += 1
return datetime.datetime.combine(next_date, next_time) return datetime.datetime.combine(next_date, next_time)
def _prepare_time_index(self, now: Optional[datetime.datetime] = None) -> None: def _prepare_time_index(self, now: datetime.datetime = MISSING) -> None:
# now kwarg should be a datetime.datetime representing the time "now" # now kwarg should be a datetime.datetime representing the time "now"
# to calculate the next time index from # to calculate the next time index from
# pre-condition: self._time is set # pre-condition: self._time is set
time_now = (now or datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)).timetz() time_now = (
now if now is not MISSING else datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)
).timetz()
for idx, time in enumerate(self._time): for idx, time in enumerate(self._time):
if time >= time_now: if time >= time_now:
self._time_index = idx self._time_index = idx
@ -597,17 +597,21 @@ class Loop(Generic[LF]):
utc: datetime.timezone = datetime.timezone.utc, utc: datetime.timezone = datetime.timezone.utc,
) -> List[datetime.time]: ) -> List[datetime.time]:
if isinstance(time, dt): if isinstance(time, dt):
ret = time if time.tzinfo is not None else time.replace(tzinfo=utc) inner = time if time.tzinfo is not None else time.replace(tzinfo=utc)
return [ret] return [inner]
if not isinstance(time, Sequence): if not isinstance(time, Sequence):
raise TypeError(f'Expected datetime.time or a sequence of datetime.time for ``time``, received {type(time)!r} instead.') raise TypeError(
f'Expected datetime.time or a sequence of datetime.time for ``time``, received {type(time)!r} instead.'
)
if not time: if not time:
raise ValueError('time parameter must not be an empty sequence.') raise ValueError('time parameter must not be an empty sequence.')
ret = [] ret: List[datetime.time] = []
for index, t in enumerate(time): for index, t in enumerate(time):
if not isinstance(t, dt): if not isinstance(t, dt):
raise TypeError(f'Expected a sequence of {dt!r} for ``time``, received {type(t).__name__!r} at index {index} instead.') raise TypeError(
f'Expected a sequence of {dt!r} for ``time``, received {type(t).__name__!r} at index {index} instead.'
)
ret.append(t if t.tzinfo is not None else t.replace(tzinfo=utc)) ret.append(t if t.tzinfo is not None else t.replace(tzinfo=utc))
ret = sorted(set(ret)) # de-dupe and sort times ret = sorted(set(ret)) # de-dupe and sort times
@ -691,7 +695,7 @@ def loop(
time: Union[datetime.time, Sequence[datetime.time]] = MISSING, time: Union[datetime.time, Sequence[datetime.time]] = MISSING,
count: Optional[int] = None, count: Optional[int] = None,
reconnect: bool = True, reconnect: bool = True,
loop: Optional[asyncio.AbstractEventLoop] = None, loop: asyncio.AbstractEventLoop = MISSING,
) -> Callable[[LF], Loop[LF]]: ) -> Callable[[LF], Loop[LF]]:
"""A decorator that schedules a task in the background for you with """A decorator that schedules a task in the background for you with
optional reconnect logic. The decorator returns a :class:`Loop`. optional reconnect logic. The decorator returns a :class:`Loop`.
@ -724,7 +728,7 @@ def loop(
Whether to handle errors and restart the task Whether to handle errors and restart the task
using an exponential back-off algorithm similar to the using an exponential back-off algorithm similar to the
one used in :meth:`discord.Client.connect`. one used in :meth:`discord.Client.connect`.
loop: Optional[:class:`asyncio.AbstractEventLoop`] loop: :class:`asyncio.AbstractEventLoop`
The loop to use to register the task, if not given The loop to use to register the task, if not given
defaults to :func:`asyncio.get_event_loop`. defaults to :func:`asyncio.get_event_loop`.
@ -736,15 +740,17 @@ def loop(
The function was not a coroutine, an invalid value for the ``time`` parameter was passed, The function was not a coroutine, an invalid value for the ``time`` parameter was passed,
or ``time`` parameter was passed in conjunction with relative time parameters. or ``time`` parameter was passed in conjunction with relative time parameters.
""" """
def decorator(func: LF) -> Loop[LF]: def decorator(func: LF) -> Loop[LF]:
kwargs = { return Loop[LF](
'seconds': seconds, func,
'minutes': minutes, seconds=seconds,
'hours': hours, minutes=minutes,
'count': count, hours=hours,
'time': time, count=count,
'reconnect': reconnect, time=time,
'loop': loop, reconnect=reconnect,
} loop=loop,
return Loop(func, **kwargs) )
return decorator return decorator

View File

@ -480,16 +480,6 @@ class Intents(BaseFlags):
self.value = self.DEFAULT_VALUE self.value = self.DEFAULT_VALUE
return self return self
@classmethod
def default(cls: Type[Intents]) -> Intents:
"""A factory method that creates a :class:`Intents` with everything enabled
except :attr:`presences` and :attr:`members`.
"""
self = cls.all()
self.presences = False
self.members = False
return self
@flag_value @flag_value
def guilds(self): def guilds(self):
""":class:`bool`: Whether guild related events are enabled. """:class:`bool`: Whether guild related events are enabled.
@ -650,6 +640,10 @@ class Intents(BaseFlags):
- :attr:`VoiceChannel.members` - :attr:`VoiceChannel.members`
- :attr:`VoiceChannel.voice_states` - :attr:`VoiceChannel.voice_states`
- :attr:`Member.voice` - :attr:`Member.voice`
.. note::
This intent is required to connect to voice.
""" """
return 1 << 7 return 1 << 7

View File

@ -58,7 +58,9 @@ if TYPE_CHECKING:
DataCallable = Callable[[Dict[str, Any]], T] DataCallable = Callable[[Dict[str, Any]], T]
Result = Optional[DataCallable[Any]] Result = Optional[DataCallable[Any]]
log: logging.Logger = logging.getLogger(__name__)
_log: logging.Logger = logging.getLogger(__name__)
__all__ = ( __all__ = (
'DiscordWebSocket', 'DiscordWebSocket',
@ -132,7 +134,7 @@ class GatewayRatelimiter:
async with self.lock: async with self.lock:
delta = self.get_delay() delta = self.get_delay()
if delta: if delta:
log.warning('WebSocket in shard ID %s is ratelimited, waiting %.2f seconds', self.shard_id, delta) _log.warning('WebSocket in shard ID %s is ratelimited, waiting %.2f seconds', self.shard_id, delta)
await asyncio.sleep(delta) await asyncio.sleep(delta)
@ -160,20 +162,20 @@ class KeepAliveHandler(threading.Thread):
def run(self) -> None: def run(self) -> None:
while not self._stop_ev.wait(self.interval): while not self._stop_ev.wait(self.interval):
if self._last_recv + self.heartbeat_timeout < time.perf_counter(): if self._last_recv + self.heartbeat_timeout < time.perf_counter():
log.warning("Shard ID %s has stopped responding to the gateway. Closing and restarting.", self.shard_id) _log.warning("Shard ID %s has stopped responding to the gateway. Closing and restarting.", self.shard_id)
coro = self.ws.close(4000) coro = self.ws.close(4000)
f = asyncio.run_coroutine_threadsafe(coro, loop=self.ws.loop) f = asyncio.run_coroutine_threadsafe(coro, loop=self.ws.loop)
try: try:
f.result() f.result()
except Exception: except Exception:
log.exception('An error occurred while stopping the gateway. Ignoring.') _log.exception('An error occurred while stopping the gateway. Ignoring.')
finally: finally:
self.stop() self.stop()
return return
data = self.get_payload() data = self.get_payload()
log.debug(self.msg, self.shard_id, data['d']) _log.debug(self.msg, self.shard_id, data['d'])
coro = self.ws.send_heartbeat(data) coro = self.ws.send_heartbeat(data)
f = asyncio.run_coroutine_threadsafe(coro, loop=self.ws.loop) f = asyncio.run_coroutine_threadsafe(coro, loop=self.ws.loop)
try: try:
@ -192,7 +194,7 @@ class KeepAliveHandler(threading.Thread):
else: else:
stack = ''.join(traceback.format_stack(frame)) stack = ''.join(traceback.format_stack(frame))
msg = f'{self.block_msg}\nLoop thread traceback (most recent call last):\n{stack}' msg = f'{self.block_msg}\nLoop thread traceback (most recent call last):\n{stack}'
log.warning(msg, self.shard_id, total) _log.warning(msg, self.shard_id, total)
except Exception: except Exception:
self.stop() self.stop()
@ -217,7 +219,7 @@ class KeepAliveHandler(threading.Thread):
self._last_ack = ack_time self._last_ack = ack_time
self.latency = ack_time - self._last_send self.latency = ack_time - self._last_send
if self.latency > 10: if self.latency > 10:
log.warning(self.behind_msg, self.shard_id, self.latency) _log.warning(self.behind_msg, self.shard_id, self.latency)
class VoiceKeepAliveHandler(KeepAliveHandler): class VoiceKeepAliveHandler(KeepAliveHandler):
def __init__(self, *args: Any, **kwargs: Any) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None:
@ -378,7 +380,7 @@ class DiscordWebSocket:
client._connection._update_references(ws) client._connection._update_references(ws)
log.debug('Created websocket connected to %s', gateway) _log.debug('Created websocket connected to %s', gateway)
# poll event for OP Hello # poll event for OP Hello
await ws.poll_event() await ws.poll_event()
@ -451,7 +453,7 @@ class DiscordWebSocket:
await self.call_hooks('before_identify', self.shard_id, initial=self._initial_identify) await self.call_hooks('before_identify', self.shard_id, initial=self._initial_identify)
await self.send_as_json(payload) await self.send_as_json(payload)
log.info('Shard ID %s has sent the IDENTIFY payload.', self.shard_id) _log.info('Shard ID %s has sent the IDENTIFY payload.', self.shard_id)
async def resume(self) -> None: async def resume(self) -> None:
"""Sends the RESUME packet.""" """Sends the RESUME packet."""
@ -465,11 +467,10 @@ class DiscordWebSocket:
} }
await self.send_as_json(payload) await self.send_as_json(payload)
log.info('Shard ID %s has sent the RESUME payload.', self.shard_id) _log.info('Shard ID %s has sent the RESUME payload.', self.shard_id)
async def received_message(self, msg, /) -> None: async def received_message(self, msg, /) -> None:
self.log_receive(msg)
if type(msg) is bytes: if type(msg) is bytes:
self._buffer.extend(msg) self._buffer.extend(msg)
@ -478,9 +479,11 @@ class DiscordWebSocket:
msg = self._zlib.decompress(self._buffer) msg = self._zlib.decompress(self._buffer)
msg = msg.decode('utf-8') msg = msg.decode('utf-8')
self._buffer = bytearray() self._buffer = bytearray()
msg = utils.from_json(msg)
log.debug('For Shard ID %s: WebSocket Event: %s', self.shard_id, msg) self.log_receive(msg)
msg = utils._from_json(msg)
_log.debug('For Shard ID %s: WebSocket Event: %s', self.shard_id, msg)
event = msg.get('t') event = msg.get('t')
if event: if event:
self._dispatch('socket_event_type', event) self._dispatch('socket_event_type', event)
@ -499,7 +502,7 @@ class DiscordWebSocket:
# "reconnect" can only be handled by the Client # "reconnect" can only be handled by the Client
# so we terminate our connection and raise an # so we terminate our connection and raise an
# internal exception signalling to reconnect. # internal exception signalling to reconnect.
log.debug('Received RECONNECT opcode.') _log.debug('Received RECONNECT opcode.')
await self.close() await self.close()
raise ReconnectWebSocket(self.shard_id) raise ReconnectWebSocket(self.shard_id)
@ -529,11 +532,11 @@ class DiscordWebSocket:
self.sequence = None self.sequence = None
self.session_id = None self.session_id = None
log.info('Shard ID %s session has been invalidated.', self.shard_id) _log.info('Shard ID %s session has been invalidated.', self.shard_id)
await self.close(code=1000) await self.close(code=1000)
raise ReconnectWebSocket(self.shard_id, resume=False) raise ReconnectWebSocket(self.shard_id, resume=False)
log.warning('Unknown OP code %s.', op) _log.warning('Unknown OP code %s.', op)
return return
if event == 'READY': if event == 'READY':
@ -542,20 +545,20 @@ class DiscordWebSocket:
self.session_id = data['session_id'] self.session_id = data['session_id']
# pass back shard ID to ready handler # pass back shard ID to ready handler
data['__shard_id__'] = self.shard_id data['__shard_id__'] = self.shard_id
log.info('Shard ID %s has connected to Gateway: %s (Session ID: %s).', _log.info('Shard ID %s has connected to Gateway: %s (Session ID: %s).',
self.shard_id, ', '.join(trace), self.session_id) self.shard_id, ', '.join(trace), self.session_id)
elif event == 'RESUMED': elif event == 'RESUMED':
self._trace = trace = data.get('_trace', []) self._trace = trace = data.get('_trace', [])
# pass back the shard ID to the resumed handler # pass back the shard ID to the resumed handler
data['__shard_id__'] = self.shard_id data['__shard_id__'] = self.shard_id
log.info('Shard ID %s has successfully RESUMED session %s under trace %s.', _log.info('Shard ID %s has successfully RESUMED session %s under trace %s.',
self.shard_id, self.session_id, ', '.join(trace)) self.shard_id, self.session_id, ', '.join(trace))
try: try:
func = self._discord_parsers[event] func = self._discord_parsers[event]
except KeyError: except KeyError:
log.debug('Unknown event %s.', event) _log.debug('Unknown event %s.', event)
else: else:
func(data) func(data)
@ -609,10 +612,10 @@ class DiscordWebSocket:
elif msg.type is aiohttp.WSMsgType.BINARY: elif msg.type is aiohttp.WSMsgType.BINARY:
await self.received_message(msg.data) await self.received_message(msg.data)
elif msg.type is aiohttp.WSMsgType.ERROR: elif msg.type is aiohttp.WSMsgType.ERROR:
log.debug('Received %s', msg) _log.debug('Received %s', msg)
raise msg.data raise msg.data
elif msg.type in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.CLOSING, aiohttp.WSMsgType.CLOSE): elif msg.type in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.CLOSING, aiohttp.WSMsgType.CLOSE):
log.debug('Received %s', msg) _log.debug('Received %s', msg)
raise WebSocketClosure raise WebSocketClosure
except (asyncio.TimeoutError, WebSocketClosure) as e: except (asyncio.TimeoutError, WebSocketClosure) as e:
# Ensure the keep alive handler is closed # Ensure the keep alive handler is closed
@ -621,15 +624,15 @@ class DiscordWebSocket:
self._keep_alive = None self._keep_alive = None
if isinstance(e, asyncio.TimeoutError): if isinstance(e, asyncio.TimeoutError):
log.info('Timed out receiving packet. Attempting a reconnect.') _log.info('Timed out receiving packet. Attempting a reconnect.')
raise ReconnectWebSocket(self.shard_id) from None raise ReconnectWebSocket(self.shard_id) from None
code = self._close_code or self.socket.close_code code = self._close_code or self.socket.close_code
if self._can_handle_close(): if self._can_handle_close():
log.info('Websocket closed with %s, attempting a reconnect.', code) _log.info('Websocket closed with %s, attempting a reconnect.', code)
raise ReconnectWebSocket(self.shard_id) from None raise ReconnectWebSocket(self.shard_id) from None
else: else:
log.info('Websocket closed with %s, cannot reconnect.', code) _log.info('Websocket closed with %s, cannot reconnect.', code)
raise ConnectionClosed(self.socket, shard_id=self.shard_id, code=code) from None raise ConnectionClosed(self.socket, shard_id=self.shard_id, code=code) from None
async def debug_send(self, data, /) -> None: async def debug_send(self, data, /) -> None:
@ -643,7 +646,7 @@ class DiscordWebSocket:
async def send_as_json(self, data) -> None: async def send_as_json(self, data) -> None:
try: try:
await self.send(utils.to_json(data)) await self.send(utils._to_json(data))
except RuntimeError as exc: except RuntimeError as exc:
if not self._can_handle_close(): if not self._can_handle_close():
raise ConnectionClosed(self.socket, shard_id=self.shard_id) from exc raise ConnectionClosed(self.socket, shard_id=self.shard_id) from exc
@ -651,7 +654,7 @@ class DiscordWebSocket:
async def send_heartbeat(self, data: Heartbeat) -> None: async def send_heartbeat(self, data: Heartbeat) -> None:
# This bypasses the rate limit handling code since it has a higher priority # This bypasses the rate limit handling code since it has a higher priority
try: try:
await self.socket.send_str(utils.to_json(data)) await self.socket.send_str(utils._to_json(data))
except RuntimeError as exc: except RuntimeError as exc:
if not self._can_handle_close(): if not self._can_handle_close():
raise ConnectionClosed(self.socket, shard_id=self.shard_id) from exc raise ConnectionClosed(self.socket, shard_id=self.shard_id) from exc
@ -677,8 +680,8 @@ class DiscordWebSocket:
} }
} }
sent = utils.to_json(payload) sent = utils._to_json(payload)
log.debug('Sending "%s" to change status', sent) _log.debug('Sending "%s" to change status', sent)
await self.send(sent) await self.send(sent)
async def request_chunks(self, guild_id: int, query: Optional[str] = None, *, limit: int, user_ids: Optional[List[int]] = None, presences: bool = False, nonce: Optional[int] = None) -> None: async def request_chunks(self, guild_id: int, query: Optional[str] = None, *, limit: int, user_ids: Optional[List[int]] = None, presences: bool = False, nonce: Optional[int] = None) -> None:
@ -714,7 +717,7 @@ class DiscordWebSocket:
} }
} }
log.debug('Updating our voice state to %s.', payload) _log.debug('Updating our voice state to %s.', payload)
await self.send_as_json(payload) await self.send_as_json(payload)
async def close(self, code: int = 4000) -> None: async def close(self, code: int = 4000) -> None:
@ -786,9 +789,10 @@ class DiscordVoiceWebSocket:
async def _hook(self, *args: Any) -> Any: async def _hook(self, *args: Any) -> Any:
pass pass
async def send_as_json(self, data) -> None: async def send_as_json(self, data) -> None:
log.debug('Sending voice websocket frame: %s.', data) _log.debug('Sending voice websocket frame: %s.', data)
await self.ws.send_str(utils.to_json(data)) await self.ws.send_str(utils._to_json(data))
send_heartbeat = send_as_json send_heartbeat = send_as_json
@ -872,8 +876,9 @@ class DiscordVoiceWebSocket:
await self.send_as_json(payload) await self.send_as_json(payload)
async def received_message(self, msg) -> None: async def received_message(self, msg) -> None:
log.debug('Voice websocket frame received: %s', msg) _log.debug('Voice websocket frame received: %s', msg)
op = msg['op'] op = msg['op']
data = msg.get('d') data = msg.get('d')
@ -882,7 +887,7 @@ class DiscordVoiceWebSocket:
elif op == self.HEARTBEAT_ACK: elif op == self.HEARTBEAT_ACK:
self._keep_alive.ack() self._keep_alive.ack()
elif op == self.RESUMED: elif op == self.RESUMED:
log.info('Voice RESUME succeeded.') _log.info('Voice RESUME succeeded.')
elif op == self.SESSION_DESCRIPTION: elif op == self.SESSION_DESCRIPTION:
self._connection.mode = data['mode'] self._connection.mode = data['mode']
await self.load_secret_key(data) await self.load_secret_key(data)
@ -905,7 +910,7 @@ class DiscordVoiceWebSocket:
struct.pack_into('>I', packet, 4, state.ssrc) struct.pack_into('>I', packet, 4, state.ssrc)
state.socket.sendto(packet, (state.endpoint_ip, state.voice_port)) state.socket.sendto(packet, (state.endpoint_ip, state.voice_port))
recv = await self.loop.sock_recv(state.socket, 70) recv = await self.loop.sock_recv(state.socket, 70)
log.debug('received packet in initial_connection: %s', recv) _log.debug('received packet in initial_connection: %s', recv)
# the ip is ascii starting at the 4th byte and ending at the first null # the ip is ascii starting at the 4th byte and ending at the first null
ip_start = 4 ip_start = 4
@ -913,15 +918,15 @@ class DiscordVoiceWebSocket:
state.ip = recv[ip_start:ip_end].decode('ascii') state.ip = recv[ip_start:ip_end].decode('ascii')
state.port = struct.unpack_from('>H', recv, len(recv) - 2)[0] state.port = struct.unpack_from('>H', recv, len(recv) - 2)[0]
log.debug('detected ip: %s port: %s', state.ip, state.port) _log.debug('detected ip: %s port: %s', state.ip, state.port)
# there *should* always be at least one supported mode (xsalsa20_poly1305) # there *should* always be at least one supported mode (xsalsa20_poly1305)
modes = [mode for mode in data['modes'] if mode in self._connection.supported_modes] modes = [mode for mode in data['modes'] if mode in self._connection.supported_modes]
log.debug('received supported encryption modes: %s', ", ".join(modes)) _log.debug('received supported encryption modes: %s', ", ".join(modes))
mode = modes[0] mode = modes[0]
await self.select_protocol(state.ip, state.port, mode) await self.select_protocol(state.ip, state.port, mode)
log.info('selected the voice protocol for use (%s)', mode) _log.info('selected the voice protocol for use (%s)', mode)
@property @property
def latency(self) -> float: def latency(self) -> float:
@ -938,8 +943,9 @@ class DiscordVoiceWebSocket:
return sum(heartbeat.recent_ack_latencies) / len(heartbeat.recent_ack_latencies) return sum(heartbeat.recent_ack_latencies) / len(heartbeat.recent_ack_latencies)
async def load_secret_key(self, data) -> None: async def load_secret_key(self, data) -> None:
log.info('received secret key for voice connection') _log.info('received secret key for voice connection')
self.secret_key = self._connection.secret_key = data.get('secret_key') self.secret_key = self._connection.secret_key = data.get('secret_key')
await self.speak() await self.speak()
await self.speak(False) await self.speak(False)
@ -948,12 +954,12 @@ class DiscordVoiceWebSocket:
# This exception is handled up the chain # This exception is handled up the chain
msg = await asyncio.wait_for(self.ws.receive(), timeout=30.0) msg = await asyncio.wait_for(self.ws.receive(), timeout=30.0)
if msg.type is aiohttp.WSMsgType.TEXT: if msg.type is aiohttp.WSMsgType.TEXT:
await self.received_message(utils.from_json(msg.data)) await self.received_message(utils._from_json(msg.data))
elif msg.type is aiohttp.WSMsgType.ERROR: elif msg.type is aiohttp.WSMsgType.ERROR:
log.debug('Received %s', msg) _log.debug('Received %s', msg)
raise ConnectionClosed(self.ws, shard_id=None) from msg.data raise ConnectionClosed(self.ws, shard_id=None) from msg.data
elif msg.type in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.CLOSE, aiohttp.WSMsgType.CLOSING): elif msg.type in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.CLOSE, aiohttp.WSMsgType.CLOSING):
log.debug('Received %s', msg) _log.debug('Received %s', msg)
raise ConnectionClosed(self.ws, shard_id=None, code=self._close_code) raise ConnectionClosed(self.ws, shard_id=None, code=self._close_code)
async def close(self, code: int = 1000) -> None: async def close(self, code: int = 1000) -> None:

View File

@ -140,6 +140,10 @@ class Guild(Hashable):
Returns the guild's name. Returns the guild's name.
.. describe:: int(x)
Returns the guild's ID.
Attributes Attributes
---------- ----------
name: :class:`str` name: :class:`str`
@ -717,7 +721,7 @@ class Guild(Hashable):
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
more_stickers = 60 if 'MORE_STICKERS' in self.features else 15 more_stickers = 60 if 'MORE_STICKERS' in self.features else 0
return max(more_stickers, self._PREMIUM_GUILD_LIMITS[self.premium_tier].stickers) return max(more_stickers, self._PREMIUM_GUILD_LIMITS[self.premium_tier].stickers)
@property @property
@ -736,7 +740,21 @@ class Guild(Hashable):
"""List[:class:`Member`]: A list of members that belong to this guild.""" """List[:class:`Member`]: A list of members that belong to this guild."""
return list(self._members.values()) return list(self._members.values())
def get_member(self, user_id: int) -> Optional[Member]: @property
def humans(self) -> List[Member]:
"""List[:class:`Member`]: A list of human members that belong to this guild.
.. versionadded:: 2.0 """
return [member for member in self.members if not member.bot]
@property
def bots(self) -> List[Member]:
"""List[:class:`Member`]: A list of bots that belong to this guild.
.. versionadded:: 2.0 """
return [member for member in self.members if member.bot]
def get_member(self, user_id: int, /) -> Optional[Member]:
"""Returns a member with the given ID. """Returns a member with the given ID.
Parameters Parameters
@ -1356,7 +1374,7 @@ class Guild(Hashable):
preferred_locale: str = MISSING, preferred_locale: str = MISSING,
rules_channel: Optional[TextChannel] = MISSING, rules_channel: Optional[TextChannel] = MISSING,
public_updates_channel: Optional[TextChannel] = MISSING, public_updates_channel: Optional[TextChannel] = MISSING,
) -> None: ) -> Guild:
r"""|coro| r"""|coro|
Edits the guild. Edits the guild.
@ -1370,6 +1388,9 @@ class Guild(Hashable):
.. versionchanged:: 2.0 .. versionchanged:: 2.0
The `discovery_splash` and `community` keyword-only parameters were added. The `discovery_splash` and `community` keyword-only parameters were added.
.. versionchanged:: 2.0
The newly updated guild is returned.
Parameters Parameters
---------- ----------
name: :class:`str` name: :class:`str`
@ -1443,6 +1464,12 @@ class Guild(Hashable):
The image format passed in to ``icon`` is invalid. It must be The image format passed in to ``icon`` is invalid. It must be
PNG or JPG. This is also raised if you are not the owner of the PNG or JPG. This is also raised if you are not the owner of the
guild and request an ownership transfer. guild and request an ownership transfer.
Returns
--------
:class:`Guild`
The newly updated guild. Note that this has the same limitations as
mentioned in :meth:`Client.fetch_guild` and may not have full data.
""" """
http = self._state.http http = self._state.http
@ -1555,7 +1582,8 @@ class Guild(Hashable):
fields['features'] = features fields['features'] = features
await http.edit_guild(self.id, reason=reason, **fields) data = await http.edit_guild(self.id, reason=reason, **fields)
return Guild(data=data, state=self._state)
async def fetch_channels(self) -> Sequence[GuildChannel]: async def fetch_channels(self) -> Sequence[GuildChannel]:
"""|coro| """|coro|

View File

@ -53,7 +53,7 @@ from .gateway import DiscordClientWebSocketResponse
from . import __version__, utils from . import __version__, utils
from .utils import MISSING from .utils import MISSING
log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
if TYPE_CHECKING: if TYPE_CHECKING:
from .file import File from .file import File
@ -99,7 +99,7 @@ async def json_or_text(response: aiohttp.ClientResponse) -> Union[Dict[str, Any]
text = await response.text(encoding='utf-8') text = await response.text(encoding='utf-8')
try: try:
if response.headers['content-type'] == 'application/json': if response.headers['content-type'] == 'application/json':
return utils.from_json(text) return utils._from_json(text)
except KeyError: except KeyError:
# Thanks Cloudflare # Thanks Cloudflare
pass pass
@ -141,7 +141,8 @@ class MaybeUnlock:
def defer(self) -> None: def defer(self) -> None:
self._unlock = False self._unlock = False
def __exit__(self, def __exit__(
self,
exc_type: Optional[Type[BE]], exc_type: Optional[Type[BE]],
exc: Optional[BE], exc: Optional[BE],
traceback: Optional[TracebackType], traceback: Optional[TracebackType],
@ -165,7 +166,7 @@ class HTTPClient:
proxy: Optional[str] = None, proxy: Optional[str] = None,
proxy_auth: Optional[aiohttp.BasicAuth] = None, proxy_auth: Optional[aiohttp.BasicAuth] = None,
loop: Optional[asyncio.AbstractEventLoop] = None, loop: Optional[asyncio.AbstractEventLoop] = None,
unsync_clock: bool = True unsync_clock: bool = True,
) -> None: ) -> None:
self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() if loop is None else loop self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() if loop is None else loop
self.connector = connector self.connector = connector
@ -209,7 +210,7 @@ class HTTPClient:
*, *,
files: Optional[Sequence[File]] = None, files: Optional[Sequence[File]] = None,
form: Optional[Iterable[Dict[str, Any]]] = None, form: Optional[Iterable[Dict[str, Any]]] = None,
**kwargs: Any **kwargs: Any,
) -> Any: ) -> Any:
bucket = route.bucket bucket = route.bucket
method = route.method method = route.method
@ -231,7 +232,7 @@ class HTTPClient:
# some checking if it's a JSON request # some checking if it's a JSON request
if 'json' in kwargs: if 'json' in kwargs:
headers['Content-Type'] = 'application/json' headers['Content-Type'] = 'application/json'
kwargs['data'] = utils.to_json(kwargs.pop('json')) kwargs['data'] = utils._to_json(kwargs.pop('json'))
try: try:
reason = kwargs.pop('reason') reason = kwargs.pop('reason')
@ -270,7 +271,7 @@ class HTTPClient:
try: try:
async with self.__session.request(method, url, **kwargs) as response: async with self.__session.request(method, url, **kwargs) as response:
log.debug('%s %s with %s has returned %s', method, url, kwargs.get('data'), response.status) _log.debug('%s %s with %s has returned %s', method, url, kwargs.get('data'), response.status)
# even errors have text involved in them so this is safe to call # even errors have text involved in them so this is safe to call
data = await json_or_text(response) data = await json_or_text(response)
@ -280,13 +281,13 @@ class HTTPClient:
if remaining == '0' and response.status != 429: if remaining == '0' and response.status != 429:
# we've depleted our current bucket # we've depleted our current bucket
delta = utils._parse_ratelimit_header(response, use_clock=self.use_clock) delta = utils._parse_ratelimit_header(response, use_clock=self.use_clock)
log.debug('A rate limit bucket has been exhausted (bucket: %s, retry: %s).', bucket, delta) _log.debug('A rate limit bucket has been exhausted (bucket: %s, retry: %s).', bucket, delta)
maybe_lock.defer() maybe_lock.defer()
self.loop.call_later(delta, lock.release) self.loop.call_later(delta, lock.release)
# the request was successful so just return the text/json # the request was successful so just return the text/json
if 300 > response.status >= 200: if 300 > response.status >= 200:
log.debug('%s %s has received %s', method, url, data) _log.debug('%s %s has received %s', method, url, data)
return data return data
# we are being rate limited # we are being rate limited
@ -299,22 +300,22 @@ class HTTPClient:
# sleep a bit # sleep a bit
retry_after: float = data['retry_after'] retry_after: float = data['retry_after']
log.warning(fmt, retry_after, bucket) _log.warning(fmt, retry_after, bucket)
# check if it's a global rate limit # check if it's a global rate limit
is_global = data.get('global', False) is_global = data.get('global', False)
if is_global: if is_global:
log.warning('Global rate limit has been hit. Retrying in %.2f seconds.', retry_after) _log.warning('Global rate limit has been hit. Retrying in %.2f seconds.', retry_after)
self._global_over.clear() self._global_over.clear()
await asyncio.sleep(retry_after) await asyncio.sleep(retry_after)
log.debug('Done sleeping for the rate limit. Retrying...') _log.debug('Done sleeping for the rate limit. Retrying...')
# release the global lock now that the # release the global lock now that the
# global rate limit has passed # global rate limit has passed
if is_global: if is_global:
self._global_over.set() self._global_over.set()
log.debug('Global rate limit is now over.') _log.debug('Global rate limit is now over.')
continue continue
@ -412,7 +413,7 @@ class HTTPClient:
def send_message( def send_message(
self, self,
channel_id: Snowflake, channel_id: Snowflake,
content: str, content: Optional[str],
*, *,
tts: bool = False, tts: bool = False,
embed: Optional[embed.Embed] = None, embed: Optional[embed.Embed] = None,
@ -493,7 +494,7 @@ class HTTPClient:
if stickers: if stickers:
payload['sticker_ids'] = stickers payload['sticker_ids'] = stickers
form.append({'name': 'payload_json', 'value': 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.append( form.append(
@ -547,11 +548,15 @@ class HTTPClient:
components=components, components=components,
) )
def delete_message(self, channel_id: Snowflake, message_id: Snowflake, *, reason: Optional[str] = None) -> Response[None]: def delete_message(
self, channel_id: Snowflake, message_id: Snowflake, *, reason: Optional[str] = None
) -> Response[None]:
r = Route('DELETE', '/channels/{channel_id}/messages/{message_id}', channel_id=channel_id, message_id=message_id) r = Route('DELETE', '/channels/{channel_id}/messages/{message_id}', channel_id=channel_id, message_id=message_id)
return self.request(r, reason=reason) return self.request(r, reason=reason)
def delete_messages(self, channel_id: Snowflake, message_ids: SnowflakeList, *, reason: Optional[str] = None) -> Response[None]: def delete_messages(
self, channel_id: Snowflake, message_ids: SnowflakeList, *, reason: Optional[str] = None
) -> Response[None]:
r = Route('POST', '/channels/{channel_id}/messages/bulk-delete', channel_id=channel_id) r = Route('POST', '/channels/{channel_id}/messages/bulk-delete', channel_id=channel_id)
payload = { payload = {
'messages': message_ids, 'messages': message_ids,
@ -573,7 +578,9 @@ class HTTPClient:
) )
return self.request(r) return self.request(r)
def remove_reaction(self, channel_id: Snowflake, message_id: Snowflake, emoji: str, member_id: Snowflake) -> Response[None]: def remove_reaction(
self, channel_id: Snowflake, message_id: Snowflake, emoji: str, member_id: Snowflake
) -> Response[None]:
r = Route( r = Route(
'DELETE', 'DELETE',
'/channels/{channel_id}/messages/{message_id}/reactions/{emoji}/{member_id}', '/channels/{channel_id}/messages/{message_id}/reactions/{emoji}/{member_id}',
@ -774,11 +781,11 @@ 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: Snowflake, payload: voice.VoiceState) -> Response[None]: def edit_my_voice_state(self, guild_id: Snowflake, payload: Dict[str, Any]) -> Response[None]:
r = Route('PATCH', '/guilds/{guild_id}/voice-states/@me', guild_id=guild_id) r = Route('PATCH', '/guilds/{guild_id}/voice-states/@me', guild_id=guild_id)
return self.request(r, json=payload) return self.request(r, json=payload)
def edit_voice_state(self, guild_id: Snowflake, user_id: Snowflake, payload: voice.VoiceState) -> Response[None]: def edit_voice_state(self, guild_id: Snowflake, user_id: Snowflake, payload: Dict[str, Any]) -> Response[None]:
r = Route('PATCH', '/guilds/{guild_id}/voice-states/{user_id}', guild_id=guild_id, user_id=user_id) r = Route('PATCH', '/guilds/{guild_id}/voice-states/{user_id}', guild_id=guild_id, user_id=user_id)
return self.request(r, json=payload) return self.request(r, json=payload)
@ -789,7 +796,7 @@ class HTTPClient:
*, *,
reason: Optional[str] = None, reason: Optional[str] = None,
**fields: Any, **fields: Any,
) -> Response[member.Member]: ) -> Response[member.MemberWithUser]:
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)
@ -819,6 +826,7 @@ class HTTPClient:
'archived', 'archived',
'auto_archive_duration', 'auto_archive_duration',
'locked', 'locked',
'invitable',
'default_auto_archive_duration', 'default_auto_archive_duration',
) )
payload = {k: v for k, v in options.items() if k in valid_keys} payload = {k: v for k, v in options.items() if k in valid_keys}
@ -900,12 +908,14 @@ class HTTPClient:
name: str, name: str,
auto_archive_duration: threads.ThreadArchiveDuration, auto_archive_duration: threads.ThreadArchiveDuration,
type: threads.ThreadType, type: threads.ThreadType,
invitable: bool = True,
reason: Optional[str] = None, reason: Optional[str] = None,
) -> Response[threads.Thread]: ) -> Response[threads.Thread]:
payload = { payload = {
'name': name, 'name': name,
'auto_archive_duration': auto_archive_duration, 'auto_archive_duration': auto_archive_duration,
'type': type, 'type': type,
'invitable': invitable,
} }
route = Route('POST', '/channels/{channel_id}/threads', channel_id=channel_id) route = Route('POST', '/channels/{channel_id}/threads', channel_id=channel_id)
@ -1122,7 +1132,9 @@ class HTTPClient:
def get_all_guild_channels(self, guild_id: Snowflake) -> Response[List[guild.GuildChannel]]: def get_all_guild_channels(self, guild_id: Snowflake) -> Response[List[guild.GuildChannel]]:
return self.request(Route('GET', '/guilds/{guild_id}/channels', guild_id=guild_id)) return self.request(Route('GET', '/guilds/{guild_id}/channels', guild_id=guild_id))
def get_members(self, guild_id: Snowflake, limit: int, after: Optional[Snowflake]) -> Response[List[member.Member]]: def get_members(
self, guild_id: Snowflake, limit: int, after: Optional[Snowflake]
) -> Response[List[member.MemberWithUser]]:
params: Dict[str, Any] = { params: Dict[str, Any] = {
'limit': limit, 'limit': limit,
} }
@ -1132,7 +1144,7 @@ class HTTPClient:
r = Route('GET', '/guilds/{guild_id}/members', guild_id=guild_id) r = Route('GET', '/guilds/{guild_id}/members', guild_id=guild_id)
return self.request(r, params=params) return self.request(r, params=params)
def get_member(self, guild_id: Snowflake, member_id: Snowflake) -> Response[member.Member]: def get_member(self, guild_id: Snowflake, member_id: Snowflake) -> Response[member.MemberWithUser]:
return self.request(Route('GET', '/guilds/{guild_id}/members/{member_id}', guild_id=guild_id, member_id=member_id)) return self.request(Route('GET', '/guilds/{guild_id}/members/{member_id}', guild_id=guild_id, member_id=member_id))
def prune_members( def prune_members(
@ -1177,9 +1189,13 @@ class HTTPClient:
return self.request(Route('GET', '/guilds/{guild_id}/stickers', guild_id=guild_id)) return self.request(Route('GET', '/guilds/{guild_id}/stickers', guild_id=guild_id))
def get_guild_sticker(self, guild_id: Snowflake, sticker_id: Snowflake) -> Response[sticker.GuildSticker]: def get_guild_sticker(self, guild_id: Snowflake, sticker_id: Snowflake) -> Response[sticker.GuildSticker]:
return self.request(Route('GET', '/guilds/{guild_id}/stickers/{sticker_id}', guild_id=guild_id, sticker_id=sticker_id)) return self.request(
Route('GET', '/guilds/{guild_id}/stickers/{sticker_id}', guild_id=guild_id, sticker_id=sticker_id)
)
def create_guild_sticker(self, guild_id: Snowflake, payload: sticker.CreateGuildSticker, file: File, reason: str) -> Response[sticker.GuildSticker]: def create_guild_sticker(
self, guild_id: Snowflake, payload: sticker.CreateGuildSticker, file: File, reason: str
) -> Response[sticker.GuildSticker]:
initial_bytes = file.fp.read(16) initial_bytes = file.fp.read(16)
try: try:
@ -1202,18 +1218,31 @@ class HTTPClient:
] ]
for k, v in payload.items(): for k, v in payload.items():
form.append({ form.append(
{
'name': k, 'name': k,
'value': v, 'value': v,
}) }
)
return self.request(Route('POST', '/guilds/{guild_id}/stickers', guild_id=guild_id), form=form, files=[file], reason=reason) return self.request(
Route('POST', '/guilds/{guild_id}/stickers', guild_id=guild_id), form=form, files=[file], reason=reason
)
def modify_guild_sticker(self, guild_id: Snowflake, sticker_id: Snowflake, payload: sticker.EditGuildSticker, reason: str) -> Response[sticker.GuildSticker]: def modify_guild_sticker(
return self.request(Route('PATCH', '/guilds/{guild_id}/stickers/{sticker_id}', guild_id=guild_id, sticker_id=sticker_id), json=payload, reason=reason) self, guild_id: Snowflake, sticker_id: Snowflake, payload: sticker.EditGuildSticker, reason: Optional[str],
) -> Response[sticker.GuildSticker]:
return self.request(
Route('PATCH', '/guilds/{guild_id}/stickers/{sticker_id}', guild_id=guild_id, sticker_id=sticker_id),
json=payload,
reason=reason,
)
def delete_guild_sticker(self, guild_id: Snowflake, sticker_id: Snowflake, reason: str) -> Response[None]: def delete_guild_sticker(self, guild_id: Snowflake, sticker_id: Snowflake, reason: Optional[str]) -> Response[None]:
return self.request(Route('DELETE', '/guilds/{guild_id}/stickers/{sticker_id}', guild_id=guild_id, sticker_id=sticker_id), reason=reason) return self.request(
Route('DELETE', '/guilds/{guild_id}/stickers/{sticker_id}', guild_id=guild_id, sticker_id=sticker_id),
reason=reason,
)
def get_all_custom_emojis(self, guild_id: Snowflake) -> Response[List[emoji.Emoji]]: def get_all_custom_emojis(self, guild_id: Snowflake) -> Response[List[emoji.Emoji]]:
return self.request(Route('GET', '/guilds/{guild_id}/emojis', guild_id=guild_id)) return self.request(Route('GET', '/guilds/{guild_id}/emojis', guild_id=guild_id))
@ -1288,7 +1317,9 @@ class HTTPClient:
return self.request(r) return self.request(r)
def delete_integration(self, guild_id: Snowflake, integration_id: Snowflake, *, reason: Optional[str] = None) -> Response[None]: def delete_integration(
self, guild_id: Snowflake, integration_id: Snowflake, *, reason: Optional[str] = None
) -> Response[None]:
r = Route( r = Route(
'DELETE', '/guilds/{guild_id}/integrations/{integration_id}', guild_id=guild_id, integration_id=integration_id 'DELETE', '/guilds/{guild_id}/integrations/{integration_id}', guild_id=guild_id, integration_id=integration_id
) )
@ -1336,7 +1367,7 @@ class HTTPClient:
unique: bool = True, unique: bool = True,
target_type: Optional[invite.InviteTargetType] = None, target_type: Optional[invite.InviteTargetType] = None,
target_user_id: Optional[Snowflake] = None, target_user_id: Optional[Snowflake] = None,
target_application_id: Optional[Snowflake] = None target_application_id: Optional[Snowflake] = None,
) -> Response[invite.Invite]: ) -> Response[invite.Invite]:
r = Route('POST', '/channels/{channel_id}/invites', channel_id=channel_id) r = Route('POST', '/channels/{channel_id}/invites', channel_id=channel_id)
payload = { payload = {
@ -1357,7 +1388,9 @@ class HTTPClient:
return self.request(r, reason=reason, json=payload) return self.request(r, reason=reason, json=payload)
def get_invite(self, invite_id: str, *, with_counts: bool = True, with_expiration: bool = True) -> Response[invite.Invite]: def get_invite(
self, invite_id: str, *, with_counts: bool = True, with_expiration: bool = True
) -> Response[invite.Invite]:
params = { params = {
'with_counts': int(with_counts), 'with_counts': int(with_counts),
'with_expiration': int(with_expiration), 'with_expiration': int(with_expiration),
@ -1378,7 +1411,9 @@ class HTTPClient:
def get_roles(self, guild_id: Snowflake) -> Response[List[role.Role]]: def get_roles(self, guild_id: Snowflake) -> Response[List[role.Role]]:
return self.request(Route('GET', '/guilds/{guild_id}/roles', guild_id=guild_id)) return self.request(Route('GET', '/guilds/{guild_id}/roles', guild_id=guild_id))
def edit_role(self, guild_id: Snowflake, role_id: Snowflake, *, reason: Optional[str] = None, **fields: Any) -> Response[role.Role]: def edit_role(
self, guild_id: Snowflake, role_id: Snowflake, *, reason: Optional[str] = None, **fields: Any
) -> Response[role.Role]:
r = Route('PATCH', '/guilds/{guild_id}/roles/{role_id}', guild_id=guild_id, role_id=role_id) r = Route('PATCH', '/guilds/{guild_id}/roles/{role_id}', guild_id=guild_id, role_id=role_id)
valid_keys = ('name', 'permissions', 'color', 'hoist', 'mentionable') valid_keys = ('name', 'permissions', 'color', 'hoist', 'mentionable')
payload = {k: v for k, v in fields.items() if k in valid_keys} payload = {k: v for k, v in fields.items() if k in valid_keys}
@ -1395,7 +1430,7 @@ class HTTPClient:
role_ids: List[int], role_ids: List[int],
*, *,
reason: Optional[str] = None, reason: Optional[str] = None,
) -> Response[member.Member]: ) -> Response[member.MemberWithUser]:
return self.edit_member(guild_id=guild_id, user_id=user_id, roles=role_ids, reason=reason) return self.edit_member(guild_id=guild_id, user_id=user_id, roles=role_ids, reason=reason)
def create_role(self, guild_id: Snowflake, *, reason: Optional[str] = None, **fields: Any) -> Response[role.Role]: def create_role(self, guild_id: Snowflake, *, reason: Optional[str] = None, **fields: Any) -> Response[role.Role]:
@ -1412,7 +1447,9 @@ class HTTPClient:
r = Route('PATCH', '/guilds/{guild_id}/roles', guild_id=guild_id) r = Route('PATCH', '/guilds/{guild_id}/roles', guild_id=guild_id)
return self.request(r, json=positions, reason=reason) return self.request(r, json=positions, reason=reason)
def add_role(self, guild_id: Snowflake, user_id: Snowflake, role_id: Snowflake, *, reason: Optional[str] = None) -> Response[None]: def add_role(
self, guild_id: Snowflake, user_id: Snowflake, role_id: Snowflake, *, reason: Optional[str] = None
) -> Response[None]:
r = Route( r = Route(
'PUT', 'PUT',
'/guilds/{guild_id}/members/{user_id}/roles/{role_id}', '/guilds/{guild_id}/members/{user_id}/roles/{role_id}',
@ -1422,7 +1459,9 @@ class HTTPClient:
) )
return self.request(r, reason=reason) return self.request(r, reason=reason)
def remove_role(self, guild_id: Snowflake, user_id: Snowflake, role_id: Snowflake, *, reason: Optional[str] = None) -> Response[None]: def remove_role(
self, guild_id: Snowflake, user_id: Snowflake, role_id: Snowflake, *, reason: Optional[str] = None
) -> Response[None]:
r = Route( r = Route(
'DELETE', 'DELETE',
'/guilds/{guild_id}/members/{user_id}/roles/{role_id}', '/guilds/{guild_id}/members/{user_id}/roles/{role_id}',
@ -1447,11 +1486,7 @@ class HTTPClient:
return self.request(r, json=payload, reason=reason) return self.request(r, json=payload, reason=reason)
def delete_channel_permissions( def delete_channel_permissions(
self, self, channel_id: Snowflake, target: channel.OverwriteType, *, reason: Optional[str] = None
channel_id: Snowflake,
target: channel.OverwriteType,
*,
reason: Optional[str] = None
) -> Response[None]: ) -> Response[None]:
r = Route('DELETE', '/channels/{channel_id}/permissions/{target}', channel_id=channel_id, target=target) r = Route('DELETE', '/channels/{channel_id}/permissions/{target}', channel_id=channel_id, target=target)
return self.request(r, reason=reason) return self.request(r, reason=reason)
@ -1465,7 +1500,7 @@ class HTTPClient:
channel_id: Snowflake, channel_id: Snowflake,
*, *,
reason: Optional[str] = None, reason: Optional[str] = None,
) -> Response[member.Member]: ) -> Response[member.MemberWithUser]:
return self.edit_member(guild_id=guild_id, user_id=user_id, channel_id=channel_id, reason=reason) return self.edit_member(guild_id=guild_id, user_id=user_id, channel_id=channel_id, reason=reason)
# Stage instance management # Stage instance management
@ -1490,7 +1525,9 @@ class HTTPClient:
) )
payload = {k: v for k, v in payload.items() if k in valid_keys} payload = {k: v for k, v in payload.items() if k in valid_keys}
return self.request(Route('PATCH', '/stage-instances/{channel_id}', channel_id=channel_id), json=payload, reason=reason) return self.request(
Route('PATCH', '/stage-instances/{channel_id}', channel_id=channel_id), json=payload, reason=reason
)
def delete_stage_instance(self, channel_id: Snowflake, *, reason: Optional[str] = None) -> Response[None]: def delete_stage_instance(self, channel_id: Snowflake, *, reason: Optional[str] = None) -> Response[None]:
return self.request(Route('DELETE', '/stage-instances/{channel_id}', channel_id=channel_id), reason=reason) return self.request(Route('DELETE', '/stage-instances/{channel_id}', channel_id=channel_id), reason=reason)
@ -1500,7 +1537,9 @@ class HTTPClient:
def get_global_commands(self, application_id: Snowflake) -> Response[List[interactions.ApplicationCommand]]: def get_global_commands(self, application_id: Snowflake) -> Response[List[interactions.ApplicationCommand]]:
return self.request(Route('GET', '/applications/{application_id}/commands', application_id=application_id)) return self.request(Route('GET', '/applications/{application_id}/commands', application_id=application_id))
def get_global_command(self, application_id: Snowflake, command_id: Snowflake) -> Response[interactions.ApplicationCommand]: def get_global_command(
self, application_id: Snowflake, command_id: Snowflake
) -> Response[interactions.ApplicationCommand]:
r = Route( r = Route(
'GET', 'GET',
'/applications/{application_id}/commands/{command_id}', '/applications/{application_id}/commands/{command_id}',
@ -1513,7 +1552,8 @@ class HTTPClient:
r = Route('POST', '/applications/{application_id}/commands', application_id=application_id) r = Route('POST', '/applications/{application_id}/commands', application_id=application_id)
return self.request(r, json=payload) return self.request(r, json=payload)
def edit_global_command(self, def edit_global_command(
self,
application_id: Snowflake, application_id: Snowflake,
command_id: Snowflake, command_id: Snowflake,
payload: interactions.EditApplicationCommand, payload: interactions.EditApplicationCommand,
@ -1541,13 +1581,17 @@ class HTTPClient:
) )
return self.request(r) return self.request(r)
def bulk_upsert_global_commands(self, application_id: Snowflake, payload) -> Response[List[interactions.ApplicationCommand]]: def bulk_upsert_global_commands(
self, application_id: Snowflake, payload
) -> Response[List[interactions.ApplicationCommand]]:
r = Route('PUT', '/applications/{application_id}/commands', application_id=application_id) r = Route('PUT', '/applications/{application_id}/commands', application_id=application_id)
return self.request(r, json=payload) return self.request(r, json=payload)
# Application commands (guild) # Application commands (guild)
def get_guild_commands(self, application_id: Snowflake, guild_id: Snowflake) -> Response[List[interactions.ApplicationCommand]]: def get_guild_commands(
self, application_id: Snowflake, guild_id: Snowflake
) -> Response[List[interactions.ApplicationCommand]]:
r = Route( r = Route(
'GET', 'GET',
'/applications/{application_id}/guilds/{guild_id}/commands', '/applications/{application_id}/guilds/{guild_id}/commands',
@ -1585,7 +1629,8 @@ class HTTPClient:
) )
return self.request(r, json=payload) return self.request(r, json=payload)
def edit_guild_command(self, def edit_guild_command(
self,
application_id: Snowflake, application_id: Snowflake,
guild_id: Snowflake, guild_id: Snowflake,
command_id: Snowflake, command_id: Snowflake,
@ -1657,7 +1702,7 @@ class HTTPClient:
form: List[Dict[str, Any]] = [ form: List[Dict[str, Any]] = [
{ {
'name': 'payload_json', 'name': 'payload_json',
'value': utils.to_json(payload), 'value': utils._to_json(payload),
} }
] ]
@ -1679,7 +1724,7 @@ class HTTPClient:
token: str, token: str,
*, *,
type: InteractionResponseType, type: InteractionResponseType,
data: Optional[interactions.InteractionApplicationCommandCallbackData] = None data: Optional[interactions.InteractionApplicationCommandCallbackData] = None,
) -> Response[None]: ) -> Response[None]:
r = Route( r = Route(
'POST', 'POST',

View File

@ -262,17 +262,10 @@ class StreamIntegration(Integration):
if enable_emoticons is not MISSING: if enable_emoticons is not MISSING:
payload['enable_emoticons'] = enable_emoticons payload['enable_emoticons'] = enable_emoticons
# This endpoint is undocumented.
# Unsure if it returns the data or not as a result
await self._state.http.edit_integration(self.guild.id, self.id, **payload) await self._state.http.edit_integration(self.guild.id, self.id, **payload)
if expire_behaviour is not MISSING:
self.expire_behaviour = expire_behaviour
if enable_emoticons is not MISSING:
self.enable_emoticons = enable_emoticons
if expire_grace_period is not MISSING:
self.expire_grace_period = expire_grace_period
async def sync(self) -> None: async def sync(self) -> None:
"""|coro| """|coro|

View File

@ -261,7 +261,7 @@ class Interaction:
files: List[File] = MISSING, files: List[File] = MISSING,
view: Optional[View] = MISSING, view: Optional[View] = MISSING,
allowed_mentions: Optional[AllowedMentions] = None, allowed_mentions: Optional[AllowedMentions] = None,
): ) -> InteractionMessage:
"""|coro| """|coro|
Edits the original interaction response message. Edits the original interaction response message.
@ -302,7 +302,12 @@ class Interaction:
TypeError TypeError
You specified both ``embed`` and ``embeds`` or ``file`` and ``files`` You specified both ``embed`` and ``embeds`` or ``file`` and ``files``
ValueError ValueError
The length of ``embeds`` was invalid The length of ``embeds`` was invalid.
Returns
--------
:class:`InteractionMessage`
The newly edited message.
""" """
previous_mentions: Optional[AllowedMentions] = self._state.allowed_mentions previous_mentions: Optional[AllowedMentions] = self._state.allowed_mentions
@ -326,8 +331,11 @@ class Interaction:
files=params.files, files=params.files,
) )
# The message channel types should always match
message = InteractionMessage(state=self._state, channel=self.channel, data=data) # type: ignore
if view and not view.is_finished(): if view and not view.is_finished():
self._state.store_view(view, int(data['id'])) self._state.store_view(view, message.id)
return message
async def delete_original_message(self) -> None: async def delete_original_message(self) -> None:
"""|coro| """|coro|
@ -672,7 +680,7 @@ class InteractionMessage(Message):
files: List[File] = MISSING, files: List[File] = MISSING,
view: Optional[View] = MISSING, view: Optional[View] = MISSING,
allowed_mentions: Optional[AllowedMentions] = None, allowed_mentions: Optional[AllowedMentions] = None,
): ) -> InteractionMessage:
"""|coro| """|coro|
Edits the message. Edits the message.
@ -707,9 +715,14 @@ class InteractionMessage(Message):
TypeError TypeError
You specified both ``embed`` and ``embeds`` or ``file`` and ``files`` You specified both ``embed`` and ``embeds`` or ``file`` and ``files``
ValueError ValueError
The length of ``embeds`` was invalid The length of ``embeds`` was invalid.
Returns
---------
:class:`InteractionMessage`
The newly edited message.
""" """
await self._state._interaction.edit_original_message( return await self._state._interaction.edit_original_message(
content=content, content=content,
embeds=embeds, embeds=embeds,
embed=embed, embed=embed,

View File

@ -230,6 +230,7 @@ class Invite(Hashable):
Returns the invite URL. Returns the invite URL.
The following table illustrates what methods will obtain the attributes: The following table illustrates what methods will obtain the attributes:
+------------------------------------+------------------------------------------------------------+ +------------------------------------+------------------------------------------------------------+
@ -433,6 +434,9 @@ class Invite(Hashable):
def __str__(self) -> str: def __str__(self) -> str:
return self.url return self.url
def __int__(self) -> int:
return 0 # To keep the object compatible with the hashable abc.
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
f'<Invite code={self.code!r} guild={self.guild!r} ' f'<Invite code={self.code!r} guild={self.guild!r} '

View File

@ -34,6 +34,7 @@ from typing import Any, Dict, List, Literal, Optional, TYPE_CHECKING, Tuple, Typ
import discord.abc import discord.abc
from . import utils from . import utils
from .asset import Asset
from .utils import MISSING from .utils import MISSING
from .user import BaseUser, User, _UserTag from .user import BaseUser, User, _UserTag
from .activity import create_activity, ActivityTypes from .activity import create_activity, ActivityTypes
@ -48,11 +49,13 @@ __all__ = (
) )
if TYPE_CHECKING: if TYPE_CHECKING:
from .channel import VoiceChannel, StageChannel from .asset import Asset
from .channel import DMChannel, VoiceChannel, StageChannel
from .flags import PublicUserFlags
from .guild import Guild from .guild import Guild
from .types.activity import PartialPresenceUpdate from .types.activity import PartialPresenceUpdate
from .types.member import ( from .types.member import (
GatewayMember as GatewayMemberPayload, MemberWithUser as MemberWithUserPayload,
Member as MemberPayload, Member as MemberPayload,
UserWithMember as UserWithMemberPayload, UserWithMember as UserWithMemberPayload,
) )
@ -223,6 +226,10 @@ class Member(discord.abc.Messageable, _UserTag):
Returns the member's name with the discriminator. Returns the member's name with the discriminator.
.. describe:: int(x)
Returns the user's ID.
Attributes Attributes
---------- ----------
joined_at: Optional[:class:`datetime.datetime`] joined_at: Optional[:class:`datetime.datetime`]
@ -261,6 +268,7 @@ class Member(discord.abc.Messageable, _UserTag):
'_client_status', '_client_status',
'_user', '_user',
'_state', '_state',
'_avatar',
) )
if TYPE_CHECKING: if TYPE_CHECKING:
@ -270,14 +278,17 @@ class Member(discord.abc.Messageable, _UserTag):
bot: bool bot: bool
system: bool system: bool
created_at: datetime.datetime created_at: datetime.datetime
default_avatar = User.default_avatar default_avatar: Asset
avatar = User.avatar avatar: Optional[Asset]
dm_channel = User.dm_channel dm_channel: Optional[DMChannel]
create_dm = User.create_dm create_dm = User.create_dm
mutual_guilds = User.mutual_guilds mutual_guilds: List[Guild]
public_flags = User.public_flags public_flags: PublicUserFlags
banner: Optional[Asset]
accent_color: Optional[Colour]
accent_colour: Optional[Colour]
def __init__(self, *, data: GatewayMemberPayload, guild: Guild, state: ConnectionState): def __init__(self, *, data: MemberWithUserPayload, guild: Guild, state: ConnectionState):
self._state: ConnectionState = state self._state: ConnectionState = state
self._user: User = state.store_user(data['user']) self._user: User = state.store_user(data['user'])
self.guild: Guild = guild self.guild: Guild = guild
@ -288,10 +299,14 @@ class Member(discord.abc.Messageable, _UserTag):
self.activities: Tuple[ActivityTypes, ...] = tuple() self.activities: Tuple[ActivityTypes, ...] = tuple()
self.nick: Optional[str] = data.get('nick', None) self.nick: Optional[str] = data.get('nick', None)
self.pending: bool = data.get('pending', False) self.pending: bool = data.get('pending', False)
self._avatar: Optional[str] = data.get('avatar')
def __str__(self) -> str: def __str__(self) -> str:
return str(self._user) return str(self._user)
def __int__(self) -> int:
return self.id
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
f'<Member id={self._user.id} name={self._user.name!r} discriminator={self._user.discriminator!r}' f'<Member id={self._user.id} name={self._user.name!r} discriminator={self._user.discriminator!r}'
@ -344,6 +359,7 @@ class Member(discord.abc.Messageable, _UserTag):
self.pending = member.pending self.pending = member.pending
self.activities = member.activities self.activities = member.activities
self._state = member._state self._state = member._state
self._avatar = member._avatar
# Reference will not be copied unless necessary by PRESENCE_UPDATE # Reference will not be copied unless necessary by PRESENCE_UPDATE
# See below # See below
@ -369,6 +385,7 @@ class Member(discord.abc.Messageable, _UserTag):
self.premium_since = utils.parse_time(data.get('premium_since')) self.premium_since = utils.parse_time(data.get('premium_since'))
self._roles = utils.SnowflakeList(map(int, data['roles'])) self._roles = utils.SnowflakeList(map(int, data['roles']))
self._avatar = data.get('avatar')
def _presence_update(self, data: PartialPresenceUpdate, user: UserPayload) -> Optional[Tuple[User, User]]: def _presence_update(self, data: PartialPresenceUpdate, user: UserPayload) -> Optional[Tuple[User, User]]:
self.activities = tuple(map(create_activity, data['activities'])) self.activities = tuple(map(create_activity, data['activities']))
@ -493,6 +510,29 @@ class Member(discord.abc.Messageable, _UserTag):
""" """
return self.nick or self.name return self.nick or self.name
@property
def display_avatar(self) -> Asset:
""":class:`Asset`: Returns the member's display avatar.
For regular members this is just their avatar, but
if they have a guild specific avatar then that
is returned instead.
.. versionadded:: 2.0
"""
return self.guild_avatar or self._user.avatar or self._user.default_avatar
@property
def guild_avatar(self) -> Optional[Asset]:
"""Optional[:class:`Asset`]: Returns an :class:`Asset` for the guild avatar
the member has. If unavailable, ``None`` is returned.
.. versionadded:: 2.0
"""
if self._avatar is None:
return None
return Asset._from_guild_avatar(self._state, self.guild.id, self.id, self._avatar)
@property @property
def activity(self) -> Optional[ActivityTypes]: def activity(self) -> Optional[ActivityTypes]:
"""Optional[Union[:class:`BaseActivity`, :class:`Spotify`]]: Returns the primary """Optional[Union[:class:`BaseActivity`, :class:`Spotify`]]: Returns the primary
@ -611,7 +651,7 @@ class Member(discord.abc.Messageable, _UserTag):
roles: List[discord.abc.Snowflake] = MISSING, roles: List[discord.abc.Snowflake] = MISSING,
voice_channel: Optional[VocalGuildChannel] = MISSING, voice_channel: Optional[VocalGuildChannel] = MISSING,
reason: Optional[str] = None, reason: Optional[str] = None,
) -> None: ) -> Optional[Member]:
"""|coro| """|coro|
Edits the member's data. Edits the member's data.
@ -637,6 +677,9 @@ class Member(discord.abc.Messageable, _UserTag):
.. versionchanged:: 1.1 .. versionchanged:: 1.1
Can now pass ``None`` to ``voice_channel`` to kick a member from voice. Can now pass ``None`` to ``voice_channel`` to kick a member from voice.
.. versionchanged:: 2.0
The newly member is now optionally returned, if applicable.
Parameters Parameters
----------- -----------
nick: Optional[:class:`str`] nick: Optional[:class:`str`]
@ -664,6 +707,12 @@ class Member(discord.abc.Messageable, _UserTag):
You do not have the proper permissions to the action requested. You do not have the proper permissions to the action requested.
HTTPException HTTPException
The operation failed. The operation failed.
Returns
--------
Optional[:class:`.Member`]
The newly updated member, if applicable. This is only returned
when certain fields are updated.
""" """
http = self._state.http http = self._state.http
guild_id = self.guild.id guild_id = self.guild.id
@ -706,7 +755,8 @@ class Member(discord.abc.Messageable, _UserTag):
payload['roles'] = tuple(r.id for r in roles) payload['roles'] = tuple(r.id for r in roles)
if payload: if payload:
await http.edit_member(guild_id, self.id, reason=reason, **payload) data = await http.edit_member(guild_id, self.id, reason=reason, **payload)
return Member(data=data, guild=self.guild, state=self._state)
async def request_to_speak(self) -> None: async def request_to_speak(self) -> None:
"""|coro| """|coro|
@ -847,7 +897,7 @@ class Member(discord.abc.Messageable, _UserTag):
for role in roles: for role in roles:
await req(guild_id, user_id, role.id, reason=reason) await req(guild_id, user_id, role.id, reason=reason)
def get_role(self, role_id: int) -> Optional[Role]: def get_role(self, role_id: int, /) -> Optional[Role]:
"""Returns a role with the given ID from roles which the member has. """Returns a role with the given ID from roles which the member has.
.. versionadded:: 2.0 .. versionadded:: 2.0

View File

@ -29,7 +29,7 @@ import datetime
import re import re
import io import io
from os import PathLike from os import PathLike
from typing import Dict, TYPE_CHECKING, Union, List, Optional, Any, Callable, Tuple, ClassVar, Optional, overload from typing import Dict, TYPE_CHECKING, Union, List, Optional, Any, Callable, Tuple, ClassVar, Optional, overload, TypeVar, Type
from . import utils from . import utils
from .reaction import Reaction from .reaction import Reaction
@ -76,6 +76,7 @@ if TYPE_CHECKING:
from .role import Role from .role import Role
from .ui.view import View from .ui.view import View
MR = TypeVar('MR', bound='MessageReference')
EmojiInputType = Union[Emoji, PartialEmoji, str] EmojiInputType = Union[Emoji, PartialEmoji, str]
__all__ = ( __all__ = (
@ -124,6 +125,10 @@ class Attachment(Hashable):
Returns the hash of the attachment. Returns the hash of the attachment.
.. describe:: int(x)
Returns the attachment's ID.
.. versionchanged:: 1.7 .. versionchanged:: 1.7
Attachment can now be casted to :class:`str` and is hashable. Attachment can now be casted to :class:`str` and is hashable.
@ -341,7 +346,8 @@ class DeletedReferencedMessage:
@property @property
def id(self) -> int: def id(self) -> int:
""":class:`int`: The message ID of the deleted referenced message.""" """:class:`int`: The message ID of the deleted referenced message."""
return self._parent.message_id # the parent's message id won't be None here
return self._parent.message_id # type: ignore
@property @property
def channel_id(self) -> int: def channel_id(self) -> int:
@ -393,13 +399,13 @@ class MessageReference:
def __init__(self, *, message_id: int, channel_id: int, guild_id: Optional[int] = None, fail_if_not_exists: bool = True): def __init__(self, *, message_id: int, channel_id: int, guild_id: Optional[int] = None, fail_if_not_exists: bool = True):
self._state: Optional[ConnectionState] = None self._state: Optional[ConnectionState] = None
self.resolved: Optional[Union[Message, DeletedReferencedMessage]] = None self.resolved: Optional[Union[Message, DeletedReferencedMessage]] = None
self.message_id: int = message_id self.message_id: Optional[int] = message_id
self.channel_id: int = channel_id self.channel_id: int = channel_id
self.guild_id: Optional[int] = guild_id self.guild_id: Optional[int] = guild_id
self.fail_if_not_exists: bool = fail_if_not_exists self.fail_if_not_exists: bool = fail_if_not_exists
@classmethod @classmethod
def with_state(cls, state: ConnectionState, data: MessageReferencePayload) -> MessageReference: def with_state(cls: Type[MR], state: ConnectionState, data: MessageReferencePayload) -> MR:
self = cls.__new__(cls) self = cls.__new__(cls)
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'))
@ -410,7 +416,7 @@ class MessageReference:
return self return self
@classmethod @classmethod
def from_message(cls, message: Message, *, fail_if_not_exists: bool = True) -> MessageReference: def from_message(cls: Type[MR], message: Message, *, fail_if_not_exists: bool = True) -> MR:
"""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
@ -457,13 +463,13 @@ class MessageReference:
return f'<MessageReference message_id={self.message_id!r} channel_id={self.channel_id!r} guild_id={self.guild_id!r}>' return f'<MessageReference message_id={self.message_id!r} channel_id={self.channel_id!r} guild_id={self.guild_id!r}>'
def to_dict(self) -> MessageReferencePayload: def to_dict(self) -> MessageReferencePayload:
result = {'message_id': self.message_id} if self.message_id is not None else {} result: MessageReferencePayload = {'message_id': self.message_id} if self.message_id is not None else {}
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: if self.fail_if_not_exists is not None:
result['fail_if_not_exists'] = self.fail_if_not_exists result['fail_if_not_exists'] = self.fail_if_not_exists
return result # type: ignore return result
to_message_reference_dict = to_dict to_message_reference_dict = to_dict
@ -501,6 +507,14 @@ class Message(Hashable):
Returns the message's hash. Returns the message's hash.
.. describe:: str(x)
Returns the message's content.
.. describe:: int(x)
Returns the message's ID.
Attributes Attributes
----------- -----------
tts: :class:`bool` tts: :class:`bool`
@ -637,7 +651,7 @@ class Message(Hashable):
_HANDLERS: ClassVar[List[Tuple[str, Callable[..., None]]]] _HANDLERS: ClassVar[List[Tuple[str, Callable[..., None]]]]
_CACHED_SLOTS: ClassVar[List[str]] _CACHED_SLOTS: ClassVar[List[str]]
guild: Optional[Guild] guild: Optional[Guild]
ref: Optional[MessageReference] reference: Optional[MessageReference]
mentions: List[Union[User, Member]] mentions: List[Union[User, Member]]
author: Union[User, Member] author: Union[User, Member]
role_mentions: List[Role] role_mentions: List[Role]
@ -646,7 +660,7 @@ class Message(Hashable):
self, self,
*, *,
state: ConnectionState, state: ConnectionState,
channel: Union[TextChannel, Thread, DMChannel, GroupChannel, PartialMessageable], channel: MessageableChannel,
data: MessagePayload, data: MessagePayload,
): ):
self._state: ConnectionState = state self._state: ConnectionState = state
@ -670,6 +684,7 @@ class Message(Hashable):
self.components: List[Component] = [_component_factory(d) for d in data.get('components', [])] self.components: List[Component] = [_component_factory(d) for d in data.get('components', [])]
try: try:
# if the channel doesn't have a guild attribute, we handle that
self.guild = channel.guild # type: ignore self.guild = channel.guild # type: ignore
except AttributeError: except AttributeError:
self.guild = state._get_guild(utils._get_as_snowflake(data, 'guild_id')) self.guild = state._get_guild(utils._get_as_snowflake(data, 'guild_id'))
@ -694,7 +709,8 @@ class Message(Hashable):
else: else:
chan, _ = state._get_guild_channel(resolved) chan, _ = state._get_guild_channel(resolved)
ref.resolved = self.__class__(channel=chan, data=resolved, state=state) # the channel will be the correct type here
ref.resolved = self.__class__(channel=chan, data=resolved, state=state) # type: ignore
for handler in ('author', 'member', 'mentions', 'mention_roles'): for handler in ('author', 'member', 'mentions', 'mention_roles'):
try: try:
@ -708,6 +724,10 @@ class Message(Hashable):
f'<{name} id={self.id} channel={self.channel!r} type={self.type!r} author={self.author!r} flags={self.flags!r}>' f'<{name} id={self.id} channel={self.channel!r} type={self.type!r} author={self.author!r} flags={self.flags!r}>'
) )
def __str__(self) -> Optional[str]:
return self.content
def _try_patch(self, data, key, transform=None) -> None: def _try_patch(self, data, key, transform=None) -> None:
try: try:
value = data[key] value = data[key]
@ -1071,6 +1091,7 @@ class Message(Hashable):
return f'{self.author.name} has added {self.content} to this channel' return f'{self.author.name} has added {self.content} to this channel'
if self.type is MessageType.guild_stream: if self.type is MessageType.guild_stream:
# the author will be a Member
return f'{self.author.name} is live! Now streaming {self.author.activity.name}' # type: ignore return f'{self.author.name} is live! Now streaming {self.author.activity.name}' # type: ignore
if self.type is MessageType.guild_discovery_disqualified: if self.type is MessageType.guild_discovery_disqualified:
@ -1095,12 +1116,13 @@ class Message(Hashable):
if self.reference is None or self.reference.resolved is None: if self.reference is None or self.reference.resolved is None:
return 'Sorry, we couldn\'t load the first message in this thread' return 'Sorry, we couldn\'t load the first message in this thread'
# the resolved message for the reference will be a Message
return self.reference.resolved.content # type: ignore return self.reference.resolved.content # type: ignore
if self.type is MessageType.guild_invite_reminder: if self.type is MessageType.guild_invite_reminder:
return 'Wondering who to invite?\nStart by inviting anyone who can help you build the server!' return 'Wondering who to invite?\nStart by inviting anyone who can help you build the server!'
async def delete(self, *, delay: Optional[float] = None) -> None: async def delete(self, *, delay: Optional[float] = None, silent: bool = False) -> None:
"""|coro| """|coro|
Deletes the message. Deletes the message.
@ -1111,12 +1133,17 @@ class Message(Hashable):
.. versionchanged:: 1.1 .. versionchanged:: 1.1
Added the new ``delay`` keyword-only parameter. Added the new ``delay`` keyword-only parameter.
.. versionchanged:: 2.0
Added the new ``silent`` keyword-only parameter.
Parameters Parameters
----------- -----------
delay: Optional[:class:`float`] delay: Optional[:class:`float`]
If provided, the number of seconds to wait in the background If provided, the number of seconds to wait in the background
before deleting the message. If the deletion fails then it is silently ignored. before deleting the message. If the deletion fails then it is silently ignored.
silent: :class:`bool`
If silent is set to ``True``, the error will not be raised, it will be ignored.
This defaults to ``False``
Raises Raises
------ ------
@ -1138,7 +1165,11 @@ class Message(Hashable):
asyncio.create_task(delete(delay)) asyncio.create_task(delete(delay))
else: else:
try:
await self._state.http.delete_message(self.channel.id, self.id) await self._state.http.delete_message(self.channel.id, self.id)
except Exception:
if not silent:
raise
@overload @overload
async def edit( async def edit(
@ -1151,7 +1182,7 @@ class Message(Hashable):
delete_after: Optional[float] = ..., delete_after: Optional[float] = ...,
allowed_mentions: Optional[AllowedMentions] = ..., allowed_mentions: Optional[AllowedMentions] = ...,
view: Optional[View] = ..., view: Optional[View] = ...,
) -> None: ) -> Message:
... ...
@overload @overload
@ -1165,7 +1196,7 @@ class Message(Hashable):
delete_after: Optional[float] = ..., delete_after: Optional[float] = ...,
allowed_mentions: Optional[AllowedMentions] = ..., allowed_mentions: Optional[AllowedMentions] = ...,
view: Optional[View] = ..., view: Optional[View] = ...,
) -> None: ) -> Message:
... ...
async def edit( async def edit(
@ -1178,7 +1209,7 @@ class Message(Hashable):
delete_after: Optional[float] = None, delete_after: Optional[float] = None,
allowed_mentions: Optional[AllowedMentions] = MISSING, allowed_mentions: Optional[AllowedMentions] = MISSING,
view: Optional[View] = MISSING, view: Optional[View] = MISSING,
) -> None: ) -> Message:
"""|coro| """|coro|
Edits the message. Edits the message.
@ -1280,9 +1311,8 @@ class Message(Hashable):
else: else:
payload['components'] = [] payload['components'] = []
if payload:
data = await self._state.http.edit_message(self.channel.id, self.id, **payload) data = await self._state.http.edit_message(self.channel.id, self.id, **payload)
self._update(data) message = Message(state=self._state, channel=self.channel, data=data)
if view and not view.is_finished(): if view and not view.is_finished():
self._state.store_view(view, self.id) self._state.store_view(view, self.id)
@ -1290,6 +1320,8 @@ class Message(Hashable):
if delete_after is not None: if delete_after is not None:
await self.delete(delay=delete_after) await self.delete(delay=delete_after)
return message
async def publish(self) -> None: async def publish(self) -> None:
"""|coro| """|coro|
@ -1489,8 +1521,8 @@ class Message(Hashable):
Creates a public thread from this message. Creates a public thread from this message.
You must have :attr:`~discord.Permissions.send_messages` and You must have :attr:`~discord.Permissions.create_public_threads` in order to
:attr:`~discord.Permissions.use_threads` in order to create a thread. create a public thread from a message.
The channel this message belongs in must be a :class:`TextChannel`. The channel this message belongs in must be a :class:`TextChannel`.
@ -1521,13 +1553,14 @@ class Message(Hashable):
if self.guild is None: if self.guild is None:
raise InvalidArgument('This message does not have guild info attached.') raise InvalidArgument('This message does not have guild info attached.')
default_auto_archive_duration: ThreadArchiveDuration = getattr(self.channel, 'default_auto_archive_duration', 1440)
data = await self._state.http.start_thread_with_message( data = await self._state.http.start_thread_with_message(
self.channel.id, self.channel.id,
self.id, self.id,
name=name, name=name,
auto_archive_duration=auto_archive_duration or self.channel.default_auto_archive_duration, auto_archive_duration=auto_archive_duration or default_auto_archive_duration,
) )
return Thread(guild=self.guild, state=self._state, data=data) # type: ignore return Thread(guild=self.guild, state=self._state, data=data)
async def reply(self, content: Optional[str] = None, **kwargs) -> Message: async def reply(self, content: Optional[str] = None, **kwargs) -> Message:
"""|coro| """|coro|
@ -1617,6 +1650,10 @@ class PartialMessage(Hashable):
Returns the partial message's hash. Returns the partial message's hash.
.. describe:: int(x)
Returns the partial message's ID.
Attributes Attributes
----------- -----------
channel: Union[:class:`TextChannel`, :class:`Thread`, :class:`DMChannel`] channel: Union[:class:`TextChannel`, :class:`Thread`, :class:`DMChannel`]
@ -1773,7 +1810,7 @@ class PartialMessage(Hashable):
fields['embed'] = embed.to_dict() fields['embed'] = embed.to_dict()
try: try:
suppress = fields.pop('suppress') suppress: bool = fields.pop('suppress')
except KeyError: except KeyError:
pass pass
else: else:
@ -1811,9 +1848,10 @@ class PartialMessage(Hashable):
data = await self._state.http.edit_message(self.channel.id, self.id, **fields) data = await self._state.http.edit_message(self.channel.id, self.id, **fields)
if delete_after is not None: if delete_after is not None:
await self.delete(delay=delete_after) # type: ignore await self.delete(delay=delete_after)
if fields: if fields:
# data isn't unbound
msg = self._state.create_message(channel=self.channel, data=data) # type: ignore msg = self._state.create_message(channel=self.channel, data=data) # type: ignore
if view and not view.is_finished(): if view and not view.is_finished():
self._state.store_view(view, self.id) self._state.store_view(view, self.id)

View File

@ -43,5 +43,8 @@ class EqualityComparable:
class Hashable(EqualityComparable): class Hashable(EqualityComparable):
__slots__ = () __slots__ = ()
def __int__(self) -> int:
return self.id
def __hash__(self) -> int: def __hash__(self) -> int:
return self.id >> 22 return self.id >> 22

View File

@ -69,6 +69,10 @@ class Object(Hashable):
Returns the object's hash. Returns the object's hash.
.. describe:: int(x)
Returns the object's ID.
Attributes Attributes
----------- -----------
id: :class:`int` id: :class:`int`

View File

@ -22,8 +22,12 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
""" """
from __future__ import annotations
import struct import struct
from typing import TYPE_CHECKING, ClassVar, IO, Generator, Tuple, Optional
from .errors import DiscordException from .errors import DiscordException
__all__ = ( __all__ = (
@ -40,22 +44,29 @@ class OggError(DiscordException):
# https://tools.ietf.org/html/rfc7845 # https://tools.ietf.org/html/rfc7845
class OggPage: class OggPage:
_header = struct.Struct('<xBQIIIB') _header: ClassVar[struct.Struct] = struct.Struct('<xBQIIIB')
if TYPE_CHECKING:
flag: int
gran_pos: int
serial: int
pagenum: int
crc: int
segnum: int
def __init__(self, stream): def __init__(self, stream: IO[bytes]) -> None:
try: try:
header = stream.read(struct.calcsize(self._header.format)) header = stream.read(struct.calcsize(self._header.format))
self.flag, self.gran_pos, self.serial, \ self.flag, self.gran_pos, self.serial, \
self.pagenum, self.crc, self.segnum = self._header.unpack(header) self.pagenum, self.crc, self.segnum = self._header.unpack(header)
self.segtable = stream.read(self.segnum) self.segtable: bytes = stream.read(self.segnum)
bodylen = sum(struct.unpack('B'*self.segnum, self.segtable)) bodylen = sum(struct.unpack('B'*self.segnum, self.segtable))
self.data = stream.read(bodylen) self.data: bytes = stream.read(bodylen)
except Exception: except Exception:
raise OggError('bad data stream') from None raise OggError('bad data stream') from None
def iter_packets(self): def iter_packets(self) -> Generator[Tuple[bytes, bool], None, None]:
packetlen = offset = 0 packetlen = offset = 0
partial = True partial = True
@ -74,10 +85,10 @@ class OggPage:
yield self.data[offset:], False yield self.data[offset:], False
class OggStream: class OggStream:
def __init__(self, stream): def __init__(self, stream: IO[bytes]) -> None:
self.stream = stream self.stream: IO[bytes] = stream
def _next_page(self): def _next_page(self) -> Optional[OggPage]:
head = self.stream.read(4) head = self.stream.read(4)
if head == b'OggS': if head == b'OggS':
return OggPage(self.stream) return OggPage(self.stream)
@ -86,13 +97,13 @@ class OggStream:
else: else:
raise OggError('invalid header magic') raise OggError('invalid header magic')
def _iter_pages(self): def _iter_pages(self) -> Generator[OggPage, None, None]:
page = self._next_page() page = self._next_page()
while page: while page:
yield page yield page
page = self._next_page() page = self._next_page()
def iter_packets(self): def iter_packets(self) -> Generator[bytes, None, None]:
partial = b'' partial = b''
for page in self._iter_pages(): for page in self._iter_pages():
for data, complete in page.iter_packets(): for data, complete in page.iter_packets():

View File

@ -22,6 +22,10 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
""" """
from __future__ import annotations
from typing import List, Tuple, TypedDict, Any, TYPE_CHECKING, Callable, TypeVar, Literal, Optional, overload
import array import array
import ctypes import ctypes
import ctypes.util import ctypes.util
@ -31,7 +35,24 @@ import os.path
import struct import struct
import sys import sys
from .errors import DiscordException from .errors import DiscordException, InvalidArgument
if TYPE_CHECKING:
T = TypeVar('T')
BAND_CTL = Literal['narrow', 'medium', 'wide', 'superwide', 'full']
SIGNAL_CTL = Literal['auto', 'voice', 'music']
class BandCtl(TypedDict):
narrow: int
medium: int
wide: int
superwide: int
full: int
class SignalCtl(TypedDict):
auto: int
voice: int
music: int
__all__ = ( __all__ = (
'Encoder', 'Encoder',
@ -39,7 +60,7 @@ __all__ = (
'OpusNotLoaded', 'OpusNotLoaded',
) )
log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
c_int_ptr = ctypes.POINTER(ctypes.c_int) c_int_ptr = ctypes.POINTER(ctypes.c_int)
c_int16_ptr = ctypes.POINTER(ctypes.c_int16) c_int16_ptr = ctypes.POINTER(ctypes.c_int16)
@ -76,7 +97,7 @@ CTL_SET_SIGNAL = 4024
CTL_SET_GAIN = 4034 CTL_SET_GAIN = 4034
CTL_LAST_PACKET_DURATION = 4039 CTL_LAST_PACKET_DURATION = 4039
band_ctl = { band_ctl: BandCtl = {
'narrow': 1101, 'narrow': 1101,
'medium': 1102, 'medium': 1102,
'wide': 1103, 'wide': 1103,
@ -84,22 +105,22 @@ band_ctl = {
'full': 1105, 'full': 1105,
} }
signal_ctl = { signal_ctl: SignalCtl = {
'auto': -1000, 'auto': -1000,
'voice': 3001, 'voice': 3001,
'music': 3002, 'music': 3002,
} }
def _err_lt(result, func, args): def _err_lt(result: int, func: Callable, args: List) -> int:
if result < OK: if result < OK:
log.info('error has happened in %s', func.__name__) _log.info('error has happened in %s', func.__name__)
raise OpusError(result) raise OpusError(result)
return result return result
def _err_ne(result, func, args): def _err_ne(result: T, func: Callable, args: List) -> T:
ret = args[-1]._obj ret = args[-1]._obj
if ret.value != OK: if ret.value != OK:
log.info('error has happened in %s', func.__name__) _log.info('error has happened in %s', func.__name__)
raise OpusError(ret.value) raise OpusError(ret.value)
return result return result
@ -108,7 +129,7 @@ def _err_ne(result, func, args):
# The second one are the types of arguments it takes. # The second one are the types of arguments it takes.
# The third is the result type. # The third is the result type.
# The fourth is the error handler. # The fourth is the error handler.
exported_functions = [ exported_functions: List[Tuple[Any, ...]] = [
# Generic # Generic
('opus_get_version_string', ('opus_get_version_string',
None, ctypes.c_char_p, None), None, ctypes.c_char_p, None),
@ -158,7 +179,7 @@ exported_functions = [
[ctypes.c_char_p, ctypes.c_int], ctypes.c_int, _err_lt), [ctypes.c_char_p, ctypes.c_int], ctypes.c_int, _err_lt),
] ]
def libopus_loader(name): def libopus_loader(name: str) -> Any:
# create the library... # create the library...
lib = ctypes.cdll.LoadLibrary(name) lib = ctypes.cdll.LoadLibrary(name)
@ -178,11 +199,11 @@ def libopus_loader(name):
if item[3]: if item[3]:
func.errcheck = item[3] func.errcheck = item[3]
except KeyError: except KeyError:
log.exception("Error assigning check function to %s", func) _log.exception("Error assigning check function to %s", func)
return lib return lib
def _load_default(): def _load_default() -> bool:
global _lib global _lib
try: try:
if sys.platform == 'win32': if sys.platform == 'win32':
@ -198,7 +219,7 @@ def _load_default():
return _lib is not None return _lib is not None
def load_opus(name): def load_opus(name: str) -> None:
"""Loads the libopus shared library for use with voice. """Loads the libopus shared library for use with voice.
If this function is not called then the library uses the function If this function is not called then the library uses the function
@ -236,7 +257,7 @@ def load_opus(name):
global _lib global _lib
_lib = libopus_loader(name) _lib = libopus_loader(name)
def is_loaded(): def is_loaded() -> bool:
"""Function to check if opus lib is successfully loaded either """Function to check if opus lib is successfully loaded either
via the :func:`ctypes.util.find_library` call of :func:`load_opus`. via the :func:`ctypes.util.find_library` call of :func:`load_opus`.
@ -259,10 +280,10 @@ class OpusError(DiscordException):
The error code returned. The error code returned.
""" """
def __init__(self, code): def __init__(self, code: int):
self.code = code self.code: int = code
msg = _lib.opus_strerror(self.code).decode('utf-8') msg = _lib.opus_strerror(self.code).decode('utf-8')
log.info('"%s" has happened', msg) _log.info('"%s" has happened', msg)
super().__init__(msg) super().__init__(msg)
class OpusNotLoaded(DiscordException): class OpusNotLoaded(DiscordException):
@ -286,92 +307,96 @@ class _OpusStruct:
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: int = APPLICATION_AUDIO):
_OpusStruct.get_opus_version() _OpusStruct.get_opus_version()
self.application = application self.application: int = application
self._state = self._create_state() self._state: EncoderStruct = self._create_state()
self.set_bitrate(128) self.set_bitrate(128)
self.set_fec(True) self.set_fec(True)
self.set_expected_packet_loss_percent(0.15) self.set_expected_packet_loss_percent(0.15)
self.set_bandwidth('full') self.set_bandwidth('full')
self.set_signal_type('auto') self.set_signal_type('auto')
def __del__(self): def __del__(self) -> None:
if hasattr(self, '_state'): if hasattr(self, '_state'):
_lib.opus_encoder_destroy(self._state) _lib.opus_encoder_destroy(self._state)
self._state = None # This is a destructor, so it's okay to assign None
self._state = None # type: ignore
def _create_state(self): def _create_state(self) -> EncoderStruct:
ret = ctypes.c_int() ret = ctypes.c_int()
return _lib.opus_encoder_create(self.SAMPLING_RATE, self.CHANNELS, self.application, ctypes.byref(ret)) return _lib.opus_encoder_create(self.SAMPLING_RATE, self.CHANNELS, self.application, ctypes.byref(ret))
def set_bitrate(self, kbps): def set_bitrate(self, kbps: int) -> int:
kbps = min(512, max(16, int(kbps))) kbps = min(512, max(16, int(kbps)))
_lib.opus_encoder_ctl(self._state, CTL_SET_BITRATE, kbps * 1024) _lib.opus_encoder_ctl(self._state, CTL_SET_BITRATE, kbps * 1024)
return kbps return kbps
def set_bandwidth(self, req): def set_bandwidth(self, req: BAND_CTL) -> None:
if req not in band_ctl: if req not in band_ctl:
raise KeyError(f'{req!r} is not a valid bandwidth setting. Try one of: {",".join(band_ctl)}') raise KeyError(f'{req!r} is not a valid bandwidth setting. Try one of: {",".join(band_ctl)}')
k = band_ctl[req] k = band_ctl[req]
_lib.opus_encoder_ctl(self._state, CTL_SET_BANDWIDTH, k) _lib.opus_encoder_ctl(self._state, CTL_SET_BANDWIDTH, k)
def set_signal_type(self, req): def set_signal_type(self, req: SIGNAL_CTL) -> None:
if req not in signal_ctl: if req not in signal_ctl:
raise KeyError(f'{req!r} is not a valid bandwidth setting. Try one of: {",".join(signal_ctl)}') raise KeyError(f'{req!r} is not a valid bandwidth setting. Try one of: {",".join(signal_ctl)}')
k = signal_ctl[req] k = signal_ctl[req]
_lib.opus_encoder_ctl(self._state, CTL_SET_SIGNAL, k) _lib.opus_encoder_ctl(self._state, CTL_SET_SIGNAL, k)
def set_fec(self, enabled=True): def set_fec(self, enabled: bool = True) -> None:
_lib.opus_encoder_ctl(self._state, CTL_SET_FEC, 1 if enabled else 0) _lib.opus_encoder_ctl(self._state, CTL_SET_FEC, 1 if enabled else 0)
def set_expected_packet_loss_percent(self, percentage): def set_expected_packet_loss_percent(self, percentage: float) -> None:
_lib.opus_encoder_ctl(self._state, CTL_SET_PLP, min(100, max(0, int(percentage * 100)))) _lib.opus_encoder_ctl(self._state, CTL_SET_PLP, min(100, max(0, int(percentage * 100)))) # type: ignore
def encode(self, pcm, frame_size): def encode(self, pcm: bytes, frame_size: int) -> bytes:
max_data_bytes = len(pcm) max_data_bytes = len(pcm)
pcm = ctypes.cast(pcm, c_int16_ptr) # bytes can be used to reference pointer
pcm_ptr = ctypes.cast(pcm, c_int16_ptr) # type: ignore
data = (ctypes.c_char * max_data_bytes)() data = (ctypes.c_char * max_data_bytes)()
ret = _lib.opus_encode(self._state, pcm, frame_size, data, max_data_bytes) ret = _lib.opus_encode(self._state, pcm_ptr, frame_size, data, max_data_bytes)
return array.array('b', data[:ret]).tobytes() # array can be initialized with bytes but mypy doesn't know
return array.array('b', data[:ret]).tobytes() # type: ignore
class Decoder(_OpusStruct): class Decoder(_OpusStruct):
def __init__(self): def __init__(self):
_OpusStruct.get_opus_version() _OpusStruct.get_opus_version()
self._state = self._create_state() self._state: DecoderStruct = self._create_state()
def __del__(self): def __del__(self) -> None:
if hasattr(self, '_state'): if hasattr(self, '_state'):
_lib.opus_decoder_destroy(self._state) _lib.opus_decoder_destroy(self._state)
self._state = None # This is a destructor, so it's okay to assign None
self._state = None # type: ignore
def _create_state(self): def _create_state(self) -> DecoderStruct:
ret = ctypes.c_int() ret = ctypes.c_int()
return _lib.opus_decoder_create(self.SAMPLING_RATE, self.CHANNELS, ctypes.byref(ret)) return _lib.opus_decoder_create(self.SAMPLING_RATE, self.CHANNELS, ctypes.byref(ret))
@staticmethod @staticmethod
def packet_get_nb_frames(data): def packet_get_nb_frames(data: bytes) -> int:
"""Gets the number of frames in an Opus packet""" """Gets the number of frames in an Opus packet"""
return _lib.opus_packet_get_nb_frames(data, len(data)) return _lib.opus_packet_get_nb_frames(data, len(data))
@staticmethod @staticmethod
def packet_get_nb_channels(data): def packet_get_nb_channels(data: bytes) -> int:
"""Gets the number of channels in an Opus packet""" """Gets the number of channels in an Opus packet"""
return _lib.opus_packet_get_nb_channels(data) return _lib.opus_packet_get_nb_channels(data)
@classmethod @classmethod
def packet_get_samples_per_frame(cls, data): def packet_get_samples_per_frame(cls, data: bytes) -> int:
"""Gets the number of samples per frame from an Opus packet""" """Gets the number of samples per frame from an Opus packet"""
return _lib.opus_packet_get_samples_per_frame(data, cls.SAMPLING_RATE) return _lib.opus_packet_get_samples_per_frame(data, cls.SAMPLING_RATE)
def _set_gain(self, adjustment): def _set_gain(self, adjustment: int) -> int:
"""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.
@ -383,26 +408,34 @@ class Decoder(_OpusStruct):
""" """
return _lib.opus_decoder_ctl(self._state, CTL_SET_GAIN, adjustment) return _lib.opus_decoder_ctl(self._state, CTL_SET_GAIN, adjustment)
def set_gain(self, dB): def set_gain(self, dB: float) -> int:
"""Sets the decoder gain in dB, from -128 to 128.""" """Sets the decoder gain in dB, from -128 to 128."""
dB_Q8 = max(-32768, min(32767, round(dB * 256))) # dB * 2^n where n is 8 (Q8) dB_Q8 = max(-32768, min(32767, round(dB * 256))) # dB * 2^n where n is 8 (Q8)
return self._set_gain(dB_Q8) return self._set_gain(dB_Q8)
def set_volume(self, mult): def set_volume(self, mult: float) -> int:
"""Sets the output volume as a float percent, i.e. 0.5 for 50%, 1.75 for 175%, etc.""" """Sets the output volume as a float percent, i.e. 0.5 for 50%, 1.75 for 175%, etc."""
return self.set_gain(20 * math.log10(mult)) # amplitude ratio return self.set_gain(20 * math.log10(mult)) # amplitude ratio
def _get_last_packet_duration(self): def _get_last_packet_duration(self) -> int:
"""Gets the duration (in samples) of the last packet successfully decoded or concealed.""" """Gets the duration (in samples) of the last packet successfully decoded or concealed."""
ret = ctypes.c_int32() ret = ctypes.c_int32()
_lib.opus_decoder_ctl(self._state, CTL_LAST_PACKET_DURATION, ctypes.byref(ret)) _lib.opus_decoder_ctl(self._state, CTL_LAST_PACKET_DURATION, ctypes.byref(ret))
return ret.value return ret.value
def decode(self, data, *, fec=False): @overload
def decode(self, data: bytes, *, fec: bool) -> bytes:
...
@overload
def decode(self, data: Literal[None], *, fec: Literal[False]) -> bytes:
...
def decode(self, data: Optional[bytes], *, fec: bool = False) -> bytes:
if data is None and fec: if data is None and fec:
raise OpusError("Invalid arguments: FEC cannot be used with null data") raise InvalidArgument("Invalid arguments: FEC cannot be used with null data")
if data is None: if data is None:
frame_size = self._get_last_packet_duration() or self.SAMPLES_PER_FRAME frame_size = self._get_last_packet_duration() or self.SAMPLES_PER_FRAME

View File

@ -147,7 +147,7 @@ class Permissions(BaseFlags):
"""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(0b11111111111111111111111111111111111111) return cls(0b111111111111111111111111111111111111111)
@classmethod @classmethod
def all_channel(cls: Type[P]) -> P: def all_channel(cls: Type[P]) -> P:
@ -169,10 +169,11 @@ class Permissions(BaseFlags):
Added :attr:`stream`, :attr:`priority_speaker` and :attr:`use_slash_commands` permissions. Added :attr:`stream`, :attr:`priority_speaker` and :attr:`use_slash_commands` permissions.
.. versionchanged:: 2.0 .. versionchanged:: 2.0
Added :attr:`use_threads`, :attr:`use_private_threads`, :attr:`manage_threads`, Added :attr:`create_public_threads`, :attr:`create_private_threads`, :attr:`manage_threads`,
:attr:`use_external_stickers` and :attr:`request_to_speak` permissions. :attr:`use_external_stickers`, :attr:`send_messages_in_threads` and
:attr:`request_to_speak` permissions.
""" """
return cls(0b11110110110011111101111111111101010001) return cls(0b111110110110011111101111111111101010001)
@classmethod @classmethod
def general(cls: Type[P]) -> P: def general(cls: Type[P]) -> P:
@ -206,10 +207,10 @@ class Permissions(BaseFlags):
Added :attr:`use_slash_commands` permission. Added :attr:`use_slash_commands` permission.
.. versionchanged:: 2.0 .. versionchanged:: 2.0
Added :attr:`use_threads`, :attr:`use_private_threads`, :attr:`manage_threads` Added :attr:`create_public_threads`, :attr:`create_private_threads`, :attr:`manage_threads`,
and :attr:`use_external_stickers` permissions. :attr:`send_messages_in_threads` and :attr:`use_external_stickers` permissions.
""" """
return cls(0b11110010000000000001111111100001000000) return cls(0b111110010000000000001111111100001000000)
@classmethod @classmethod
def voice(cls: Type[P]) -> P: def voice(cls: Type[P]) -> P:
@ -471,7 +472,7 @@ class Permissions(BaseFlags):
return 1 << 30 return 1 << 30
@make_permission_alias('manage_emojis') @make_permission_alias('manage_emojis')
def manage_emojis_and_stickers(self): def manage_emojis_and_stickers(self) -> int:
""":class:`bool`: An alias for :attr:`manage_emojis`. """:class:`bool`: An alias for :attr:`manage_emojis`.
.. versionadded:: 2.0 .. versionadded:: 2.0
@ -511,16 +512,16 @@ class Permissions(BaseFlags):
return 1 << 34 return 1 << 34
@flag_value @flag_value
def use_threads(self) -> int: def create_public_threads(self) -> int:
""":class:`bool`: Returns ``True`` if a user can create and participate in public threads. """:class:`bool`: Returns ``True`` if a user can create public threads.
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
return 1 << 35 return 1 << 35
@flag_value @flag_value
def use_private_threads(self) -> int: def create_private_threads(self) -> int:
""":class:`bool`: Returns ``True`` if a user can create and participate in private threads. """:class:`bool`: Returns ``True`` if a user can create private threads.
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
@ -542,6 +543,14 @@ class Permissions(BaseFlags):
""" """
return 1 << 37 return 1 << 37
@flag_value
def send_messages_in_threads(self) -> int:
""":class:`bool`: Returns ``True`` if a user can send messages in threads.
.. versionadded:: 2.0
"""
return 1 << 38
PO = TypeVar('PO', bound='PermissionOverwrite') PO = TypeVar('PO', bound='PermissionOverwrite')
def _augment_from_permissions(cls): def _augment_from_permissions(cls):
@ -645,12 +654,16 @@ class PermissionOverwrite:
manage_permissions: Optional[bool] manage_permissions: Optional[bool]
manage_webhooks: Optional[bool] manage_webhooks: Optional[bool]
manage_emojis: Optional[bool] manage_emojis: Optional[bool]
manage_emojis_and_stickers: Optional[bool]
use_slash_commands: Optional[bool] use_slash_commands: Optional[bool]
request_to_speak: Optional[bool] request_to_speak: Optional[bool]
manage_events: Optional[bool] manage_events: Optional[bool]
manage_threads: Optional[bool] manage_threads: Optional[bool]
use_threads: Optional[bool] create_public_threads: Optional[bool]
use_private_threads: Optional[bool] create_private_threads: Optional[bool]
send_messages_in_threads: Optional[bool]
external_stickers: Optional[bool]
use_external_stickers: Optional[bool]
def __init__(self, **kwargs: Optional[bool]): def __init__(self, **kwargs: Optional[bool]):
self._values: Dict[str, Optional[bool]] = {} self._values: Dict[str, Optional[bool]] = {}
@ -673,7 +686,7 @@ class PermissionOverwrite:
else: else:
self._values[key] = value self._values[key] = value
def pair(self): def pair(self) -> Tuple[Permissions, Permissions]:
"""Tuple[:class:`Permissions`, :class:`Permissions`]: Returns the (allow, deny) pair from this overwrite.""" """Tuple[:class:`Permissions`, :class:`Permissions`]: Returns the (allow, deny) pair from this overwrite."""
allow = Permissions.none() allow = Permissions.none()

View File

@ -50,7 +50,7 @@ if TYPE_CHECKING:
AT = TypeVar('AT', bound='AudioSource') AT = TypeVar('AT', bound='AudioSource')
FT = TypeVar('FT', bound='FFmpegOpusAudio') FT = TypeVar('FT', bound='FFmpegOpusAudio')
log: logging.Logger = logging.getLogger(__name__) _log = logging.getLogger(__name__)
__all__ = ( __all__ = (
'AudioSource', 'AudioSource',
@ -140,13 +140,25 @@ class FFmpegAudio(AudioSource):
.. versionadded:: 1.3 .. versionadded:: 1.3
""" """
def __init__(self, source: str, *, executable: str = 'ffmpeg', args: Any, **subprocess_kwargs: Any): def __init__(self, source: Union[str, io.BufferedIOBase], *, executable: str = 'ffmpeg', args: Any, **subprocess_kwargs: Any):
piping = subprocess_kwargs.get('stdin') == subprocess.PIPE
if piping and isinstance(source, str):
raise TypeError("parameter conflict: 'source' parameter cannot be a string when piping to stdin")
args = [executable, *args] args = [executable, *args]
kwargs = {'stdout': subprocess.PIPE} kwargs = {'stdout': subprocess.PIPE}
kwargs.update(subprocess_kwargs) kwargs.update(subprocess_kwargs)
self._process: subprocess.Popen = self._spawn_process(args, **kwargs) self._process: subprocess.Popen = self._spawn_process(args, **kwargs)
self._stdout: IO[bytes] = self._process.stdout # type: ignore self._stdout: IO[bytes] = self._process.stdout # type: ignore
self._stdin: Optional[IO[Bytes]] = None
self._pipe_thread: Optional[threading.Thread] = None
if piping:
n = f'popen-stdin-writer:{id(self):#x}'
self._stdin = self._process.stdin
self._pipe_thread = threading.Thread(target=self._pipe_writer, args=(source,), daemon=True, name=n)
self._pipe_thread.start()
def _spawn_process(self, args: Any, **subprocess_kwargs: Any) -> subprocess.Popen: def _spawn_process(self, args: Any, **subprocess_kwargs: Any) -> subprocess.Popen:
process = None process = None
@ -160,26 +172,44 @@ class FFmpegAudio(AudioSource):
else: else:
return process return process
def cleanup(self) -> None: def _kill_process(self) -> None:
proc = self._process proc = self._process
if proc is MISSING: if proc is MISSING:
return return
log.info('Preparing to terminate ffmpeg process %s.', proc.pid) _log.info('Preparing to terminate ffmpeg process %s.', proc.pid)
try: try:
proc.kill() proc.kill()
except Exception: except Exception:
log.exception("Ignoring error attempting to kill ffmpeg process %s", proc.pid) _log.exception('Ignoring error attempting to kill ffmpeg process %s', proc.pid)
if proc.poll() is None: if proc.poll() is None:
log.info('ffmpeg process %s has not terminated. Waiting to terminate...', proc.pid) _log.info('ffmpeg process %s has not terminated. Waiting to terminate...', proc.pid)
proc.communicate() proc.communicate()
log.info('ffmpeg process %s should have terminated with a return code of %s.', proc.pid, proc.returncode) _log.info('ffmpeg process %s should have terminated with a return code of %s.', proc.pid, proc.returncode)
else: else:
log.info('ffmpeg process %s successfully terminated with return code of %s.', proc.pid, proc.returncode) _log.info('ffmpeg process %s successfully terminated with return code of %s.', proc.pid, proc.returncode)
self._process = self._stdout = MISSING
def _pipe_writer(self, source: io.BufferedIOBase) -> None:
while self._process:
# arbitrarily large read size
data = source.read(8192)
if not data:
self._process.terminate()
return
try:
self._stdin.write(data)
except Exception:
_log.debug('Write error for %s, this is probably not a problem', self, exc_info=True)
# at this point the source data is either exhausted or the process is fubar
self._process.terminate()
return
def cleanup(self) -> None:
self._kill_process()
self._process = self._stdout = self._stdin = MISSING
class FFmpegPCMAudio(FFmpegAudio): class FFmpegPCMAudio(FFmpegAudio):
"""An audio source from FFmpeg (or AVConv). """An audio source from FFmpeg (or AVConv).
@ -218,7 +248,7 @@ class FFmpegPCMAudio(FFmpegAudio):
def __init__( def __init__(
self, self,
source: str, source: Union[str, io.BufferedIOBase],
*, *,
executable: str = 'ffmpeg', executable: str = 'ffmpeg',
pipe: bool = False, pipe: bool = False,
@ -227,7 +257,7 @@ class FFmpegPCMAudio(FFmpegAudio):
options: Optional[str] = None options: Optional[str] = None
) -> None: ) -> None:
args = [] args = []
subprocess_kwargs = {'stdin': source if pipe else subprocess.DEVNULL, 'stderr': stderr} subprocess_kwargs = {'stdin': subprocess.PIPE if pipe else subprocess.DEVNULL, 'stderr': stderr}
if isinstance(before_options, str): if isinstance(before_options, str):
args.extend(shlex.split(before_options)) args.extend(shlex.split(before_options))
@ -315,7 +345,7 @@ class FFmpegOpusAudio(FFmpegAudio):
def __init__( def __init__(
self, self,
source: str, source: Union[str, io.BufferedIOBase],
*, *,
bitrate: int = 128, bitrate: int = 128,
codec: Optional[str] = None, codec: Optional[str] = None,
@ -327,7 +357,7 @@ class FFmpegOpusAudio(FFmpegAudio):
) -> None: ) -> None:
args = [] args = []
subprocess_kwargs = {'stdin': source if pipe else subprocess.DEVNULL, 'stderr': stderr} subprocess_kwargs = {'stdin': subprocess.PIPE if pipe else subprocess.DEVNULL, 'stderr': stderr}
if isinstance(before_options, str): if isinstance(before_options, str):
args.extend(shlex.split(before_options)) args.extend(shlex.split(before_options))
@ -384,7 +414,6 @@ class FFmpegOpusAudio(FFmpegAudio):
def custom_probe(source, executable): def custom_probe(source, executable):
# some analysis code here # some analysis code here
return codec, bitrate return codec, bitrate
source = await discord.FFmpegOpusAudio.from_probe("song.webm", method=custom_probe) source = await discord.FFmpegOpusAudio.from_probe("song.webm", method=custom_probe)
@ -480,18 +509,18 @@ class FFmpegOpusAudio(FFmpegAudio):
codec, bitrate = await loop.run_in_executor(None, lambda: probefunc(source, executable)) # type: ignore codec, bitrate = await loop.run_in_executor(None, lambda: probefunc(source, executable)) # type: ignore
except Exception: except Exception:
if not fallback: if not fallback:
log.exception("Probe '%s' using '%s' failed", method, executable) _log.exception("Probe '%s' using '%s' failed", method, executable)
return # type: ignore return # type: ignore
log.exception("Probe '%s' using '%s' failed, trying fallback", method, executable) _log.exception("Probe '%s' using '%s' failed, trying fallback", method, executable)
try: try:
codec, bitrate = await loop.run_in_executor(None, lambda: fallback(source, executable)) # type: ignore codec, bitrate = await loop.run_in_executor(None, lambda: fallback(source, executable)) # type: ignore
except Exception: except Exception:
log.exception("Fallback probe using '%s' failed", executable) _log.exception("Fallback probe using '%s' failed", executable)
else: else:
log.info("Fallback probe found codec=%s, bitrate=%s", codec, bitrate) _log.info("Fallback probe found codec=%s, bitrate=%s", codec, bitrate)
else: else:
log.info("Probe found codec=%s, bitrate=%s", codec, bitrate) _log.info("Probe found codec=%s, bitrate=%s", codec, bitrate)
finally: finally:
return codec, bitrate return codec, bitrate
@ -656,12 +685,12 @@ class AudioPlayer(threading.Thread):
try: try:
self.after(error) self.after(error)
except Exception as exc: except Exception as exc:
log.exception('Calling the after function failed.') _log.exception('Calling the after function failed.')
exc.__context__ = error exc.__context__ = error
traceback.print_exception(type(exc), exc, exc.__traceback__) traceback.print_exception(type(exc), exc, exc.__traceback__)
elif error: elif error:
msg = f'Exception in voice thread {self.name}' msg = f'Exception in voice thread {self.name}'
log.exception(msg, exc_info=error) _log.exception(msg, exc_info=error)
print(msg, file=sys.stderr) print(msg, file=sys.stderr)
traceback.print_exception(type(error), error, error.__traceback__) traceback.print_exception(type(error), error, error.__traceback__)
@ -698,4 +727,4 @@ class AudioPlayer(threading.Thread):
try: try:
asyncio.run_coroutine_threadsafe(self.client.ws.speak(speaking), self.client.loop) asyncio.run_coroutine_threadsafe(self.client.ws.speak(speaking), self.client.loop)
except Exception as e: except Exception as e:
log.info("Speaking call in player failed: %s", e) _log.info("Speaking call in player failed: %s", e)

0
discord/py.typed Normal file
View File

View File

@ -42,6 +42,7 @@ if TYPE_CHECKING:
Role as RolePayload, Role as RolePayload,
RoleTags as RoleTagPayload, RoleTags as RoleTagPayload,
) )
from .types.guild import RolePositionUpdate
from .guild import Guild from .guild import Guild
from .member import Member from .member import Member
from .state import ConnectionState from .state import ConnectionState
@ -140,6 +141,14 @@ class Role(Hashable):
Returns the role's name. Returns the role's name.
.. describe:: str(x)
Returns the role's ID.
.. describe:: int(x)
Returns the role's ID.
Attributes Attributes
---------- ----------
id: :class:`int` id: :class:`int`
@ -194,6 +203,9 @@ class Role(Hashable):
def __str__(self) -> str: def __str__(self) -> str:
return self.name return self.name
def __int__(self) -> int:
return self.id
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<Role id={self.id} name={self.name!r}>' return f'<Role id={self.id} name={self.name!r}>'
@ -336,7 +348,7 @@ class Role(Hashable):
else: else:
roles.append(self.id) roles.append(self.id)
payload = [{"id": z[0], "position": z[1]} for z in zip(roles, change_range)] payload: List[RolePositionUpdate] = [{"id": z[0], "position": z[1]} for z in zip(roles, change_range)]
await http.move_role_position(self.guild.id, payload, reason=reason) await http.move_role_position(self.guild.id, payload, reason=reason)
async def edit( async def edit(
@ -350,7 +362,7 @@ class Role(Hashable):
mentionable: bool = MISSING, mentionable: bool = MISSING,
position: int = MISSING, position: int = MISSING,
reason: Optional[str] = MISSING, reason: Optional[str] = MISSING,
) -> None: ) -> Optional[Role]:
"""|coro| """|coro|
Edits the role. Edits the role.
@ -363,6 +375,9 @@ class Role(Hashable):
.. versionchanged:: 1.4 .. versionchanged:: 1.4
Can now pass ``int`` to ``colour`` keyword-only parameter. Can now pass ``int`` to ``colour`` keyword-only parameter.
.. versionchanged:: 2.0
Edits are no longer in-place, the newly edited role is returned instead.
Parameters Parameters
----------- -----------
name: :class:`str` name: :class:`str`
@ -390,11 +405,14 @@ class Role(Hashable):
InvalidArgument InvalidArgument
An invalid position was given or the default An invalid position was given or the default
role was asked to be moved. role was asked to be moved.
"""
Returns
--------
:class:`Role`
The newly edited role.
"""
if position is not MISSING: if position is not MISSING:
await self._move(position, reason=reason) await self._move(position, reason=reason)
self.position = position
payload: Dict[str, Any] = {} payload: Dict[str, Any] = {}
if color is not MISSING: if color is not MISSING:
@ -419,7 +437,7 @@ class Role(Hashable):
payload['mentionable'] = mentionable payload['mentionable'] = mentionable
data = await self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload) data = await self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload)
self._update(data) return Role(guild=self.guild, data=data, state=self._state)
async def delete(self, *, reason: Optional[str] = None) -> None: async def delete(self, *, reason: Optional[str] = None) -> None:
"""|coro| """|coro|

View File

@ -22,8 +22,9 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
""" """
from __future__ import annotations
import asyncio import asyncio
import itertools
import logging import logging
import aiohttp import aiohttp
@ -34,22 +35,30 @@ from .backoff import ExponentialBackoff
from .gateway import * from .gateway import *
from .errors import ( from .errors import (
ClientException, ClientException,
InvalidArgument,
HTTPException, HTTPException,
GatewayNotFound, GatewayNotFound,
ConnectionClosed, ConnectionClosed,
PrivilegedIntentsRequired, PrivilegedIntentsRequired,
) )
from . import utils
from .enums import Status from .enums import Status
from typing import TYPE_CHECKING, Any, Callable, Tuple, Type, Optional, List, Dict, TypeVar
if TYPE_CHECKING:
from .gateway import DiscordWebSocket
from .activity import BaseActivity
from .enums import Status
EI = TypeVar('EI', bound='EventItem')
__all__ = ( __all__ = (
'AutoShardedClient', 'AutoShardedClient',
'ShardInfo', 'ShardInfo',
) )
log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
class EventType: class EventType:
close = 0 close = 0
@ -59,39 +68,41 @@ class EventType:
terminate = 4 terminate = 4
clean_close = 5 clean_close = 5
class EventItem: class EventItem:
__slots__ = ('type', 'shard', 'error') __slots__ = ('type', 'shard', 'error')
def __init__(self, etype, shard, error): def __init__(self, etype: int, shard: Optional['Shard'], error: Optional[Exception]) -> None:
self.type = etype self.type: int = etype
self.shard = shard self.shard: Optional['Shard'] = shard
self.error = error self.error: Optional[Exception] = error
def __lt__(self, other): def __lt__(self: EI, other: EI) -> bool:
if not isinstance(other, EventItem): if not isinstance(other, EventItem):
return NotImplemented return NotImplemented
return self.type < other.type return self.type < other.type
def __eq__(self, other): def __eq__(self: EI, other: EI) -> bool:
if not isinstance(other, EventItem): if not isinstance(other, EventItem):
return NotImplemented return NotImplemented
return self.type == other.type return self.type == other.type
def __hash__(self): def __hash__(self) -> int:
return hash(self.type) return hash(self.type)
class Shard: class Shard:
def __init__(self, ws, client, queue_put): def __init__(self, ws: DiscordWebSocket, client: AutoShardedClient, queue_put: Callable[[EventItem], None]) -> None:
self.ws = ws self.ws: DiscordWebSocket = ws
self._client = client self._client: Client = client
self._dispatch = client.dispatch self._dispatch: Callable[..., None] = client.dispatch
self._queue_put = queue_put self._queue_put: Callable[[EventItem], None] = queue_put
self.loop = self._client.loop self.loop: asyncio.AbstractEventLoop = self._client.loop
self._disconnect = False self._disconnect: bool = False
self._reconnect = client._reconnect self._reconnect = client._reconnect
self._backoff = ExponentialBackoff() self._backoff: ExponentialBackoff = ExponentialBackoff()
self._task = None self._task: Optional[asyncio.Task] = None
self._handled_exceptions = ( self._handled_exceptions: Tuple[Type[Exception], ...] = (
OSError, OSError,
HTTPException, HTTPException,
GatewayNotFound, GatewayNotFound,
@ -101,25 +112,26 @@ class Shard:
) )
@property @property
def id(self): def id(self) -> int:
return self.ws.shard_id # DiscordWebSocket.shard_id is set in the from_client classmethod
return self.ws.shard_id # type: ignore
def launch(self): def launch(self) -> None:
self._task = self.loop.create_task(self.worker()) self._task = self.loop.create_task(self.worker())
def _cancel_task(self): def _cancel_task(self) -> None:
if self._task is not None and not self._task.done(): if self._task is not None and not self._task.done():
self._task.cancel() self._task.cancel()
async def close(self): async def close(self) -> None:
self._cancel_task() self._cancel_task()
await self.ws.close(code=1000) await self.ws.close(code=1000)
async def disconnect(self): async def disconnect(self) -> None:
await self.close() await self.close()
self._dispatch('shard_disconnect', self.id) self._dispatch('shard_disconnect', self.id)
async def _handle_disconnect(self, e): async def _handle_disconnect(self, e: Exception) -> None:
self._dispatch('disconnect') self._dispatch('disconnect')
self._dispatch('shard_disconnect', self.id) self._dispatch('shard_disconnect', self.id)
if not self._reconnect: if not self._reconnect:
@ -144,11 +156,11 @@ class Shard:
return return
retry = self._backoff.delay() retry = self._backoff.delay()
log.error('Attempting a reconnect for shard ID %s in %.2fs', self.id, retry, exc_info=e) _log.error('Attempting a reconnect for shard ID %s in %.2fs', self.id, retry, exc_info=e)
await asyncio.sleep(retry) await asyncio.sleep(retry)
self._queue_put(EventItem(EventType.reconnect, self, e)) self._queue_put(EventItem(EventType.reconnect, self, e))
async def worker(self): async def worker(self) -> None:
while not self._client.is_closed(): while not self._client.is_closed():
try: try:
await self.ws.poll_event() await self.ws.poll_event()
@ -165,14 +177,19 @@ class Shard:
self._queue_put(EventItem(EventType.terminate, self, e)) self._queue_put(EventItem(EventType.terminate, self, e))
break break
async def reidentify(self, exc): async def reidentify(self, exc: ReconnectWebSocket) -> None:
self._cancel_task() self._cancel_task()
self._dispatch('disconnect') self._dispatch('disconnect')
self._dispatch('shard_disconnect', self.id) self._dispatch('shard_disconnect', self.id)
log.info('Got a request to %s the websocket at Shard ID %s.', exc.op, self.id) _log.info('Got a request to %s the websocket at Shard ID %s.', exc.op, self.id)
try: try:
coro = DiscordWebSocket.from_client(self._client, resume=exc.resume, shard_id=self.id, coro = DiscordWebSocket.from_client(
session=self.ws.session_id, sequence=self.ws.sequence) self._client,
resume=exc.resume,
shard_id=self.id,
session=self.ws.session_id,
sequence=self.ws.sequence,
)
self.ws = await asyncio.wait_for(coro, timeout=60.0) self.ws = await asyncio.wait_for(coro, timeout=60.0)
except self._handled_exceptions as e: except self._handled_exceptions as e:
await self._handle_disconnect(e) await self._handle_disconnect(e)
@ -183,7 +200,7 @@ class Shard:
else: else:
self.launch() self.launch()
async def reconnect(self): async def reconnect(self) -> None:
self._cancel_task() self._cancel_task()
try: try:
coro = DiscordWebSocket.from_client(self._client, shard_id=self.id) coro = DiscordWebSocket.from_client(self._client, shard_id=self.id)
@ -197,6 +214,7 @@ class Shard:
else: else:
self.launch() self.launch()
class ShardInfo: class ShardInfo:
"""A class that gives information and control over a specific shard. """A class that gives information and control over a specific shard.
@ -215,16 +233,16 @@ class ShardInfo:
__slots__ = ('_parent', 'id', 'shard_count') __slots__ = ('_parent', 'id', 'shard_count')
def __init__(self, parent, shard_count): def __init__(self, parent: Shard, shard_count: Optional[int]) -> None:
self._parent = parent self._parent: Shard = parent
self.id = parent.id self.id: int = parent.id
self.shard_count = shard_count self.shard_count: Optional[int] = shard_count
def is_closed(self): def is_closed(self) -> bool:
""":class:`bool`: Whether the shard connection is currently closed.""" """:class:`bool`: Whether the shard connection is currently closed."""
return not self._parent.ws.open return not self._parent.ws.open
async def disconnect(self): async def disconnect(self) -> None:
"""|coro| """|coro|
Disconnects a shard. When this is called, the shard connection will no Disconnects a shard. When this is called, the shard connection will no
@ -237,7 +255,7 @@ class ShardInfo:
await self._parent.disconnect() await self._parent.disconnect()
async def reconnect(self): async def reconnect(self) -> None:
"""|coro| """|coro|
Disconnects and then connects the shard again. Disconnects and then connects the shard again.
@ -246,7 +264,7 @@ class ShardInfo:
await self._parent.disconnect() await self._parent.disconnect()
await self._parent.reconnect() await self._parent.reconnect()
async def connect(self): async def connect(self) -> None:
"""|coro| """|coro|
Connects a shard. If the shard is already connected this does nothing. Connects a shard. If the shard is already connected this does nothing.
@ -257,11 +275,11 @@ class ShardInfo:
await self._parent.reconnect() await self._parent.reconnect()
@property @property
def latency(self): def latency(self) -> float:
""":class:`float`: Measures latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds for this shard.""" """:class:`float`: Measures latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds for this shard."""
return self._parent.ws.latency return self._parent.ws.latency
def is_ws_ratelimited(self): def is_ws_ratelimited(self) -> bool:
""":class:`bool`: Whether the websocket is currently rate limited. """:class:`bool`: Whether the websocket is currently rate limited.
This can be useful to know when deciding whether you should query members This can be useful to know when deciding whether you should query members
@ -271,6 +289,7 @@ class ShardInfo:
""" """
return self._parent.ws.is_ratelimited() return self._parent.ws.is_ratelimited()
class AutoShardedClient(Client): class AutoShardedClient(Client):
"""A client similar to :class:`Client` except it handles the complications """A client similar to :class:`Client` except it handles the complications
of sharding for the user into a more manageable and transparent single of sharding for the user into a more manageable and transparent single
@ -297,9 +316,13 @@ class AutoShardedClient(Client):
shard_ids: Optional[List[:class:`int`]] shard_ids: Optional[List[:class:`int`]]
An optional list of shard_ids to launch the shards with. An optional list of shard_ids to launch the shards with.
""" """
def __init__(self, *args, loop=None, **kwargs):
if TYPE_CHECKING:
_connection: AutoShardedConnectionState
def __init__(self, *args: Any, loop: Optional[asyncio.AbstractEventLoop] = None, **kwargs: Any) -> None:
kwargs.pop('shard_id', None) kwargs.pop('shard_id', None)
self.shard_ids = kwargs.pop('shard_ids', None) self.shard_ids: Optional[List[int]] = kwargs.pop('shard_ids', None)
super().__init__(*args, loop=loop, **kwargs) super().__init__(*args, loop=loop, **kwargs)
if self.shard_ids is not None: if self.shard_ids is not None:
@ -315,18 +338,24 @@ class AutoShardedClient(Client):
self._connection._get_client = lambda: self self._connection._get_client = lambda: self
self.__queue = asyncio.PriorityQueue() self.__queue = asyncio.PriorityQueue()
def _get_websocket(self, guild_id=None, *, shard_id=None): def _get_websocket(self, guild_id: Optional[int] = None, *, shard_id: Optional[int] = None) -> DiscordWebSocket:
if shard_id is None: if shard_id is None:
shard_id = (guild_id >> 22) % self.shard_count # guild_id won't be None if shard_id is None and shard_count won't be None here
shard_id = (guild_id >> 22) % self.shard_count # type: ignore
return self.__shards[shard_id].ws return self.__shards[shard_id].ws
def _get_state(self, **options): def _get_state(self, **options: Any) -> AutoShardedConnectionState:
return AutoShardedConnectionState(dispatch=self.dispatch, return AutoShardedConnectionState(
dispatch=self.dispatch,
handlers=self._handlers, handlers=self._handlers,
hooks=self._hooks, http=self.http, loop=self.loop, **options) hooks=self._hooks,
http=self.http,
loop=self.loop,
**options,
)
@property @property
def latency(self): def latency(self) -> float:
""":class:`float`: Measures latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds. """:class:`float`: Measures latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds.
This operates similarly to :meth:`Client.latency` except it uses the average This operates similarly to :meth:`Client.latency` except it uses the average
@ -338,14 +367,14 @@ class AutoShardedClient(Client):
return sum(latency for _, latency in self.latencies) / len(self.__shards) return sum(latency for _, latency in self.latencies) / len(self.__shards)
@property @property
def latencies(self): def latencies(self) -> List[Tuple[int, float]]:
"""List[Tuple[:class:`int`, :class:`float`]]: A list of latencies between a HEARTBEAT and a HEARTBEAT_ACK in seconds. """List[Tuple[:class:`int`, :class:`float`]]: A list of latencies between a HEARTBEAT and a HEARTBEAT_ACK in seconds.
This returns a list of tuples with elements ``(shard_id, latency)``. This returns a list of tuples with elements ``(shard_id, latency)``.
""" """
return [(shard_id, shard.ws.latency) for shard_id, shard in self.__shards.items()] return [(shard_id, shard.ws.latency) for shard_id, shard in self.__shards.items()]
def get_shard(self, shard_id): def get_shard(self, shard_id: int) -> Optional[ShardInfo]:
"""Optional[:class:`ShardInfo`]: Gets the shard information at a given shard ID or ``None`` if not found.""" """Optional[:class:`ShardInfo`]: Gets the shard information at a given shard ID or ``None`` if not found."""
try: try:
parent = self.__shards[shard_id] parent = self.__shards[shard_id]
@ -355,16 +384,16 @@ class AutoShardedClient(Client):
return ShardInfo(parent, self.shard_count) return ShardInfo(parent, self.shard_count)
@property @property
def shards(self): def shards(self) -> Dict[int, ShardInfo]:
"""Mapping[int, :class:`ShardInfo`]: Returns a mapping of shard IDs to their respective info object.""" """Mapping[int, :class:`ShardInfo`]: Returns a mapping of shard IDs to their respective info object."""
return {shard_id: ShardInfo(parent, self.shard_count) for shard_id, parent in self.__shards.items()} return {shard_id: ShardInfo(parent, self.shard_count) for shard_id, parent in self.__shards.items()}
async def launch_shard(self, gateway, shard_id, *, initial=False): async def launch_shard(self, gateway: str, shard_id: int, *, initial: bool = False) -> None:
try: try:
coro = DiscordWebSocket.from_client(self, initial=initial, gateway=gateway, shard_id=shard_id) coro = DiscordWebSocket.from_client(self, initial=initial, gateway=gateway, shard_id=shard_id)
ws = await asyncio.wait_for(coro, timeout=180.0) ws = await asyncio.wait_for(coro, timeout=180.0)
except Exception: except Exception:
log.exception('Failed to connect for shard_id: %s. Retrying...', shard_id) _log.exception('Failed to connect for shard_id: %s. Retrying...', shard_id)
await asyncio.sleep(5.0) await asyncio.sleep(5.0)
return await self.launch_shard(gateway, shard_id) return await self.launch_shard(gateway, shard_id)
@ -372,7 +401,7 @@ class AutoShardedClient(Client):
self.__shards[shard_id] = ret = Shard(ws, self, self.__queue.put_nowait) self.__shards[shard_id] = ret = Shard(ws, self, self.__queue.put_nowait)
ret.launch() ret.launch()
async def launch_shards(self): async def launch_shards(self) -> None:
if self.shard_count is None: if self.shard_count is None:
self.shard_count, gateway = await self.http.get_bot_gateway() self.shard_count, gateway = await self.http.get_bot_gateway()
else: else:
@ -389,7 +418,7 @@ class AutoShardedClient(Client):
self._connection.shards_launched.set() self._connection.shards_launched.set()
async def connect(self, *, reconnect=True): async def connect(self, *, reconnect: bool = True) -> None:
self._reconnect = reconnect self._reconnect = reconnect
await self.launch_shards() await self.launch_shards()
@ -413,7 +442,7 @@ class AutoShardedClient(Client):
elif item.type == EventType.clean_close: elif item.type == EventType.clean_close:
return return
async def close(self): async def close(self) -> None:
"""|coro| """|coro|
Closes the connection to Discord. Closes the connection to Discord.
@ -425,7 +454,7 @@ class AutoShardedClient(Client):
for vc in self.voice_clients: for vc in self.voice_clients:
try: try:
await vc.disconnect() await vc.disconnect(force=True)
except Exception: except Exception:
pass pass
@ -436,7 +465,13 @@ class AutoShardedClient(Client):
await self.http.close() await self.http.close()
self.__queue.put_nowait(EventItem(EventType.clean_close, None, None)) self.__queue.put_nowait(EventItem(EventType.clean_close, None, None))
async def change_presence(self, *, activity=None, status=None, shard_id=None): async def change_presence(
self,
*,
activity: Optional[BaseActivity] = None,
status: Optional[Status] = None,
shard_id: int = None,
) -> None:
"""|coro| """|coro|
Changes the client's presence. Changes the client's presence.
@ -468,23 +503,23 @@ class AutoShardedClient(Client):
""" """
if status is None: if status is None:
status = 'online' status_value = 'online'
status_enum = Status.online status_enum = Status.online
elif status is Status.offline: elif status is Status.offline:
status = 'invisible' status_value = 'invisible'
status_enum = Status.offline status_enum = Status.offline
else: else:
status_enum = status status_enum = status
status = str(status) status_value = str(status)
if shard_id is None: if shard_id is None:
for shard in self.__shards.values(): for shard in self.__shards.values():
await shard.ws.change_presence(activity=activity, status=status) await shard.ws.change_presence(activity=activity, status=status_value)
guilds = self._connection.guilds guilds = self._connection.guilds
else: else:
shard = self.__shards[shard_id] shard = self.__shards[shard_id]
await shard.ws.change_presence(activity=activity, status=status) await shard.ws.change_presence(activity=activity, status=status_value)
guilds = [g for g in self._connection.guilds if g.shard_id == shard_id] guilds = [g for g in self._connection.guilds if g.shard_id == shard_id]
activities = () if activity is None else (activity,) activities = () if activity is None else (activity,)
@ -493,10 +528,11 @@ class AutoShardedClient(Client):
if me is None: if me is None:
continue continue
me.activities = activities # Member.activities is typehinted as Tuple[ActivityType, ...], we may be setting it as Tuple[BaseActivity, ...]
me.activities = activities # type: ignore
me.status = status_enum me.status = status_enum
def is_ws_ratelimited(self): def is_ws_ratelimited(self) -> bool:
""":class:`bool`: Whether the websocket is currently rate limited. """:class:`bool`: Whether the websocket is currently rate limited.
This can be useful to know when deciding whether you should query members This can be useful to know when deciding whether you should query members

View File

@ -61,6 +61,10 @@ class StageInstance(Hashable):
Returns the stage instance's hash. Returns the stage instance's hash.
.. describe:: int(x)
Returns the stage instance's ID.
Attributes Attributes
----------- -----------
id: :class:`int` id: :class:`int`
@ -106,7 +110,8 @@ class StageInstance(Hashable):
@cached_slot_property('_cs_channel') @cached_slot_property('_cs_channel')
def channel(self) -> Optional[StageChannel]: def channel(self) -> Optional[StageChannel]:
"""Optional[:class:`StageChannel`]: The channel that stage instance is running in.""" """Optional[:class:`StageChannel`]: The channel that stage instance is running in."""
return self._state.get_channel(self.channel_id) # the returned channel will always be a StageChannel or None
return self._state.get_channel(self.channel_id) # type: ignore
def is_public(self) -> bool: def is_public(self) -> bool:
return self.privacy_level is StagePrivacyLevel.public return self.privacy_level is StagePrivacyLevel.public

File diff suppressed because it is too large Load Diff

View File

@ -51,7 +51,8 @@ if TYPE_CHECKING:
Sticker as StickerPayload, Sticker as StickerPayload,
StandardSticker as StandardStickerPayload, StandardSticker as StandardStickerPayload,
GuildSticker as GuildStickerPayload, GuildSticker as GuildStickerPayload,
ListPremiumStickerPacks as ListPremiumStickerPacksPayload ListPremiumStickerPacks as ListPremiumStickerPacksPayload,
EditGuildSticker,
) )
@ -66,6 +67,14 @@ class StickerPack(Hashable):
Returns the name of the sticker pack. Returns the name of the sticker pack.
.. describe:: hash(x)
Returns the hash of the sticker pack.
.. describe:: int(x)
Returns the ID of the sticker pack.
.. describe:: x == y .. describe:: x == y
Checks if the sticker pack is equal to another sticker pack. Checks if the sticker pack is equal to another sticker pack.
@ -440,10 +449,10 @@ class GuildSticker(Sticker):
description: str = MISSING, description: str = MISSING,
emoji: str = MISSING, emoji: str = MISSING,
reason: Optional[str] = None, reason: Optional[str] = None,
) -> None: ) -> GuildSticker:
"""|coro| """|coro|
Edits a :class:`Sticker` for the guild. Edits a :class:`GuildSticker` for the guild.
Parameters Parameters
----------- -----------
@ -462,8 +471,13 @@ class GuildSticker(Sticker):
You are not allowed to edit stickers. You are not allowed to edit stickers.
HTTPException HTTPException
An error occurred editing the sticker. An error occurred editing the sticker.
Returns
--------
:class:`GuildSticker`
The newly modified sticker.
""" """
payload = {} payload: EditGuildSticker = {}
if name is not MISSING: if name is not MISSING:
payload['name'] = name payload['name'] = name
@ -482,8 +496,7 @@ class GuildSticker(Sticker):
payload['tags'] = emoji payload['tags'] = emoji
data: GuildStickerPayload = await self._state.http.modify_guild_sticker(self.guild_id, self.id, payload, reason) data: GuildStickerPayload = await self._state.http.modify_guild_sticker(self.guild_id, self.id, payload, reason)
return GuildSticker(state=self._state, data=data)
self._from_data(data)
async def delete(self, *, reason: Optional[str] = None) -> None: async def delete(self, *, reason: Optional[str] = None) -> None:
"""|coro| """|coro|

View File

@ -206,7 +206,7 @@ class Template:
data = await self._state.http.create_from_template(self.code, name, region_value, 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) -> None: async def sync(self) -> Template:
"""|coro| """|coro|
Sync the template to the guild's current state. Sync the template to the guild's current state.
@ -216,6 +216,9 @@ class Template:
.. versionadded:: 1.7 .. versionadded:: 1.7
.. versionchanged:: 2.0
The template is no longer edited in-place, instead it is returned.
Raises Raises
------- -------
HTTPException HTTPException
@ -224,17 +227,22 @@ class Template:
You don't have permissions to edit the template. You don't have permissions to edit the template.
NotFound NotFound
This template does not exist. This template does not exist.
Returns
--------
:class:`Template`
The newly edited template.
""" """
data = await self._state.http.sync_template(self.source_guild.id, self.code) data = await self._state.http.sync_template(self.source_guild.id, self.code)
self._store(data) return Template(state=self._state, data=data)
async def edit( async def edit(
self, self,
*, *,
name: str = MISSING, name: str = MISSING,
description: Optional[str] = MISSING, description: Optional[str] = MISSING,
) -> None: ) -> Template:
"""|coro| """|coro|
Edit the template metadata. Edit the template metadata.
@ -244,6 +252,9 @@ class Template:
.. versionadded:: 1.7 .. versionadded:: 1.7
.. versionchanged:: 2.0
The template is no longer edited in-place, instead it is returned.
Parameters Parameters
------------ ------------
name: :class:`str` name: :class:`str`
@ -259,6 +270,11 @@ class Template:
You don't have permissions to edit the template. You don't have permissions to edit the template.
NotFound NotFound
This template does not exist. This template does not exist.
Returns
--------
:class:`Template`
The newly edited template.
""" """
payload = {} payload = {}
@ -268,7 +284,7 @@ class Template:
payload['description'] = description payload['description'] = description
data = await self._state.http.edit_template(self.source_guild.id, self.code, payload) data = await self._state.http.edit_template(self.source_guild.id, self.code, payload)
self._store(data) return Template(state=self._state, data=data)
async def delete(self) -> None: async def delete(self) -> None:
"""|coro| """|coro|

View File

@ -46,6 +46,7 @@ if TYPE_CHECKING:
ThreadMetadata, ThreadMetadata,
ThreadArchiveDuration, ThreadArchiveDuration,
) )
from .types.snowflake import SnowflakeList
from .guild import Guild from .guild import Guild
from .channel import TextChannel, CategoryChannel from .channel import TextChannel, CategoryChannel
from .member import Member from .member import Member
@ -73,6 +74,10 @@ class Thread(Messageable, Hashable):
Returns the thread's hash. Returns the thread's hash.
.. describe:: int(x)
Returns the thread's ID.
.. describe:: str(x) .. describe:: str(x)
Returns the thread's name. Returns the thread's name.
@ -110,6 +115,9 @@ class Thread(Messageable, Hashable):
Whether the thread is archived. Whether the thread is archived.
locked: :class:`bool` locked: :class:`bool`
Whether the thread is locked. Whether the thread is locked.
invitable: :class:`bool`
Whether non-moderators can add other non-moderators to this thread.
This is always ``True`` for public threads.
archiver_id: Optional[:class:`int`] archiver_id: Optional[:class:`int`]
The user's ID that archived this thread. The user's ID that archived this thread.
auto_archive_duration: :class:`int` auto_archive_duration: :class:`int`
@ -135,6 +143,7 @@ class Thread(Messageable, Hashable):
'me', 'me',
'locked', 'locked',
'archived', 'archived',
'invitable',
'archiver_id', 'archiver_id',
'auto_archive_duration', 'auto_archive_duration',
'archive_timestamp', 'archive_timestamp',
@ -183,6 +192,7 @@ class Thread(Messageable, Hashable):
self.auto_archive_duration = data['auto_archive_duration'] self.auto_archive_duration = data['auto_archive_duration']
self.archive_timestamp = parse_time(data['archive_timestamp']) self.archive_timestamp = parse_time(data['archive_timestamp'])
self.locked = data.get('locked', False) self.locked = data.get('locked', False)
self.invitable = data.get('invitable', True)
def _update(self, data): def _update(self, data):
try: try:
@ -394,7 +404,7 @@ class Thread(Messageable, Hashable):
if len(messages) > 100: if len(messages) > 100:
raise ClientException('Can only bulk delete messages up to 100 messages') raise ClientException('Can only bulk delete messages up to 100 messages')
message_ids = [m.id for m in messages] message_ids: SnowflakeList = [m.id for m in messages]
await self._state.http.delete_messages(self.id, message_ids) await self._state.http.delete_messages(self.id, message_ids)
async def purge( async def purge(
@ -520,9 +530,10 @@ class Thread(Messageable, Hashable):
name: str = MISSING, name: str = MISSING,
archived: bool = MISSING, archived: bool = MISSING,
locked: bool = MISSING, locked: bool = MISSING,
invitable: bool = MISSING,
slowmode_delay: int = MISSING, slowmode_delay: int = MISSING,
auto_archive_duration: ThreadArchiveDuration = MISSING, auto_archive_duration: ThreadArchiveDuration = MISSING,
): ) -> Thread:
"""|coro| """|coro|
Edits the thread. Edits the thread.
@ -542,6 +553,9 @@ class Thread(Messageable, Hashable):
Whether to archive the thread or not. Whether to archive the thread or not.
locked: :class:`bool` locked: :class:`bool`
Whether to lock the thread or not. Whether to lock the thread or not.
invitable: :class:`bool`
Whether non-moderators can add other non-moderators to this thread.
Only available for private threads.
auto_archive_duration: :class:`int` auto_archive_duration: :class:`int`
The new duration in minutes before a thread is automatically archived for inactivity. The new duration in minutes before a thread is automatically archived for inactivity.
Must be one of ``60``, ``1440``, ``4320``, or ``10080``. Must be one of ``60``, ``1440``, ``4320``, or ``10080``.
@ -555,6 +569,11 @@ class Thread(Messageable, Hashable):
You do not have permissions to edit the thread. You do not have permissions to edit the thread.
HTTPException HTTPException
Editing the thread failed. Editing the thread failed.
Returns
--------
:class:`Thread`
The newly edited thread.
""" """
payload = {} payload = {}
if name is not MISSING: if name is not MISSING:
@ -565,20 +584,22 @@ class Thread(Messageable, Hashable):
payload['auto_archive_duration'] = auto_archive_duration payload['auto_archive_duration'] = auto_archive_duration
if locked is not MISSING: if locked is not MISSING:
payload['locked'] = locked payload['locked'] = locked
if invitable is not MISSING:
payload['invitable'] = invitable
if slowmode_delay is not MISSING: if slowmode_delay is not MISSING:
payload['rate_limit_per_user'] = slowmode_delay payload['rate_limit_per_user'] = slowmode_delay
await self._state.http.edit_channel(self.id, **payload) data = await self._state.http.edit_channel(self.id, **payload)
# The data payload will always be a Thread payload
return Thread(data=data, state=self._state, guild=self.guild) # type: ignore
async def join(self): async def join(self):
"""|coro| """|coro|
Joins this thread. Joins this thread.
You must have :attr:`~Permissions.send_messages` and :attr:`~Permissions.use_threads` You must have :attr:`~Permissions.send_messages_in_threads` to join a thread.
to join a public thread. If the thread is private then :attr:`~Permissions.send_messages` If the thread is private, :attr:`~Permissions.manage_threads` is also needed.
and either :attr:`~Permissions.use_private_threads` or :attr:`~Permissions.manage_messages`
is required to join the thread.
Raises Raises
------- -------
@ -731,6 +752,10 @@ class ThreadMember(Hashable):
Returns the thread member's hash. Returns the thread member's hash.
.. describe:: int(x)
Returns the thread member's ID.
.. describe:: str(x) .. describe:: str(x)
Returns the thread member's name. Returns the thread member's name.

View File

@ -61,6 +61,7 @@ class _PartialAppInfoOptional(TypedDict, total=False):
terms_of_service_url: str terms_of_service_url: str
privacy_policy_url: str privacy_policy_url: str
max_participants: int max_participants: int
flags: int
class PartialAppInfo(_PartialAppInfoOptional, BaseAppInfo): class PartialAppInfo(_PartialAppInfoOptional, BaseAppInfo):
pass pass

View File

@ -39,6 +39,7 @@ class PartialMember(TypedDict):
class Member(PartialMember, total=False): class Member(PartialMember, total=False):
avatar: str
user: User user: User
nick: str nick: str
premium_since: str premium_since: str
@ -46,16 +47,17 @@ class Member(PartialMember, total=False):
permissions: str permissions: str
class _OptionalGatewayMember(PartialMember, total=False): class _OptionalMemberWithUser(PartialMember, total=False):
avatar: str
nick: str nick: str
premium_since: str premium_since: str
pending: bool pending: bool
permissions: str permissions: str
class GatewayMember(_OptionalGatewayMember): class MemberWithUser(_OptionalMemberWithUser):
user: User user: User
class UserWithMember(User, total=False): class UserWithMember(User, total=False):
member: _OptionalGatewayMember member: _OptionalMemberWithUser

View File

@ -41,6 +41,7 @@ class ThreadMember(TypedDict):
class _ThreadMetadataOptional(TypedDict, total=False): class _ThreadMetadataOptional(TypedDict, total=False):
archiver_id: Snowflake archiver_id: Snowflake
locked: bool locked: bool
invitable: bool
class ThreadMetadata(_ThreadMetadataOptional): class ThreadMetadata(_ThreadMetadataOptional):

View File

@ -24,14 +24,14 @@ DEALINGS IN THE SOFTWARE.
from typing import Optional, TypedDict, List, Literal from typing import Optional, TypedDict, List, Literal
from .snowflake import Snowflake from .snowflake import Snowflake
from .member import Member from .member import MemberWithUser
SupportedModes = Literal['xsalsa20_poly1305_lite', 'xsalsa20_poly1305_suffix', 'xsalsa20_poly1305'] SupportedModes = Literal['xsalsa20_poly1305_lite', 'xsalsa20_poly1305_suffix', 'xsalsa20_poly1305']
class _PartialVoiceStateOptional(TypedDict, total=False): class _PartialVoiceStateOptional(TypedDict, total=False):
member: Member member: MemberWithUser
self_stream: bool self_stream: bool

View File

@ -197,13 +197,13 @@ class Select(Item[V]):
----------- -----------
label: :class:`str` label: :class:`str`
The label of the option. This is displayed to users. The label of the option. This is displayed to users.
Can only be up to 25 characters. Can only be up to 100 characters.
value: :class:`str` value: :class:`str`
The value of the option. This is not displayed to users. The value of the option. This is not displayed to users.
If not given, defaults to the label. Can only be up to 100 characters. If not given, defaults to the label. Can only be up to 100 characters.
description: Optional[:class:`str`] description: Optional[:class:`str`]
An additional description of the option, if any. An additional description of the option, if any.
Can only be up to 50 characters. Can only be up to 100 characters.
emoji: Optional[Union[:class:`str`, :class:`.Emoji`, :class:`.PartialEmoji`]] emoji: Optional[Union[:class:`str`, :class:`.Emoji`, :class:`.PartialEmoji`]]
The emoji of the option, if available. This can either be a string representing The emoji of the option, if available. This can either be a string representing
the custom or unicode emoji or an instance of :class:`.PartialEmoji` or :class:`.Emoji`. the custom or unicode emoji or an instance of :class:`.PartialEmoji` or :class:`.Emoji`.

View File

@ -49,7 +49,6 @@ __all__ = (
'ClientUser', 'ClientUser',
) )
U = TypeVar('U', bound='User')
BU = TypeVar('BU', bound='BaseUser') BU = TypeVar('BU', bound='BaseUser')
@ -59,7 +58,18 @@ class _UserTag:
class BaseUser(_UserTag): class BaseUser(_UserTag):
__slots__ = ('name', 'id', 'discriminator', '_avatar', '_banner', '_accent_colour', 'bot', 'system', '_public_flags', '_state') __slots__ = (
'name',
'id',
'discriminator',
'_avatar',
'_banner',
'_accent_colour',
'bot',
'system',
'_public_flags',
'_state',
)
if TYPE_CHECKING: if TYPE_CHECKING:
name: str name: str
@ -68,7 +78,7 @@ class BaseUser(_UserTag):
bot: bool bot: bool
system: bool system: bool
_state: ConnectionState _state: ConnectionState
_avatar: str _avatar: Optional[str]
_banner: Optional[str] _banner: Optional[str]
_accent_colour: Optional[str] _accent_colour: Optional[str]
_public_flags: int _public_flags: int
@ -86,6 +96,9 @@ class BaseUser(_UserTag):
def __str__(self) -> str: def __str__(self) -> str:
return f'{self.name}#{self.discriminator}' return f'{self.name}#{self.discriminator}'
def __int__(self) -> int:
return self.id
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
return isinstance(other, _UserTag) and other.id == self.id return isinstance(other, _UserTag) and other.id == self.id
@ -137,22 +150,31 @@ class BaseUser(_UserTag):
return PublicUserFlags._from_value(self._public_flags) return PublicUserFlags._from_value(self._public_flags)
@property @property
def avatar(self) -> Asset: def avatar(self) -> Optional[Asset]:
""":class:`Asset`: Returns an :class:`Asset` for the avatar the user has. """Optional[:class:`Asset`]: Returns an :class:`Asset` for the avatar the user has.
If the user does not have a traditional avatar, an asset for If the user does not have a traditional avatar, ``None`` is returned.
the default avatar is returned instead. If you want the avatar that a user has displayed, consider :attr:`display_avatar`.
""" """
if self._avatar is None: if self._avatar is not None:
return Asset._from_default_avatar(self._state, int(self.discriminator) % len(DefaultAvatar))
else:
return Asset._from_avatar(self._state, self.id, self._avatar) return Asset._from_avatar(self._state, self.id, self._avatar)
return None
@property @property
def default_avatar(self) -> Asset: def default_avatar(self) -> Asset:
""":class:`Asset`: Returns the default avatar for a given user. This is calculated by the user's discriminator.""" """:class:`Asset`: Returns the default avatar for a given user. This is calculated by the user's discriminator."""
return Asset._from_default_avatar(self._state, int(self.discriminator) % len(DefaultAvatar)) return Asset._from_default_avatar(self._state, int(self.discriminator) % len(DefaultAvatar))
@property
def display_avatar(self) -> Asset:
""":class:`Asset`: Returns the user's display avatar.
For regular users this is just their default avatar or uploaded avatar.
.. versionadded:: 2.0
"""
return self.avatar or self.default_avatar
@property @property
def banner(self) -> Optional[Asset]: def banner(self) -> Optional[Asset]:
"""Optional[:class:`Asset`]: Returns the user's banner asset, if available. """Optional[:class:`Asset`]: Returns the user's banner asset, if available.
@ -327,7 +349,7 @@ class ClientUser(BaseUser):
self._flags = data.get('flags', 0) self._flags = data.get('flags', 0)
self.mfa_enabled = data.get('mfa_enabled', False) self.mfa_enabled = data.get('mfa_enabled', False)
async def edit(self, *, username: str = MISSING, avatar: bytes = MISSING) -> None: async def edit(self, *, username: str = MISSING, avatar: bytes = MISSING) -> ClientUser:
"""|coro| """|coro|
Edits the current profile of the client. Edits the current profile of the client.
@ -341,6 +363,9 @@ class ClientUser(BaseUser):
The only image formats supported for uploading is JPEG and PNG. The only image formats supported for uploading is JPEG and PNG.
.. versionchanged:: 2.0
The edit is no longer in-place, instead the newly edited client user is returned.
Parameters Parameters
----------- -----------
username: :class:`str` username: :class:`str`
@ -355,6 +380,11 @@ class ClientUser(BaseUser):
Editing your profile failed. Editing your profile failed.
InvalidArgument InvalidArgument
Wrong image format passed for ``avatar``. Wrong image format passed for ``avatar``.
Returns
---------
:class:`ClientUser`
The newly edited client user.
""" """
payload: Dict[str, Any] = {} payload: Dict[str, Any] = {}
if username is not MISSING: if username is not MISSING:
@ -364,7 +394,7 @@ class ClientUser(BaseUser):
payload['avatar'] = _bytes_to_base64_data(avatar) payload['avatar'] = _bytes_to_base64_data(avatar)
data: UserPayload = await self._state.http.edit_profile(payload) data: UserPayload = await self._state.http.edit_profile(payload)
self._update(data) return ClientUser(state=self._state, data=data)
class User(BaseUser, discord.abc.Messageable): class User(BaseUser, discord.abc.Messageable):
@ -388,6 +418,10 @@ class User(BaseUser, discord.abc.Messageable):
Returns the user's name with discriminator. Returns the user's name with discriminator.
.. describe:: int(x)
Returns the user's ID.
Attributes Attributes
----------- -----------
name: :class:`str` name: :class:`str`
@ -419,7 +453,7 @@ class User(BaseUser, discord.abc.Messageable):
pass pass
@classmethod @classmethod
def _copy(cls: Type[U], user: U) -> U: def _copy(cls, user: User):
self = super()._copy(user) self = super()._copy(user)
self._stored = False self._stored = False
return self return self

View File

@ -120,6 +120,9 @@ class _cached_property:
if TYPE_CHECKING: if TYPE_CHECKING:
from functools import cached_property as cached_property from functools import cached_property as cached_property
from typing_extensions import ParamSpec
from .permissions import Permissions from .permissions import Permissions
from .abc import Snowflake from .abc import Snowflake
from .invite import Invite from .invite import Invite
@ -129,6 +132,8 @@ if TYPE_CHECKING:
headers: Mapping[str, Any] headers: Mapping[str, Any]
P = ParamSpec('P')
else: else:
cached_property = _cached_property cached_property = _cached_property
@ -231,8 +236,8 @@ def parse_time(timestamp: Optional[str]) -> Optional[datetime.datetime]:
return None return None
def copy_doc(original: Callable[..., Any]) -> Callable[[Callable[..., Any]], Callable[..., Any]]: def copy_doc(original: Callable) -> Callable[[T], T]:
def decorator(overriden: Callable[..., Any]) -> Callable[..., Any]: def decorator(overriden: T) -> T:
overriden.__doc__ = original.__doc__ overriden.__doc__ = original.__doc__
overriden.__signature__ = _signature(original) # type: ignore overriden.__signature__ = _signature(original) # type: ignore
return overriden return overriden
@ -240,10 +245,10 @@ def copy_doc(original: Callable[..., Any]) -> Callable[[Callable[..., Any]], Cal
return decorator return decorator
def deprecated(instead: Optional[str] = None) -> Callable[[Callable[..., T]], Callable[..., T]]: def deprecated(instead: Optional[str] = None) -> Callable[[Callable[P, T]], Callable[P, T]]:
def actual_decorator(func: Callable[..., T]) -> Callable[..., T]: def actual_decorator(func: Callable[P, T]) -> Callable[P, T]:
@functools.wraps(func) @functools.wraps(func)
def decorated(*args, **kwargs) -> T: def decorated(*args: P.args, **kwargs: P.kwargs) -> T:
warnings.simplefilter('always', DeprecationWarning) # turn off filter warnings.simplefilter('always', DeprecationWarning) # turn off filter
if instead: if instead:
fmt = "{0.__name__} is deprecated, use {1} instead." fmt = "{0.__name__} is deprecated, use {1} instead."
@ -267,7 +272,7 @@ def oauth_url(
redirect_uri: str = MISSING, redirect_uri: str = MISSING,
scopes: Iterable[str] = MISSING, scopes: Iterable[str] = MISSING,
disable_guild_select: bool = False, disable_guild_select: bool = False,
): ) -> str:
"""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.
@ -479,17 +484,17 @@ def _bytes_to_base64_data(data: bytes) -> str:
if HAS_ORJSON: if HAS_ORJSON:
def to_json(obj: Any) -> str: # type: ignore def _to_json(obj: Any) -> str: # type: ignore
return orjson.dumps(obj).decode('utf-8') return orjson.dumps(obj).decode('utf-8')
from_json = orjson.loads # type: ignore _from_json = orjson.loads # type: ignore
else: else:
def to_json(obj: Any) -> str: def _to_json(obj: Any) -> str:
return json.dumps(obj, separators=(',', ':'), ensure_ascii=True) return json.dumps(obj, separators=(',', ':'), ensure_ascii=True)
from_json = json.loads _from_json = json.loads
def _parse_ratelimit_header(request: Any, *, use_clock: bool = False) -> float: def _parse_ratelimit_header(request: Any, *, use_clock: bool = False) -> float:

View File

@ -84,7 +84,7 @@ __all__ = (
log: logging.Logger = logging.getLogger(__name__) _log = logging.getLogger(__name__)
class VoiceProtocol: class VoiceProtocol:
"""A class that represents the Discord voice protocol. """A class that represents the Discord voice protocol.
@ -304,7 +304,7 @@ class VoiceClient(VoiceProtocol):
async def on_voice_server_update(self, data: VoiceServerUpdatePayload) -> None: async def on_voice_server_update(self, data: VoiceServerUpdatePayload) -> None:
if self._voice_server_complete.is_set(): if self._voice_server_complete.is_set():
log.info('Ignoring extraneous voice server update.') _log.info('Ignoring extraneous voice server update.')
return return
self.token = data.get('token') self.token = data.get('token')
@ -312,7 +312,7 @@ class VoiceClient(VoiceProtocol):
endpoint = data.get('endpoint') endpoint = data.get('endpoint')
if endpoint is None or self.token is None: if endpoint is None or self.token is None:
log.warning('Awaiting endpoint... This requires waiting. ' \ _log.warning('Awaiting endpoint... This requires waiting. ' \
'If timeout occurred considering raising the timeout and reconnecting.') 'If timeout occurred considering raising the timeout and reconnecting.')
return return
@ -338,18 +338,18 @@ class VoiceClient(VoiceProtocol):
await self.channel.guild.change_voice_state(channel=self.channel) await self.channel.guild.change_voice_state(channel=self.channel)
async def voice_disconnect(self) -> None: async def voice_disconnect(self) -> None:
log.info('The voice handshake is being terminated for Channel ID %s (Guild ID %s)', self.channel.id, self.guild.id) _log.info('The voice handshake is being terminated for Channel ID %s (Guild ID %s)', self.channel.id, self.guild.id)
await self.channel.guild.change_voice_state(channel=None) await self.channel.guild.change_voice_state(channel=None)
def prepare_handshake(self) -> None: def prepare_handshake(self) -> None:
self._voice_state_complete.clear() self._voice_state_complete.clear()
self._voice_server_complete.clear() self._voice_server_complete.clear()
self._handshaking = True self._handshaking = True
log.info('Starting voice handshake... (connection attempt %d)', self._connections + 1) _log.info('Starting voice handshake... (connection attempt %d)', self._connections + 1)
self._connections += 1 self._connections += 1
def finish_handshake(self) -> None: def finish_handshake(self) -> None:
log.info('Voice handshake complete. Endpoint found %s', self.endpoint) _log.info('Voice handshake complete. Endpoint found %s', self.endpoint)
self._handshaking = False self._handshaking = False
self._voice_server_complete.clear() self._voice_server_complete.clear()
self._voice_state_complete.clear() self._voice_state_complete.clear()
@ -363,7 +363,7 @@ class VoiceClient(VoiceProtocol):
return ws return ws
async def connect(self, *, reconnect: bool, timeout: float) ->None: async def connect(self, *, reconnect: bool, timeout: float) ->None:
log.info('Connecting to voice...') _log.info('Connecting to voice...')
self.timeout = timeout self.timeout = timeout
for i in range(5): for i in range(5):
@ -391,7 +391,7 @@ class VoiceClient(VoiceProtocol):
break break
except (ConnectionClosed, asyncio.TimeoutError): except (ConnectionClosed, asyncio.TimeoutError):
if reconnect: if reconnect:
log.exception('Failed to connect to voice... Retrying...') _log.exception('Failed to connect to voice... Retrying...')
await asyncio.sleep(1 + i * 2.0) await asyncio.sleep(1 + i * 2.0)
await self.voice_disconnect() await self.voice_disconnect()
continue continue
@ -456,14 +456,14 @@ class VoiceClient(VoiceProtocol):
# 4014 - voice channel has been deleted. # 4014 - voice channel has been deleted.
# 4015 - voice server has crashed # 4015 - voice server has crashed
if exc.code in (1000, 4015): if exc.code in (1000, 4015):
log.info('Disconnecting from voice normally, close code %d.', exc.code) _log.info('Disconnecting from voice normally, close code %d.', exc.code)
await self.disconnect() await self.disconnect()
break break
if exc.code == 4014: if exc.code == 4014:
log.info('Disconnected from voice by force... potentially reconnecting.') _log.info('Disconnected from voice by force... potentially reconnecting.')
successful = await self.potential_reconnect() successful = await self.potential_reconnect()
if not successful: if not successful:
log.info('Reconnect was unsuccessful, disconnecting from voice normally...') _log.info('Reconnect was unsuccessful, disconnecting from voice normally...')
await self.disconnect() await self.disconnect()
break break
else: else:
@ -474,7 +474,7 @@ class VoiceClient(VoiceProtocol):
raise raise
retry = backoff.delay() retry = backoff.delay()
log.exception('Disconnected from voice... Reconnecting in %.2fs.', retry) _log.exception('Disconnected from voice... Reconnecting in %.2fs.', retry)
self._connected.clear() self._connected.clear()
await asyncio.sleep(retry) await asyncio.sleep(retry)
await self.voice_disconnect() await self.voice_disconnect()
@ -482,7 +482,7 @@ class VoiceClient(VoiceProtocol):
await self.connect(reconnect=True, timeout=self.timeout) await self.connect(reconnect=True, timeout=self.timeout)
except asyncio.TimeoutError: except asyncio.TimeoutError:
# at this point we've retried 5 times... let's continue the loop. # at this point we've retried 5 times... let's continue the loop.
log.warning('Could not connect to voice... Retrying...') _log.warning('Could not connect to voice... Retrying...')
continue continue
async def disconnect(self, *, force: bool = False) -> None: async def disconnect(self, *, force: bool = False) -> None:
@ -674,6 +674,6 @@ class VoiceClient(VoiceProtocol):
try: try:
self.socket.sendto(packet, (self.endpoint_ip, self.voice_port)) self.socket.sendto(packet, (self.endpoint_ip, self.voice_port))
except BlockingIOError: except BlockingIOError:
log.warning('A packet has been dropped (seq: %s, timestamp: %s)', self.sequence, self.timestamp) _log.warning('A packet has been dropped (seq: %s, timestamp: %s)', self.sequence, self.timestamp)
self.checked_add('timestamp', opus.Encoder.SAMPLES_PER_FRAME, 4294967295) self.checked_add('timestamp', opus.Encoder.SAMPLES_PER_FRAME, 4294967295)

View File

@ -24,7 +24,6 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
import contextvars
import logging import logging
import asyncio import asyncio
import json import json
@ -32,6 +31,7 @@ import re
from urllib.parse import quote as urlquote from urllib.parse import quote as urlquote
from typing import Any, Dict, List, Literal, NamedTuple, Optional, TYPE_CHECKING, Tuple, Union, overload from typing import Any, Dict, List, Literal, NamedTuple, Optional, TYPE_CHECKING, Tuple, Union, overload
from contextvars import ContextVar
import aiohttp import aiohttp
@ -43,7 +43,7 @@ from ..user import BaseUser, User
from ..asset import Asset from ..asset import Asset
from ..http import Route from ..http import Route
from ..mixins import Hashable from ..mixins import Hashable
from ..object import Object from ..channel import PartialMessageable
__all__ = ( __all__ = (
'Webhook', 'Webhook',
@ -52,15 +52,20 @@ __all__ = (
'PartialWebhookGuild', 'PartialWebhookGuild',
) )
log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
if TYPE_CHECKING: if TYPE_CHECKING:
from ..file import File from ..file import File
from ..embeds import Embed from ..embeds import Embed
from ..mentions import AllowedMentions from ..mentions import AllowedMentions
from ..state import ConnectionState
from ..http import Response
from ..types.webhook import ( from ..types.webhook import (
Webhook as WebhookPayload, Webhook as WebhookPayload,
) )
from ..types.message import (
Message as MessagePayload,
)
from ..guild import Guild from ..guild import Guild
from ..channel import TextChannel from ..channel import TextChannel
from ..abc import Snowflake from ..abc import Snowflake
@ -116,7 +121,7 @@ class AsyncWebhookAdapter:
if payload is not None: if payload is not None:
headers['Content-Type'] = 'application/json' headers['Content-Type'] = 'application/json'
to_send = utils.to_json(payload) to_send = utils._to_json(payload)
if auth_token is not None: if auth_token is not None:
headers['Authorization'] = f'Bot {auth_token}' headers['Authorization'] = f'Bot {auth_token}'
@ -143,7 +148,7 @@ class AsyncWebhookAdapter:
try: try:
async with session.request(method, url, data=to_send, headers=headers, params=params) as response: async with session.request(method, url, data=to_send, headers=headers, params=params) as response:
log.debug( _log.debug(
'Webhook ID %s with %s %s has returned status code %s', 'Webhook ID %s with %s %s has returned status code %s',
webhook_id, webhook_id,
method, method,
@ -157,7 +162,7 @@ class AsyncWebhookAdapter:
remaining = response.headers.get('X-Ratelimit-Remaining') remaining = response.headers.get('X-Ratelimit-Remaining')
if remaining == '0' and response.status != 429: if remaining == '0' and response.status != 429:
delta = utils._parse_ratelimit_header(response) delta = utils._parse_ratelimit_header(response)
log.debug( _log.debug(
'Webhook ID %s has been pre-emptively rate limited, waiting %.2f seconds', webhook_id, delta 'Webhook ID %s has been pre-emptively rate limited, waiting %.2f seconds', webhook_id, delta
) )
lock.delay_by(delta) lock.delay_by(delta)
@ -170,7 +175,7 @@ class AsyncWebhookAdapter:
raise HTTPException(response, data) raise HTTPException(response, data)
retry_after: float = data['retry_after'] # type: ignore retry_after: float = data['retry_after'] # type: ignore
log.warning('Webhook ID %s is rate limited. Retrying in %.2f seconds', webhook_id, retry_after) _log.warning('Webhook ID %s is rate limited. Retrying in %.2f seconds', webhook_id, retry_after)
await asyncio.sleep(retry_after) await asyncio.sleep(retry_after)
continue continue
@ -205,7 +210,7 @@ class AsyncWebhookAdapter:
token: Optional[str] = None, token: Optional[str] = None,
session: aiohttp.ClientSession, session: aiohttp.ClientSession,
reason: Optional[str] = None, reason: Optional[str] = None,
): ) -> Response[None]:
route = Route('DELETE', '/webhooks/{webhook_id}', webhook_id=webhook_id) route = Route('DELETE', '/webhooks/{webhook_id}', webhook_id=webhook_id)
return self.request(route, session, reason=reason, auth_token=token) return self.request(route, session, reason=reason, auth_token=token)
@ -216,7 +221,7 @@ class AsyncWebhookAdapter:
*, *,
session: aiohttp.ClientSession, session: aiohttp.ClientSession,
reason: Optional[str] = None, reason: Optional[str] = None,
): ) -> Response[None]:
route = Route('DELETE', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token) route = Route('DELETE', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token)
return self.request(route, session, reason=reason) return self.request(route, session, reason=reason)
@ -228,7 +233,7 @@ class AsyncWebhookAdapter:
*, *,
session: aiohttp.ClientSession, session: aiohttp.ClientSession,
reason: Optional[str] = None, reason: Optional[str] = None,
): ) -> Response[WebhookPayload]:
route = Route('PATCH', '/webhooks/{webhook_id}', webhook_id=webhook_id) route = Route('PATCH', '/webhooks/{webhook_id}', webhook_id=webhook_id)
return self.request(route, session, reason=reason, payload=payload, auth_token=token) return self.request(route, session, reason=reason, payload=payload, auth_token=token)
@ -240,7 +245,7 @@ class AsyncWebhookAdapter:
*, *,
session: aiohttp.ClientSession, session: aiohttp.ClientSession,
reason: Optional[str] = None, reason: Optional[str] = None,
): ) -> Response[WebhookPayload]:
route = Route('PATCH', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token) route = Route('PATCH', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token)
return self.request(route, session, reason=reason, payload=payload) return self.request(route, session, reason=reason, payload=payload)
@ -255,7 +260,7 @@ class AsyncWebhookAdapter:
files: Optional[List[File]] = None, files: Optional[List[File]] = None,
thread_id: Optional[int] = None, thread_id: Optional[int] = None,
wait: bool = False, wait: bool = False,
): ) -> Response[Optional[MessagePayload]]:
params = {'wait': int(wait)} params = {'wait': int(wait)}
if thread_id: if thread_id:
params['thread_id'] = thread_id params['thread_id'] = thread_id
@ -269,7 +274,7 @@ class AsyncWebhookAdapter:
message_id: int, message_id: int,
*, *,
session: aiohttp.ClientSession, session: aiohttp.ClientSession,
): ) -> Response[MessagePayload]:
route = Route( route = Route(
'GET', 'GET',
'/webhooks/{webhook_id}/{webhook_token}/messages/{message_id}', '/webhooks/{webhook_id}/{webhook_token}/messages/{message_id}',
@ -289,7 +294,7 @@ class AsyncWebhookAdapter:
payload: Optional[Dict[str, Any]] = None, payload: Optional[Dict[str, Any]] = None,
multipart: Optional[List[Dict[str, Any]]] = None, multipart: Optional[List[Dict[str, Any]]] = None,
files: Optional[List[File]] = None, files: Optional[List[File]] = None,
): ) -> Response[Message]:
route = Route( route = Route(
'PATCH', 'PATCH',
'/webhooks/{webhook_id}/{webhook_token}/messages/{message_id}', '/webhooks/{webhook_id}/{webhook_token}/messages/{message_id}',
@ -306,7 +311,7 @@ class AsyncWebhookAdapter:
message_id: int, message_id: int,
*, *,
session: aiohttp.ClientSession, session: aiohttp.ClientSession,
): ) -> Response[None]:
route = Route( route = Route(
'DELETE', 'DELETE',
'/webhooks/{webhook_id}/{webhook_token}/messages/{message_id}', '/webhooks/{webhook_id}/{webhook_token}/messages/{message_id}',
@ -322,7 +327,7 @@ class AsyncWebhookAdapter:
token: str, token: str,
*, *,
session: aiohttp.ClientSession, session: aiohttp.ClientSession,
): ) -> Response[WebhookPayload]:
route = Route('GET', '/webhooks/{webhook_id}', webhook_id=webhook_id) route = Route('GET', '/webhooks/{webhook_id}', webhook_id=webhook_id)
return self.request(route, session=session, auth_token=token) return self.request(route, session=session, auth_token=token)
@ -332,7 +337,7 @@ class AsyncWebhookAdapter:
token: str, token: str,
*, *,
session: aiohttp.ClientSession, session: aiohttp.ClientSession,
): ) -> Response[WebhookPayload]:
route = Route('GET', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token) route = Route('GET', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token)
return self.request(route, session=session) return self.request(route, session=session)
@ -344,7 +349,7 @@ class AsyncWebhookAdapter:
session: aiohttp.ClientSession, session: aiohttp.ClientSession,
type: int, type: int,
data: Optional[Dict[str, Any]] = None, data: Optional[Dict[str, Any]] = None,
): ) -> Response[None]:
payload: Dict[str, Any] = { payload: Dict[str, Any] = {
'type': type, 'type': type,
} }
@ -367,7 +372,7 @@ class AsyncWebhookAdapter:
token: str, token: str,
*, *,
session: aiohttp.ClientSession, session: aiohttp.ClientSession,
): ) -> Response[MessagePayload]:
r = Route( r = Route(
'GET', 'GET',
'/webhooks/{webhook_id}/{webhook_token}/messages/@original', '/webhooks/{webhook_id}/{webhook_token}/messages/@original',
@ -385,7 +390,7 @@ class AsyncWebhookAdapter:
payload: Optional[Dict[str, Any]] = None, payload: Optional[Dict[str, Any]] = None,
multipart: Optional[List[Dict[str, Any]]] = None, multipart: Optional[List[Dict[str, Any]]] = None,
files: Optional[List[File]] = None, files: Optional[List[File]] = None,
): ) -> Response[MessagePayload]:
r = Route( r = Route(
'PATCH', 'PATCH',
'/webhooks/{webhook_id}/{webhook_token}/messages/@original', '/webhooks/{webhook_id}/{webhook_token}/messages/@original',
@ -400,7 +405,7 @@ class AsyncWebhookAdapter:
token: str, token: str,
*, *,
session: aiohttp.ClientSession, session: aiohttp.ClientSession,
): ) -> Response[None]:
r = Route( r = Route(
'DELETE', 'DELETE',
'/webhooks/{webhook_id}/{wehook_token}/messages/@original', '/webhooks/{webhook_id}/{wehook_token}/messages/@original',
@ -420,7 +425,7 @@ def handle_message_parameters(
content: Optional[str] = MISSING, content: Optional[str] = MISSING,
*, *,
username: str = MISSING, username: str = MISSING,
avatar_url: str = MISSING, avatar_url: Any = MISSING,
tts: bool = False, tts: bool = False,
ephemeral: bool = False, ephemeral: bool = False,
file: File = MISSING, file: File = MISSING,
@ -481,7 +486,7 @@ def handle_message_parameters(
files = [file] files = [file]
if files: if files:
multipart.append({'name': 'payload_json', 'value': utils.to_json(payload)}) multipart.append({'name': 'payload_json', 'value': utils._to_json(payload)})
payload = None payload = None
if len(files) == 1: if len(files) == 1:
file = files[0] file = files[0]
@ -507,7 +512,7 @@ def handle_message_parameters(
return ExecuteWebhookParameters(payload=payload, multipart=multipart, files=files) return ExecuteWebhookParameters(payload=payload, multipart=multipart, files=files)
async_context = contextvars.ContextVar('async_webhook_context', default=AsyncWebhookAdapter()) async_context: ContextVar[AsyncWebhookAdapter] = ContextVar('async_webhook_context', default=AsyncWebhookAdapter())
class PartialWebhookChannel(Hashable): class PartialWebhookChannel(Hashable):
@ -579,10 +584,11 @@ class _FriendlyHttpAttributeErrorHelper:
class _WebhookState: class _WebhookState:
__slots__ = ('_parent', '_webhook') __slots__ = ('_parent', '_webhook')
def __init__(self, webhook, parent): def __init__(self, webhook: Any, parent: Optional[Union[ConnectionState, _WebhookState]]):
self._webhook = webhook self._webhook: Any = webhook
if isinstance(parent, self.__class__): self._parent: Optional[ConnectionState]
if isinstance(parent, _WebhookState):
self._parent = None self._parent = None
else: else:
self._parent = parent self._parent = parent
@ -595,10 +601,12 @@ class _WebhookState:
def store_user(self, data): def store_user(self, data):
if self._parent is not None: if self._parent is not None:
return self._parent.store_user(data) return self._parent.store_user(data)
return BaseUser(state=self, data=data) # state parameter is artificial
return BaseUser(state=self, data=data) # type: ignore
def create_user(self, data): def create_user(self, data):
return BaseUser(state=self, data=data) # state parameter is artificial
return BaseUser(state=self, data=data) # type: ignore
@property @property
def http(self): def http(self):
@ -639,13 +647,16 @@ class WebhookMessage(Message):
files: List[File] = MISSING, files: List[File] = MISSING,
view: Optional[View] = MISSING, view: Optional[View] = MISSING,
allowed_mentions: Optional[AllowedMentions] = None, allowed_mentions: Optional[AllowedMentions] = None,
): ) -> WebhookMessage:
"""|coro| """|coro|
Edits the message. Edits the message.
.. versionadded:: 1.6 .. versionadded:: 1.6
.. versionchanged:: 2.0
The edit is no longer in-place, instead the newly edited message is returned.
Parameters Parameters
------------ ------------
content: Optional[:class:`str`] content: Optional[:class:`str`]
@ -685,8 +696,13 @@ class WebhookMessage(Message):
The length of ``embeds`` was invalid The length of ``embeds`` was invalid
InvalidArgument InvalidArgument
There was no token associated with this webhook. There was no token associated with this webhook.
Returns
--------
:class:`WebhookMessage`
The newly edited message.
""" """
await self._state._webhook.edit_message( return await self._state._webhook.edit_message(
self.id, self.id,
content=content, content=content,
embeds=embeds, embeds=embeds,
@ -748,9 +764,9 @@ class BaseWebhook(Hashable):
'_state', '_state',
) )
def __init__(self, data: WebhookPayload, token: Optional[str] = None, state=None): def __init__(self, data: WebhookPayload, token: Optional[str] = None, state: Optional[ConnectionState] = None):
self.auth_token: Optional[str] = token self.auth_token: Optional[str] = token
self._state = state or _WebhookState(self, parent=state) self._state: Union[ConnectionState, _WebhookState] = state or _WebhookState(self, parent=state)
self._update(data) self._update(data)
def _update(self, data: WebhookPayload): def _update(self, data: WebhookPayload):
@ -765,10 +781,8 @@ class BaseWebhook(Hashable):
user = data.get('user') user = data.get('user')
self.user: Optional[Union[BaseUser, User]] = None self.user: Optional[Union[BaseUser, User]] = None
if user is not None: if user is not None:
if self._state is None: # state parameter may be _WebhookState
self.user = BaseUser(state=None, data=user) self.user = User(state=self._state, data=user) # type: ignore
else:
self.user = User(state=self._state, data=user)
source_channel = data.get('source_channel') source_channel = data.get('source_channel')
if source_channel: if source_channel:
@ -872,6 +886,10 @@ class Webhook(BaseWebhook):
Returns the webhooks's hash. Returns the webhooks's hash.
.. describe:: int(x)
Returns the webhooks's ID.
.. versionchanged:: 1.4 .. versionchanged:: 1.4
Webhooks are now comparable and hashable. Webhooks are now comparable and hashable.
@ -919,12 +937,12 @@ class Webhook(BaseWebhook):
return f'<Webhook id={self.id!r}>' return f'<Webhook id={self.id!r}>'
@property @property
def url(self): def url(self) -> str:
""":class:`str` : Returns the webhook's url.""" """:class:`str` : Returns the webhook's url."""
return f'https://discord.com/api/webhooks/{self.id}/{self.token}' return f'https://discord.com/api/webhooks/{self.id}/{self.token}'
@classmethod @classmethod
def partial(cls, id: int, token: str, *, session: aiohttp.ClientSession, bot_token: Optional[str] = None): def partial(cls, id: int, token: str, *, session: aiohttp.ClientSession, bot_token: Optional[str] = None) -> Webhook:
"""Creates a partial :class:`Webhook`. """Creates a partial :class:`Webhook`.
Parameters Parameters
@ -960,7 +978,7 @@ class Webhook(BaseWebhook):
return cls(data, session, token=bot_token) return cls(data, session, token=bot_token)
@classmethod @classmethod
def from_url(cls, url: str, *, session: aiohttp.ClientSession, bot_token: Optional[str] = None): def from_url(cls, url: str, *, session: aiohttp.ClientSession, bot_token: Optional[str] = None) -> Webhook:
"""Creates a partial :class:`Webhook` from a webhook URL. """Creates a partial :class:`Webhook` from a webhook URL.
Parameters Parameters
@ -999,7 +1017,7 @@ class Webhook(BaseWebhook):
return cls(data, session, token=bot_token) # type: ignore return cls(data, session, token=bot_token) # type: ignore
@classmethod @classmethod
def _as_follower(cls, data, *, channel, user): def _as_follower(cls, data, *, channel, user) -> Webhook:
name = f"{channel.guild} #{channel}" name = f"{channel.guild} #{channel}"
feed: WebhookPayload = { feed: WebhookPayload = {
'id': data['webhook_id'], 'id': data['webhook_id'],
@ -1015,7 +1033,7 @@ class Webhook(BaseWebhook):
return cls(feed, session=session, state=state, token=state.http.token) return cls(feed, session=session, state=state, token=state.http.token)
@classmethod @classmethod
def from_state(cls, data, state): def from_state(cls, data, state) -> Webhook:
session = state.http._HTTPClient__session session = state.http._HTTPClient__session
return cls(data, session=session, state=state, token=state.http.token) return cls(data, session=session, state=state, token=state.http.token)
@ -1111,7 +1129,7 @@ class Webhook(BaseWebhook):
avatar: Optional[bytes] = MISSING, avatar: Optional[bytes] = MISSING,
channel: Optional[Snowflake] = None, channel: Optional[Snowflake] = None,
prefer_auth: bool = True, prefer_auth: bool = True,
): ) -> Webhook:
"""|coro| """|coro|
Edits this Webhook. Edits this Webhook.
@ -1158,6 +1176,7 @@ class Webhook(BaseWebhook):
adapter = async_context.get() adapter = async_context.get()
data: Optional[WebhookPayload] = None
# If a channel is given, always use the authenticated endpoint # If a channel is given, always use the authenticated endpoint
if channel is not None: if channel is not None:
if self.auth_token is None: if self.auth_token is None:
@ -1165,21 +1184,24 @@ class Webhook(BaseWebhook):
payload['channel_id'] = channel.id payload['channel_id'] = channel.id
data = await adapter.edit_webhook(self.id, self.auth_token, payload=payload, session=self.session, reason=reason) data = await adapter.edit_webhook(self.id, self.auth_token, payload=payload, session=self.session, reason=reason)
self._update(data)
return
if prefer_auth and self.auth_token: if prefer_auth and self.auth_token:
data = await adapter.edit_webhook(self.id, self.auth_token, payload=payload, session=self.session, reason=reason) data = await adapter.edit_webhook(self.id, self.auth_token, payload=payload, session=self.session, reason=reason)
self._update(data)
elif self.token: elif self.token:
data = await adapter.edit_webhook_with_token( data = await adapter.edit_webhook_with_token(
self.id, self.token, payload=payload, session=self.session, reason=reason self.id, self.token, payload=payload, session=self.session, reason=reason
) )
self._update(data)
if data is None:
raise RuntimeError('Unreachable code hit: data was not assigned')
return Webhook(data=data, session=self.session, token=self.auth_token, state=self._state)
def _create_message(self, data): def _create_message(self, data):
state = _WebhookState(self, parent=self._state) state = _WebhookState(self, parent=self._state)
channel = self.channel or Object(id=int(data['channel_id'])) # state may be artificial (unlikely at this point...)
channel = self.channel or PartialMessageable(state=self._state, id=int(data['channel_id'])) # type: ignore
# state is artificial
return WebhookMessage(data=data, state=state, channel=channel) # type: ignore return WebhookMessage(data=data, state=state, channel=channel) # type: ignore
@overload @overload
@ -1188,7 +1210,7 @@ class Webhook(BaseWebhook):
content: str = MISSING, content: str = MISSING,
*, *,
username: str = MISSING, username: str = MISSING,
avatar_url: str = MISSING, avatar_url: Any = MISSING,
tts: bool = MISSING, tts: bool = MISSING,
ephemeral: bool = MISSING, ephemeral: bool = MISSING,
file: File = MISSING, file: File = MISSING,
@ -1208,7 +1230,7 @@ class Webhook(BaseWebhook):
content: str = MISSING, content: str = MISSING,
*, *,
username: str = MISSING, username: str = MISSING,
avatar_url: str = MISSING, avatar_url: Any = MISSING,
tts: bool = MISSING, tts: bool = MISSING,
ephemeral: bool = MISSING, ephemeral: bool = MISSING,
file: File = MISSING, file: File = MISSING,
@ -1227,7 +1249,7 @@ class Webhook(BaseWebhook):
content: str = MISSING, content: str = MISSING,
*, *,
username: str = MISSING, username: str = MISSING,
avatar_url: str = MISSING, avatar_url: Any = MISSING,
tts: bool = False, tts: bool = False,
ephemeral: bool = False, ephemeral: bool = False,
file: File = MISSING, file: File = MISSING,
@ -1264,9 +1286,10 @@ class Webhook(BaseWebhook):
username: :class:`str` username: :class:`str`
The username to send with this message. If no username is provided The username to send with this message. If no username is provided
then the default username for the webhook is used. then the default username for the webhook is used.
avatar_url: Union[:class:`str`, :class:`Asset`] avatar_url: :class:`str`
The avatar URL to send with this message. If no avatar URL is provided The avatar URL to send with this message. If no avatar URL is provided
then the default avatar for the webhook is used. then the default avatar for the webhook is used. If this is not a
string then it is explicitly cast using ``str``.
tts: :class:`bool` tts: :class:`bool`
Indicates if the message should be sent using text-to-speech. Indicates if the message should be sent using text-to-speech.
ephemeral: :class:`bool` ephemeral: :class:`bool`
@ -1438,7 +1461,7 @@ class Webhook(BaseWebhook):
files: List[File] = MISSING, files: List[File] = MISSING,
view: Optional[View] = MISSING, view: Optional[View] = MISSING,
allowed_mentions: Optional[AllowedMentions] = None, allowed_mentions: Optional[AllowedMentions] = None,
): ) -> WebhookMessage:
"""|coro| """|coro|
Edits a message owned by this webhook. Edits a message owned by this webhook.
@ -1448,6 +1471,9 @@ class Webhook(BaseWebhook):
.. versionadded:: 1.6 .. versionadded:: 1.6
.. versionchanged:: 2.0
The edit is no longer in-place, instead the newly edited message is returned.
Parameters Parameters
------------ ------------
message_id: :class:`int` message_id: :class:`int`
@ -1491,6 +1517,11 @@ class Webhook(BaseWebhook):
InvalidArgument InvalidArgument
There was no token associated with this webhook or the webhook had There was no token associated with this webhook or the webhook had
no state. no state.
Returns
--------
:class:`WebhookMessage`
The newly edited webhook message.
""" """
if self.token is None: if self.token is None:
@ -1514,7 +1545,7 @@ class Webhook(BaseWebhook):
previous_allowed_mentions=previous_mentions, previous_allowed_mentions=previous_mentions,
) )
adapter = async_context.get() adapter = async_context.get()
await adapter.edit_webhook_message( data = await adapter.edit_webhook_message(
self.id, self.id,
self.token, self.token,
message_id, message_id,
@ -1524,10 +1555,12 @@ class Webhook(BaseWebhook):
files=params.files, files=params.files,
) )
message = self._create_message(data)
if view and not view.is_finished(): if view and not view.is_finished():
self._state.store_view(view, message_id) self._state.store_view(view, message_id)
return message
async def delete_message(self, message_id: int): async def delete_message(self, message_id: int, /) -> None:
"""|coro| """|coro|
Deletes a message owned by this webhook. Deletes a message owned by this webhook.

View File

@ -43,7 +43,7 @@ from .. import utils
from ..errors import InvalidArgument, HTTPException, Forbidden, NotFound, DiscordServerError from ..errors import InvalidArgument, HTTPException, Forbidden, NotFound, DiscordServerError
from ..message import Message from ..message import Message
from ..http import Route from ..http import Route
from ..object import Object from ..channel import PartialMessageable
from .async_ import BaseWebhook, handle_message_parameters, _WebhookState from .async_ import BaseWebhook, handle_message_parameters, _WebhookState
@ -52,7 +52,7 @@ __all__ = (
'SyncWebhookMessage', 'SyncWebhookMessage',
) )
log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
if TYPE_CHECKING: if TYPE_CHECKING:
from ..file import File from ..file import File
@ -117,7 +117,7 @@ class WebhookAdapter:
if payload is not None: if payload is not None:
headers['Content-Type'] = 'application/json' headers['Content-Type'] = 'application/json'
to_send = utils.to_json(payload) to_send = utils._to_json(payload)
if auth_token is not None: if auth_token is not None:
headers['Authorization'] = f'Bot {auth_token}' headers['Authorization'] = f'Bot {auth_token}'
@ -150,7 +150,7 @@ class WebhookAdapter:
with session.request( with session.request(
method, url, data=to_send, files=file_data, headers=headers, params=params method, url, data=to_send, files=file_data, headers=headers, params=params
) as response: ) as response:
log.debug( _log.debug(
'Webhook ID %s with %s %s has returned status code %s', 'Webhook ID %s with %s %s has returned status code %s',
webhook_id, webhook_id,
method, method,
@ -168,7 +168,7 @@ class WebhookAdapter:
remaining = response.headers.get('X-Ratelimit-Remaining') remaining = response.headers.get('X-Ratelimit-Remaining')
if remaining == '0' and response.status_code != 429: if remaining == '0' and response.status_code != 429:
delta = utils._parse_ratelimit_header(response) delta = utils._parse_ratelimit_header(response)
log.debug( _log.debug(
'Webhook ID %s has been pre-emptively rate limited, waiting %.2f seconds', webhook_id, delta 'Webhook ID %s has been pre-emptively rate limited, waiting %.2f seconds', webhook_id, delta
) )
lock.delay_by(delta) lock.delay_by(delta)
@ -181,7 +181,7 @@ class WebhookAdapter:
raise HTTPException(response, data) raise HTTPException(response, data)
retry_after: float = data['retry_after'] # type: ignore retry_after: float = data['retry_after'] # type: ignore
log.warning('Webhook ID %s is rate limited. Retrying in %.2f seconds', webhook_id, retry_after) _log.warning('Webhook ID %s is rate limited. Retrying in %.2f seconds', webhook_id, retry_after)
time.sleep(retry_after) time.sleep(retry_after)
continue continue
@ -373,6 +373,8 @@ class SyncWebhookMessage(Message):
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
_state: _WebhookState
def edit( def edit(
self, self,
content: Optional[str] = MISSING, content: Optional[str] = MISSING,
@ -381,7 +383,7 @@ class SyncWebhookMessage(Message):
file: File = MISSING, file: File = MISSING,
files: List[File] = MISSING, files: List[File] = MISSING,
allowed_mentions: Optional[AllowedMentions] = None, allowed_mentions: Optional[AllowedMentions] = None,
): ) -> SyncWebhookMessage:
"""Edits the message. """Edits the message.
Parameters Parameters
@ -414,8 +416,13 @@ class SyncWebhookMessage(Message):
The length of ``embeds`` was invalid The length of ``embeds`` was invalid
InvalidArgument InvalidArgument
There was no token associated with this webhook. There was no token associated with this webhook.
Returns
--------
:class:`SyncWebhookMessage`
The newly edited message.
""" """
self._state._webhook.edit_message( return self._state._webhook.edit_message(
self.id, self.id,
content=content, content=content,
embeds=embeds, embeds=embeds,
@ -468,6 +475,10 @@ class SyncWebhook(BaseWebhook):
Returns the webhooks's hash. Returns the webhooks's hash.
.. describe:: int(x)
Returns the webhooks's ID.
.. versionchanged:: 1.4 .. versionchanged:: 1.4
Webhooks are now comparable and hashable. Webhooks are now comparable and hashable.
@ -515,12 +526,12 @@ class SyncWebhook(BaseWebhook):
return f'<Webhook id={self.id!r}>' return f'<Webhook id={self.id!r}>'
@property @property
def url(self): def url(self) -> str:
""":class:`str` : Returns the webhook's url.""" """:class:`str` : Returns the webhook's url."""
return f'https://discord.com/api/webhooks/{self.id}/{self.token}' return f'https://discord.com/api/webhooks/{self.id}/{self.token}'
@classmethod @classmethod
def partial(cls, id: int, token: str, *, session: Session = MISSING, bot_token: Optional[str] = None): def partial(cls, id: int, token: str, *, session: Session = MISSING, bot_token: Optional[str] = None) -> SyncWebhook:
"""Creates a partial :class:`Webhook`. """Creates a partial :class:`Webhook`.
Parameters Parameters
@ -559,7 +570,7 @@ class SyncWebhook(BaseWebhook):
return cls(data, session, token=bot_token) return cls(data, session, token=bot_token)
@classmethod @classmethod
def from_url(cls, url: str, *, session: Session = MISSING, bot_token: Optional[str] = None): def from_url(cls, url: str, *, session: Session = MISSING, bot_token: Optional[str] = None) -> SyncWebhook:
"""Creates a partial :class:`Webhook` from a webhook URL. """Creates a partial :class:`Webhook` from a webhook URL.
Parameters Parameters
@ -643,7 +654,7 @@ class SyncWebhook(BaseWebhook):
return SyncWebhook(data, self.session, token=self.auth_token, state=self._state) return SyncWebhook(data, self.session, token=self.auth_token, state=self._state)
def delete(self, *, reason: Optional[str] = None, prefer_auth: bool = True): def delete(self, *, reason: Optional[str] = None, prefer_auth: bool = True) -> None:
"""Deletes this Webhook. """Deletes this Webhook.
Parameters Parameters
@ -685,7 +696,7 @@ class SyncWebhook(BaseWebhook):
avatar: Optional[bytes] = MISSING, avatar: Optional[bytes] = MISSING,
channel: Optional[Snowflake] = None, channel: Optional[Snowflake] = None,
prefer_auth: bool = True, prefer_auth: bool = True,
): ) -> SyncWebhook:
"""Edits this Webhook. """Edits this Webhook.
Parameters Parameters
@ -713,6 +724,11 @@ class SyncWebhook(BaseWebhook):
InvalidArgument InvalidArgument
This webhook does not have a token associated with it This webhook does not have a token associated with it
or it tried editing a channel without authentication. or it tried editing a channel without authentication.
Returns
--------
:class:`SyncWebhook`
The newly edited webhook.
""" """
if self.token is None and self.auth_token is None: if self.token is None and self.auth_token is None:
raise InvalidArgument('This webhook does not have a token associated with it') raise InvalidArgument('This webhook does not have a token associated with it')
@ -726,6 +742,7 @@ class SyncWebhook(BaseWebhook):
adapter: WebhookAdapter = _get_webhook_adapter() adapter: WebhookAdapter = _get_webhook_adapter()
data: Optional[WebhookPayload] = None
# If a channel is given, always use the authenticated endpoint # If a channel is given, always use the authenticated endpoint
if channel is not None: if channel is not None:
if self.auth_token is None: if self.auth_token is None:
@ -733,20 +750,23 @@ class SyncWebhook(BaseWebhook):
payload['channel_id'] = channel.id payload['channel_id'] = channel.id
data = adapter.edit_webhook(self.id, self.auth_token, payload=payload, session=self.session, reason=reason) data = adapter.edit_webhook(self.id, self.auth_token, payload=payload, session=self.session, reason=reason)
self._update(data)
return
if prefer_auth and self.auth_token: if prefer_auth and self.auth_token:
data = adapter.edit_webhook(self.id, self.auth_token, payload=payload, session=self.session, reason=reason) data = adapter.edit_webhook(self.id, self.auth_token, payload=payload, session=self.session, reason=reason)
self._update(data)
elif self.token: elif self.token:
data = adapter.edit_webhook_with_token(self.id, self.token, payload=payload, session=self.session, reason=reason) data = adapter.edit_webhook_with_token(self.id, self.token, payload=payload, session=self.session, reason=reason)
self._update(data)
if data is None:
raise RuntimeError('Unreachable code hit: data was not assigned')
return SyncWebhook(data=data, session=self.session, token=self.auth_token, state=self._state)
def _create_message(self, data): def _create_message(self, data):
state = _WebhookState(self, parent=self._state) state = _WebhookState(self, parent=self._state)
channel = self.channel or Object(id=int(data['channel_id'])) # state may be artificial (unlikely at this point...)
return SyncWebhookMessage(data=data, state=state, channel=channel) channel = self.channel or PartialMessageable(state=self._state, id=int(data['channel_id'])) # type: ignore
# state is artificial
return SyncWebhookMessage(data=data, state=state, channel=channel) # type: ignore
@overload @overload
def send( def send(
@ -754,7 +774,7 @@ class SyncWebhook(BaseWebhook):
content: str = MISSING, content: str = MISSING,
*, *,
username: str = MISSING, username: str = MISSING,
avatar_url: str = MISSING, avatar_url: Any = MISSING,
tts: bool = MISSING, tts: bool = MISSING,
file: File = MISSING, file: File = MISSING,
files: List[File] = MISSING, files: List[File] = MISSING,
@ -771,7 +791,7 @@ class SyncWebhook(BaseWebhook):
content: str = MISSING, content: str = MISSING,
*, *,
username: str = MISSING, username: str = MISSING,
avatar_url: str = MISSING, avatar_url: Any = MISSING,
tts: bool = MISSING, tts: bool = MISSING,
file: File = MISSING, file: File = MISSING,
files: List[File] = MISSING, files: List[File] = MISSING,
@ -787,7 +807,7 @@ class SyncWebhook(BaseWebhook):
content: str = MISSING, content: str = MISSING,
*, *,
username: str = MISSING, username: str = MISSING,
avatar_url: str = MISSING, avatar_url: Any = MISSING,
tts: bool = False, tts: bool = False,
file: File = MISSING, file: File = MISSING,
files: List[File] = MISSING, files: List[File] = MISSING,
@ -819,9 +839,10 @@ class SyncWebhook(BaseWebhook):
username: :class:`str` username: :class:`str`
The username to send with this message. If no username is provided The username to send with this message. If no username is provided
then the default username for the webhook is used. then the default username for the webhook is used.
avatar_url: Union[:class:`str`, :class:`Asset`] avatar_url: :class:`str`
The avatar URL to send with this message. If no avatar URL is provided The avatar URL to send with this message. If no avatar URL is provided
then the default avatar for the webhook is used. then the default avatar for the webhook is used. If this is not a
string then it is explicitly cast using ``str``.
tts: :class:`bool` tts: :class:`bool`
Indicates if the message should be sent using text-to-speech. Indicates if the message should be sent using text-to-speech.
file: :class:`File` file: :class:`File`
@ -902,7 +923,7 @@ class SyncWebhook(BaseWebhook):
if wait: if wait:
return self._create_message(data) return self._create_message(data)
def fetch_message(self, id: int) -> SyncWebhookMessage: def fetch_message(self, id: int, /) -> SyncWebhookMessage:
"""Retrieves a single :class:`~discord.SyncWebhookMessage` owned by this webhook. """Retrieves a single :class:`~discord.SyncWebhookMessage` owned by this webhook.
.. versionadded:: 2.0 .. versionadded:: 2.0
@ -951,7 +972,7 @@ class SyncWebhook(BaseWebhook):
file: File = MISSING, file: File = MISSING,
files: List[File] = MISSING, files: List[File] = MISSING,
allowed_mentions: Optional[AllowedMentions] = None, allowed_mentions: Optional[AllowedMentions] = None,
): ) -> SyncWebhookMessage:
"""Edits a message owned by this webhook. """Edits a message owned by this webhook.
This is a lower level interface to :meth:`WebhookMessage.edit` in case This is a lower level interface to :meth:`WebhookMessage.edit` in case
@ -1007,7 +1028,7 @@ class SyncWebhook(BaseWebhook):
previous_allowed_mentions=previous_mentions, previous_allowed_mentions=previous_mentions,
) )
adapter: WebhookAdapter = _get_webhook_adapter() adapter: WebhookAdapter = _get_webhook_adapter()
adapter.edit_webhook_message( data = adapter.edit_webhook_message(
self.id, self.id,
self.token, self.token,
message_id, message_id,
@ -1016,8 +1037,9 @@ class SyncWebhook(BaseWebhook):
multipart=params.multipart, multipart=params.multipart,
files=params.files, files=params.files,
) )
return self._create_message(data)
def delete_message(self, message_id: int): def delete_message(self, message_id: int, /) -> None:
"""Deletes a message owned by this webhook. """Deletes a message owned by this webhook.
This is a lower level interface to :meth:`WebhookMessage.delete` in case This is a lower level interface to :meth:`WebhookMessage.delete` in case

View File

@ -101,6 +101,7 @@ VoiceClient
.. autoclass:: VoiceClient() .. autoclass:: VoiceClient()
:members: :members:
:exclude-members: connect, on_voice_state_update, on_voice_server_update
VoiceProtocol VoiceProtocol
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
@ -311,9 +312,9 @@ to handle it, which defaults to print a traceback and ignoring the exception.
.. function:: on_socket_raw_receive(msg) .. function:: on_socket_raw_receive(msg)
Called whenever a message is received from the WebSocket, before Called whenever a message is completely received from the WebSocket, before
it's processed. This event is always dispatched when a message is it's processed and parsed. This event is always dispatched when a
received and the passed data is not processed in any way. complete message is received and the passed data is not parsed in any way.
This is only really useful for grabbing the WebSocket stream and This is only really useful for grabbing the WebSocket stream and
debugging purposes. debugging purposes.
@ -326,9 +327,7 @@ to handle it, which defaults to print a traceback and ignoring the exception.
WebSocket. The voice WebSocket will not trigger this event. WebSocket. The voice WebSocket will not trigger this event.
:param msg: The message passed in from the WebSocket library. :param msg: The message passed in from the WebSocket library.
Could be :class:`bytes` for a binary message or :class:`str` :type msg: :class:`str`
for a regular message.
:type msg: Union[:class:`bytes`, :class:`str`]
.. function:: on_socket_raw_send(payload) .. function:: on_socket_raw_send(payload)
@ -1591,6 +1590,8 @@ of :class:`enum.Enum`.
.. container:: operations .. container:: operations
.. versionadded:: 2.0
.. describe:: x == y .. describe:: x == y
Checks if two verification levels are equal. Checks if two verification levels are equal.
@ -1633,6 +1634,29 @@ of :class:`enum.Enum`.
Specifies whether a :class:`Guild` has notifications on for all messages or mentions only by default. Specifies whether a :class:`Guild` has notifications on for all messages or mentions only by default.
.. container:: operations
.. versionadded:: 2.0
.. describe:: x == y
Checks if two notification levels are equal.
.. describe:: x != y
Checks if two notification levels are not equal.
.. describe:: x > y
Checks if a notification level is higher than another.
.. describe:: x < y
Checks if a notification level is lower than another.
.. describe:: x >= y
Checks if a notification level is higher or equal to another.
.. describe:: x <= y
Checks if a notification level is lower or equal to another.
.. attribute:: all_messages .. attribute:: all_messages
Members receive notifications for every message regardless of them being mentioned. Members receive notifications for every message regardless of them being mentioned.
@ -1648,6 +1672,8 @@ of :class:`enum.Enum`.
.. container:: operations .. container:: operations
.. versionadded:: 2.0
.. describe:: x == y .. describe:: x == y
Checks if two content filter levels are equal. Checks if two content filter levels are equal.
@ -1908,7 +1934,7 @@ of :class:`enum.Enum`.
.. attribute:: member_role_update .. attribute:: member_role_update
A member's role has been updated. This triggers when a member A member's role has been updated. This triggers when a member
either gains a role or losses a role. either gains a role or loses a role.
When this is the action, the type of :attr:`~AuditLogEntry.target` is When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`Member` or :class:`User` who got the role. the :class:`Member` or :class:`User` who got the role.
@ -2532,6 +2558,27 @@ of :class:`enum.Enum`.
.. versionadded:: 2.0 .. versionadded:: 2.0
.. container:: operations
.. describe:: x == y
Checks if two NSFW levels are equal.
.. describe:: x != y
Checks if two NSFW levels are not equal.
.. describe:: x > y
Checks if a NSFW level is higher than another.
.. describe:: x < y
Checks if a NSFW level is lower than another.
.. describe:: x >= y
Checks if a NSFW level is higher or equal to another.
.. describe:: x <= y
Checks if a NSFW level is lower or equal to another.
.. attribute:: default .. attribute:: default
The guild has not been categorised yet. The guild has not been categorised yet.
@ -3884,7 +3931,7 @@ most of these yourself, even if they can also be used to hold attributes.
Nearly all classes here have :ref:`py:slots` defined which means that it is Nearly all classes here have :ref:`py:slots` defined which means that it is
impossible to have dynamic attributes to the data classes. impossible to have dynamic attributes to the data classes.
The only exception to this rule is :class:`abc.Snowflake`, which is made with The only exception to this rule is :class:`Object`, which is made with
dynamic attributes in mind. dynamic attributes in mind.

File diff suppressed because it is too large Load Diff

View File

@ -1,179 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: discordpy\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-10-23 22:41-0400\n"
"PO-Revision-Date: 2020-10-24 02:41+0000\n"
"Last-Translator: \n"
"Language: ja_JP\n"
"Language-Team: Japanese\n"
"Plural-Forms: nplurals=1; plural=0\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.5.3\n"
#: ../../discord.rst:4
msgid "Creating a Bot Account"
msgstr "Botアカウント作成"
#: ../../discord.rst:6
msgid ""
"In order to work with the library and the Discord API in general, we must"
" first create a Discord Bot account."
msgstr "ライブラリとDiscord APIを使用するには、BotのDiscordアカウントを用意する必要があります。"
#: ../../discord.rst:8
msgid "Creating a Bot account is a pretty straightforward process."
msgstr "Botのアカウント作成はとても簡単です。"
#: ../../discord.rst:10 ../../discord.rst:64
#, fuzzy
msgid ""
"Make sure you're logged on to the `Discord website "
"<https://discord.com>`_."
msgstr "まずは `Discordのウェブサイト <https://discordapp.com>`_ にログインしてください。"
#: ../../discord.rst:11 ../../discord.rst:65
#, fuzzy
msgid ""
"Navigate to the `application page "
"<https://discord.com/developers/applications>`_"
msgstr "`アプリケーションページ <https://discordapp.com/developers/applications>`_ に移動します。"
#: ../../discord.rst:12
msgid "Click on the \"New Application\" button."
msgstr "「New Application」ボタンをクリックします。"
#: ../../discord.rst:17
msgid "Give the application a name and click \"Create\"."
msgstr "アプリケーションの名前を決めて、「Create」をクリックします。"
#: ../../discord.rst:22
msgid ""
"Create a Bot User by navigating to the \"Bot\" tab and clicking \"Add "
"Bot\"."
msgstr "「Bot」タブへ移動し、「Add Bot」をクリックしてBotユーザーを作成します。"
#: ../../discord.rst:24
msgid "Click \"Yes, do it!\" to continue."
msgstr "「Yes, do it!」をクリックして続行します。"
#: ../../discord.rst:28
msgid ""
"Make sure that **Public Bot** is ticked if you want others to invite your"
" bot."
msgstr "他人にBotの招待を許可する場合には、 **Public Bot** にチェックを入れてください。"
#: ../../discord.rst:30
msgid ""
"You should also make sure that **Require OAuth2 Code Grant** is unchecked"
" unless you are developing a service that needs it. If you're unsure, "
"then **leave it unchecked**."
msgstr ""
"また、必要なサービスを開発している場合を除いて、 **Require OAuth2 Code Grant** "
"がオフになっていることを確認する必要があります。わからない場合は **チェックを外してください** 。"
#: ../../discord.rst:36
msgid "Copy the token using the \"Copy\" button."
msgstr "「Copy」ボタンを使ってトークンをコピーします。"
#: ../../discord.rst:38
msgid "**This is not the Client Secret at the General Information page**"
msgstr "**General InformationページのClient Secretではないので注意してください**"
#: ../../discord.rst:42
msgid ""
"It should be worth noting that this token is essentially your bot's "
"password. You should **never** share this to someone else. In doing so, "
"someone can log in to your bot and do malicious things, such as leaving "
"servers, ban all members inside a server, or pinging everyone "
"maliciously."
msgstr "このトークンは、あなたのBotのパスワードと同義であることを覚えておきましょう。誰か他の人とトークンを共有することは絶対に避けてください。トークンがあれば、誰かがあなたのBotにログインし、サーバーから退出したり、サーバー内のすべてのメンバーをBANしたり、すべての人にメンションを送るなどといった悪質な行為を行える様になってしまいます。"
#: ../../discord.rst:47
msgid "The possibilities are endless, so **do not share this token.**"
msgstr "可能性は無限にあるので、絶対に **トークンを共有しないでください** 。"
#: ../../discord.rst:49
msgid ""
"If you accidentally leaked your token, click the \"Regenerate\" button as"
" soon as possible. This revokes your old token and re-generates a new "
"one. Now you need to use the new token to login."
msgstr "誤ってトークンを流出させてしまった場合、可能な限り速急に「Regenerate」ボタンをクリックしましょう。これによって古いトークンが無効になり、新しいトークンが再生成されます。今度からは新しいトークンを利用してログインを行う必要があります。"
#: ../../discord.rst:53
msgid ""
"And that's it. You now have a bot account and you can login with that "
"token."
msgstr "以上です。 これでボットアカウントが作成され、そのトークンでログインできます。"
#: ../../discord.rst:58
msgid "Inviting Your Bot"
msgstr "Botを招待する"
#: ../../discord.rst:60
msgid "So you've made a Bot User but it's not actually in any server."
msgstr "Botのユーザーを作成しましたが、現時点ではどのサーバーにも参加していない状態です。"
#: ../../discord.rst:62
msgid "If you want to invite your bot you must create an invite URL for it."
msgstr "Botを招待したい場合は、そのための招待URLを作成する必要があります。"
#: ../../discord.rst:66
msgid "Click on your bot's page."
msgstr "Botのページを開きます。"
#: ../../discord.rst:67
msgid "Go to the \"OAuth2\" tab."
msgstr "「OAuth2」タブへ移動します。"
#: ../../discord.rst:72
msgid "Tick the \"bot\" checkbox under \"scopes\"."
msgstr "「scopes」下にある「bot」チェックボックスを選択してください。"
#: ../../discord.rst:77
msgid ""
"Tick the permissions required for your bot to function under \"Bot "
"Permissions\"."
msgstr "「Bot Permissions」からBotの機能に必要な権限を選択してください。"
#: ../../discord.rst:79
msgid ""
"Please be aware of the consequences of requiring your bot to have the "
"\"Administrator\" permission."
msgstr "Botに「管理者」権限を要求させることによる影響は認識しておきましょう。"
#: ../../discord.rst:81
#, fuzzy
msgid ""
"Bot owners must have 2FA enabled for certain actions and permissions when"
" added in servers that have Server-Wide 2FA enabled. Check the `2FA "
"support page <https://support.discord.com/hc/en-us/articles/219576828"
"-Setting-up-Two-Factor-Authentication>`_ for more information."
msgstr ""
"二段階認証が有効になっているサーバーにBotが追加された場合、Botの所有者は特定の動作と権限のために二段階認証を有効化させなければいけません。詳細は"
" `二段階認証のサポートページ <https://support.discordapp.com/hc/en-"
"us/articles/219576828-Setting-up-Two-Factor-Authentication>`_ を参照してください。"
#: ../../discord.rst:86
msgid ""
"Now the resulting URL can be used to add your bot to a server. Copy and "
"paste the URL into your browser, choose a server to invite the bot to, "
"and click \"Authorize\"."
msgstr "結果的に生成されたURLを使ってBotをサーバーに追加することができます。URLをコピーしてブラウザに貼り付け、Botを招待したいサーバーを選択した後、「認証」をクリックしてください。"
#: ../../discord.rst:91
msgid "The person adding the bot needs \"Manage Server\" permissions to do so."
msgstr "Botを追加する人には「サーバー管理」権限が必要です。"
#: ../../discord.rst:93
msgid ""
"If you want to generate this URL dynamically at run-time inside your bot "
"and using the :class:`discord.Permissions` interface, you can use "
":func:`discord.utils.oauth_url`."
msgstr ""
"このURLを実行時に動的に生成したい場合は、 :class:`discord.Permissions` インターフェイスから "
":func:`discord.utils.oauth_url` を使用できます。"

File diff suppressed because it is too large Load Diff

View File

@ -1,179 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: discordpy\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-06-22 09:35-0400\n"
"PO-Revision-Date: 2020-10-24 02:41\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: discordpy\n"
"X-Crowdin-Project-ID: 362783\n"
"X-Crowdin-Language: ja\n"
"X-Crowdin-File: /ext/commands/cogs.pot\n"
"X-Crowdin-File-ID: 60\n"
"Language: ja_JP\n"
#: ../../ext/commands/cogs.rst:6
msgid "Cogs"
msgstr "コグ"
#: ../../ext/commands/cogs.rst:8
msgid "There comes a point in your bot's development when you want to organize a collection of commands, listeners, and some state into one class. Cogs allow you to do just that."
msgstr "Bot開発においてコマンドやリスナー、いくつかの状態を一つのクラスにまとめてしまいたい場合があるでしょう。コグはそれを実現したものです。"
#: ../../ext/commands/cogs.rst:10
msgid "The gist:"
msgstr "要旨:"
#: ../../ext/commands/cogs.rst:12
msgid "Each cog is a Python class that subclasses :class:`.commands.Cog`."
msgstr "すべてのコグは :class:`.commands.Cog` を継承したPythonクラスです。"
#: ../../ext/commands/cogs.rst:13
msgid "Every command is marked with the :func:`.commands.command` decorator."
msgstr "すべてのコマンドは :func:`.commands.command` デコレータでデコレートされます。"
#: ../../ext/commands/cogs.rst:14
msgid "Every listener is marked with the :meth:`.commands.Cog.listener` decorator."
msgstr "すべてのリスナーは :meth:`.commands.Cog.listener` デコレータでデコレートされます。"
#: ../../ext/commands/cogs.rst:15
msgid "Cogs are then registered with the :meth:`.Bot.add_cog` call."
msgstr "コグは :meth:`.Bot.add_cog` を呼び出して登録します。"
#: ../../ext/commands/cogs.rst:16
msgid "Cogs are subsequently removed with the :meth:`.Bot.remove_cog` call."
msgstr "コグは :meth:`.Bot.remove_cog` の呼び出しで削除されます。"
#: ../../ext/commands/cogs.rst:18
msgid "It should be noted that cogs are typically used alongside with :ref:`ext_commands_extensions`."
msgstr "コグは :ref:`ext_commands_extensions` とともに使用されるのが一般的であることを覚えておきましょう。"
#: ../../ext/commands/cogs.rst:21
msgid "Quick Example"
msgstr "簡単な例"
#: ../../ext/commands/cogs.rst:23
msgid "This example cog defines a ``Greetings`` category for your commands, with a single :ref:`command <ext_commands_commands>` named ``hello`` as well as a listener to listen to an :ref:`Event <discord-api-events>`."
msgstr "この例で紹介するコグは ``hello`` という名前の :ref:`command <ext_commands_commands>` と、 :ref:`Event <discord-api-events>` をリッスンするリスナーを実装した ``Greetings`` という名前のコマンドカテゴリを定義しています。"
#: ../../ext/commands/cogs.rst:48
msgid "A couple of technical notes to take into consideration:"
msgstr "考慮すべき二つのテクニカルノート:"
#: ../../ext/commands/cogs.rst:50
msgid "All listeners must be explicitly marked via decorator, :meth:`~.commands.Cog.listener`."
msgstr "すべてのリスナーは :meth:`~.commands.Cog.listener` で明示的にデコレートする必要があります。"
#: ../../ext/commands/cogs.rst:51
msgid "The name of the cog is automatically derived from the class name but can be overridden. See :ref:`ext_commands_cogs_meta_options`."
msgstr "コグの名前は、自動的にクラスの名前が引用されますが、上書きも可能です。 :ref:`ext_commands_cogs_meta_options` を参照してください。"
#: ../../ext/commands/cogs.rst:52
msgid "All commands must now take a ``self`` parameter to allow usage of instance attributes that can be used to maintain state."
msgstr "すべてのコマンドは状態を保持するインスタンスの属性を使用するために ``self`` パラメータを持つ必要があります。"
#: ../../ext/commands/cogs.rst:55
msgid "Cog Registration"
msgstr "コグの登録"
#: ../../ext/commands/cogs.rst:57
msgid "Once you have defined your cogs, you need to tell the bot to register the cogs to be used. We do this via the :meth:`~.commands.Bot.add_cog` method."
msgstr "コグを定義したら、Botにコグを登録する処理が必要になります。 :meth:`~.commands.Bot.add_cog` メソッドを用いて登録ができます。"
#: ../../ext/commands/cogs.rst:63
msgid "This binds the cog to the bot, adding all commands and listeners to the bot automatically."
msgstr "これはコグとBotを紐づけ、すべてのコマンドとリスナーを自動的にBotに追加します。"
#: ../../ext/commands/cogs.rst:65
msgid "Note that we reference the cog by name, which we can override through :ref:`ext_commands_cogs_meta_options`. So if we ever want to remove the cog eventually, we would have to do the following."
msgstr "コグを名前で参照している点に注意してください。これは :ref:`ext_commands_cogs_meta_options` でオーバーライドが可能です。そのため、最終的にコグを削除したい場合は、次の処理を行う必要があります。"
#: ../../ext/commands/cogs.rst:72
msgid "Using Cogs"
msgstr "コグの使用"
#: ../../ext/commands/cogs.rst:74
msgid "Just as we remove a cog by its name, we can also retrieve it by its name as well. This allows us to use a cog as an inter-command communication protocol to share data. For example:"
msgstr "コグを名前で削除するのと同様に、名前でコグを検索することもできます。これによってコグをデータ共有のためのコマンド間通信プロトコルとして使うことができます。例えば:"
#: ../../ext/commands/cogs.rst:109
msgid "Special Methods"
msgstr "特殊なメソッド"
#: ../../ext/commands/cogs.rst:111
msgid "As cogs get more complicated and have more commands, there comes a point where we want to customise the behaviour of the entire cog or bot."
msgstr "コグが複雑化し、多くのコマンドを持つようになるにつれ、コグあるいはBot全体の挙動をカスタマイズしたくなることがあります。"
#: ../../ext/commands/cogs.rst:113
msgid "They are as follows:"
msgstr "そのための特殊なメソッドは以下のとおりです:"
#: ../../ext/commands/cogs.rst:115
msgid ":meth:`.Cog.cog_unload`"
msgstr ":meth:`.Cog.cog_unload`"
#: ../../ext/commands/cogs.rst:116
msgid ":meth:`.Cog.cog_check`"
msgstr ":meth:`.Cog.cog_check`"
#: ../../ext/commands/cogs.rst:117
msgid ":meth:`.Cog.cog_command_error`"
msgstr ":meth:`.Cog.cog_command_error`"
#: ../../ext/commands/cogs.rst:118
msgid ":meth:`.Cog.cog_before_invoke`"
msgstr ":meth:`.Cog.cog_before_invoke`"
#: ../../ext/commands/cogs.rst:119
msgid ":meth:`.Cog.cog_after_invoke`"
msgstr ":meth:`.Cog.cog_after_invoke`"
#: ../../ext/commands/cogs.rst:120
msgid ":meth:`.Cog.bot_check`"
msgstr ":meth:`.Cog.bot_check`"
#: ../../ext/commands/cogs.rst:121
msgid ":meth:`.Cog.bot_check_once`"
msgstr ":meth:`.Cog.bot_check_once`"
#: ../../ext/commands/cogs.rst:123
msgid "You can visit the reference to get more detail."
msgstr "詳細はリファレンスを参照してください。"
#: ../../ext/commands/cogs.rst:128
msgid "Meta Options"
msgstr "メタオプション"
#: ../../ext/commands/cogs.rst:130
msgid "At the heart of a cog resides a metaclass, :class:`.commands.CogMeta`, which can take various options to customise some of the behaviour. To do this, we pass keyword arguments to the class definition line. For example, to change the cog name we can pass the ``name`` keyword argument as follows:"
msgstr "コグの中核にはメタクラスである :class:`.commands.CogMeta` が存在します。これにはいくつかの挙動を変更ができる様々なオプションが用意されています。オプションを使用する際はキーワード引数をクラス定義の行で渡します。例えば、コグの名前を変更する場合はキーワード引数 ``name`` を次のように渡します。"
#: ../../ext/commands/cogs.rst:137
msgid "To see more options that you can set, see the documentation of :class:`.commands.CogMeta`."
msgstr "設定可能な他のオプションについては :class:`.commands.CogMeta` のドキュメントを参照してください。"
#: ../../ext/commands/cogs.rst:140
msgid "Inspection"
msgstr "インスペクション"
#: ../../ext/commands/cogs.rst:142
msgid "Since cogs ultimately are classes, we have some tools to help us inspect certain properties of the cog."
msgstr "コグは究極的にはクラスのため、コグの特定のプロパティを調べるのに役立つツールがいくつか用意されています。"
#: ../../ext/commands/cogs.rst:145
msgid "To get a :class:`list` of commands, we can use :meth:`.Cog.get_commands`. ::"
msgstr ":meth:`.Cog.get_commands` を使うことで、コマンドの :class:`list` を取得できます。"
#: ../../ext/commands/cogs.rst:151
msgid "If we want to get the subcommands as well, we can use the :meth:`.Cog.walk_commands` generator. ::"
msgstr "サブコマンドを取得したい場合は :meth:`.Cog.walk_commands` ジェネレータを使うことができます。"
#: ../../ext/commands/cogs.rst:155
msgid "To do the same with listeners, we can query them with :meth:`.Cog.get_listeners`. This returns a list of tuples -- the first element being the listener name and the second one being the actual function itself. ::"
msgstr "これ同様の処理をリスナーで行う場合は、 :meth:`.Cog.get_listeners` が使用できます。これはタプルのリストを返します -- 最初の要素がリスナーの名前で、二つ目の要素が関数そのものです。"

View File

@ -1,901 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: discordpy\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-10-23 22:41-0400\n"
"PO-Revision-Date: 2020-10-24 02:41+0000\n"
"Last-Translator: \n"
"Language: ja_JP\n"
"Language-Team: Japanese\n"
"Plural-Forms: nplurals=1; plural=0\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.5.3\n"
#: ../../ext/commands/commands.rst:6
msgid "Commands"
msgstr "コマンド"
#: ../../ext/commands/commands.rst:8
msgid ""
"One of the most appealing aspect of the command extension is how easy it "
"is to define commands and how you can arbitrarily nest groups and "
"commands to have a rich sub-command system."
msgstr "コマンド拡張の最も魅力的な機能の一つは、簡単にコマンドが定義でき、かつそのコマンドを好きなようにネスト状にして、豊富なサブコマンドを用意することができる点です。"
#: ../../ext/commands/commands.rst:11
msgid ""
"Commands are defined by attaching it to a regular Python function. The "
"command is then invoked by the user using a similar signature to the "
"Python function."
msgstr "コマンドは、Pythonの関数と関連付けすることによって定義され、同様のシグネチャを使用してユーザーに呼び出されます。"
#: ../../ext/commands/commands.rst:14
msgid "For example, in the given command definition:"
msgstr "例えば、指定されたコマンド定義を使うと次のようになります。"
#: ../../ext/commands/commands.rst:22
msgid "With the following prefix (``$``), it would be invoked by the user via:"
msgstr "Prefixを (``$``) としたとすると、このコマンドは次の用に実行できます。"
#: ../../ext/commands/commands.rst:28
msgid ""
"A command must always have at least one parameter, ``ctx``, which is the "
":class:`.Context` as the first one."
msgstr "コマンドには、少なくとも :class:`.Context` を渡すための引数 ``ctx`` が必要です。"
#: ../../ext/commands/commands.rst:30
msgid ""
"There are two ways of registering a command. The first one is by using "
":meth:`.Bot.command` decorator, as seen in the example above. The second "
"is using the :func:`~ext.commands.command` decorator followed by "
":meth:`.Bot.add_command` on the instance."
msgstr ""
"コマンドを登録するには二通りの方法があります。一つ目は :meth:`.Bot.command` を使用する方法で、二つ目が "
":func:`~ext.commands.command` デコレータを使用して :meth:`.Bot.add_command` "
"でインスタンスにコマンドを追加していく方法です。"
#: ../../ext/commands/commands.rst:34
msgid "Essentially, these two are equivalent: ::"
msgstr "本質的に、これら2つは同等になります: ::"
#: ../../ext/commands/commands.rst:52
msgid ""
"Since the :meth:`.Bot.command` decorator is shorter and easier to "
"comprehend, it will be the one used throughout the documentation here."
msgstr ":meth:`.Bot.command` が簡単かつ理解がしやすいので、ドキュメント上ではこちらを使っています。"
#: ../../ext/commands/commands.rst:55
msgid ""
"Any parameter that is accepted by the :class:`.Command` constructor can "
"be passed into the decorator. For example, to change the name to "
"something other than the function would be as simple as doing this:"
msgstr ""
":class:`.Command` "
"のコンストラクタの引数はデコレータに渡すことで利用できます。例えば、コマンドの名前を関数以外のものへと変更したい場合は以下のように簡単に設定することができます。"
#: ../../ext/commands/commands.rst:65
msgid "Parameters"
msgstr "パラメーター"
#: ../../ext/commands/commands.rst:67
msgid ""
"Since we define commands by making Python functions, we also define the "
"argument passing behaviour by the function parameters."
msgstr "Pythonの関数定義によって、同時にコマンドを定義するので、関数のパラメーターを設定することにより、コマンドの引数受け渡し動作も定義することができます。"
#: ../../ext/commands/commands.rst:70
msgid ""
"Certain parameter types do different things in the user side and most "
"forms of parameter types are supported."
msgstr "特定のパラメータタイプはユーザーサイドで異なる動作を行い、そしてほとんどの形式のパラメータタイプがサポートされています。"
#: ../../ext/commands/commands.rst:73
msgid "Positional"
msgstr "位置引数"
#: ../../ext/commands/commands.rst:75
msgid ""
"The most basic form of parameter passing is the positional parameter. "
"This is where we pass a parameter as-is:"
msgstr "最も基本的な引数は位置パラメーターです。与えられた値をそのまま渡します。"
#: ../../ext/commands/commands.rst:84
msgid ""
"On the bot using side, you can provide positional arguments by just "
"passing a regular string:"
msgstr "Botの使用者側は、通常の文字列を渡すだけで位置引数に値を渡すことができます。"
#: ../../ext/commands/commands.rst:88
msgid "To make use of a word with spaces in between, you should quote it:"
msgstr "間に空白を含む文字列を渡す場合は、文字列を引用符で囲む必要があります。"
#: ../../ext/commands/commands.rst:92
msgid ""
"As a note of warning, if you omit the quotes, you will only get the first"
" word:"
msgstr "引用符を用いなかった場合、最初の文字列のみが渡されます。"
#: ../../ext/commands/commands.rst:96
msgid ""
"Since positional arguments are just regular Python arguments, you can "
"have as many as you want:"
msgstr "位置引数は、Pythonの引数と同じものなので、好きなだけ設定することが可能です。"
#: ../../ext/commands/commands.rst:105
msgid "Variable"
msgstr "可変長引数"
#: ../../ext/commands/commands.rst:107
msgid ""
"Sometimes you want users to pass in an undetermined number of parameters."
" The library supports this similar to how variable list parameters are "
"done in Python:"
msgstr "場合によっては、可変長のパラメーターを設定したい場合もあるでしょう。このライブラリはPythonの可変長パラメーターと同様にこれをサポートしています。"
#: ../../ext/commands/commands.rst:116
msgid ""
"This allows our user to accept either one or many arguments as they "
"please. This works similar to positional arguments, so multi-word "
"parameters should be quoted."
msgstr "これによって一つ、あるいは複数の引数を受け取ることができます。ただし、引数を渡す際の挙動は位置引数と同様のため、複数の単語を含む文字列は引用符で囲む必要があります。"
#: ../../ext/commands/commands.rst:119
msgid "For example, on the bot side:"
msgstr "例えば、Bot側ではこのように動きます。"
#: ../../ext/commands/commands.rst:123
msgid ""
"If the user wants to input a multi-word argument, they have to quote it "
"like earlier:"
msgstr "複数単語の文字列を渡す際は、引用符で囲んでください。"
#: ../../ext/commands/commands.rst:127
msgid ""
"Do note that similar to the Python function behaviour, a user can "
"technically pass no arguments at all:"
msgstr "Pythonの振る舞いと同様に、ユーザーは引数なしの状態を技術的に渡すことができます。"
#: ../../ext/commands/commands.rst:132
msgid ""
"Since the ``args`` variable is a :class:`py:tuple`, you can do anything "
"you would usually do with one."
msgstr ""
#: ../../ext/commands/commands.rst:136
msgid "Keyword-Only Arguments"
msgstr "キーワード引数"
#: ../../ext/commands/commands.rst:138
msgid ""
"When you want to handle parsing of the argument yourself or do not feel "
"like you want to wrap multi-word user input into quotes, you can ask the "
"library to give you the rest as a single argument. We do this by using a "
"**keyword-only argument**, seen below:"
msgstr "引数の構文解析を自分で行う場合や、複数単語の入力を引用符で囲む必要のないようにしたい場合は、渡された値を単一の引数として受け取るようにライブラリに求めることができます。以下のコードのようにキーワード引数のみを使用することでこれが可能になります。"
#: ../../ext/commands/commands.rst:150
msgid "You can only have one keyword-only argument due to parsing ambiguities."
msgstr "解析が曖昧になるため、一つのキーワードのみの引数しか扱えません。"
#: ../../ext/commands/commands.rst:152
msgid "On the bot side, we do not need to quote input with spaces:"
msgstr "Bot側では、スペースを含む入力を引用符で囲む必要がありません:"
#: ../../ext/commands/commands.rst:156
msgid "Do keep in mind that wrapping it in quotes leaves it as-is:"
msgstr "引用符で囲んだ場合、消えずに残るので注意してください:"
#: ../../ext/commands/commands.rst:160
msgid ""
"By default, the keyword-only arguments are stripped of white space to "
"make it easier to work with. This behaviour can be toggled by the "
":attr:`.Command.rest_is_raw` argument in the decorator."
msgstr ""
"通常、キーワード引数は利便性のために空白文字で分割されます。この動作はデコレータの引数として "
":attr:`.Command.rest_is_raw` を使うことで切り替えることが可能です。"
#: ../../ext/commands/commands.rst:166
msgid "Invocation Context"
msgstr "呼び出しコンテクスト"
#: ../../ext/commands/commands.rst:168
msgid ""
"As seen earlier, every command must take at least a single parameter, "
"called the :class:`~ext.commands.Context`."
msgstr "前述の通り、すべてのコマンドは必ず :class:`~ext.commands.Context` と呼ばれるパラメータを受け取らなければいけません。"
#: ../../ext/commands/commands.rst:170
msgid ""
"This parameter gives you access to something called the \"invocation "
"context\". Essentially all the information you need to know how the "
"command was executed. It contains a lot of useful information:"
msgstr "このパラメータにより、「呼び出しコンテクスト」というものにアクセスできます。言うなればコマンドがどのように実行されたのかを知るのに必要な基本的情報です。これにはたくさんの有用な情報が含まれています。"
#: ../../ext/commands/commands.rst:173
msgid ":attr:`.Context.guild` to fetch the :class:`Guild` of the command, if any."
msgstr "存在する場合に限り、コマンドの :class:`Guild` を取得できる :attr:`.Context.guild` 。"
#: ../../ext/commands/commands.rst:174
msgid ":attr:`.Context.message` to fetch the :class:`Message` of the command."
msgstr "コマンドの :class:`Message` を取得できる :attr:`.Context.message` 。"
#: ../../ext/commands/commands.rst:175
msgid ""
":attr:`.Context.author` to fetch the :class:`Member` or :class:`User` "
"that called the command."
msgstr ""
"コマンドを実行した :class:`Member` あるいは :class:`User` を取得できる "
":attr:`.Context.author` 。"
#: ../../ext/commands/commands.rst:176
msgid ""
":meth:`.Context.send` to send a message to the channel the command was "
"used in."
msgstr "コマンドが実行されたチャンネルにメッセージを送信する :meth:`.Context.send` 。"
#: ../../ext/commands/commands.rst:178
msgid ""
"The context implements the :class:`abc.Messageable` interface, so "
"anything you can do on a :class:`abc.Messageable` you can do on the "
":class:`~ext.commands.Context`."
msgstr ""
"コンテクストは :class:`abc.Messageable` インタフェースを実装しているため、 "
":class:`abc.Messageable` 上でできることは :class:`~ext.commands.Context` "
"上でも行うことが可能です。"
#: ../../ext/commands/commands.rst:182
msgid "Converters"
msgstr "コンバータ"
#: ../../ext/commands/commands.rst:184
msgid ""
"Adding bot arguments with function parameters is only the first step in "
"defining your bot's command interface. To actually make use of the "
"arguments, we usually want to convert the data into a target type. We "
"call these :ref:`ext_commands_api_converters`."
msgstr ""
"Botの引数を関数のパラメータとして設定するのは、Botのコマンドインタフェースを定義する第一歩です。引数を実際に扱うには、大抵の場合、データを目的の型へとと変換する必要があります。私達はこれを"
" :ref:`ext_commands_api_converters` と呼んでいます。"
#: ../../ext/commands/commands.rst:188
msgid "Converters come in a few flavours:"
msgstr "コンバータにはいくつかの種類があります:"
#: ../../ext/commands/commands.rst:190
msgid ""
"A regular callable object that takes an argument as a sole parameter and "
"returns a different type."
msgstr "引数を一つのパラメータとして受け取り、異なる型として返す、通常の呼び出し可能オブジェクト。"
#: ../../ext/commands/commands.rst:192
msgid ""
"These range from your own function, to something like :class:`bool` or "
":class:`int`."
msgstr "これらにはあなたの作った関数、 :class:`bool` や :class:`int` といったものまで含まれます。"
#: ../../ext/commands/commands.rst:194
msgid "A custom class that inherits from :class:`~ext.commands.Converter`."
msgstr ":class:`~ext.commands.Converter` を継承したカスタムクラス。"
#: ../../ext/commands/commands.rst:197
msgid "Basic Converters"
msgstr "基本的なコンバーター"
#: ../../ext/commands/commands.rst:199
msgid ""
"At its core, a basic converter is a callable that takes in an argument "
"and turns it into something else."
msgstr "基本的なコンバーターは、中核をなすものであり、受け取った引数を別のものへと変換します。"
#: ../../ext/commands/commands.rst:201
msgid ""
"For example, if we wanted to add two numbers together, we could request "
"that they are turned into integers for us by specifying the converter:"
msgstr "例えば、二つの値を加算したい場合、コンバーターを指定することにより、受け取った値を整数型へ変換するように要求できます。"
#: ../../ext/commands/commands.rst:210
msgid ""
"We specify converters by using something called a **function "
"annotation**. This is a Python 3 exclusive feature that was introduced in"
" :pep:`3107`."
msgstr ""
"コンバーターの指定には関数アノテーションというもの用います。これは :pep:`3107` にて追加された Python 3 "
"にのみ実装されている機能です。"
#: ../../ext/commands/commands.rst:213
msgid ""
"This works with any callable, such as a function that would convert a "
"string to all upper-case:"
msgstr "これは、文字列をすべて大文字に変換する関数などといった、任意の呼び出し可能関数でも動作します。"
#: ../../ext/commands/commands.rst:225
msgid "bool"
msgstr "論理型"
#: ../../ext/commands/commands.rst:227
#, fuzzy
msgid ""
"Unlike the other basic converters, the :class:`bool` converter is treated"
" slightly different. Instead of casting directly to the :class:`bool` "
"type, which would result in any non-empty argument returning ``True``, it"
" instead evaluates the argument as ``True`` or ``False`` based on its "
"given content:"
msgstr ""
"他の基本的なコンバーターとは異なり、 :class:`bool` のコンバーターは若干異なる扱いになります。 :class:`bool` "
"型に直接キャストする代わりに、与えられた値に基づいて ``True`` か ``False`` を返します。"
#: ../../ext/commands/commands.rst:239
msgid "Advanced Converters"
msgstr "応用的なコンバータ"
#: ../../ext/commands/commands.rst:241
msgid ""
"Sometimes a basic converter doesn't have enough information that we need."
" For example, sometimes we want to get some information from the "
":class:`Message` that called the command or we want to do some "
"asynchronous processing."
msgstr ""
"場合によっては、基本的なコンバータを動かすのに必要な情報が不足していることがあります。例えば、実行されたコマンドの "
":class:`Message` から情報を取得したい場合や、非同期処理を行いたい場合です。"
#: ../../ext/commands/commands.rst:244
msgid ""
"For this, the library provides the :class:`~ext.commands.Converter` "
"interface. This allows you to have access to the :class:`.Context` and "
"have the callable be asynchronous. Defining a custom converter using this"
" interface requires overriding a single method, "
":meth:`.Converter.convert`."
msgstr ""
"そういった用途のために、このライブラリは :class:`~ext.commands.Converter` "
"インタフェースを提供します。これによって :class:`.Context` "
"にアクセスが可能になり、また、呼び出し可能関数を非同期にもできるようになります。このインタフェースを使用して、カスタムコンバーターを定義したい場合は"
" :meth:`.Converter.convert` をオーバーライドしてください。"
#: ../../ext/commands/commands.rst:248
msgid "An example converter:"
msgstr "コンバーターの例"
#: ../../ext/commands/commands.rst:263
msgid ""
"The converter provided can either be constructed or not. Essentially "
"these two are equivalent:"
msgstr "コンバーターはインスタンス化されていなくても構いません。以下の例の二つのは同じ処理になります。"
#: ../../ext/commands/commands.rst:277
msgid ""
"Having the possibility of the converter be constructed allows you to set "
"up some state in the converter's ``__init__`` for fine tuning the "
"converter. An example of this is actually in the library, "
":class:`~ext.commands.clean_content`."
msgstr ""
"コンバーターをインスタンス化する可能性がある場合、コンバーターの調整を行うために ``__init__`` "
"で何かしらの状態を設定することが出来ます。この例としてライブラリに実際に存在する "
":class:`~ext.commands.clean_content` があります。"
#: ../../ext/commands/commands.rst:293
msgid ""
"If a converter fails to convert an argument to its designated target "
"type, the :exc:`.BadArgument` exception must be raised."
msgstr "コンバーターが渡された引数を指定の型に変換できなかった場合は :exc:`.BadArgument` を発生させてください。"
#: ../../ext/commands/commands.rst:297
msgid "Inline Advanced Converters"
msgstr "埋込み型の応用的なコンバーター"
#: ../../ext/commands/commands.rst:299
msgid ""
"If we don't want to inherit from :class:`~ext.commands.Converter`, we can"
" still provide a converter that has the advanced functionalities of an "
"advanced converter and save us from specifying two types."
msgstr ""
":class:`~ext.commands.Converter` "
"を継承したくない場合のために、応用的なコンバータの高度な機能を備えたコンバータを提供しています。これを使用することで2つのクラスを作成する必要がなくなります。"
#: ../../ext/commands/commands.rst:302
msgid ""
"For example, a common idiom would be to have a class and a converter for "
"that class:"
msgstr "例えば、一般的な書き方だと、クラスとそのクラスへのコンバーターを定義します:"
#: ../../ext/commands/commands.rst:328
msgid ""
"This can get tedious, so an inline advanced converter is possible through"
" a ``classmethod`` inside the type:"
msgstr ""
"これでは面倒に感じてしまうこともあるでしょう。しかし、埋込み型の応用的なコンバーターは ``classmethod`` "
"としてクラスへ埋め込むことが可能です:"
#: ../../ext/commands/commands.rst:355
msgid "Discord Converters"
msgstr "Discord コンバーター"
#: ../../ext/commands/commands.rst:357
msgid ""
"Working with :ref:`discord_api_models` is a fairly common thing when "
"defining commands, as a result the library makes working with them easy."
msgstr ""
":ref:`discord_api_models` "
"を使用して作業を行うのは、コマンドを定義する際には一般的なことです。そのため、このライブラリでは簡単に作業が行えるようになっています。"
#: ../../ext/commands/commands.rst:360
msgid ""
"For example, to receive a :class:`Member` you can just pass it as a "
"converter:"
msgstr "例えば、 :class:`Member` を受け取るには、これをコンバーターとして渡すだけです。"
#: ../../ext/commands/commands.rst:368
msgid ""
"When this command is executed, it attempts to convert the string given "
"into a :class:`Member` and then passes it as a parameter for the "
"function. This works by checking if the string is a mention, an ID, a "
"nickname, a username + discriminator, or just a regular username. The "
"default set of converters have been written to be as easy to use as "
"possible."
msgstr ""
"このコマンドが実行されると、与えられた文字列を :class:`Member` "
"に変換して、それを関数のパラメーターとして渡します。これは文字列がメンション、ID、ニックネーム、ユーザー名 + "
"Discordタグ、または普通のユーザー名かどうかをチェックすることで機能しています。デフォルトで定義されているコンバーターは、できるだけ簡単に使えるように作られています。"
#: ../../ext/commands/commands.rst:372
msgid "A lot of discord models work out of the gate as a parameter:"
msgstr "Discordモデルの多くがコンバーターとして動作します。"
#: ../../ext/commands/commands.rst:374 ../../ext/commands/commands.rst:396
msgid ":class:`Member`"
msgstr ":class:`Member`"
#: ../../ext/commands/commands.rst:375 ../../ext/commands/commands.rst:400
msgid ":class:`User`"
msgstr ":class:`User`"
#: ../../ext/commands/commands.rst:376 ../../ext/commands/commands.rst:402
msgid ":class:`TextChannel`"
msgstr ":class:`TextChannel`"
#: ../../ext/commands/commands.rst:377 ../../ext/commands/commands.rst:404
msgid ":class:`VoiceChannel`"
msgstr ":class:`VoiceChannel`"
#: ../../ext/commands/commands.rst:378 ../../ext/commands/commands.rst:406
msgid ":class:`CategoryChannel`"
msgstr ":class:`CategoryChannel`"
#: ../../ext/commands/commands.rst:379 ../../ext/commands/commands.rst:408
msgid ":class:`Role`"
msgstr ":class:`Role`"
#: ../../ext/commands/commands.rst:380
msgid ":class:`Message` (since v1.1)"
msgstr ":class:`Message` (v1.1 から)"
#: ../../ext/commands/commands.rst:381 ../../ext/commands/commands.rst:410
msgid ":class:`Invite`"
msgstr ":class:`Invite`"
#: ../../ext/commands/commands.rst:382 ../../ext/commands/commands.rst:412
msgid ":class:`Game`"
msgstr ":class:`Game`"
#: ../../ext/commands/commands.rst:383 ../../ext/commands/commands.rst:414
msgid ":class:`Emoji`"
msgstr ":class:`Emoji`"
#: ../../ext/commands/commands.rst:384 ../../ext/commands/commands.rst:416
msgid ":class:`PartialEmoji`"
msgstr ":class:`PartialEmoji`"
#: ../../ext/commands/commands.rst:385 ../../ext/commands/commands.rst:418
msgid ":class:`Colour`"
msgstr ":class:`Colour`"
#: ../../ext/commands/commands.rst:387
msgid ""
"Having any of these set as the converter will intelligently convert the "
"argument to the appropriate target type you specify."
msgstr "これらをコンバーターとして設定すると、引数を指定した型へとインテリジェントに変換します。"
#: ../../ext/commands/commands.rst:390
msgid ""
"Under the hood, these are implemented by the "
":ref:`ext_commands_adv_converters` interface. A table of the equivalent "
"converter is given below:"
msgstr ""
"これらは :ref:`ext_commands_adv_converters` "
"インタフェースによって実装されています。コンバーターとクラスの関係は以下の通りです。"
#: ../../ext/commands/commands.rst:394
msgid "Discord Class"
msgstr "Discord クラス"
#: ../../ext/commands/commands.rst:394
msgid "Converter"
msgstr "コンバーター"
#: ../../ext/commands/commands.rst:396
msgid ":class:`~ext.commands.MemberConverter`"
msgstr ":class:`~ext.commands.MemberConverter`"
#: ../../ext/commands/commands.rst:398
msgid ":class:`Message`"
msgstr ":class:`Message`"
#: ../../ext/commands/commands.rst:398
msgid ":class:`~ext.commands.MessageConverter`"
msgstr ":class:`~ext.commands.MessageConverter`"
#: ../../ext/commands/commands.rst:400
msgid ":class:`~ext.commands.UserConverter`"
msgstr ":class:`~ext.commands.UserConverter`"
#: ../../ext/commands/commands.rst:402
msgid ":class:`~ext.commands.TextChannelConverter`"
msgstr ":class:`~ext.commands.TextChannelConverter`"
#: ../../ext/commands/commands.rst:404
msgid ":class:`~ext.commands.VoiceChannelConverter`"
msgstr ":class:`~ext.commands.VoiceChannelConverter`"
#: ../../ext/commands/commands.rst:406
msgid ":class:`~ext.commands.CategoryChannelConverter`"
msgstr ":class:`~ext.commands.CategoryChannelConverter`"
#: ../../ext/commands/commands.rst:408
msgid ":class:`~ext.commands.RoleConverter`"
msgstr ":class:`~ext.commands.RoleConverter`"
#: ../../ext/commands/commands.rst:410
msgid ":class:`~ext.commands.InviteConverter`"
msgstr ":class:`~ext.commands.InviteConverter`"
#: ../../ext/commands/commands.rst:412
msgid ":class:`~ext.commands.GameConverter`"
msgstr ":class:`~ext.commands.GameConverter`"
#: ../../ext/commands/commands.rst:414
msgid ":class:`~ext.commands.EmojiConverter`"
msgstr ":class:`~ext.commands.EmojiConverter`"
#: ../../ext/commands/commands.rst:416
msgid ":class:`~ext.commands.PartialEmojiConverter`"
msgstr ":class:`~ext.commands.PartialEmojiConverter`"
#: ../../ext/commands/commands.rst:418
msgid ":class:`~ext.commands.ColourConverter`"
msgstr ":class:`~ext.commands.ColourConverter`"
#: ../../ext/commands/commands.rst:421
msgid ""
"By providing the converter it allows us to use them as building blocks "
"for another converter:"
msgstr "コンバーターを継承することで、他のコンバーターの一部として使うことができます:"
#: ../../ext/commands/commands.rst:438
msgid "Special Converters"
msgstr "特殊なコンバーター"
#: ../../ext/commands/commands.rst:440
msgid ""
"The command extension also has support for certain converters to allow "
"for more advanced and intricate use cases that go beyond the generic "
"linear parsing. These converters allow you to introduce some more relaxed"
" and dynamic grammar to your commands in an easy to use manner."
msgstr "コマンド拡張機能は一般的な線形解析を超える、より高度で複雑なユースケースに対応するため、特殊なコンバータをサポートしています。これらのコンバータは、簡単な方法でコマンドに更に容易で動的な文法の導入を可能にします。"
#: ../../ext/commands/commands.rst:445
msgid "typing.Union"
msgstr "typing.Union"
#: ../../ext/commands/commands.rst:447
msgid ""
"A :data:`typing.Union` is a special type hint that allows for the command"
" to take in any of the specific types instead of a singular type. For "
"example, given the following:"
msgstr ":data:`typing.Union` はコマンドが単数の型の代わりに、複数の特定の型を取り込める特殊な型ヒントです。例えば:"
#: ../../ext/commands/commands.rst:459
msgid ""
"The ``what`` parameter would either take a :class:`discord.TextChannel` "
"converter or a :class:`discord.Member` converter. The way this works is "
"through a left-to-right order. It first attempts to convert the input to "
"a :class:`discord.TextChannel`, and if it fails it tries to convert it to"
" a :class:`discord.Member`. If all converters fail, then a special error "
"is raised, :exc:`~ext.commands.BadUnionArgument`."
msgstr ""
"``what`` パラメータには :class:`discord.TextChannel` コンバーターか "
":class:`discord.Member` "
"コンバーターのいずれかが用いられます。これは左から右の順で変換できるか試行することになります。最初に渡された値を "
":class:`discord.TextChannel` へ変換しようと試み、失敗した場合は :class:`discord.Member` "
"に変換しようとします。すべてのコンバーターで失敗した場合は :exc:`~ext.commands.BadUnionArgument` "
"というエラーが発生します。"
#: ../../ext/commands/commands.rst:464
msgid ""
"Note that any valid converter discussed above can be passed in to the "
"argument list of a :data:`typing.Union`."
msgstr "以前に説明した有効なコンバーターは、すべて :data:`typing.Union` にわたすことが可能です。"
#: ../../ext/commands/commands.rst:467
msgid "typing.Optional"
msgstr "typing.Optional"
#: ../../ext/commands/commands.rst:469
msgid ""
"A :data:`typing.Optional` is a special type hint that allows for \"back-"
"referencing\" behaviour. If the converter fails to parse into the "
"specified type, the parser will skip the parameter and then either "
"``None`` or the specified default will be passed into the parameter "
"instead. The parser will then continue on to the next parameters and "
"converters, if any."
msgstr ""
":data:`typing.Optional` "
"は「後方参照」のような動作をする特殊な型ヒントです。コンバーターが指定された型へのパースに失敗した場合、パーサーは代わりに ``None`` "
"または指定されたデフォルト値をパラメータに渡したあと、そのパラメータをスキップします。次のパラメータまたはコンバータがあれば、そちらに進みます。"
#: ../../ext/commands/commands.rst:473 ../../ext/commands/commands.rst:500
msgid "Consider the following example:"
msgstr "次の例をみてください:"
#: ../../ext/commands/commands.rst:486
msgid ""
"In this example, since the argument could not be converted into an "
"``int``, the default of ``99`` is passed and the parser resumes handling,"
" which in this case would be to pass it into the ``liquid`` parameter."
msgstr ""
"この例では引数を ``int`` に変換することができなかったので、デフォルト値である ``99`` "
"を代入し、パーサーは処理を続行しています。この場合、先程の変換に失敗した引数は ``liquid`` パラメータに渡されます。"
#: ../../ext/commands/commands.rst:491
msgid ""
"This converter only works in regular positional parameters, not variable "
"parameters or keyword-only parameters."
msgstr "このコンバーターは位置パラメータでのみ動作し、可変長パラメータやキーワードパラメータでは機能しません。"
#: ../../ext/commands/commands.rst:494
msgid "Greedy"
msgstr "Greedy"
#: ../../ext/commands/commands.rst:496
msgid ""
"The :data:`~ext.commands.Greedy` converter is a generalisation of the "
":data:`typing.Optional` converter, except applied to a list of arguments."
" In simple terms, this means that it tries to convert as much as it can "
"until it can't convert any further."
msgstr ""
":data:`~ext.commands.Greedy` コンバータは引数にリストが適用される以外は "
":data:`typing.Optional` "
"を一般化したものです。簡単に言うと、与えられた引数を変換ができなくなるまで指定の型に変換しようと試みます。"
#: ../../ext/commands/commands.rst:509
msgid "When invoked, it allows for any number of members to be passed in:"
msgstr "これが呼び出されると、任意の数のメンバーを渡すことができます:"
#: ../../ext/commands/commands.rst:513
msgid ""
"The type passed when using this converter depends on the parameter type "
"that it is being attached to:"
msgstr ""
#: ../../ext/commands/commands.rst:515
msgid ""
"Positional parameter types will receive either the default parameter or a"
" :class:`list` of the converted values."
msgstr ""
#: ../../ext/commands/commands.rst:516
msgid "Variable parameter types will be a :class:`tuple` as usual."
msgstr ""
#: ../../ext/commands/commands.rst:517
msgid ""
"Keyword-only parameter types will be the same as if "
":data:`~ext.commands.Greedy` was not passed at all."
msgstr ""
#: ../../ext/commands/commands.rst:519
msgid ""
":data:`~ext.commands.Greedy` parameters can also be made optional by "
"specifying an optional value."
msgstr ""
#: ../../ext/commands/commands.rst:521
msgid ""
"When mixed with the :data:`typing.Optional` converter you can provide "
"simple and expressive command invocation syntaxes:"
msgstr ""
#: ../../ext/commands/commands.rst:536
msgid "This command can be invoked any of the following ways:"
msgstr ""
#: ../../ext/commands/commands.rst:546
msgid ""
"The usage of :data:`~ext.commands.Greedy` and :data:`typing.Optional` are"
" powerful and useful, however as a price, they open you up to some "
"parsing ambiguities that might surprise some people."
msgstr ""
#: ../../ext/commands/commands.rst:549
msgid ""
"For example, a signature expecting a :data:`typing.Optional` of a "
":class:`discord.Member` followed by a :class:`int` could catch a member "
"named after a number due to the different ways a "
":class:`~ext.commands.MemberConverter` decides to fetch members. You "
"should take care to not introduce unintended parsing ambiguities in your "
"code. One technique would be to clamp down the expected syntaxes allowed "
"through custom converters or reordering the parameters to minimise "
"clashes."
msgstr ""
#: ../../ext/commands/commands.rst:555
msgid ""
"To help aid with some parsing ambiguities, :class:`str`, ``None``, "
":data:`typing.Optional` and :data:`~ext.commands.Greedy` are forbidden as"
" parameters for the :data:`~ext.commands.Greedy` converter."
msgstr ""
#: ../../ext/commands/commands.rst:561
msgid "Error Handling"
msgstr "エラーハンドリング"
#: ../../ext/commands/commands.rst:563
#, fuzzy
msgid ""
"When our commands fail to parse we will, by default, receive a noisy "
"error in ``stderr`` of our console that tells us that an error has "
"happened and has been silently ignored."
msgstr ""
"コマンドの解析に失敗したとき、通常では煩わしいエラーはエラーの発生を伝えるためにコンソールの ``stderr`` "
"で受け取られ、無視されていました。"
#: ../../ext/commands/commands.rst:566
msgid ""
"In order to handle our errors, we must use something called an error "
"handler. There is a global error handler, called :func:`on_command_error`"
" which works like any other event in the :ref:`discord-api-events`. This "
"global error handler is called for every error reached."
msgstr ""
#: ../../ext/commands/commands.rst:570
msgid ""
"Most of the time however, we want to handle an error local to the command"
" itself. Luckily, commands come with local error handlers that allow us "
"to do just that. First we decorate an error handler function with "
":meth:`.Command.error`:"
msgstr ""
#: ../../ext/commands/commands.rst:586
msgid ""
"The first parameter of the error handler is the :class:`.Context` while "
"the second one is an exception that is derived from "
":exc:`~ext.commands.CommandError`. A list of errors is found in the "
":ref:`ext_commands_api_errors` page of the documentation."
msgstr ""
#: ../../ext/commands/commands.rst:590
msgid "Checks"
msgstr "チェック"
#: ../../ext/commands/commands.rst:592
msgid ""
"There are cases when we don't want a user to use our commands. They don't"
" have permissions to do so or maybe we blocked them from using our bot "
"earlier. The commands extension comes with full support for these things "
"in a concept called a :ref:`ext_commands_api_checks`."
msgstr ""
"コマンドをユーザーに使ってほしくない場合などがあります。例えば、使用者が権限を持っていない場合や、Botをブロックしている場合などです。コマンド拡張ではこのような機能を"
" :ref:`ext_commands_api_checks` と呼び、完全にサポートしています。"
#: ../../ext/commands/commands.rst:596
msgid ""
"A check is a basic predicate that can take in a :class:`.Context` as its "
"sole parameter. Within it, you have the following options:"
msgstr ""
#: ../../ext/commands/commands.rst:599
msgid "Return ``True`` to signal that the person can run the command."
msgstr ""
#: ../../ext/commands/commands.rst:600
msgid "Return ``False`` to signal that the person cannot run the command."
msgstr ""
#: ../../ext/commands/commands.rst:601
msgid ""
"Raise a :exc:`~ext.commands.CommandError` derived exception to signal the"
" person cannot run the command."
msgstr ""
#: ../../ext/commands/commands.rst:603
msgid ""
"This allows you to have custom error messages for you to handle in the "
":ref:`error handlers <ext_commands_error_handler>`."
msgstr ""
#: ../../ext/commands/commands.rst:606
msgid ""
"To register a check for a command, we would have two ways of doing so. "
"The first is using the :meth:`~ext.commands.check` decorator. For "
"example:"
msgstr ""
#: ../../ext/commands/commands.rst:620
msgid ""
"This would only evaluate the command if the function ``is_owner`` returns"
" ``True``. Sometimes we re-use a check often and want to split it into "
"its own decorator. To do that we can just add another level of depth:"
msgstr ""
#: ../../ext/commands/commands.rst:637
msgid ""
"Since an owner check is so common, the library provides it for you "
"(:func:`~ext.commands.is_owner`):"
msgstr ""
#: ../../ext/commands/commands.rst:647
msgid "When multiple checks are specified, **all** of them must be ``True``:"
msgstr ""
#: ../../ext/commands/commands.rst:663
msgid ""
"If any of those checks fail in the example above, then the command will "
"not be run."
msgstr ""
#: ../../ext/commands/commands.rst:665
msgid ""
"When an error happens, the error is propagated to the :ref:`error "
"handlers <ext_commands_error_handler>`. If you do not raise a custom "
":exc:`~ext.commands.CommandError` derived exception, then it will get "
"wrapped up into a :exc:`~ext.commands.CheckFailure` exception as so:"
msgstr ""
#: ../../ext/commands/commands.rst:683
msgid ""
"If you want a more robust error system, you can derive from the exception"
" and raise it instead of returning ``False``:"
msgstr ""
#: ../../ext/commands/commands.rst:708
msgid ""
"Since having a ``guild_only`` decorator is pretty common, it comes built-"
"in via :func:`~ext.commands.guild_only`."
msgstr ""
#: ../../ext/commands/commands.rst:711
msgid "Global Checks"
msgstr "グローバルチェック"
#: ../../ext/commands/commands.rst:713
msgid ""
"Sometimes we want to apply a check to **every** command, not just certain"
" commands. The library supports this as well using the global check "
"concept."
msgstr ""
#: ../../ext/commands/commands.rst:716
msgid ""
"Global checks work similarly to regular checks except they are registered"
" with the :func:`.Bot.check` decorator."
msgstr ""
#: ../../ext/commands/commands.rst:718
msgid "For example, to block all DMs we could do the following:"
msgstr ""
#: ../../ext/commands/commands.rst:728
msgid ""
"Be careful on how you write your global checks, as it could also lock you"
" out of your own bot."
msgstr ""
#~ msgid ""
#~ "To help aid with some parsing "
#~ "ambiguities, :class:`str`, ``None`` and "
#~ ":data:`~ext.commands.Greedy` are forbidden as "
#~ "parameters for the :data:`~ext.commands.Greedy` "
#~ "converter."
#~ msgstr ""

View File

@ -1,83 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: discordpy\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-06-22 09:35-0400\n"
"PO-Revision-Date: 2020-10-24 02:41\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: discordpy\n"
"X-Crowdin-Project-ID: 362783\n"
"X-Crowdin-Language: ja\n"
"X-Crowdin-File: /ext/commands/extensions.pot\n"
"X-Crowdin-File-ID: 68\n"
"Language: ja_JP\n"
#: ../../ext/commands/extensions.rst:6
msgid "Extensions"
msgstr "エクステンション"
#: ../../ext/commands/extensions.rst:8
msgid "There comes a time in the bot development when you want to extend the bot functionality at run-time and quickly unload and reload code (also called hot-reloading). The command framework comes with this ability built-in, with a concept called **extensions**."
msgstr "Bot開発ではBotを起動している間にコードを素早くアンロードし、再度ロードし直したい (ホットリロードとも呼ばれます) という時があります。コマンドフレームワークでは **エクステンション** と呼ばれる概念でこの機能が組み込まれています。"
#: ../../ext/commands/extensions.rst:11
msgid "Primer"
msgstr "はじめに"
#: ../../ext/commands/extensions.rst:13
msgid "An extension at its core is a python file with an entry point called ``setup``. This setup must be a plain Python function (not a coroutine). It takes a single parameter -- the :class:`~.commands.Bot` that loads the extension."
msgstr "その中核となるエクステンションは ``setup`` というエントリポイントを持つPythonファイルです。このsetupは通常のPython関数である必要があります (コルーチンではありません)。この関数はエクステンションをロードする :class:`~.commands.Bot` を受け取るための単一のパラメータを持ちます。"
#: ../../ext/commands/extensions.rst:15
msgid "An example extension looks like this:"
msgstr "エクステンションの例は以下のとおりです:"
#: ../../ext/commands/extensions.rst:17
msgid "hello.py"
msgstr "hello.py"
#: ../../ext/commands/extensions.rst:30
msgid "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')``."
msgstr "この例では単純なコマンドを実装しており、エクステンションがロードされることでこのコマンドがBotに追加されます。最後にこのエクステンションをロードする必要があります。ロードには :meth:`.commands.Bot.load_extension` を実行します。このエクステンションを読み込むために ``bot.load_extension('hello')`` を実行します。"
#: ../../ext/commands/extensions.rst:32
msgid "Cogs"
msgstr "コグ"
#: ../../ext/commands/extensions.rst:35
msgid "Extensions are usually used in conjunction with cogs. To read more about them, check out the documentation, :ref:`ext_commands_cogs`."
msgstr "エクステンションは通常、コグと組み合わせて使用します。詳細については :ref:`ext_commands_cogs` のドキュメントを参照してください。"
#: ../../ext/commands/extensions.rst:39
msgid "Extension paths are ultimately similar to the import mechanism. What this means is that if there is a folder, then it must be dot-qualified. For example to load an extension in ``plugins/hello.py`` then we use the string ``plugins.hello``."
msgstr "エクステンションのパスは究極的にはimportのメカニズムと似ています。これはフォルダ等がある場合、それをドットで区切らなければならないということです。例えば ``plugins/hello.py`` というエクステンションをロードする場合は、 ``plugins.hello`` という文字列を使います。"
#: ../../ext/commands/extensions.rst:42
msgid "Reloading"
msgstr "リロード"
#: ../../ext/commands/extensions.rst:44
msgid "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`."
msgstr "エクステンションを更新し、その参照を再読込したい場合のために、ライブラリには :meth:`Bot.reload_extension` が用意されています。"
#: ../../ext/commands/extensions.rst:50
msgid "Once the extension reloads, any changes that we did will be applied. This is useful if we want to add or remove functionality without restarting our bot. If an error occurred during the reloading process, the bot will pretend as if the reload never happened."
msgstr "エクステンションを再読込すると、その変更が適用されます。Botを再起動せずに機能の追加や削除を行いたい場合に便利です。再読込処理中にエラーが発生した場合、Botは再読込処理をする前の状態に戻ります。"
#: ../../ext/commands/extensions.rst:53
msgid "Cleaning Up"
msgstr "クリーンアップ"
#: ../../ext/commands/extensions.rst:55
msgid "Although rare, sometimes an extension needs to clean-up or know when it's being unloaded. For cases like these, there is another entry point named ``teardown`` which is similar to ``setup`` except called when the extension is unloaded."
msgstr "稀ではありますが、エクステンションにクリーンアップが必要だったり、いつアンロードするかを確認したい場合があります。このために ``setup`` に似たエクステンションがアンロードされるときに呼び出される ``teardown`` というエントリポイントが用意されています。"
#: ../../ext/commands/extensions.rst:57
msgid "basic_ext.py"
msgstr "basic_ext.py"

View File

@ -1,27 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: discordpy\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-06-22 09:35-0400\n"
"PO-Revision-Date: 2020-10-24 02:41\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: discordpy\n"
"X-Crowdin-Project-ID: 362783\n"
"X-Crowdin-Language: ja\n"
"X-Crowdin-File: /ext/commands/index.pot\n"
"X-Crowdin-File-ID: 66\n"
"Language: ja_JP\n"
#: ../../ext/commands/index.rst:4
msgid "``discord.ext.commands`` -- Bot commands framework"
msgstr "``discord.ext.commands`` -- ボットコマンドのフレームワーク"
#: ../../ext/commands/index.rst:6
msgid "``discord.py`` offers a lower level aspect on interacting with Discord. Often times, the library is used for the creation of bots. However this task can be daunting and confusing to get correctly the first time. Many times there comes a repetition in creating a bot command framework that is extensible, flexible, and powerful. For this reason, ``discord.py`` comes with an extension library that handles this for you."
msgstr "``discord.py`` は、Discordと連携するための低レベルな機能を提供します。ときどき、このライブラリーはBotの作成に用いられています。しかしこの作業を正しくやるのは最初のときは気が重くややこしいものです。何度も繰り返し、拡張可能で柔軟、そしてパワフルなBotコマンドフレームワークを作成しています。この理由より、 ``discord.py`` にはこれを扱う拡張ライブラリがついてきます。"

View File

@ -1,417 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: discordpy\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-10-23 22:41-0400\n"
"PO-Revision-Date: 2020-10-24 02:41+0000\n"
"Last-Translator: \n"
"Language: ja_JP\n"
"Language-Team: Japanese\n"
"Plural-Forms: nplurals=1; plural=0\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.5.3\n"
#: ../../ext/tasks/index.rst:4
msgid "``discord.ext.tasks`` -- asyncio.Task helpers"
msgstr "``discord.ext.tasks`` -- asyncio.Task ヘルパー"
#: ../../ext/tasks/index.rst:8
msgid ""
"One of the most common operations when making a bot is having a loop run "
"in the background at a specified interval. This pattern is very common "
"but has a lot of things you need to look out for:"
msgstr "ボットを作成するときの最も一般的な操作の1つは、指定した間隔でバックグラウンドでループを実行させることです。このパターンは非常に一般的ですが、注意すべきことがたくさんあります。"
#: ../../ext/tasks/index.rst:10
msgid "How do I handle :exc:`asyncio.CancelledError`?"
msgstr ":exc:`asyncio.CancelledError` はどのように処理するべきですか?"
#: ../../ext/tasks/index.rst:11
msgid "What do I do if the internet goes out?"
msgstr "インターネット接続が切れた場合はどうするべきですか?"
#: ../../ext/tasks/index.rst:12
msgid "What is the maximum number of seconds I can sleep anyway?"
msgstr "スリープできる最大時間は何秒ですか?"
#: ../../ext/tasks/index.rst:14
msgid ""
"The goal of this discord.py extension is to abstract all these worries "
"away from you."
msgstr "discord.pyの拡張機能の目的は、こういった苦労の種を抽象化することです。"
#: ../../ext/tasks/index.rst:17
msgid "Recipes"
msgstr "レシピ"
#: ../../ext/tasks/index.rst:19
msgid "A simple background task in a :class:`~discord.ext.commands.Cog`:"
msgstr ":class:`~discord.ext.commands.Cog` におけるシンプルなバックグラウンドタスク:"
#: ../../ext/tasks/index.rst:38
msgid "Adding an exception to handle during reconnect:"
msgstr "再接続中に処理する例外を追加します:"
#: ../../ext/tasks/index.rst:61
msgid "Looping a certain amount of times before exiting:"
msgstr "特定の回数ループさせる:"
#: ../../ext/tasks/index.rst:77
msgid "Waiting until the bot is ready before the loop starts:"
msgstr "ループが始まる前に、Botの準備が整うまで待機する: "
#: ../../ext/tasks/index.rst:102
msgid "Doing something during cancellation:"
msgstr "キャンセルする場合、その間に何らかの処理を行う:"
#: ../../ext/tasks/index.rst:136
msgid "API Reference"
msgstr "APIリファレンス"
#: discord.ext.tasks.Loop:1 of
msgid ""
"A background task helper that abstracts the loop and reconnection logic "
"for you."
msgstr "ループと再接続処理を抽象化するバックグラウンドタスクのヘルパー。"
#: discord.ext.tasks.Loop:3 of
msgid "The main interface to create this is through :func:`loop`."
msgstr ":func:`loop` はこれを作成するための主要なインタフェースです。"
#: discord.ext.tasks.Loop.current_loop:1 of
msgid "The current iteration of the loop."
msgstr ""
#: discord.ext.tasks.Loop.current_loop discord.ext.tasks.Loop.next_iteration of
msgid "type"
msgstr ""
#: discord.ext.tasks.Loop.current_loop:3 of
#, fuzzy
msgid ":class:`int`"
msgstr ":class:`asyncio.Task`"
#: discord.ext.tasks.Loop.next_iteration:1 of
msgid "When the next iteration of the loop will occur."
msgstr ""
#: discord.ext.tasks.Loop.next_iteration:5 of
msgid "Optional[:class:`datetime.datetime`]"
msgstr ""
#: discord.ext.tasks.Loop.start:1 of
msgid "Starts the internal task in the event loop."
msgstr ""
#: discord.ext.tasks.Loop.add_exception_type discord.ext.tasks.Loop.after_loop
#: discord.ext.tasks.Loop.before_loop discord.ext.tasks.Loop.change_interval
#: discord.ext.tasks.Loop.error discord.ext.tasks.Loop.remove_exception_type
#: discord.ext.tasks.Loop.restart discord.ext.tasks.Loop.start
#: discord.ext.tasks.loop of
msgid "Parameters"
msgstr "パラメーター"
#: discord.ext.tasks.Loop.start:3 of
msgid "The arguments to use."
msgstr ""
#: discord.ext.tasks.Loop.restart:9 discord.ext.tasks.Loop.start:4 of
msgid "The keyword arguments to use."
msgstr ""
#: discord.ext.tasks.Loop.add_exception_type discord.ext.tasks.Loop.after_loop
#: discord.ext.tasks.Loop.before_loop discord.ext.tasks.Loop.change_interval
#: discord.ext.tasks.Loop.error discord.ext.tasks.Loop.start
#: discord.ext.tasks.loop of
#, fuzzy
msgid "Raises"
msgstr "例外"
#: discord.ext.tasks.Loop.start:6 of
msgid "A task has already been launched and is running."
msgstr ""
#: discord.ext.tasks.Loop.remove_exception_type discord.ext.tasks.Loop.start of
msgid "Returns"
msgstr "戻り値"
#: discord.ext.tasks.Loop.start:8 of
msgid "The task that has been created."
msgstr "タスクが作成されました。"
#: discord.ext.tasks.Loop.remove_exception_type discord.ext.tasks.Loop.start of
msgid "Return type"
msgstr "戻り値の型"
#: discord.ext.tasks.Loop.start:9 of
msgid ":class:`asyncio.Task`"
msgstr ":class:`asyncio.Task`"
#: discord.ext.tasks.Loop.stop:1 of
msgid "Gracefully stops the task from running."
msgstr ""
#: discord.ext.tasks.Loop.stop:3 of
msgid ""
"Unlike :meth:`cancel`\\, this allows the task to finish its current "
"iteration before gracefully exiting."
msgstr ""
#: discord.ext.tasks.Loop.stop:8 of
msgid ""
"If the internal function raises an error that can be handled before "
"finishing then it will retry until it succeeds."
msgstr ""
#: discord.ext.tasks.Loop.stop:12 of
msgid ""
"If this is undesirable, either remove the error handling before stopping "
"via :meth:`clear_exception_types` or use :meth:`cancel` instead."
msgstr ""
#: discord.ext.tasks.Loop.cancel:1 of
msgid "Cancels the internal task, if it is running."
msgstr ""
#: discord.ext.tasks.Loop.restart:1 of
msgid "A convenience method to restart the internal task."
msgstr ""
#: discord.ext.tasks.Loop.restart:5 of
msgid ""
"Due to the way this function works, the task is not returned like "
":meth:`start`."
msgstr ""
#: discord.ext.tasks.Loop.restart:8 of
msgid "The arguments to to use."
msgstr ""
#: discord.ext.tasks.Loop.add_exception_type:1 of
#, fuzzy
msgid "Adds exception types to be handled during the reconnect logic."
msgstr "再接続中に処理する例外を追加します:"
#: discord.ext.tasks.Loop.add_exception_type:3 of
msgid ""
"By default the exception types handled are those handled by "
":meth:`discord.Client.connect`\\, which includes a lot of internet "
"disconnection errors."
msgstr ""
#: discord.ext.tasks.Loop.add_exception_type:7 of
msgid ""
"This function is useful if you're interacting with a 3rd party library "
"that raises its own set of exceptions."
msgstr ""
#: discord.ext.tasks.Loop.add_exception_type:10
#: discord.ext.tasks.Loop.remove_exception_type:3 of
msgid "An argument list of exception classes to handle."
msgstr ""
#: discord.ext.tasks.Loop.add_exception_type:13 of
msgid ""
"An exception passed is either not a class or not inherited from "
":class:`BaseException`."
msgstr ""
#: discord.ext.tasks.Loop.clear_exception_types:1 of
msgid "Removes all exception types that are handled."
msgstr ""
#: discord.ext.tasks.Loop.clear_exception_types:5 of
msgid "This operation obviously cannot be undone!"
msgstr ""
#: discord.ext.tasks.Loop.remove_exception_type:1 of
#, fuzzy
msgid "Removes exception types from being handled during the reconnect logic."
msgstr "再接続中に処理する例外を追加します:"
#: discord.ext.tasks.Loop.remove_exception_type:6 of
msgid "Whether all exceptions were successfully removed."
msgstr ""
#: discord.ext.tasks.Loop.remove_exception_type:7 of
msgid ":class:`bool`"
msgstr ""
#: discord.ext.tasks.Loop.get_task:1 of
msgid ""
"Optional[:class:`asyncio.Task`]: Fetches the internal task or ``None`` if"
" there isn't one running."
msgstr ""
#: discord.ext.tasks.Loop.is_being_cancelled:1 of
msgid "Whether the task is being cancelled."
msgstr ""
#: discord.ext.tasks.Loop.failed:1 of
msgid ":class:`bool`: Whether the internal task has failed."
msgstr ""
#: discord.ext.tasks.Loop.is_running:1 of
msgid ":class:`bool`: Check if the task is currently running."
msgstr ""
#: discord.ext.tasks.Loop.before_loop:1 of
msgid ""
"A decorator that registers a coroutine to be called before the loop "
"starts running."
msgstr ""
#: discord.ext.tasks.Loop.before_loop:3 of
msgid ""
"This is useful if you want to wait for some bot state before the loop "
"starts, such as :meth:`discord.Client.wait_until_ready`."
msgstr ""
#: discord.ext.tasks.Loop.after_loop:3 discord.ext.tasks.Loop.before_loop:6 of
msgid "The coroutine must take no arguments (except ``self`` in a class context)."
msgstr ""
#: discord.ext.tasks.Loop.before_loop:8 of
msgid "The coroutine to register before the loop runs."
msgstr ""
#: discord.ext.tasks.Loop.after_loop:14 discord.ext.tasks.Loop.before_loop:11
#: discord.ext.tasks.Loop.error:13 discord.ext.tasks.loop:22 of
msgid "The function was not a coroutine."
msgstr ""
#: discord.ext.tasks.Loop.after_loop:1 of
msgid ""
"A decorator that register a coroutine to be called after the loop "
"finished running."
msgstr ""
#: discord.ext.tasks.Loop.after_loop:7 of
msgid ""
"This coroutine is called even during cancellation. If it is desirable to "
"tell apart whether something was cancelled or not, check to see whether "
":meth:`is_being_cancelled` is ``True`` or not."
msgstr ""
#: discord.ext.tasks.Loop.after_loop:11 of
msgid "The coroutine to register after the loop finishes."
msgstr ""
#: discord.ext.tasks.Loop.error:1 of
msgid ""
"A decorator that registers a coroutine to be called if the task "
"encounters an unhandled exception."
msgstr ""
#: discord.ext.tasks.Loop.error:3 of
msgid ""
"The coroutine must take only one argument the exception raised (except "
"``self`` in a class context)."
msgstr ""
#: discord.ext.tasks.Loop.error:5 of
msgid ""
"By default this prints to :data:`sys.stderr` however it could be "
"overridden to have a different implementation."
msgstr ""
#: discord.ext.tasks.Loop.error:10 of
msgid "The coroutine to register in the event of an unhandled exception."
msgstr ""
#: discord.ext.tasks.Loop.change_interval:1 of
msgid "Changes the interval for the sleep time."
msgstr ""
#: discord.ext.tasks.Loop.change_interval:5 of
msgid ""
"This only applies on the next loop iteration. If it is desirable for the "
"change of interval to be applied right away, cancel the task with "
":meth:`cancel`."
msgstr ""
#: discord.ext.tasks.Loop.change_interval:10 discord.ext.tasks.loop:4 of
msgid "The number of seconds between every iteration."
msgstr ""
#: discord.ext.tasks.Loop.change_interval:12 discord.ext.tasks.loop:6 of
msgid "The number of minutes between every iteration."
msgstr ""
#: discord.ext.tasks.Loop.change_interval:14 discord.ext.tasks.loop:8 of
msgid "The number of hours between every iteration."
msgstr ""
#: discord.ext.tasks.Loop.change_interval:17 discord.ext.tasks.loop:21 of
msgid "An invalid value was given."
msgstr ""
#: discord.ext.tasks.loop:1 of
msgid ""
"A decorator that schedules a task in the background for you with optional"
" reconnect logic. The decorator returns a :class:`Loop`."
msgstr ""
#: discord.ext.tasks.loop:10 of
msgid "The number of loops to do, ``None`` if it should be an infinite loop."
msgstr ""
#: discord.ext.tasks.loop:13 of
msgid ""
"Whether to handle errors and restart the task using an exponential back-"
"off algorithm similar to the one used in :meth:`discord.Client.connect`."
msgstr ""
#: discord.ext.tasks.loop:17 of
msgid ""
"The loop to use to register the task, if not given defaults to "
":func:`asyncio.get_event_loop`."
msgstr ""
#~ msgid ":class:`int` -- The current iteration of the loop."
#~ msgstr ""
#~ msgid ":exc:`RuntimeError` -- A task has already been launched and is running."
#~ msgstr ""
#~ msgid "Adds an exception type to be handled during the reconnect logic."
#~ msgstr ""
#~ msgid "The exception class to handle."
#~ msgstr ""
#~ msgid ""
#~ ":exc:`TypeError` -- The exception passed "
#~ "is either not a class or not "
#~ "inherited from :class:`BaseException`."
#~ msgstr ""
#~ msgid ""
#~ "Removes an exception type from being "
#~ "handled during the reconnect logic."
#~ msgstr ""
#~ msgid "Whether it was successfully removed."
#~ msgstr ""
#~ msgid ":exc:`TypeError` -- The function was not a coroutine."
#~ msgstr ""
#~ msgid ":exc:`ValueError` -- An invalid value was given."
#~ msgstr ""
#~ msgid ""
#~ "A decorator that schedules a task "
#~ "in the background for you with "
#~ "optional reconnect logic."
#~ msgstr ""
#~ msgid "The loop helper that handles the background task."
#~ msgstr ""
#~ msgid ":class:`Loop`"
#~ msgstr ""

View File

@ -1,582 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: discordpy\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-10-23 22:41-0400\n"
"PO-Revision-Date: 2020-10-24 02:41+0000\n"
"Last-Translator: \n"
"Language: ja_JP\n"
"Language-Team: Japanese\n"
"Plural-Forms: nplurals=1; plural=0\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.5.3\n"
#: ../../faq.rst:5
msgid "Frequently Asked Questions"
msgstr "よくある質問"
#: ../../faq.rst:7
msgid ""
"This is a list of Frequently Asked Questions regarding using "
"``discord.py`` and its extension modules. Feel free to suggest a new "
"question or submit one via pull requests."
msgstr "これは ``discord.py`` 及び 拡張モジュールに対して、よくある質問をまとめたものです。気軽に質問やプルリクエストを提出してください。"
#: ../../faq.rst:11
msgid "Questions"
msgstr "質問"
#: ../../faq.rst:14
msgid "Coroutines"
msgstr "コルーチン"
#: ../../faq.rst:16
msgid "Questions regarding coroutines and asyncio belong here."
msgstr "コルーチンとasyncioに関する質問。"
#: ../../faq.rst:19
msgid "What is a coroutine?"
msgstr "コルーチンとはなんですか。"
#: ../../faq.rst:21
msgid ""
"A |coroutine_link|_ is a function that must be invoked with ``await`` or "
"``yield from``. When Python encounters an ``await`` it stops the "
"function's execution at that point and works on other things until it "
"comes back to that point and finishes off its work. This allows for your "
"program to be doing multiple things at the same time without using "
"threads or complicated multiprocessing."
msgstr ""
"|coroutine_link|_ とは ``await`` または ``yield from`` から呼び出さなければならない関数です。 "
"``await`` にエンカウントした場合、そのポイントで関数の実行を停止し、他の作業を実行します。 "
"これは作業が終了し、このポイントに戻ってくるまで続きます。 "
"これにより、スレッドや複雑なマルチプロセッシングを用いずに複数の処理を並列実行することができます。"
#: ../../faq.rst:26
msgid ""
"**If you forget to await a coroutine then the coroutine will not run. "
"Never forget to await a coroutine.**"
msgstr "**コルーチンにawaitを記述し忘れた場合、コルーチンは実行されません。awaitの記述を忘れないように注意してください。**"
#: ../../faq.rst:29
msgid "Where can I use ``await``\\?"
msgstr "``await`` はどこで使用することができますか。"
#: ../../faq.rst:31
msgid ""
"You can only use ``await`` inside ``async def`` functions and nowhere "
"else."
msgstr "``await`` は ``async def`` 関数の中でのみ使用できます。"
#: ../../faq.rst:34
msgid "What does \"blocking\" mean?"
msgstr "「ブロッキング」とはなんですか。"
#: ../../faq.rst:36
msgid ""
"In asynchronous programming a blocking call is essentially all the parts "
"of the function that are not ``await``. Do not despair however, because "
"not all forms of blocking are bad! Using blocking calls is inevitable, "
"but you must work to make sure that you don't excessively block "
"functions. Remember, if you block for too long then your bot will freeze "
"since it has not stopped the function's execution at that point to do "
"other things."
msgstr ""
"非同期プログラミングにおけるブロッキングとは、関数内の ``await`` 修飾子がないコードすべてを指します。 "
"しかし、全てのブロッキングが悪いというわけではありません。ブロッキングを使用することは避けられませんが、ブロックの発生は出来るだけ少なくする必要があります。長時間のブロックが発生すると、関数の実行が停止しないため、長時間Botがフリーズすることになることを覚えておきましょう。"
#: ../../faq.rst:41
msgid ""
"If logging is enabled, this library will attempt to warn you that "
"blocking is occurring with the message: ``Heartbeat blocked for more than"
" N seconds.`` See :ref:`logging_setup` for details on enabling logging."
msgstr ""
"もしロギングを有効にしている場合、ライブラリはブロッキングが起きていることを次のメッセージで警告しようと試みます: ``Heartbeat "
"blocked for more than N seconds.`` "
"ロギングを有効にするには、:ref:`logging_setup`をご覧ください。"
#: ../../faq.rst:45
msgid ""
"A common source of blocking for too long is something like "
":func:`time.sleep`. Don't do that. Use :func:`asyncio.sleep` instead. "
"Similar to this example: ::"
msgstr ""
"長時間ブロックの原因として一般的なのは :func:`time.sleep` などです。 これは使用せず、下記の例のように "
":func:`asyncio.sleep` を使用してください。"
#: ../../faq.rst:54
msgid ""
"Another common source of blocking for too long is using HTTP requests "
"with the famous module :doc:`req:index`. While :doc:`req:index` is an "
"amazing module for non-asynchronous programming, it is not a good choice "
"for :mod:`asyncio` because certain requests can block the event loop too "
"long. Instead, use the :doc:`aiohttp <aio:index>` library which is "
"installed on the side with this library."
msgstr ""
"また、これだけでなく、有名なモジュール :doc:`req:index` のHTTPリクエストも長時間ブロックの原因になります。 "
":doc:`req:index` "
"モジュールは非非同期プログラミングでは素晴らしいモジュールですが、特定のリクエストがイベントループを長時間ブロックする可能性があるため、 "
":mod:`asyncio` には適していません。 代わりにこのライブラリと一緒にインストールされた :doc:`aiohttp "
"<aio:index>` を使用してください。"
#: ../../faq.rst:59
msgid "Consider the following example: ::"
msgstr "次の例を見てみましょう。"
#: ../../faq.rst:75
msgid "General"
msgstr "一般"
#: ../../faq.rst:77
msgid "General questions regarding library usage belong here."
msgstr "ライブラリの使用に関する一般的な質問。"
#: ../../faq.rst:80
msgid "Where can I find usage examples?"
msgstr ""
#: ../../faq.rst:82
msgid ""
"Example code can be found in the `examples folder "
"<https://github.com/Rapptz/discord.py/tree/master/examples>`_ in the "
"repository."
msgstr ""
#: ../../faq.rst:86
msgid "How do I set the \"Playing\" status?"
msgstr "「プレイ中」状態の設定をするにはどうすればいいですか。"
#: ../../faq.rst:88
msgid ""
"There is a method for this under :class:`Client` called "
":meth:`Client.change_presence`. The relevant aspect of this is its "
"``activity`` keyword argument which takes in an :class:`Activity` object."
msgstr ""
":class:`Client` 下にプレイ中状態の設定を行うためのメソッド :meth:`Client.change_presence` "
"が用意されています。 これの引数 ``activity`` に :class:`Activity` を渡します。"
#: ../../faq.rst:91
msgid ""
"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:"
msgstr ""
"ステータスタイプ(プレイ中、再生中、配信中、視聴中)は列挙型の :class:`ActivityType` "
"を指定することで設定が可能です。メモリの最適化のため、一部のアクティビティはスリム化したバージョンで提供しています。"
#: ../../faq.rst:94
msgid ":class:`Game`"
msgstr ":class:`Game`"
#: ../../faq.rst:95
msgid ":class:`Streaming`"
msgstr ":class:`Streaming`"
#: ../../faq.rst:97
msgid "Putting both of these pieces of info together, you get the following: ::"
msgstr "これらの情報をまとめると以下のようになります: ::"
#: ../../faq.rst:106
msgid "How do I send a message to a specific channel?"
msgstr "特定のチャンネルにメッセージを送るにはどうすればいいですか。"
#: ../../faq.rst:108
msgid ""
"You must fetch the channel directly and then call the appropriate method."
" Example: ::"
msgstr "チャンネルを直接取得してから、適切なメソッドの呼び出しを行う必要があります。以下がその例です。"
#: ../../faq.rst:114
msgid "How do I send a DM?"
msgstr ""
#: ../../faq.rst:116
msgid ""
"Get the :class:`User` or :class:`Member` object and call "
":meth:`abc.Messageable.send`. For example: ::"
msgstr ""
#: ../../faq.rst:121
msgid ""
"If you are responding to an event, such as :func:`on_message`, you "
"already have the :class:`User` object via :attr:`Message.author`: ::"
msgstr ""
#: ../../faq.rst:126
#, fuzzy
msgid "How do I get the ID of a sent message?"
msgstr "元の ``message`` を取得するにはどうすればよいですか。"
#: ../../faq.rst:128
msgid ""
":meth:`abc.Messageable.send` returns the :class:`Message` that was sent. "
"The ID of a message can be accessed via :attr:`Message.id`: ::"
msgstr ""
#: ../../faq.rst:135
msgid "How do I upload an image?"
msgstr "画像をアップロードするにはどうすればいいですか。"
#: ../../faq.rst:137
msgid "To upload something to Discord you have to use the :class:`File` object."
msgstr "Discordに何かをアップロードする際には :class:`File` オブジェクトを使用する必要があります。"
#: ../../faq.rst:139
msgid ""
"A :class:`File` accepts two parameters, the file-like object (or file "
"path) and the filename to pass to Discord when uploading."
msgstr ":class:`File` は二つのパラメータがあり、ファイルライクなオブジェクト(または、そのファイルパス)と、ファイル名を渡すことができます。"
#: ../../faq.rst:142
msgid "If you want to upload an image it's as simple as: ::"
msgstr "画像をアップロードするだけなら、以下のように簡単に行なえます。"
#: ../../faq.rst:146
msgid "If you have a file-like object you can do as follows: ::"
msgstr "もし、ファイルライクなオブジェクトがあるなら、以下のような実装が可能です。"
#: ../../faq.rst:151
msgid ""
"To upload multiple files, you can use the ``files`` keyword argument "
"instead of ``file``\\: ::"
msgstr "複数のファイルをアップロードするには、 ``file`` の代わりに ``files`` を使用しましょう。"
#: ../../faq.rst:159
msgid ""
"If you want to upload something from a URL, you will have to use an HTTP "
"request using :doc:`aiohttp <aio:index>` and then pass an "
":class:`io.BytesIO` instance to :class:`File` like so:"
msgstr ""
"URLから何かをアップロードする場合は、 :doc:`aiohttp <aio:index>` のHTTPリクエストを使用し、 "
":class:`io.BytesIO` インスタンスを :class:`File` にわたす必要があります。"
#: ../../faq.rst:176
msgid "How can I add a reaction to a message?"
msgstr "メッセージにリアクションをつけるにはどうすればいいですか。"
#: ../../faq.rst:178
msgid "You use the :meth:`Message.add_reaction` method."
msgstr ":meth:`Client.add_reaction` を使用してください。"
#: ../../faq.rst:180
msgid ""
"If you want to use unicode emoji, you must pass a valid unicode code "
"point in a string. In your code, you can write this in a few different "
"ways:"
msgstr "Unicodeの絵文字を使用する場合は、文字列内の有効なUnicodeのコードポイントを渡す必要があります。 例を挙げると、このようになります。"
#: ../../faq.rst:182
msgid "``'👍'``"
msgstr "``'👍'``"
#: ../../faq.rst:183
msgid "``'\\U0001F44D'``"
msgstr "``'\\U0001F44D'``"
#: ../../faq.rst:184
msgid "``'\\N{THUMBS UP SIGN}'``"
msgstr "``'\\N{THUMBS UP SIGN}'``"
#: ../../faq.rst:186 ../../faq.rst:202 ../../faq.rst:277 ../../faq.rst:293
#: ../../faq.rst:313
msgid "Quick example: ::"
msgstr "簡単な例。"
#: ../../faq.rst:192
msgid ""
"In case you want to use emoji that come from a message, you already get "
"their code points in the content without needing to do anything special. "
"You **cannot** send ``':thumbsup:'`` style shorthands."
msgstr ""
"メッセージから来た絵文字を使用したい場合は、特になにをするでもなく、コンテンツのコードポイントをあなたは取得しています。また、 "
"``':thumbsup:'`` のような簡略化したものを送信することは **できません** 。"
#: ../../faq.rst:195
msgid ""
"For custom emoji, you should pass an instance of :class:`Emoji`. You can "
"also pass a ``'<:name:id>'`` string, but if you can use said emoji, you "
"should be able to use :meth:`Client.get_emoji` to get an emoji via ID or "
"use :func:`utils.find`/ :func:`utils.get` on :attr:`Client.emojis` or "
":attr:`Guild.emojis` collections."
msgstr ""
"カスタム絵文字については、:class:`Emoji`のインスタンスを渡すといいでしょう。``'<:名前:ID>'``形式の文字列も渡せますが、その絵文字が使えるなら、:meth:`Client.get_emoji`でIDから絵文字を取得したり、:attr:`Client.emojis`"
" や :attr:`Guild.emojis`に対して:func:`utils.find`/ "
":func:`utils.get`を使ったりできるでしょう。"
#: ../../faq.rst:199
msgid ""
"The name and ID of a custom emoji can be found with the client by "
"prefixing ``:custom_emoji:`` with a backslash. For example, sending the "
"message ``\\:python3:`` with the client will result in "
"``<:python3:232720527448342530>``."
msgstr "カスタム絵文字の名前とIDをクライアント側で知るには、``:カスタム絵文字:``の頭にバックスラッシュ(円記号)をつけます。たとえば、メッセージ``\\:python3:``を送信すると、結果は``<:python3:232720527448342530>``になります。"
#: ../../faq.rst:219
msgid "How do I pass a coroutine to the player's \"after\" function?"
msgstr "どうやってコルーチンをプレイヤーの後処理に渡すのですか。"
#: ../../faq.rst:221
msgid ""
"The library's music player launches on a separate thread, ergo it does "
"not execute inside a coroutine. This does not mean that it is not "
"possible to call a coroutine in the ``after`` parameter. To do so you "
"must pass a callable that wraps up a couple of aspects."
msgstr ""
"ライブラリの音楽プレーヤーは別のスレッドで起動するもので、コルーチン内で実行されるものではありません。しかし、 ``after`` "
"にコルーチンが渡せないというわけではありません。コルーチンを渡すためには、いくつかの機能を包括した呼び出し可能コードで渡す必要があります。"
#: ../../faq.rst:225
msgid ""
"The first gotcha that you must be aware of is that calling a coroutine is"
" not a thread-safe operation. Since we are technically in another thread,"
" we must take caution in calling thread-safe operations so things do not "
"bug out. Luckily for us, :mod:`asyncio` comes with a "
":func:`asyncio.run_coroutine_threadsafe` function that allows us to call "
"a coroutine from another thread."
msgstr ""
"コルーチンを呼び出すという動作はスレッドセーフなものではないということを最初に理解しておく必要があります。技術的に別スレッドなので、スレッドセーフに呼び出す際には注意が必要です。幸運にも、"
" :mod:`asyncio` には :func:`asyncio.run_coroutine_threadsafe` "
"という関数があります。これを用いることで、別スレッドからコルーチンを呼び出すことが可能です。"
#: ../../faq.rst:230
msgid ""
"However, this function returns a :class:`concurrent.Future` and to "
"actually call it we have to fetch its result. Putting all of this "
"together we can do the following: ::"
msgstr ""
"しかし、この関数は :class:`concurrent.Future` "
"を返すので、実際にはそこから結果を読み出す必要があります。これをすべてまとめると、次のことができます。"
#: ../../faq.rst:245
msgid "How do I run something in the background?"
msgstr "バックグラウンドで何かを動かすにはどうすればいいですか。"
#: ../../faq.rst:247
msgid ""
"`Check the background_task.py example. "
"<https://github.com/Rapptz/discord.py/blob/master/examples/background_task.py>`_"
msgstr ""
"`background_task.pyの例を参照してください。 "
"<https://github.com/Rapptz/discord.py/blob/master/examples/background_task.py>`_"
#: ../../faq.rst:250
msgid "How do I get a specific model?"
msgstr "特定のユーザー、役割、チャンネル、サーバを取得するにはどうすればいいですか。"
#: ../../faq.rst:252
msgid ""
"There are multiple ways of doing this. If you have a specific model's ID "
"then you can use one of the following functions:"
msgstr "方法は複数ありますが、特定のモデルのIDがわかっていれば、以下の方法が使えます。"
#: ../../faq.rst:255
msgid ":meth:`Client.get_channel`"
msgstr ":meth:`Client.get_channel`"
#: ../../faq.rst:256
msgid ":meth:`Client.get_guild`"
msgstr ":meth:`Client.get_guild`"
#: ../../faq.rst:257
msgid ":meth:`Client.get_user`"
msgstr ":meth:`Client.get_user`"
#: ../../faq.rst:258
msgid ":meth:`Client.get_emoji`"
msgstr ":meth:`Client.get_emoji`"
#: ../../faq.rst:259
msgid ":meth:`Guild.get_member`"
msgstr ":meth:`Guild.get_member`"
#: ../../faq.rst:260
msgid ":meth:`Guild.get_channel`"
msgstr ":meth:`Guild.get_channel`"
#: ../../faq.rst:261
msgid ":meth:`Guild.get_role`"
msgstr ":meth:`Guild.get_role`"
#: ../../faq.rst:263
msgid "The following use an HTTP request:"
msgstr "以下の例ではHTTPリクエストを使用します。"
#: ../../faq.rst:265
msgid ":meth:`abc.Messageable.fetch_message`"
msgstr ":meth:`abc.Messageable.fetch_message`"
#: ../../faq.rst:266
msgid ":meth:`Client.fetch_user`"
msgstr ":meth:`Client.fetch_user`"
#: ../../faq.rst:267
msgid ":meth:`Client.fetch_guilds`"
msgstr ":meth:`Client.fetch_guilds`"
#: ../../faq.rst:268
msgid ":meth:`Client.fetch_guild`"
msgstr ":meth:`Client.fetch_guild`"
#: ../../faq.rst:269
msgid ":meth:`Guild.fetch_emoji`"
msgstr ":meth:`Guild.fetch_emoji`"
#: ../../faq.rst:270
msgid ":meth:`Guild.fetch_emojis`"
msgstr ":meth:`Guild.fetch_emojis`"
#: ../../faq.rst:271
msgid ":meth:`Guild.fetch_member`"
msgstr ":meth:`Guild.fetch_member`"
#: ../../faq.rst:274
msgid ""
"If the functions above do not help you, then use of :func:`utils.find` or"
" :func:`utils.get` would serve some use in finding specific models."
msgstr "上記の関数を使えない状況の場合、 :func:`utils.find` または :func:`utils.get` が役に立つでしょう。"
#: ../../faq.rst:288
msgid "How do I make a web request?"
msgstr "Webリクエストはどうやって作ればよいですか。"
#: ../../faq.rst:290
msgid ""
"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``."
msgstr ""
"リクエストを送るには、ノンブロッキングのライブラリを使わなければなりません。このライブラリは、リクエストを作成するのにサードパーティー製の "
"``aiohttp`` を必要とします。"
#: ../../faq.rst:300
msgid ""
"See `aiohttp's full documentation "
"<http://aiohttp.readthedocs.io/en/stable/>`_ for more information."
msgstr ""
"詳細は `aiohttpの完全なドキュメント <http://aiohttp.readthedocs.io/en/stable/>`_ "
"を参照してください。"
#: ../../faq.rst:303
msgid "How do I use a local image file for an embed image?"
msgstr "Embedの画像にローカルの画像を使用するにはどうすればいいですか。"
#: ../../faq.rst:305
msgid ""
"Discord special-cases uploading an image attachment and using it within "
"an embed so that it will not display separately, but instead in the "
"embed's thumbnail, image, footer or author icon."
msgstr "特殊なケースとして、画像が別々に表示されないようDiscordにembedを用いてアップロードする際、画像は代わりにembedのサムネイルや画像、フッター、製作者アイコンに表示されます。"
#: ../../faq.rst:308
msgid ""
"To do so, upload the image normally with :meth:`abc.Messageable.send`, "
"and set the embed's image URL to ``attachment://image.png``, where "
"``image.png`` is the filename of the image you will send."
msgstr ""
"これを行うには、通常通り :meth:`abc.Messageable.send` を用いて画像をアップロードし、Embedの画像URLに "
"``attachment://image.png`` を設定します。このとき ``image.png`` は送信したい画像のファイル名にです。"
#: ../../faq.rst:322
msgid "Due to a Discord limitation, filenames may not include underscores."
msgstr "Discord側の制限により、ファイル名にアンダースコアが含まれていない場合があります。"
#: ../../faq.rst:325
#, fuzzy
msgid "Is there an event for audit log entries being created?"
msgstr "招待、または監査ログのエントリが作成されるイベントはありますか。"
#: ../../faq.rst:327
msgid ""
"Since Discord does not dispatch this information in the gateway, the "
"library cannot provide this information. This is currently a Discord "
"limitation."
msgstr "Discordはゲートウェイでこの情報をディスパッチしないため、ライブラリによってこの情報を提供することはできません。これは現在、Discord側の制限です。"
#: ../../faq.rst:331
msgid "Commands Extension"
msgstr "コマンド拡張"
#: ../../faq.rst:333
msgid "Questions regarding ``discord.ext.commands`` belong here."
msgstr "``discord.ext.commands`` に関する質問。"
#: ../../faq.rst:336
msgid "Why does ``on_message`` make my commands stop working?"
msgstr "``on_message`` を使うとコマンドが動作しなくなります。どうしてですか。"
#: ../../faq.rst:338
msgid ""
"Overriding the default provided ``on_message`` forbids any extra commands"
" from running. To fix this, add a ``bot.process_commands(message)`` line "
"at the end of your ``on_message``. For example: ::"
msgstr ""
"デフォルトで提供されている ``on_message`` をオーバーライドすると、コマンドが実行されなくなります。これを修正するには "
"``on_message`` の最後に ``bot.process_commands(message)`` を追加してみてください。"
#: ../../faq.rst:347
msgid ""
"Alternatively, you can place your ``on_message`` logic into a "
"**listener**. In this setup, you should not manually call "
"``bot.process_commands()``. This also allows you to do multiple things "
"asynchronously in response to a message. Example::"
msgstr ""
#: ../../faq.rst:357
msgid "Why do my arguments require quotes?"
msgstr "コマンドの引数にクォーテーションが必要なのはなぜですか。"
#: ../../faq.rst:359
msgid "In a simple command defined as: ::"
msgstr "次の簡単なコマンドを見てみましょう。"
#: ../../faq.rst:365
msgid ""
"Calling it via ``?echo a b c`` will only fetch the first argument and "
"disregard the rest. To fix this you should either call it via ``?echo \"a"
" b c\"`` or change the signature to have \"consume rest\" behaviour. "
"Example: ::"
msgstr ""
"このコマンドを ``?echo a b c`` "
"のように実行したとき、コマンドに渡されるのは最初の引数だけです。その後の引数はすべて無視されます。これを正常に動かすためには ``?echo "
"\"a b c\"`` のようにしてコマンドを実行するか、コマンドの引数を下記の例のようにしてみましょう"
#: ../../faq.rst:372
msgid "This will allow you to use ``?echo a b c`` without needing the quotes."
msgstr "これにより、クォーテーションなしで ``?echo a b c`` を使用することができます。"
#: ../../faq.rst:375
msgid "How do I get the original ``message``\\?"
msgstr "元の ``message`` を取得するにはどうすればよいですか。"
#: ../../faq.rst:377
msgid ""
"The :class:`~ext.commands.Context` contains an attribute, "
":attr:`~.Context.message` to get the original message."
msgstr ""
":class:`~ext.commands.Context` は元のメッセージを取得するための属性である "
":attr:`~.Context.message` を持っています。"
#: ../../faq.rst:380 ../../faq.rst:392
msgid "Example: ::"
msgstr "例:"
#: ../../faq.rst:387
msgid "How do I make a subcommand?"
msgstr "サブコマンドを作るにはどうすればいいですか。"
#: ../../faq.rst:389
msgid ""
"Use the ``group`` decorator. This will transform the callback into a "
"``Group`` which will allow you to add commands into the group operating "
"as \"subcommands\". These groups can be arbitrarily nested as well."
msgstr ""
"``group`` デコレータを使います。これにより、コールバックが ``Group`` "
"に変換され、groupに「サブコマンド」として動作するコマンドを追加できます。これらのグループは、ネストすることもできます。"
#: ../../faq.rst:403
msgid "This could then be used as ``?git push origin master``."
msgstr "これは ``?git push origin master`` のように使うことができます。"

View File

@ -1,83 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: discordpy\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-10-23 22:41-0400\n"
"PO-Revision-Date: 2020-10-24 02:41+0000\n"
"Last-Translator: \n"
"Language: ja_JP\n"
"Language-Team: Japanese\n"
"Plural-Forms: nplurals=1; plural=0\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.5.3\n"
#: ../../index.rst:7
msgid "Welcome to discord.py"
msgstr "discord.py へようこそ。"
#: ../../index.rst:11
msgid ""
"discord.py is a modern, easy to use, feature-rich, and async ready API "
"wrapper for Discord."
msgstr "discord.py は機能豊富かつモダンで使いやすい、非同期処理にも対応したDiscord用のAPIラッパーです。"
#: ../../index.rst:14
msgid "**Features:**"
msgstr "**特徴:**"
#: ../../index.rst:16
msgid "Modern Pythonic API using ``async``\\/``await`` syntax"
msgstr "``async``\\/``await`` 構文を使ったモダンなPythonらしいAPI"
#: ../../index.rst:17
msgid "Sane rate limit handling that prevents 429s"
msgstr "429エラー防止の為のレート制限"
#: ../../index.rst:18
msgid "Implements the entire Discord API"
msgstr "Discord APIを完全にカバー"
#: ../../index.rst:19
msgid "Command extension to aid with bot creation"
msgstr "Bot作成に便利なコマンド拡張"
#: ../../index.rst:20
msgid "Easy to use with an object oriented design"
msgstr "オブジェクト指向設計で使いやすい"
#: ../../index.rst:21
msgid "Optimised for both speed and memory"
msgstr "メモリと速度の両方を最適化"
#: ../../index.rst:24
msgid "Documentation Contents"
msgstr "ドキュメントの目次"
#: ../../index.rst:36
msgid "Extensions"
msgstr "拡張機能"
#: ../../index.rst:46
msgid "Additional Information"
msgstr "追加情報"
#: ../../index.rst:57
msgid ""
"If you still can't find what you're looking for, try in one of the "
"following pages:"
msgstr "探しているものが見つからない場合は、以下のページを試してください。"
#: ../../index.rst:59
msgid ":ref:`genindex`"
msgstr ":ref:`genindex`"
#: ../../index.rst:60
msgid ":ref:`search`"
msgstr ":ref:`search`"
#~ msgid ":ref:`modindex`"
#~ msgstr ":ref:`modindex`"

View File

@ -1,429 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) 2015-present, Rapptz
# This file is distributed under the same license as the discord.py package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: discord.py 1.5.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-10-23 22:41-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.5.3\n"
#: ../../intents.rst:6
msgid "A Primer to Gateway Intents"
msgstr ""
#: ../../intents.rst:8
msgid ""
"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."
msgstr ""
#: ../../intents.rst:10
msgid ""
"These intents are passed to the constructor of :class:`Client` or its "
"subclasses (:class:`AutoShardedClient`, :class:`~.AutoShardedBot`, "
":class:`~.Bot`) with the ``intents`` argument."
msgstr ""
#: ../../intents.rst:12
msgid ""
"If intents are not passed, then the library defaults to every intent "
"being enabled except the privileged intents, currently "
":attr:`Intents.members` and :attr:`Intents.presences`."
msgstr ""
#: ../../intents.rst:15
msgid "What intents are needed?"
msgstr ""
#: ../../intents.rst:17
msgid ""
"The intents that are necessary for your bot can only be dictated by "
"yourself. Each attribute in the :class:`Intents` class documents what "
":ref:`events <discord-api-events>` it corresponds to and what kind of "
"cache it enables."
msgstr ""
#: ../../intents.rst:19
msgid ""
"For example, if you want a bot that functions without spammy events like "
"presences or typing then we could do the following:"
msgstr ""
#: ../../intents.rst:34
msgid ""
"Note that this doesn't enable :attr:`Intents.members` since it's a "
"privileged intent."
msgstr ""
#: ../../intents.rst:36
msgid ""
"Another example showing a bot that only deals with messages and guild "
"information:"
msgstr ""
#: ../../intents.rst:54
msgid "Privileged Intents"
msgstr ""
#: ../../intents.rst:56
msgid ""
"With the API change requiring bot authors to specify intents, some "
"intents were restricted further and require more manual steps. These "
"intents are called **privileged intents**."
msgstr ""
#: ../../intents.rst:58
msgid ""
"A privileged intent is one that requires you to go to the developer "
"portal and manually enable it. To enable privileged intents do the "
"following:"
msgstr ""
#: ../../intents.rst:60
msgid ""
"Make sure you're logged on to the `Discord website "
"<https://discord.com>`_."
msgstr ""
#: ../../intents.rst:61
msgid ""
"Navigate to the `application page "
"<https://discord.com/developers/applications>`_"
msgstr ""
#: ../../intents.rst:62
msgid "Click on the bot you want to enable privileged intents for."
msgstr ""
#: ../../intents.rst:63
msgid "Navigate to the bot tab on the left side of the screen."
msgstr ""
#: ../../intents.rst:68
msgid ""
"Scroll down to the \"Privileged Gateway Intents\" section and enable the "
"ones you want."
msgstr ""
#: ../../intents.rst:75
msgid ""
"Enabling privileged intents when your bot is in over 100 guilds requires "
"going through `bot verification <https://support.discord.com/hc/en-"
"us/articles/360040720412>`_. If your bot is already verified and you "
"would like to enable a privileged intent you must go through `discord "
"support <https://dis.gd/contact>`_ and talk to them about it."
msgstr ""
#: ../../intents.rst:79
msgid ""
"Even if you enable intents through the developer portal, you still have "
"to enable the intents through code as well."
msgstr ""
#: ../../intents.rst:83
msgid "Do I need privileged intents?"
msgstr ""
#: ../../intents.rst:85
msgid "This is a quick checklist to see if you need specific privileged intents."
msgstr ""
#: ../../intents.rst:90
msgid "Presence Intent"
msgstr ""
#: ../../intents.rst:92
msgid "Whether you use :attr:`Member.status` at all to track member statuses."
msgstr ""
#: ../../intents.rst:93
msgid ""
"Whether you use :attr:`Member.activity` or :attr:`Member.activities` to "
"check member's activities."
msgstr ""
#: ../../intents.rst:98
msgid "Member Intent"
msgstr ""
#: ../../intents.rst:100
msgid ""
"Whether you track member joins or member leaves, corresponds to "
":func:`on_member_join` and :func:`on_member_remove` events."
msgstr ""
#: ../../intents.rst:101
msgid "Whether you want to track member updates such as nickname or role changes."
msgstr ""
#: ../../intents.rst:102
msgid ""
"Whether you want to track user updates such as usernames, avatars, "
"discriminators, etc."
msgstr ""
#: ../../intents.rst:103
msgid ""
"Whether you want to request the guild member list through "
":meth:`Guild.chunk` or :meth:`Guild.fetch_members`."
msgstr ""
#: ../../intents.rst:104
msgid "Whether you want high accuracy member cache under :attr:`Guild.members`."
msgstr ""
#: ../../intents.rst:109
msgid "Member Cache"
msgstr ""
#: ../../intents.rst:111
msgid ""
"Along with intents, Discord now further restricts the ability to cache "
"members and expects bot authors to cache as little as is necessary. "
"However, to properly maintain a cache the :attr:`Intents.members` intent "
"is required in order to track the members who left and properly evict "
"them."
msgstr ""
#: ../../intents.rst:113
msgid ""
"To aid with member cache where we don't need members to be cached, the "
"library now has a :class:`MemberCacheFlags` flag to control the member "
"cache. The documentation page for the class goes over the specific "
"policies that are possible."
msgstr ""
#: ../../intents.rst:115
msgid ""
"It should be noted that certain things do not need a member cache since "
"Discord will provide full member information if possible. For example:"
msgstr ""
#: ../../intents.rst:117
msgid ""
":func:`on_message` will have :attr:`Message.author` be a member even if "
"cache is disabled."
msgstr ""
#: ../../intents.rst:118
msgid ""
":func:`on_voice_state_update` will have the ``member`` parameter be a "
"member even if cache is disabled."
msgstr ""
#: ../../intents.rst:119
msgid ""
":func:`on_reaction_add` will have the ``user`` parameter be a member even"
" if cache is disabled."
msgstr ""
#: ../../intents.rst:120
msgid ""
":func:`on_raw_reaction_add` will have "
":attr:`RawReactionActionEvent.member` be a member even if cache is "
"disabled."
msgstr ""
#: ../../intents.rst:121
msgid ""
"The reaction removal events do not have the member information. This is a"
" Discord limitation."
msgstr ""
#: ../../intents.rst:123
msgid ""
"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."
msgstr ""
#: ../../intents.rst:128
msgid "Retrieving Members"
msgstr ""
#: ../../intents.rst:130
msgid ""
"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:"
msgstr ""
#: ../../intents.rst:134
msgid ":meth:`Guild.query_members`"
msgstr ""
#: ../../intents.rst:133
msgid "Used to query members by a prefix matching nickname or username."
msgstr ""
#: ../../intents.rst:134
msgid "This can also be used to query members by their user ID."
msgstr ""
#: ../../intents.rst:135
msgid "This uses the gateway and not the HTTP."
msgstr ""
#: ../../intents.rst:136
msgid ":meth:`Guild.chunk`"
msgstr ""
#: ../../intents.rst:137
msgid "This can be used to fetch the entire member list through the gateway."
msgstr ""
#: ../../intents.rst:138
msgid ":meth:`Guild.fetch_member`"
msgstr ""
#: ../../intents.rst:139
msgid "Used to fetch a member by ID through the HTTP API."
msgstr ""
#: ../../intents.rst:141
msgid ":meth:`Guild.fetch_members`"
msgstr ""
#: ../../intents.rst:141
msgid "used to fetch a large number of members through the HTTP API."
msgstr ""
#: ../../intents.rst:143
msgid ""
"It should be noted that the gateway has a strict rate limit of 120 "
"requests per 60 seconds."
msgstr ""
#: ../../intents.rst:146
msgid "Troubleshooting"
msgstr ""
#: ../../intents.rst:148
msgid "Some common issues relating to the mandatory intent change."
msgstr ""
#: ../../intents.rst:151
msgid "Where'd my members go?"
msgstr ""
#: ../../intents.rst:153
msgid ""
"Due to an :ref:`API change <intents_member_cache>` Discord is now forcing"
" developers who want member caching to explicitly opt-in to it. This is a"
" Discord mandated change and there is no way to bypass it. In order to "
"get members back you have to explicitly enable the :ref:`members "
"privileged intent <privileged_intents>` and change the "
":attr:`Intents.members` attribute to true."
msgstr ""
#: ../../intents.rst:155
msgid "For example:"
msgstr ""
#: ../../intents.rst:170
msgid "Why does ``on_ready`` take so long to fire?"
msgstr ""
#: ../../intents.rst:172
msgid ""
"As part of the API change regarding intents, Discord also changed how "
"members are loaded in the beginning. Originally the library could request"
" 75 guilds at once and only request members from guilds that have the "
":attr:`Guild.large` attribute set to ``True``. With the new intent "
"changes, Discord mandates that we can only send 1 guild per request. This"
" causes a 75x slowdown which is further compounded by the fact that *all*"
" guilds, not just large guilds are being requested."
msgstr ""
#: ../../intents.rst:174
msgid "There are a few solutions to fix this."
msgstr ""
#: ../../intents.rst:176
msgid ""
"The first solution is to request the privileged presences intent along "
"with the privileged members intent and enable both of them. This allows "
"the initial member list to contain online members just like the old "
"gateway. Note that we're still limited to 1 guild per request but the "
"number of guilds we request is significantly reduced."
msgstr ""
#: ../../intents.rst:178
msgid ""
"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>`."
msgstr ""
#: ../../intents.rst:180
msgid ""
"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)."
msgstr ""
#: ../../intents.rst:182
msgid ""
"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."
msgstr ""
#: ../../intents.rst:184
msgid ""
"Unfortunately due to this change being required from Discord there is "
"nothing that the library can do to mitigate this."
msgstr ""
#: ../../intents.rst:187
msgid "I don't like this, can I go back?"
msgstr ""
#: ../../intents.rst:189
msgid ""
"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."
msgstr ""
#: ../../intents.rst:191
msgid "To downgrade you can do the following:"
msgstr ""
#: ../../intents.rst:197
msgid "On Windows use ``py -3`` instead of ``python3``."
msgstr ""
#: ../../intents.rst:201
msgid ""
"There is no currently set date in which the old gateway will stop working"
" so it is recommended to update your code instead."
msgstr ""
#: ../../intents.rst:203
msgid ""
"If you truly dislike the direction Discord is going with their API, you "
"can contact them via `support <https://dis.gd/contact>`_"
msgstr ""

View File

@ -1,123 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: discordpy\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-06-22 09:35-0400\n"
"PO-Revision-Date: 2020-10-24 02:41\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: discordpy\n"
"X-Crowdin-Project-ID: 362783\n"
"X-Crowdin-Language: ja\n"
"X-Crowdin-File: intro.pot\n"
"X-Crowdin-File-ID: 80\n"
"Language: ja_JP\n"
#: ../../intro.rst:6
msgid "Introduction"
msgstr "はじめに"
#: ../../intro.rst:8
msgid "This is the documentation for discord.py, a library for Python to aid in creating applications that utilise the Discord API."
msgstr "これはDiscord APIを利用したアプリケーションを作成するのに便利なPythonライブラリ、discord.pyのドキュメントです。"
#: ../../intro.rst:12
msgid "Prerequisites"
msgstr "前提"
#: ../../intro.rst:14
msgid "discord.py works with Python 3.5.3 or higher. Support for earlier versions of Python is not provided. Python 2.7 or lower is not supported. Python 3.4 or lower is not supported due to one of the dependencies (:doc:`aiohttp <aio:index>`) not supporting Python 3.4."
msgstr "discord.pyは3.5.3以降のバージョンのPythonで動作します。Python2.7のような旧バージョンはサポートされていません。Python3.4以下は依存関係にあるライブラリ (:doc:`aiohttp <aio:index>`) がサポートされていないため、サポートしていません。"
#: ../../intro.rst:22
msgid "Installing"
msgstr "インストール"
#: ../../intro.rst:24
msgid "You can get the library directly from PyPI: ::"
msgstr "PyPIから直接ライブラリをインストールできます。"
#: ../../intro.rst:28
msgid "If you are using Windows, then the following should be used instead: ::"
msgstr "Windowsを使用している場合は、以下のコマンドで実行してください。"
#: ../../intro.rst:33
msgid "To get voice support, you should use ``discord.py[voice]`` instead of ``discord.py``, e.g. ::"
msgstr "音声のサポートが必要な場合は、 ``discord.py`` ではなく、以下の例のように ``discord.py[voice]`` を使うべきです。"
#: ../../intro.rst:37
msgid "On Linux environments, installing voice requires getting the following dependencies:"
msgstr "Linux環境では、依存関係にある以下のライブラリが必要になるので注意してください。"
#: ../../intro.rst:39
msgid "`libffi <https://github.com/libffi/libffi>`_"
msgstr "`libffi <https://github.com/libffi/libffi>`_"
#: ../../intro.rst:40
msgid "`libnacl <https://github.com/saltstack/libnacl>`_"
msgstr "`libnacl <https://github.com/saltstack/libnacl>`_"
#: ../../intro.rst:41
msgid "`python3-dev <https://packages.debian.org/python3-dev>`_"
msgstr "`python3-dev <https://packages.debian.org/python3-dev>`_"
#: ../../intro.rst:43
msgid "For a Debian-based system, the following command will get these dependencies:"
msgstr "Debianベースのシステムでは、次のコマンドで依存関係にあるライブラリを取得できます。"
#: ../../intro.rst:49
msgid "Remember to check your permissions!"
msgstr "自分の権限の確認は忘れないようにしてください!"
#: ../../intro.rst:52
msgid "Virtual Environments"
msgstr "仮想環境"
#: ../../intro.rst:54
msgid "Sometimes you want to keep libraries from polluting system installs or use a different version of libraries than the ones installed on the system. You might also not have permissions to install libaries system-wide. For this purpose, the standard library as of Python 3.3 comes with a concept called \"Virtual Environment\"s to help maintain these separate versions."
msgstr "システムへのインストールをライブラリによって汚したくない場合や、現在インストールされているシステムとは異なるバージョンのライブラリを使用したい場合があります。または、システムへのライブラリのインストール権限がない場合などです。こういった目的のため、Python3.3の標準ライブラリには、このように別々のバージョンを保持したい場合のために、「仮想環境」というものが用意されています。"
#: ../../intro.rst:59
msgid "A more in-depth tutorial is found on :doc:`py:tutorial/venv`."
msgstr "より詳しいチュートリアルは :doc:`py:tutorial/venv` にあります。"
#: ../../intro.rst:61
msgid "However, for the quick and dirty:"
msgstr "簡単に仮想環境を構築する方法。"
#: ../../intro.rst:63
msgid "Go to your project's working directory:"
msgstr "プロジェクトの作業ディレクトリに移動してください。"
#: ../../intro.rst:70
msgid "Activate the virtual environment:"
msgstr "下記コマンドで仮想環境を有効化します。"
#: ../../intro.rst:76
msgid "On Windows you activate it with:"
msgstr "Windowsの場合は、こちらを使ってください。"
#: ../../intro.rst:82
msgid "Use pip like usual:"
msgstr "いつものようにpipインストールを実行します。"
#: ../../intro.rst:88
msgid "Congratulations. You now have a virtual environment all set up."
msgstr "おめでとうございます。これで仮想環境のセットアップができました。"
#: ../../intro.rst:91
msgid "Basic Concepts"
msgstr "基本概念"
#: ../../intro.rst:93
msgid "discord.py revolves around the concept of :ref:`events <discord-api-events>`. An event is something you listen to and then respond to. For example, when a message happens, you will receive an event about it that you can respond to."
msgstr "discord.pyは :ref:`イベント <discord-api-events>` の概念を中心としています。イベントは何かを受け取り、それに対する応答を行います。例えば、メッセージが発生すると、メッセージの発生に関連するイベントを受け取り、そのイベントに対して応答を返すことができます。"
#: ../../intro.rst:97
msgid "A quick example to showcase how events work:"
msgstr "以下はイベントの仕組みを紹介する簡単な例です。"

View File

@ -1,77 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: discordpy\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-10-23 22:41-0400\n"
"PO-Revision-Date: 2020-10-24 02:41+0000\n"
"Last-Translator: \n"
"Language: ja_JP\n"
"Language-Team: Japanese\n"
"Plural-Forms: nplurals=1; plural=0\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.5.3\n"
#: ../../logging.rst:5
msgid "Setting Up Logging"
msgstr "ログの設定"
#: ../../logging.rst:7
msgid ""
"*discord.py* logs errors and debug information via the :mod:`logging` "
"python module. It is strongly recommended that the logging module is "
"configured, as no errors or warnings will be output if it is not set up. "
"Configuration of the ``logging`` module can be as simple as::"
msgstr ""
"*discord.py* はPythonの :mod:`logging` "
"モジュールを介してエラーやデバッグの情報を記録します。loggingモジュールが設定されていない場合は、エラーや警告が出力されないため、設定するとを強くおすすめします。loggingモジュールの設定は下記手順で簡単に実装が可能です。"
#: ../../logging.rst:16
#, fuzzy
msgid ""
"Placed at the start of the application. This will output the logs from "
"discord as well as other libraries that use the ``logging`` module "
"directly to the console."
msgstr ""
"アプリケーションの始めにこれを書き加えるだけです。これはdiscordからのログを ``logging`` "
"モジュールを用いた他のライブラリ同様、コンソールに出力します。"
#: ../../logging.rst:20
#, fuzzy
msgid ""
"The optional ``level`` argument specifies what level of events to log out"
" and can be any of ``CRITICAL``, ``ERROR``, ``WARNING``, ``INFO``, and "
"``DEBUG`` and if not specified defaults to ``WARNING``."
msgstr ""
"オプションである ``level`` の引数は出力するイベントのレベルを指定するためのもので、 ``CRITICAL``, ``ERROR``, "
"``WARNING``, ``INFO`` そして ``DEBUG`` を指定することが可能です。指定されていない場合はデフォルトである "
"``WARNING`` に設定されます。"
#: ../../logging.rst:24
#, fuzzy
msgid ""
"More advanced setups are possible with the :mod:`logging` module. For "
"example to write the logs to a file called ``discord.log`` instead of "
"outputting them to the console the following snippet can be used::"
msgstr ""
"また、 :mod:`logging` モジュールでは更に高度な設定が可能です。たとえば、コンソールへ出力するのではなく、 "
"``discord.log`` というファイルにログを出力するには、以下のスニペットが利用できます。"
#: ../../logging.rst:37
#, fuzzy
msgid ""
"This is recommended, especially at verbose levels such as ``INFO`` and "
"``DEBUG``, as there are a lot of events logged and it would clog the "
"stdout of your program."
msgstr ""
"特に、 ``INFO`` や ``DEBUG`` "
"といった冗長なイベントレベルを設定している場合、プログラムの標準出力をつまらせてしまう原因になるため、ファイルへの出力が推奨されます。"
#: ../../logging.rst:43
msgid ""
"For more information, check the documentation and tutorial of the "
":mod:`logging` module."
msgstr "詳細は、:mod:`logging` モジュールのドキュメントを参照してください。"

File diff suppressed because it is too large Load Diff

View File

@ -1,363 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: discordpy\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-06-22 09:35-0400\n"
"PO-Revision-Date: 2020-10-24 02:41\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: discordpy\n"
"X-Crowdin-Project-ID: 362783\n"
"X-Crowdin-Language: ja\n"
"X-Crowdin-File: migrating_to_async.pot\n"
"X-Crowdin-File-ID: 48\n"
"Language: ja_JP\n"
#: ../../migrating_to_async.rst:8
msgid "Migrating to v0.10.0"
msgstr "v0.10.0への移行"
#: ../../migrating_to_async.rst:10
msgid "v0.10.0 is one of the biggest breaking changes in the library due to massive fundamental changes in how the library operates."
msgstr "v0.10.0は、ライブラリの根本的動作が大幅に変更された、ライブラリの中でも大きな更新の一つです。"
#: ../../migrating_to_async.rst:13
msgid "The biggest major change is that the library has dropped support to all versions prior to Python 3.4.2. This was made to support :mod:`asyncio`, in which more detail can be seen :issue:`in the corresponding issue <50>`. To reiterate this, the implication is that **python version 2.7 and 3.3 are no longer supported**."
msgstr "最大の大きな変更点はPython 3.4.2以前のすべてのバージョンのサポートが打ち切られたことです。これは :mod:`asyncio` をサポートするためで、対応する :issue:`in the corresponding issue <50>` で詳細が見られます。繰り返しになりますが、 **Python 2.7及びPython3.3は、既にサポートされていません** 。"
#: ../../migrating_to_async.rst:18
msgid "Below are all the other major changes from v0.9.0 to v0.10.0."
msgstr "以下は、v0.9.0からv0.10.0までのその他の主要な変更点です。"
#: ../../migrating_to_async.rst:21
msgid "Event Registration"
msgstr "イベント登録"
#: ../../migrating_to_async.rst:23
msgid "All events before were registered using :meth:`Client.event`. While this is still possible, the events must be decorated with ``@asyncio.coroutine``."
msgstr "以前まではすべてのイベントが :meth:`Client.event` を使用して登録されていました。このやり方はまだ可能ですが、 ``@asyncio.coroutine`` で修飾が必要です。"
#: ../../migrating_to_async.rst:26
#: ../../migrating_to_async.rst:71
#: ../../migrating_to_async.rst:105
#: ../../migrating_to_async.rst:166
msgid "Before:"
msgstr "更新前:"
#: ../../migrating_to_async.rst:34
#: ../../migrating_to_async.rst:83
#: ../../migrating_to_async.rst:111
#: ../../migrating_to_async.rst:174
#: ../../migrating_to_async.rst:284
msgid "After:"
msgstr "更新後:"
#: ../../migrating_to_async.rst:43
msgid "Or in Python 3.5+:"
msgstr "Python 3.5以降の場合:"
#: ../../migrating_to_async.rst:51
msgid "Because there is a lot of typing, a utility decorator (:meth:`Client.async_event`) is provided for easier registration. For example:"
msgstr "多くの型付けがあるため、登録を簡単にするためにユーティリティデコレータ (:meth:`Client.async_event`) が用意されています。例:"
#: ../../migrating_to_async.rst:61
msgid "Be aware however, that this is still a coroutine and your other functions that are coroutines must be decorated with ``@asyncio.coroutine`` or be ``async def``."
msgstr "しかし、これはまだコルーチンであり、コルーチンである関数には ``@asyncio.coroutine`` あるいは ``async def`` での修飾が必要であることに注意してください。"
#: ../../migrating_to_async.rst:65
msgid "Event Changes"
msgstr "イベントの変更"
#: ../../migrating_to_async.rst:67
msgid "Some events in v0.9.0 were considered pretty useless due to having no separate states. The main events that were changed were the ``_update`` events since previously they had no context on what was changed."
msgstr "v0.9.0でいくつかのイベントは、別の状態を持たないために、役に立たないと考えられていました。変更された主なイベントは、更新前と更新後の二つの状態を持つ ``_update`` イベントです。"
#: ../../migrating_to_async.rst:93
msgid "Note that ``on_status`` was removed. If you want its functionality, use :func:`on_member_update`. See :ref:`discord-api-events` for more information. Other removed events include ``on_socket_closed``, ``on_socket_receive``, and ``on_socket_opened``."
msgstr "``on_status`` が削除されていることに注意してください。似たような機能が使いたい場合は、 :func:`on_member_update` を使用します。詳細は :ref:`discord-api-events` を参照してください。他に、 ``on_socket_closed`` 、 ``on_socket_receive`` や ``on_socket_opened`` も削除されています。"
#: ../../migrating_to_async.rst:98
msgid "Coroutines"
msgstr "コルーチン"
#: ../../migrating_to_async.rst:100
msgid "The biggest change that the library went through is that almost every function in :class:`Client` was changed to be a `coroutine <py:library/asyncio-task.html>`_. Functions that are marked as a coroutine in the documentation must be awaited from or yielded from in order for the computation to be done. For example..."
msgstr "ライブラリの最大の変更点は :class:`Client` を使用していたほとんどの関数が `コルーチン <py:library/asyncio-task.html>`_ へと変更されたことです。ドキュメントのコルーチンとして定義された関数は、処理の終了を待機します。例えば、"
#: ../../migrating_to_async.rst:120
msgid "In order for you to ``yield from`` or ``await`` a coroutine then your function must be decorated with ``@asyncio.coroutine`` or ``async def``."
msgstr "``yield from`` あるいは ``await`` を使用するには、関数が ``@asyncio.coroutine`` または ``async def`` で修飾されている必要があります。"
#: ../../migrating_to_async.rst:124
msgid "Iterables"
msgstr "イテラブル"
#: ../../migrating_to_async.rst:126
msgid "For performance reasons, many of the internal data structures were changed into a dictionary to support faster lookup. As a consequence, this meant that some lists that were exposed via the API have changed into iterables and not sequences. In short, this means that certain attributes now only support iteration and not any of the sequence functions."
msgstr "パフォーマンス上の理由から、より高速な情報の取得を可能とするため、多くの内部データ構造が辞書に変更されました。結果として、これはAPIを介して公開された一部リストが、シーケンスではなくイテラブルに変更されたことを意味します。つまるところ、現在、特定の属性はイテラブルのみをサポートしており、シーケンスを扱うことができないことを意味しています。"
#: ../../migrating_to_async.rst:131
msgid "The affected attributes are as follows:"
msgstr "影響を受ける属性は以下のとおりです。"
#: ../../migrating_to_async.rst:133
msgid ":attr:`Client.servers`"
msgstr ":attr:`Client.servers`"
#: ../../migrating_to_async.rst:134
msgid ":attr:`Client.private_channels`"
msgstr ":attr:`Client.private_channels`"
#: ../../migrating_to_async.rst:135
msgid ":attr:`Server.channels`"
msgstr ":attr:`Server.channels`"
#: ../../migrating_to_async.rst:136
msgid ":attr:`Server.members`"
msgstr ":attr:`Server.members`"
#: ../../migrating_to_async.rst:138
msgid "Some examples of previously valid behaviour that is now invalid"
msgstr "以前は有効であったが、現在は無効である操作の例。"
#: ../../migrating_to_async.rst:145
msgid "Since they are no longer :obj:`list`\\s, they no longer support indexing or any operation other than iterating. In order to get the old behaviour you should explicitly cast it to a list."
msgstr "これらは、既に :obj:`list` ではないため、インデックスや反復処理以外の操作はサポートされなくなりました。以前の動作で処理を行うには、listに明示的にキャストする必要があります。"
#: ../../migrating_to_async.rst:155
msgid "Due to internal changes of the structure, the order you receive the data in is not in a guaranteed order."
msgstr "構造の内部的な変更のため、データを受け取った順序は保証されません。"
#: ../../migrating_to_async.rst:159
msgid "Enumerations"
msgstr "列挙型"
#: ../../migrating_to_async.rst:161
msgid "Due to dropping support for versions lower than Python 3.4.2, the library can now use :doc:`py:library/enum` in places where it makes sense."
msgstr "Python 3.4.2以前のバージョンのサポートを打ち切ったため、ライブラリは理にかなった場所での :doc:`py:library/enum` の使用が可能になりました。"
#: ../../migrating_to_async.rst:164
msgid "The common places where this was changed was in the server region, member status, and channel type."
msgstr "変更された主な場所は、サーバーリージョン、メンバーのステータス、およびチャンネルタイプです。"
#: ../../migrating_to_async.rst:182
msgid "The main reason for this change was to reduce the use of finicky strings in the API as this could give users a false sense of power. More information can be found in the :ref:`discord-api-enums` page."
msgstr "この変更の主な理由は、APIでの厄介な文字列の使用を削減することでした。ユーザーに誤った感覚を与える可能性があったためです。詳細は :ref:`discord-api-enums` を参照してください。"
#: ../../migrating_to_async.rst:186
msgid "Properties"
msgstr "プロパティ"
#: ../../migrating_to_async.rst:188
msgid "A lot of function calls that returned constant values were changed into Python properties for ease of use in format strings."
msgstr "定数値を返す関数呼び出しの多くは、書式化した文字列での使いやすさ向上を図るため、Pythonのプロパティに変更されました。"
#: ../../migrating_to_async.rst:191
msgid "The following functions were changed into properties:"
msgstr "以下はプロパティに変更された関数です。"
#: ../../migrating_to_async.rst:194
#: ../../migrating_to_async.rst:223
#: ../../migrating_to_async.rst:238
msgid "Before"
msgstr "変更前"
#: ../../migrating_to_async.rst:194
#: ../../migrating_to_async.rst:223
#: ../../migrating_to_async.rst:238
msgid "After"
msgstr "変更後"
#: ../../migrating_to_async.rst:196
msgid "``User.avatar_url()``"
msgstr "``User.avatar_url()``"
#: ../../migrating_to_async.rst:196
msgid ":attr:`User.avatar_url`"
msgstr ":attr:`User.avatar_url`"
#: ../../migrating_to_async.rst:198
msgid "``User.mention()``"
msgstr "``User.mention()``"
#: ../../migrating_to_async.rst:198
msgid ":attr:`User.mention`"
msgstr ":attr:`User.mention`"
#: ../../migrating_to_async.rst:200
msgid "``Channel.mention()``"
msgstr "``Channel.mention()``"
#: ../../migrating_to_async.rst:200
msgid ":attr:`Channel.mention`"
msgstr ":attr:`Channel.mention`"
#: ../../migrating_to_async.rst:202
msgid "``Channel.is_default_channel()``"
msgstr "``Channel.is_default_channel()``"
#: ../../migrating_to_async.rst:202
msgid ":attr:`Channel.is_default`"
msgstr ":attr:`Channel.is_default`"
#: ../../migrating_to_async.rst:204
msgid "``Role.is_everyone()``"
msgstr "``Role.is_everyone()``"
#: ../../migrating_to_async.rst:204
msgid ":attr:`Role.is_everyone`"
msgstr ":attr:`Role.is_everyone`"
#: ../../migrating_to_async.rst:206
msgid "``Server.get_default_role()``"
msgstr "``Server.get_default_role()``"
#: ../../migrating_to_async.rst:206
msgid ":attr:`Server.default_role`"
msgstr ":attr:`Server.default_role`"
#: ../../migrating_to_async.rst:208
msgid "``Server.icon_url()``"
msgstr "``Server.icon_url()``"
#: ../../migrating_to_async.rst:208
msgid ":attr:`Server.icon_url`"
msgstr ":attr:`Server.icon_url`"
#: ../../migrating_to_async.rst:210
msgid "``Server.get_default_channel()``"
msgstr "``Server.get_default_channel()``"
#: ../../migrating_to_async.rst:210
msgid ":attr:`Server.default_channel`"
msgstr ":attr:`Server.default_channel`"
#: ../../migrating_to_async.rst:212
msgid "``Message.get_raw_mentions()``"
msgstr "``Message.get_raw_mentions()``"
#: ../../migrating_to_async.rst:212
msgid ":attr:`Message.raw_mentions`"
msgstr ":attr:`Message.raw_mentions`"
#: ../../migrating_to_async.rst:214
msgid "``Message.get_raw_channel_mentions()``"
msgstr "``Message.get_raw_channel_mentions()``"
#: ../../migrating_to_async.rst:214
msgid ":attr:`Message.raw_channel_mentions`"
msgstr ":attr:`Message.raw_channel_mentions`"
#: ../../migrating_to_async.rst:218
msgid "Member Management"
msgstr "メンバー管理"
#: ../../migrating_to_async.rst:220
msgid "Functions that involved banning and kicking were changed."
msgstr "BANやキックを含む関数が追加されました。"
#: ../../migrating_to_async.rst:225
msgid "``Client.ban(server, user)``"
msgstr "``Client.ban(server, user)``"
#: ../../migrating_to_async.rst:225
msgid "``Client.ban(member)``"
msgstr "``Client.ban(member)``"
#: ../../migrating_to_async.rst:227
msgid "``Client.kick(server, user)``"
msgstr "``Client.kick(server, user)``"
#: ../../migrating_to_async.rst:227
msgid "``Client.kick(member)``"
msgstr "``Client.kick(member)``"
#: ../../migrating_to_async.rst:233
msgid "Renamed Functions"
msgstr "関数の改名"
#: ../../migrating_to_async.rst:235
msgid "Functions have been renamed."
msgstr "いくつかの関数名が変更されました。"
#: ../../migrating_to_async.rst:240
msgid "``Client.set_channel_permissions``"
msgstr "``Client.set_channel_permissions``"
#: ../../migrating_to_async.rst:240
#: ../../migrating_to_async.rst:263
msgid ":meth:`Client.edit_channel_permissions`"
msgstr ":meth:`Client.edit_channel_permissions`"
#: ../../migrating_to_async.rst:243
msgid "All the :class:`Permissions` related attributes have been renamed and the `can_` prefix has been dropped. So for example, ``can_manage_messages`` has become ``manage_messages``."
msgstr "すべての :class:`Permissions` 関連の属性の名称が変更され、 接頭詞であった `can_` が削除されました。例を挙げると ``can_manage_messages`` が ``manage_messages`` になりました。"
#: ../../migrating_to_async.rst:247
msgid "Forced Keyword Arguments"
msgstr "強制キーワード引数"
#: ../../migrating_to_async.rst:249
msgid "Since 3.0+ of Python, we can now force questions to take in forced keyword arguments. A keyword argument is when you explicitly specify the name of the variable and assign to it, for example: ``foo(name='test')``. Due to this support, some functions in the library were changed to force things to take said keyword arguments. This is to reduce errors of knowing the argument order and the issues that could arise from them."
msgstr "Python 3.0以降では、強制的にキーワード引数をとるようにすることができるようになりました。キーワード引数は、変数の名前を明示的に指定してそれに割り当てることで、例えば ``foo(name='test')`` などです。このサポートにより、ライブラリ内のいくつかの関数が変更され、キーワード引数を取るようになりました。これは引数の順序が誤っていることが原因で発生するエラーを減らすためです。"
#: ../../migrating_to_async.rst:254
msgid "The following parameters are now exclusively keyword arguments:"
msgstr "次のパラメータは、現在、排他的なキーワード引数です。"
#: ../../migrating_to_async.rst:256
msgid ":meth:`Client.send_message`"
msgstr ":meth:`Client.send_message`"
#: ../../migrating_to_async.rst:257
msgid "``tts``"
msgstr "``tts``"
#: ../../migrating_to_async.rst:259
msgid ":meth:`Client.logs_from`"
msgstr ":meth:`Client.logs_from`"
#: ../../migrating_to_async.rst:259
msgid "``before``"
msgstr "``before``"
#: ../../migrating_to_async.rst:260
msgid "``after``"
msgstr "``after``"
#: ../../migrating_to_async.rst:262
msgid "``allow``"
msgstr "``allow``"
#: ../../migrating_to_async.rst:263
msgid "``deny``"
msgstr "``deny``"
#: ../../migrating_to_async.rst:265
msgid "In the documentation you can tell if a function parameter is a forced keyword argument if it is after ``\\*,`` in the function signature."
msgstr "ドキュメントでは、関数シグネチャ内の ``\\*,`` の後に引数があるかどうかで、関数の引数が強制的なキーワード引数であるかを知ることができます。"
#: ../../migrating_to_async.rst:271
msgid "Running the Client"
msgstr "クライアントの実行"
#: ../../migrating_to_async.rst:273
msgid "In earlier versions of discord.py, ``client.run()`` was a blocking call to the main thread that called it. In v0.10.0 it is still a blocking call but it handles the event loop for you. However, in order to do that you must pass in your credentials to :meth:`Client.run`."
msgstr "以前のバージョンのdiscord.pyでは ``client.run()`` は呼び出したメインスレッドをブロッキングするブロック付き呼び出しでした。v0.10.0でも未だブロック付き呼び出しですが、イベントループで処理を行います。ただし、それを行うためには認証情報を :meth:`Client.run` に渡す必要があります。"
#: ../../migrating_to_async.rst:277
msgid "Basically, before:"
msgstr "以前:"
#: ../../migrating_to_async.rst:292
msgid "Like in the older ``Client.run`` function, the newer one must be the one of the last functions to call. This is because the function is **blocking**. Registering events or doing anything after :meth:`Client.run` will not execute until the function returns."
msgstr "以前の ``Client.run`` 同様、新しくなった関数も最後に呼び出す必要があります。これは関数がブロッキングを行うためです。 :meth:`Client.run` の後に何かを定義しても、この関数が終了するまで、それらの処理は行われません。"
#: ../../migrating_to_async.rst:297
msgid "This is a utility function that abstracts the event loop for you. There's no need for the run call to be blocking and out of your control. Indeed, if you want control of the event loop then doing so is quite straightforward:"
msgstr "これはイベントループを抽象化するユーティリティ関数です。 実行呼び出しがブロックされ、コントロールから外れる必要はありません。実際に、イベントループを制御したい場合、この方法では非常に簡単です。"

View File

@ -1,91 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: discordpy\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-06-22 09:35-0400\n"
"PO-Revision-Date: 2020-10-24 02:41\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: discordpy\n"
"X-Crowdin-Project-ID: 362783\n"
"X-Crowdin-Language: ja\n"
"X-Crowdin-File: quickstart.pot\n"
"X-Crowdin-File-ID: 50\n"
"Language: ja_JP\n"
#: ../../quickstart.rst:6
msgid "Quickstart"
msgstr "クイックスタート"
#: ../../quickstart.rst:8
msgid "This page gives a brief introduction to the library. It assumes you have the library installed, if you don't check the :ref:`installing` portion."
msgstr "ここでは、ライブラリの概要を説明します。ライブラリがインストールされていることを前提としているので、インストールを終えていない人は :ref:`installing` を参照してください。"
#: ../../quickstart.rst:12
msgid "A Minimal Bot"
msgstr "最小限のBot"
#: ../../quickstart.rst:14
msgid "Let's make a bot that replies to a specific message and walk you through it."
msgstr "では早速、特定のメッセージに対して、返事をするBotを作ってみましょう。"
#: ../../quickstart.rst:16
msgid "It looks something like this:"
msgstr "結論から書くと、このように書くことができます。"
#: ../../quickstart.rst:38
msgid "Let's name this file ``example_bot.py``. Make sure not to name it ``discord.py`` as that'll conflict with the library."
msgstr "ファイルの名前を ``example_bot.py`` としましょう。ライブラリと競合してしまうので、 ``discord.py`` というファイル名にはしないでください。"
#: ../../quickstart.rst:41
msgid "There's a lot going on here, so let's walk you through it step by step."
msgstr "さて、では順を追って一つづつ説明していきます。"
#: ../../quickstart.rst:43
msgid "The first line just imports the library, if this raises a `ModuleNotFoundError` or `ImportError` then head on over to :ref:`installing` section to properly install."
msgstr "最初の行は、ただライブラリをインポートしただけです。 `ModuleNotFoundError` や `ImportError` が発生した場合は :ref:`installing` を見て、ライブラリをきちんとインストールしましょう。"
#: ../../quickstart.rst:45
msgid "Next, we create an instance of a :class:`Client`. This client is our connection to Discord."
msgstr "次に、 :class:`Client` のインスタンスを作成します。クライアントはDiscordへの接続を行います。"
#: ../../quickstart.rst:46
msgid "We then use the :meth:`Client.event` decorator to register an event. This library has many events. Since this library is asynchronous, we do things in a \"callback\" style manner."
msgstr "続いて、 :meth:`Client.event` デコレータを使用してイベントを登録します。ライブラリにはたくさんのイベントが用意されています。このライブラリは非同期のため、「コールバック」のスタイルで処理を行います。"
#: ../../quickstart.rst:49
msgid "A callback is essentially a function that is called when something happens. In our case, the :func:`on_ready` event is called when the bot has finished logging in and setting things up and the :func:`on_message` event is called when the bot has received a message."
msgstr "コールバックは基本的に、何かが発生した場合に呼び出される関数です。今回の場合だと、Botがログインして、設定などを終えたときに :func:`on_ready` が、メッセージを受信したときに :func:`on_message` が呼び出されます。"
#: ../../quickstart.rst:52
msgid "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` is the same as the :attr:`Client.user`."
msgstr ":func:`on_message` イベントは受信したメッセージすべてに対して呼び出されるため、Bot自身からのメッセージは無視するように設定する必要があります。これは、メッセージの送信者である :attr:`Message.author` と、Bot自身を表す :attr:`Client.user` が等しいか比較することで実装できます。"
#: ../../quickstart.rst:55
msgid "Afterwards, we check if the :class:`Message.content` starts with ``'$hello'``. If it is, then we reply in the channel it was used in with ``'Hello!'``."
msgstr "その後、 :class:`Message.content` が ``'$hello'`` から始まるかどうかを確認し、当てはまればそのチャンネルに ``'Hello!'`` という返事を送信します。"
#: ../../quickstart.rst:57
msgid "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."
msgstr "最後に、ログイン用トークンを用いてBotを起動します。トークンの取得やBotの作成について分からないことがあれば、 :ref:`discord-intro` を参照してください。"
#: ../../quickstart.rst:61
msgid "Now that we've made a bot, we have to *run* the bot. Luckily, this is simple since this is just a Python script, we can run it directly."
msgstr "さて、これでBotは完成なので、Botを *実行* してみましょう。幸いにも、これはただのPythonスクリプトなので実行は簡単です。直接実行が可能です。"
#: ../../quickstart.rst:64
msgid "On Windows:"
msgstr "Windowsの場合:"
#: ../../quickstart.rst:70
msgid "On other systems:"
msgstr "その他のシステムの場合:"
#: ../../quickstart.rst:76
msgid "Now you can try playing around with your basic bot."
msgstr "これで、あなたが作ったBotと遊ぶことができます。"

View File

@ -1,23 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: discordpy\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-06-22 09:35-0400\n"
"PO-Revision-Date: 2020-10-24 02:41\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: discordpy\n"
"X-Crowdin-Project-ID: 362783\n"
"X-Crowdin-Language: ja\n"
"X-Crowdin-File: sphinx.pot\n"
"X-Crowdin-File-ID: 70\n"
"Language: ja_JP\n"
#: ../../_templates/layout.html:24
msgid "Created using <a href=\"http://sphinx-doc.org/\">Sphinx</a> %(sphinx_version)s."
msgstr "<a href=\"http://sphinx-doc.org/\">Sphinx</a> %(sphinx_version)s で作成されました。"

View File

@ -1,79 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: discordpy\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-06-22 09:35-0400\n"
"PO-Revision-Date: 2020-10-24 02:41\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: discordpy\n"
"X-Crowdin-Project-ID: 362783\n"
"X-Crowdin-Language: ja\n"
"X-Crowdin-File: version_guarantees.pot\n"
"X-Crowdin-File-ID: 46\n"
"Language: ja_JP\n"
#: ../../version_guarantees.rst:4
msgid "Version Guarantees"
msgstr "バージョン保証"
#: ../../version_guarantees.rst:6
msgid "The library follows a `semantic versioning principle <https://semver.org/>`_ which means that the major version is updated every time there is an incompatible API change. However due to the lack of guarantees on the Discord side when it comes to breaking changes along with the fairly dynamic nature of Python it can be hard to discern what can be considered a breaking change and what isn't."
msgstr "このライブラリは `セマンティック バージョニングの原則 <https://semver.org/>`_ に従います。それが意味するのは、互換性のないAPIの変更が行われるたびにメジャーバージョンが更新されるということです。しかしながら、Discord側にはPythonの非常に動的な性質とともに破壊的変更を行う際の保証がないため、破壊的変更とみなされるもの、そうでないものを区別するのは困難です。"
#: ../../version_guarantees.rst:8
msgid "The first thing to keep in mind is that breaking changes only apply to **publicly documented functions and classes**. If it's not listed in the documentation here then it is not part of the public API and is thus bound to change. This includes attributes that start with an underscore or functions without an underscore that are not documented."
msgstr "最初に覚えておくべきことは、破壊的変更は **公開ドキュメント化してある関数とクラス** のみに適用されるということです。ドキュメントにないものはパブリックAPIの一部ではないため、変更される可能性があります。これにはドキュメントに載っていないアンダースコアから始まる関数や、通常の関数が含まれます。"
#: ../../version_guarantees.rst:12
msgid "The examples below are non-exhaustive."
msgstr "以下の例は網羅的なものではありません。"
#: ../../version_guarantees.rst:15
msgid "Examples of Breaking Changes"
msgstr "破壊的変更の例"
#: ../../version_guarantees.rst:17
msgid "Changing the default parameter value to something else."
msgstr "デフォルトのパラメータ値を別のものに変更。"
#: ../../version_guarantees.rst:18
msgid "Renaming a function without an alias to an old function."
msgstr "古い関数へのエイリアスのない関数の名称を変更。"
#: ../../version_guarantees.rst:19
msgid "Adding or removing parameters to an event."
msgstr "イベントへのパラメータの追加、あるいは削除。"
#: ../../version_guarantees.rst:22
msgid "Examples of Non-Breaking Changes"
msgstr "破壊的変更ではないものの例"
#: ../../version_guarantees.rst:24
msgid "Adding or removing private underscored attributes."
msgstr "アンダースコア付きのプライベート関数の追加、あるいは削除。"
#: ../../version_guarantees.rst:25
msgid "Adding an element into the ``__slots__`` of a data class."
msgstr "データクラスの ``__slots__`` への要素の追加。"
#: ../../version_guarantees.rst:26
msgid "Changing the behaviour of a function to fix a bug."
msgstr "バグ修正のための関数の動作の変更。"
#: ../../version_guarantees.rst:27
msgid "Changes in the documentation."
msgstr "ドキュメントの変更。"
#: ../../version_guarantees.rst:28
msgid "Modifying the internal HTTP handling."
msgstr "内部HTTP処理の変更。"
#: ../../version_guarantees.rst:29
msgid "Upgrading the dependencies to a new version, major or otherwise."
msgstr "依存関係をメジャー、またはそれ以外の新しいバージョンへアップグレード。"

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,134 @@ 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.
.. _vp2p0p0:
v2.0.0
--------
This version was partly developed by Danny, and partly by the enhanced-discord.py contributors.
The library has been updated with breaking changes, and as such the major version was changed.
- Performance of the library has improved significantly (all times with 1 process and 1 AutoShardedBot):
- 735 guilds boot up time (with chunking): 57s/1.7 GiB RAM -> 42s/1.4 GiB RAM
- 27k guilds boot up time (with chunking): 477s/8 GiB RAM -> 303s/7.2 GiB RAM
- 48k guilds boot up time (without chunking): 109s -> 67s
- 106k guilds boot up time (without chunking): 3300s -> 3090s
- The entire public API of the library is now completely type hinted.
- There may still be bugs however.
- For best type hinting experience consider using Pyright.
- Almost all edit methods now return their updated counterpart rather than doing an in-place edit.
- Japanese docs were removed, as we are no longer able to keep them in sync.
Breaking Changes
~~~~~~~~~~~~~~~~~
- :meth:`Asset.replace` now only accepts keyword arguments
- ``Asset.with_`` functions now only accept positional only arguments
- :meth:`TextChannel.get_partial_message` is now pos-only
- :meth:`TextChannel.get_thread` is now pos-only
- ``permissions_for`` is now pos-only
- :attr:`GroupChannel.owner` is now Optional
- ``edit`` methods now only accept None if it actually means something (e.g. clearing it)
- ``timeout`` parameter for ``ui.View.__init__`` is now keyword only
- When an interaction has already been responded and another one is sent, :exc:`InteractionResponded`is now raised.
- Discord's API only allows a single :attr:`interaction.response`.
- Separate :func:`on_member_update` and :func:`on_presence_update`
- The new event :func:`on_presence_update` is now called when status/activity is changed.
- :func:`on_member_update` will now no longer have status/activity changes.
- afk parameter in :meth:`Client.change_presence` is removed
- The undocumented private :func:`on_socket_response` event got removed.
- Consider using the newer documented :func:`on_socket_event_type` event instead.
- Using :func:`on_socket_raw_receive` and :func:`on_socket_raw_send` are now opt-in via :attr:`enable_debug_events` toggle.
- :func:`on_socket_raw_receive` is now only dispatched after decompressing the payload.
- Persistent View dispatch mechanism now uses the ``message_id`` key if provided.
- :meth:`Message.start_thread` was renamed to :meth:`Message.create_thread`
- :meth:`TextChannel.start_thread` was renamed to :meth:`TextChannel.create_thread`
- All ``get_`` lookup functions now use positional-only parameters for the id parameter.
- Remove :meth:`TextChannel.active_threads` due to the endpoint being deprecated and slated for removal.
- Use :meth:`Guild.active_threads` instead.
- :attr:`User.avatar` now returns None if the user did not upload an avatar.
- Use :attr:`User.display_avatar` to get the avatar and fallback to the default avatar to go back to the old behaviour.
New Features
~~~~~~~~~~~~~~
- Channel types are now typed
- Member is now typed
- Client is now typed
- Permissions are now typed
- Core library errors are now typed
- Add various examples showing how to use views. There are more to come.
- :attr:`GroupChannel.owner_id` now gets the owner ID
- ``edit`` methods now don't rely on previous state
- :meth:`View.from_message` converts a Message.components to a View
- :attr:`Thread.type` to get the thread channel type
- :attr:`ButtonStyle.url` alias for :attr:`ButtonStyle.link`
- Add default style for :class:`ui.Button` constructor
- This makes it so creating a URL button is as simple as ``ui.Button(url='...', label='...')``
- :attr:`Thread.mention` to get the mention string for a thread
- :meth:`Thread.is_nsfw` to check whether the parent channel of the thread is NSFW
- Add support for fetching the original interaction response message.
- :meth:`Interaction.original_message` will retrieve it and returns an InteractionMessage
- :meth:`InteractionMessage.edit` or :meth:`Interaction.edit_original_message` will edit it
- :meth:`InteractionMessage.delete` or :meth:`Interaction.delete_original_message` will delete it
- :attr:`MessageFlags.ephemeral` to get whether a message is ephemeral
- :meth:`Client.fetch_channel` now fetches threads as well
- :class:`SelectOption` now has a __str__ that matches the client representation.
- This might change in the future to remove the description from it.
- Add a converter for :class:`discord.Thread`
- Allow ``clean_content`` converter to work stand-alone
- Add :meth:`User.banner` to get a user's banner and :meth:`User.accent_colour` to get the set banner colour.
- Due to an API limitation this requires using :meth:`Client.fetch_user`.
- Add ``reason`` keyword argument to more methods
- Add audit log events for threads
- Allow public threads to be created without a starter message
- Add :meth:`Guild.get_channel_or_thread` helper method
- Add full support for the new sticker API
- Add :func:`on_socket_event_type` event to get the event type of an event
- Add :attr:`TextChannel.default_auto_archive_duration`
- Add :class:`PartialMessageable` type to allow for sending messages to a channel using only its ``channel_id``.
- This is constructed with :meth:`Client.get_partial_messageable`.
- Add :meth:`Guild.active_threads` to get all of a guild's active threads.
- Add :attr:`Thread.members` to get all cached :class:`ThreadMember` instances of a thread.
- Add :meth:`Thread.fetch_members` to fetch all :class:`ThreadMember` instances of a thread.
- These two require :attr:`Intents.members` to be useful.
- Add support for guild avatars for members under :attr:`Member.guild_avatar`
- Add :attr:`User.display_avatar` and :attr:`Member.display_avatar` to get the user's displayed avatar.
- Add :attr:`Colour.brand_green` and :attr:`Colour.brand_red`
- |commands| :attr:`CommandOnCooldown.type` to get back the type of the cooldown since it was removed from :class:`Cooldown`
- Add :attr:`Guild.bots` and :attr:`Guild.humans`
Bug Fixes
~~~~~~~~~~~
- :class:`Channel` converters now work in DMs again
- Fix :attr:`Interaction.channel` being None in threads
- Change timeouts in :class:`ui.View` to work as documented
- :attr:`Message.__repr__` now shows the proper type, e.g. :class:`WebhookMessage` and :class:`InteractionMessage`
- Change :class:`Cooldown` handling to not reset token window when the number of tokens reaches 0
- Fix audit log permission construction breaking due to unexpected type errors.
- Fix :func:`on_thread_join` not dispatching when a thread is unarchived
- Fix :attr:`Message.guild` being None when a thread is unarchived due to a new message
- :class:`MessageConverter` now works with threads
- Retry requests when a 504 is hit
- Fix :attr:`Thread.slowmode_delay` not updating on edit
- Fix ``permissions_for`` for roles
- Update :attr:`Message.system_content` for newer message types
- Fix :class:`PartialMessage` not working with threads
- Fix crash with stage instances not having the documented ``discoverable_enabled`` key
- Fix some built-in checks not working with threads
- Fix :class:`SyncWebhook` not working in a multi-threaded context
- Fix :func:`on_thread_member_remove` not dispatching properly
- Fix :func:`on_typing` not dispatching for threads
- Update :attr:`Message.is_system` to work with newer message types
- Fix some enums like :class:`VerificationLevel` not being comparable.
- Fix ``io.BytesIO`` sources not working with ffmpeg players
- Fix :meth:`Client.fetch_channel` and :meth:`Guild.fetch_channel` not returning threads
.. _vp1p7p3: .. _vp1p7p3:
v1.7.3 v1.7.3

View File

@ -26,5 +26,5 @@ class MyClient(discord.Client):
async def before_my_task(self): async def before_my_task(self):
await self.wait_until_ready() # wait until the bot logs in await self.wait_until_ready() # wait until the bot logs in
client = MyClient() client = MyClient(intents=discord.Intents(guilds=True))
client.run('token') client.run('token')

View File

@ -22,5 +22,5 @@ class MyClient(discord.Client):
await asyncio.sleep(60) # task runs every 60 seconds await asyncio.sleep(60) # task runs every 60 seconds
client = MyClient() client = MyClient(intents=discord.Intents(guilds=True))
client.run('token') client.run('token')

View File

@ -9,10 +9,8 @@ module.
There are a number of utility commands being showcased here.''' There are a number of utility commands being showcased here.'''
intents = discord.Intents.default() intents = discord.Intents(guilds=True, messages=True, members=True)
intents.members = True bot = commands.Bot(command_prefix='t-', description=description, intents=intents)
bot = commands.Bot(command_prefix='?', description=description, intents=intents)
@bot.event @bot.event
async def on_ready(): async def on_ready():

View File

@ -123,8 +123,11 @@ class Music(commands.Cog):
elif ctx.voice_client.is_playing(): elif ctx.voice_client.is_playing():
ctx.voice_client.stop() ctx.voice_client.stop()
bot = commands.Bot(command_prefix=commands.when_mentioned_or("!"), bot = commands.Bot(
description='Relatively simple music bot example') command_prefix=commands.when_mentioned_or("!"),
description='Relatively simple music bot example',
intents=discord.Intents(guilds=True, guild_messages=True, voice_states=True)
)
@bot.event @bot.event
async def on_ready(): async def on_ready():

View File

@ -5,9 +5,8 @@ import typing
import discord import discord
from discord.ext import commands from discord.ext import commands
intents = discord.Intents.default()
intents.members = True
intents = discord.Intents(guilds=True, messages=True, members=True)
bot = commands.Bot('!', intents=intents) bot = commands.Bot('!', intents=intents)

View File

@ -29,7 +29,7 @@ class MyBot(commands.Bot):
return await super().get_context(message, cls=cls) return await super().get_context(message, cls=cls)
bot = MyBot(command_prefix='!') bot = MyBot(command_prefix='!', intents=discord.Intents(guilds=True, messages=True))
@bot.command() @bot.command()
async def guess(ctx, number: int): async def guess(ctx, number: int):

View File

@ -17,5 +17,5 @@ class MyClient(discord.Client):
msg = f'{message.author} has deleted the message: {message.content}' msg = f'{message.author} has deleted the message: {message.content}'
await message.channel.send(msg) await message.channel.send(msg)
client = MyClient() client = MyClient(intents=discord.Intents(guilds=True, messages=True))
client.run('token') client.run('token')

View File

@ -16,5 +16,5 @@ class MyClient(discord.Client):
msg = f'**{before.author}** edited their message:\n{before.content} -> {after.content}' msg = f'**{before.author}** edited their message:\n{before.content} -> {after.content}'
await before.channel.send(msg) await before.channel.send(msg)
client = MyClient() client = MyClient(intents=discord.Intents(guilds=True, messages=True))
client.run('token') client.run('token')

View File

@ -30,5 +30,5 @@ class MyClient(discord.Client):
else: else:
await message.channel.send(f'Oops. It is actually {answer}.') await message.channel.send(f'Oops. It is actually {answer}.')
client = MyClient() client = MyClient(intents=discord.Intents(guilds=True, messages=True))
client.run('token') client.run('token')

View File

@ -14,8 +14,5 @@ class MyClient(discord.Client):
await guild.system_channel.send(to_send) await guild.system_channel.send(to_send)
intents = discord.Intents.default() client = MyClient(intents=discord.Intents(guilds=True, members=True))
intents.members = True
client = MyClient(intents=intents)
client.run('token') client.run('token')

View File

@ -78,8 +78,6 @@ class MyClient(discord.Client):
# 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
intents = discord.Intents.default() intents = discord.Intents(guilds=True, members=True, guild_reactions=True)
intents.members = True
client = MyClient(intents=intents) client = MyClient(intents=intents)
client.run('token') client.run('token')

View File

@ -13,5 +13,5 @@ class MyClient(discord.Client):
if message.content.startswith('!hello'): if message.content.startswith('!hello'):
await message.reply('Hello!', mention_author=True) await message.reply('Hello!', mention_author=True)
client = MyClient() client = MyClient(intents=discord.Intents(guilds=True, messages=True))
client.run('token') client.run('token')

View File

@ -3,7 +3,11 @@ import typing
import discord import discord
from discord.ext import commands from discord.ext import commands
bot = commands.Bot(command_prefix=commands.when_mentioned, description="Nothing to see here!") bot = commands.Bot(
command_prefix=commands.when_mentioned,
description="Nothing to see here!",
intents=discord.Intents(guilds=True, messages=True)
)
# the `hidden` keyword argument hides it from the help command. # the `hidden` keyword argument hides it from the help command.
@bot.group(hidden=True) @bot.group(hidden=True)

View File

@ -5,7 +5,10 @@ import discord
class Bot(commands.Bot): class Bot(commands.Bot):
def __init__(self): def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$')) super().__init__(
command_prefix=commands.when_mentioned_or('$'),
intents=discord.Intents(guilds=True, messages=True)
)
async def on_ready(self): async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})') print(f'Logged in as {self.user} (ID: {self.user.id})')

View File

@ -5,7 +5,10 @@ import discord
class CounterBot(commands.Bot): class CounterBot(commands.Bot):
def __init__(self): def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$')) super().__init__(
command_prefix=commands.when_mentioned_or('$'),
intents=discord.Intents(guilds=True, messages=True)
)
async def on_ready(self): async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})') print(f'Logged in as {self.user} (ID: {self.user.id})')

View File

@ -1,5 +1,3 @@
import typing
import discord import discord
from discord.ext import commands from discord.ext import commands
@ -39,7 +37,10 @@ class DropdownView(discord.ui.View):
class Bot(commands.Bot): class Bot(commands.Bot):
def __init__(self): def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$')) super().__init__(
command_prefix=commands.when_mentioned_or('$'),
intents=discord.Intents(guilds=True, messages=True)
)
async def on_ready(self): async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})') print(f'Logged in as {self.user} (ID: {self.user.id})')

View File

@ -4,7 +4,10 @@ import discord
class EphemeralCounterBot(commands.Bot): class EphemeralCounterBot(commands.Bot):
def __init__(self): def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$')) super().__init__(
command_prefix=commands.when_mentioned_or('$'),
intents=discord.Intents(guilds=True, messages=True)
)
async def on_ready(self): async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})') print(f'Logged in as {self.user} (ID: {self.user.id})')

View File

@ -5,7 +5,10 @@ from urllib.parse import quote_plus
class GoogleBot(commands.Bot): class GoogleBot(commands.Bot):
def __init__(self): def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$')) super().__init__(
command_prefix=commands.when_mentioned_or('$'),
intents=discord.Intents(guilds=True, messages=True)
)
async def on_ready(self): async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})') print(f'Logged in as {self.user} (ID: {self.user.id})')
@ -36,4 +39,4 @@ async def google(ctx: commands.Context, *, query: str):
await ctx.send(f'Google Result for: `{query}`', view=Google(query)) await ctx.send(f'Google Result for: `{query}`', view=Google(query))
bot.run('token') bot.run()

View File

@ -29,7 +29,11 @@ class PersistentView(discord.ui.View):
class PersistentViewBot(commands.Bot): class PersistentViewBot(commands.Bot):
def __init__(self): def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$')) super().__init__(
command_prefix=commands.when_mentioned_or('$'),
intents=discord.Intents(guilds=True, messages=True)
)
self.persistent_views_added = False self.persistent_views_added = False
async def on_ready(self): async def on_ready(self):

View File

@ -120,7 +120,10 @@ class TicTacToe(discord.ui.View):
class TicTacToeBot(commands.Bot): class TicTacToeBot(commands.Bot):
def __init__(self): def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$')) super().__init__(
command_prefix=commands.when_mentioned_or('$'),
intents=discord.Intents(guilds=True, messages=True)
)
async def on_ready(self): async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})') print(f'Logged in as {self.user} (ID: {self.user.id})')

Some files were not shown because too many files have changed in this diff Show More